Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 4 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ exclude = ["test_app"]

[package]
name = "cargo_pup"
version = "0.1.4"
version = "0.1.5"
edition = "2024"
description = "A Rust architectural linting tool that integrates with rustc to enforce architectural patterns and boundaries"
license = "Apache-2.0"
Expand All @@ -34,9 +34,9 @@ anyhow = { workspace = true }
tempfile = { workspace = true }
ron = { workspace = true }
cargo_metadata = { workspace = true }
cargo_pup_common = { path = "cargo_pup_common", version = "=0.1.4" }
cargo_pup_lint_impl = { path = "cargo_pup_lint_impl", version = "=0.1.4" }
cargo_pup_lint_config = { path = "cargo_pup_lint_config", version = "=0.1.4" }
cargo_pup_common = { path = "cargo_pup_common", version = "=0.1.5" }
cargo_pup_lint_impl = { path = "cargo_pup_lint_impl", version = "=0.1.5" }
cargo_pup_lint_config = { path = "cargo_pup_lint_config", version = "=0.1.5" }

#
# These bits are just to keep rust rover happy.
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ First, make sure to install [rustup](https://rustup.rs/) to manage your local ru

Then install pup; **you must use this nightly toolchain, as pup depends on compiler internals that are otherwise unavailable!**
```bash
rustup component add --toolchain nightly-2025-07-25 rust-src rustc-dev llvm-tools-preview
cargo +nightly-2025-07-25 install cargo_pup
rustup component add --toolchain nightly-2026-01-22 rust-src rustc-dev llvm-tools-preview
cargo +nightly-2026-01-22 install cargo_pup
```

## Getting Started
Expand Down Expand Up @@ -121,7 +121,7 @@ First, add the following to your `Cargo.toml`:

```toml
[dev-dependencies]
cargo_pup_lint_config = "0.1.4"
cargo_pup_lint_config = "0.1.5"
```

## Examples
Expand Down
2 changes: 1 addition & 1 deletion cargo_pup_common/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "cargo_pup_common"
version = "0.1.4"
version = "0.1.5"
edition = "2024"
description = "Common utilities and shared components for cargo-pup architectural linting tool"
license = "Apache-2.0"
Expand Down
4 changes: 2 additions & 2 deletions cargo_pup_lint_config/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "cargo_pup_lint_config"
version = "0.1.4"
version = "0.1.5"
edition = "2024"
description = "Configuration and rule builder utilities for cargo-pup architectural linting"
license = "Apache-2.0"
Expand All @@ -13,4 +13,4 @@ serde.workspace = true
ron.workspace = true
tempfile.workspace = true
anyhow.workspace = true
cargo_pup_common = { path = "../cargo_pup_common", version = "=0.1.4" }
cargo_pup_common = { path = "../cargo_pup_common", version = "=0.1.5" }
2 changes: 1 addition & 1 deletion cargo_pup_lint_config/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ Add this to your `Cargo.toml`:

```toml
[dev-dependencies]
cargo_pup_lint_config = "0.1.4"
cargo_pup_lint_config = "0.1.5"
```

## Example
Expand Down
31 changes: 31 additions & 0 deletions cargo_pup_lint_config/src/function_lint/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,37 @@ impl<'a> FunctionConstraintBuilder<'a> {
self
}

/// Forbid unwrap/expect calls on Option and Result
/// This detects: Option::unwrap, Option::expect, Result::unwrap, Result::expect,
/// Result::unwrap_err, Result::expect_err
pub fn no_unwrap(mut self) -> Self {
self.add_rule_internal(FunctionRule::NoUnwrap(self.current_severity));
self
}

/// Forbid all panic-family macros: panic!(), unreachable!(), unimplemented!(), todo!(), assert!()
/// Note: MIR-level analysis cannot distinguish between these macros as they all
/// compile to similar underlying panic functions.
pub fn no_panic(mut self) -> Self {
self.add_rule_internal(FunctionRule::NoPanic(self.current_severity));
self
}

/// Forbid index bounds panics
pub fn no_index_panic(mut self) -> Self {
self.add_rule_internal(FunctionRule::NoIndexPanic(self.current_severity));
self
}

/// Convenience method: forbid ALL panic sources
/// This adds all panic-related rules: NoUnwrap, NoPanic, and NoIndexPanic
pub fn no_panics(mut self) -> Self {
self.add_rule_internal(FunctionRule::NoUnwrap(self.current_severity));
self.add_rule_internal(FunctionRule::NoPanic(self.current_severity));
self.add_rule_internal(FunctionRule::NoIndexPanic(self.current_severity));
self
}

/// Create a new MaxLength rule with the current severity
pub fn create_max_length_rule(&self, length: usize) -> FunctionRule {
FunctionRule::MaxLength(length, self.current_severity)
Expand Down
5 changes: 5 additions & 0 deletions cargo_pup_lint_config/src/function_lint/matcher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,11 @@ impl FunctionMatcher {
pub fn is_async(&self) -> FunctionMatchNode {
FunctionMatchNode::Leaf(FunctionMatch::IsAsync)
}

/// Matches unsafe functions
pub fn is_unsafe(&self) -> FunctionMatchNode {
FunctionMatchNode::Leaf(FunctionMatch::IsUnsafe)
}
}

/// Node in the matcher expression tree
Expand Down
11 changes: 11 additions & 0 deletions cargo_pup_lint_config/src/function_lint/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ pub enum FunctionMatch {
ReturnsType(ReturnTypePattern),
/// Match async functions
IsAsync,
/// Match unsafe functions
IsUnsafe,
/// Logical AND - both patterns must match
AndMatches(Box<FunctionMatch>, Box<FunctionMatch>),
/// Logical OR - either pattern must match
Expand Down Expand Up @@ -64,6 +66,15 @@ pub enum FunctionRule {
MustNotExist(Severity),
/// Enforces that a function must not perform heap allocations
NoAllocation(Severity),
/// Enforces that a function must not call unwrap/expect on Option or Result
NoUnwrap(Severity),
/// Enforces that a function must not use any panic-family macros.
/// This includes: panic!(), unreachable!(), unimplemented!(), todo!(), and assert!().
/// Note: MIR-level analysis cannot distinguish between these macros as they all
/// compile to similar underlying panic functions.
NoPanic(Severity),
/// Enforces that a function must not trigger index bounds panics
NoIndexPanic(Severity),
}

// Helper methods for FunctionRule
Expand Down
19 changes: 10 additions & 9 deletions cargo_pup_lint_config/src/lint_builder_ext.rs
Original file line number Diff line number Diff line change
Expand Up @@ -189,23 +189,24 @@ fn find_workspace_root() -> Result<std::path::PathBuf> {
let mut check_dir = current_exe.parent();

while let Some(dir) = check_dir {
// Check if this directory has the specific cargo-pup source structure
// This is the most reliable indicator that we're in the cargo-pup workspace
if dir.join("src").join("pup_driver.rs").exists() {
return Ok(dir.to_path_buf());
}

let cargo_toml = dir.join("Cargo.toml");
if cargo_toml.exists()
&& let Ok(contents) = std::fs::read_to_string(&cargo_toml)
{
// Look specifically for cargo-pup project or workspace with cargo-pup
if contents.contains("name = \"cargo-pup\"")
|| (contents.contains("[workspace]") && contents.contains("cargo_pup"))
{
// Look specifically for the cargo-pup package itself (not a dependency)
// We check for the exact package name pattern to avoid matching projects
// that merely depend on cargo_pup_lint_config
if contents.contains("name = \"cargo_pup\"") {
return Ok(dir.to_path_buf());
}
}

// Also check if this directory has the specific cargo-pup source structure
if dir.join("src").join("pup_driver.rs").exists() {
return Ok(dir.to_path_buf());
}

check_dir = dir.parent();
}

Expand Down
6 changes: 6 additions & 0 deletions cargo_pup_lint_config/src/struct_lint/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -148,4 +148,10 @@ impl<'a> StructConstraintBuilder<'a> {
self.add_rule_internal(StructRule::MustBePublic(self.current_severity));
self
}

/// Add a rule requiring the struct to have pub(crate) visibility
pub fn must_be_pub_crate(mut self) -> Self {
self.add_rule_internal(StructRule::MustBePubCrate(self.current_severity));
self
}
}
6 changes: 4 additions & 2 deletions cargo_pup_lint_config/src/struct_lint/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,12 @@ pub enum StructRule {
MustBeNamed(String, Severity),
/// Enforces that the struct name does not match the specified pattern
MustNotBeNamed(String, Severity),
/// Enforces that the struct has private visibility
/// Enforces that the struct has private visibility (not pub, not pub(crate), not pub(super))
MustBePrivate(Severity),
/// Enforces that the struct has public visibility
/// Enforces that the struct has public visibility (pub)
MustBePublic(Severity),
/// Enforces that the struct has pub(crate) visibility
MustBePubCrate(Severity),
/// Enforces that the struct implements a specific trait
ImplementsTrait(String, Severity),
/// Logical AND - both rules must pass
Expand Down
6 changes: 3 additions & 3 deletions cargo_pup_lint_impl/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "cargo_pup_lint_impl"
version = "0.1.4"
version = "0.1.5"
edition = "2024"
description = "Core lint implementations and rustc integration for cargo-pup architectural linting"
license = "Apache-2.0"
Expand All @@ -9,8 +9,8 @@ homepage = "https://github.com/datadog/cargo-pup"
readme = "README.md"

[dependencies]
cargo_pup_lint_config = { path = "../cargo_pup_lint_config", version = "=0.1.4" }
cargo_pup_common = { path = "../cargo_pup_common", version = "=0.1.4" }
cargo_pup_lint_config = { path = "../cargo_pup_lint_config", version = "=0.1.5" }
cargo_pup_common = { path = "../cargo_pup_common", version = "=0.1.5" }
anyhow.workspace = true
regex.workspace = true
serde_json.workspace = true
Expand Down
5 changes: 1 addition & 4 deletions cargo_pup_lint_impl/src/helpers/architecture_lint_runner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ impl ArchitectureLintRunner {
{
// This is a trait implementation
// Get the canonical trait name using the centralized helper
let trait_def_id = trait_ref.path.res.def_id();
let trait_def_id = trait_ref.trait_ref.path.res.def_id();
let canonical_full_name =
crate::helpers::queries::get_full_canonical_trait_name_from_def_id(
&tcx,
Expand Down Expand Up @@ -261,7 +261,6 @@ impl Callbacks for ArchitectureLintRunner {
config.register_lints = Some(Box::new(move |_sess, lint_store| {
// If we're actually linting, recreate the lints and add them all
if let Mode::Check = mode {
//let lints = setup_lints_yaml().expect("can load lints");
for lint in lint_collection.lints() {
lint.register_late_pass(lint_store);
}
Expand Down Expand Up @@ -326,8 +325,6 @@ impl Callbacks for ArchitectureLintRunner {
_compiler: &rustc_interface::interface::Compiler,
_tcx: TyCtxt<'_>,
) -> rustc_driver::Compilation {
_compiler.sess.coverage_discard_all_spans_in_codegen();

rustc_driver::Compilation::Continue
}
}
Expand Down
1 change: 0 additions & 1 deletion cargo_pup_lint_impl/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
#![feature(rustc_private)]
// This product includes software developed at Datadog (https://www.datadoghq.com/) Copyright 2024 Datadog, Inc.
#![feature(array_windows)]
#![feature(try_blocks)]

pub mod helpers;
Expand Down
Loading