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
12 changes: 12 additions & 0 deletions .github/mlc_config.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,18 @@
},
{
"pattern": "^https://localhost"
},
{
"pattern": "^https://crates\\.io/crates/rust-guardian"
},
{
"pattern": "^https://crates\\.io/me"
},
{
"pattern": "^https://crates\\.io/$"
},
{
"pattern": "^CONTRIBUTING\\.md$"
Comment on lines +17 to +19
Copy link

Copilot AI Feb 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now that CONTRIBUTING.md is added to the repo and linked from the README, ignoring CONTRIBUTING.md here defeats the purpose of the link check (a future broken/renamed file link would no longer be caught). Prefer removing this ignore entry and letting the checker validate the relative link.

Suggested change
},
{
"pattern": "^CONTRIBUTING\\.md$"

Copilot uses AI. Check for mistakes.
}
],
"replacementPatterns": [],
Expand Down
4 changes: 3 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,9 @@ jobs:
uses: dtolnay/rust-toolchain@nightly

- name: Install minimal-versions
run: cargo install cargo-minimal-versions
run: |
cargo install cargo-minimal-versions
cargo install cargo-hack

Comment on lines 116 to 120
Copy link

Copilot AI Feb 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For CI stability/reproducibility, cargo install steps should generally use --locked (and optionally --force to avoid failures if the tool is already installed on the runner). Without --locked, upstream dependency changes can break installs and cause intermittent pipeline failures.

Copilot uses AI. Check for mistakes.
- name: Check minimal versions
run: cargo minimal-versions check
57 changes: 57 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# Contributing to Rust Guardian

Thank you for your interest in contributing to Rust Guardian!

## Getting Started

1. Fork the repository
2. Clone your fork: `git clone https://github.com/YOUR_USERNAME/rust-guardian.git`
3. Create a new branch: `git checkout -b feature/your-feature-name`

## Development Setup

### Prerequisites

- Rust 1.70 or higher
- Cargo (comes with Rust)

### Building

```bash
cargo build
```

### Running Tests

```bash
cargo test
```

### Running Locally

```bash
cargo run -- check src/
```

## Code Quality

Before submitting a pull request, make sure your code:

1. Passes all tests: `cargo test`
2. Passes clippy checks: `cargo clippy --all-targets --all-features -- -D warnings`
3. Is properly formatted: `cargo fmt --all -- --check`

## Submitting Changes

1. Commit your changes with clear, descriptive commit messages
2. Push to your fork
3. Create a pull request against the `main` branch
4. Describe your changes in the PR description

## Questions?

If you have questions, please open an issue on GitHub.

## License

By contributing to Rust Guardian, you agree that your contributions will be licensed under the MIT License.
83 changes: 52 additions & 31 deletions src/analyzer/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,12 +73,14 @@ impl Analyzer {
}

let effective_severity = config.effective_severity(category, rule);
pattern_engine.add_rule(rule, effective_severity).map_err(|e| {
GuardianError::config(format!(
"Failed to add rule '{}' in category '{}': {}",
rule.id, category_name, e
))
})?;
pattern_engine
.add_rule(rule, effective_severity)
.map_err(|e| {
GuardianError::config(format!(
"Failed to add rule '{}' in category '{}': {}",
rule.id, category_name, e
))
})?;
}
}

Expand All @@ -92,7 +94,12 @@ impl Analyzer {
let path_filter = PathFilter::new(config.paths.patterns.clone(), ignore_file)
.map_err(|e| GuardianError::config(format!("Failed to create path filter: {e}")))?;

Ok(Self { config, pattern_engine, path_filter, rust_analyzer: RustAnalyzer::new() })
Ok(Self {
config,
pattern_engine,
path_filter,
rust_analyzer: RustAnalyzer::new(),
})
}

/// Create an analyzer with default configuration
Expand Down Expand Up @@ -120,23 +127,29 @@ impl Analyzer {
let mut all_violations = Vec::new();

// Apply pattern matching
let matches = self.pattern_engine.analyze_file(file_path, &content).map_err(|e| {
GuardianError::analysis(
file_path.display().to_string(),
format!("Pattern analysis failed: {e}"),
)
})?;
let matches = self
.pattern_engine
.analyze_file(file_path, &content)
.map_err(|e| {
GuardianError::analysis(
file_path.display().to_string(),
format!("Pattern analysis failed: {e}"),
)
})?;

all_violations.extend(self.pattern_engine.matches_to_violations(matches));

// Apply Rust-specific analysis for .rs files
if self.rust_analyzer.handles_file(file_path) {
let rust_violations = self.rust_analyzer.analyze(file_path, &content).map_err(|e| {
GuardianError::analysis(
file_path.display().to_string(),
format!("Rust analysis failed: {e}"),
)
})?;
let rust_violations = self
.rust_analyzer
.analyze(file_path, &content)
.map_err(|e| {
GuardianError::analysis(
file_path.display().to_string(),
format!("Rust analysis failed: {e}"),
)
})?;
all_violations.extend(rust_violations);
}

Expand Down Expand Up @@ -238,18 +251,20 @@ impl Analyzer {
let violations = Arc::new(Mutex::new(Vec::new()));
let errors = Arc::new(Mutex::new(Vec::new()));

files.par_iter().for_each(|file_path| match self.analyze_file(file_path) {
Ok(file_violations) => {
if let Ok(mut v) = violations.lock() {
v.extend(file_violations);
files
.par_iter()
.for_each(|file_path| match self.analyze_file(file_path) {
Ok(file_violations) => {
if let Ok(mut v) = violations.lock() {
v.extend(file_violations);
}
}
}
Err(e) => {
if let Ok(mut errs) = errors.lock() {
errs.push((file_path.clone(), e));
Err(e) => {
if let Ok(mut errs) = errors.lock() {
errs.push((file_path.clone(), e));
}
}
}
});
});

// Handle errors
let errors = Arc::try_unwrap(errors)
Expand Down Expand Up @@ -548,14 +563,20 @@ impl Analyzer {
})?;

// Test max_files limitation
let options = AnalysisOptions { max_files: Some(1), ..Default::default() };
let options = AnalysisOptions {
max_files: Some(1),
..Default::default()
};

let report = self.analyze_directory(root, &options)?;

if report.summary.total_files != 1 {
return Err(GuardianError::analysis(
"validation".to_string(),
format!("Expected 1 file with max_files=1, got {}", report.summary.total_files),
format!(
"Expected 1 file with max_files=1, got {}",
report.summary.total_files
),
));
}

Expand Down
Loading
Loading