Repository navigation
Add per-file ignores and update tags - #13
Conversation
|
Warning Rate limit exceeded
Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 41 minutes and 43 seconds. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (3)
📝 WalkthroughWalkthroughThe changes introduce support for a new Changes
Sequence DiagramsequenceDiagram
participant User
participant CLI
participant ConfigLoader
participant Engine
participant PatternMatcher
User->>CLI: Run safelint
CLI->>ConfigLoader: Load configuration
ConfigLoader->>ConfigLoader: Merge per_file_ignores defaults
ConfigLoader-->>CLI: Config with per_file_ignores patterns
loop For each file
CLI->>Engine: check_file(file_path, config)
Engine->>PatternMatcher: Match file against glob patterns
PatternMatcher-->>Engine: Matched ignore patterns
Engine->>Engine: Accumulate ignored rule names/codes
Engine->>Engine: Filter violations by per_file ignores
Engine->>Engine: Filter violations by inline # nosafe
Engine-->>CLI: Violations, suppressed_count
end
CLI->>CLI: Generate summary with suppressed counts
CLI-->>User: Report results
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 inconclusive)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
CONFIGURATION.md (1)
87-101:⚠️ Potential issue | 🟡 MinorExample output is stale — CLI no longer emits "via # nosafe".
_make_summaryinsrc/safelint/cli.pywas changed in this PR to print a generic(N suppressed)suffix regardless of suppression source. The two code blocks below still reference the old wording, which will confuse users comparing docs against actual output.📝 Proposed doc fix
Found 2 errors, 1 warning. [--fail-on=error]. -No fixes available (safelint does not auto-fix violations). (3 suppressed via # nosafe) +No fixes available (safelint does not auto-fix violations). (3 suppressed)-All checks passed. (3 suppressed via # nosafe) -No fixes available (safelint does not auto-fix violations). (3 suppressed via # nosafe) +All checks passed. (3 suppressed) +No fixes available (safelint does not auto-fix violations). (3 suppressed)🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@CONFIGURATION.md` around lines 87 - 101, The example output in CONFIGURATION.md is stale: update the two example code blocks under "End-of-run summary" to match the current _make_summary behavior (it now emits a generic "(N suppressed)" suffix rather than "via # nosafe"); specifically, replace occurrences of " (3 suppressed via # nosafe)" with " (3 suppressed)" and adjust both examples to show the generic "(N suppressed)" text so the docs match the output generated by the _make_summary function.
🧹 Nitpick comments (3)
src/safelint/core/engine.py (2)
189-205: Docstring is now stale —check_filealso appliesper_file_ignores, andsuppressednow counts both sources.The docstring still describes only inline
# nosafesuppression and sayssuppressedreflects only those filtered out "by an inline# nosafedirective". That's no longer accurate: lines 235–236 add a second filtering pass, and both contribute toLintResult.suppressed. Thefail_fastdescription is also now aboutafter_pfirather than only the# nosaferemainder.📝 Suggested docstring update
- Violations on lines that carry a ``# nosafe`` comment (optionally - followed by a comma-separated list of codes / rule names) are filtered - out and counted in :attr:`LintResult.suppressed` instead of appearing - in :attr:`LintResult.violations`. - - When ``fail_fast`` is enabled the loop stops after the first rule that - produces at least one unsuppressed (active) violation, i.e. a violation - that is not filtered out by an inline ``# nosafe`` directive. + Violations are filtered in two passes and counted in + :attr:`LintResult.suppressed` instead of appearing in + :attr:`LintResult.violations`: + + 1. Inline ``# nosafe`` comments on the violation's line (optionally + followed by a comma-separated list of codes / rule names). + 2. Rule codes / names listed under any ``per_file_ignores`` glob + pattern that matches *filepath*. + + When ``fail_fast`` is enabled the loop stops after the first rule + that produces at least one violation surviving both filters.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/safelint/core/engine.py` around lines 189 - 205, Update the check_file docstring to reflect that suppression comes from both inline "# nosafe" directives and per_file_ignores: state that violations filtered by either mechanism are counted in LintResult.suppressed, and clarify that when fail_fast is enabled the loop stops after the first rule that produces at least one unsuppressed violation after applying per_file_ignores (i.e., after the second filtering pass). Mention the names per_file_ignores and LintResult.suppressed in the text so readers can locate the relevant behavior.
170-171: Consider validating and warning on unknown entries inper_file_ignores, and splitting the comprehension for readability.Two small points on this initializer:
- Consistency with
ignore. Lines 141–146 above emit a_log.warning(...)when the globalignorelist contains entries that match neither a known rule name nor a known code (typo / stale rule). The same class of bug silently no-ops here — a misspelled"SAFE1O1"inper_file_ignoresnever fires. Reusing the existingknown_names/known_codes_uppersets for per-file entries would catch these cleanly.- Robustness. If a user writes
per_file_ignores = ["SAFE101"](list instead of mapping) or"tests/**" = "SAFE101"(bare string instead of list), the current code will raiseAttributeError/TypeErrorfrom deep inside the engine constructor rather than a friendly config error. A type-check onraw_pfi/entrieswould fail fast with a clear message.- Nit. The single comprehension packs three responsibilities into one line; wrapping it makes the
(pattern, names, codes_upper)shape obvious.♻️ Proposed refactor
- raw_pfi: dict[str, list[str]] = config.get("per_file_ignores", {}) - self.per_file_ignores: list[tuple[str, frozenset[str], frozenset[str]]] = [(pattern, frozenset(entries), frozenset(e.upper() for e in entries)) for pattern, entries in raw_pfi.items()] + raw_pfi: dict[str, list[str]] = config.get("per_file_ignores", {}) or {} + if not isinstance(raw_pfi, dict): + _log.warning("per_file_ignores must be a mapping of glob → list; got %r — ignoring", type(raw_pfi).__name__) + raw_pfi = {} + self.per_file_ignores: list[tuple[str, frozenset[str], frozenset[str]]] = [] + for pattern, entries in raw_pfi.items(): + entries = list(entries or []) + unknown_pfi = frozenset( + e for e in entries if e not in known_names and e.upper() not in known_codes_upper + ) + if unknown_pfi: + _log.warning( + "Unknown entries in per_file_ignores[%r]: %s", + pattern, + ", ".join(sorted(unknown_pfi)), + ) + self.per_file_ignores.append( + (pattern, frozenset(entries), frozenset(e.upper() for e in entries)), + )🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/safelint/core/engine.py` around lines 170 - 171, Validate and normalize per-file ignores by first checking that raw_pfi is a mapping and each entries value is an iterable of strings, then iterate (not in a single comprehension) over raw_pfi and for each pattern build (pattern, frozenset(names), frozenset(codes_upper)) but also check each entry against the existing known_names and known_codes_upper sets and call _log.warning when an entry matches neither (similar to the global ignore logic); on type mismatches raise a clear ValueError/TypeError mentioning invalid per_file_ignores so callers get a friendly config error instead of an AttributeError deep in the constructor, and assign the validated list to self.per_file_ignores.CONFIGURATION.md (1)
184-221: New section reads clearly — one follow-up on glob semantics.The comparison table at lines 212–218 is a particularly helpful addition. Two small suggestions:
- Consider explicitly stating which glob dialect
per_file_ignores(andexclude_paths) use. Today the engine delegates topathlib.PurePath.match, whose**semantics differ fromglob/shell and only gained recursive support in Python 3.13 — see the separate comment onsrc/safelint/core/engine.py. Once that is addressed (or if you pin tofull_match()), a one-line note like "Patterns follow shell-glob semantics;**matches any number of path segments" will save users trial-and-error.- Minor nit: the YAML example (lines 196–206) omits
migrations/**'s"src/legacy/**"counterpart shown in the TOML block. Not important, but symmetry helps copy-paste parity.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@CONFIGURATION.md` around lines 184 - 221, Update the CONFIGURATION.md per_file_ignores section to (1) add a one-line note explaining the glob dialect used by per_file_ignores and exclude_paths — state that patterns currently follow Python's pathlib.PurePath.match (shell-glob-like) semantics where "**" matches any number of path segments (or indicate intent to switch to a full_match behavior once src/safelint/core/engine.py is changed), and (2) make the YAML example mirror the TOML example by adding the missing "src/legacy/**" entry under per_file_ignores so the two examples are symmetric; reference the per_file_ignores and exclude_paths terminology and pathlib.PurePath.match when editing the text.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/safelint/cli.py`:
- Line 134: Update the CONFIGURATION.md example blocks to match the CLI's new
summary wording produced by suppressed_note (the string now uses "{n}
suppressed" instead of "{n} suppressed via # nosafe"); specifically, edit the
two fenced code examples that currently show "(3 suppressed via # nosafe)" and
change them to "(3 suppressed)" so docs reflect the output generated by
suppressed_note; optionally note that if you later choose to split counters
(e.g., suppressed_inline vs suppressed_per_file) you should update these
examples again to show both counts.
In `@src/safelint/core/engine.py`:
- Around line 178-187: Path.match is incorrectly used for glob patterns with **
(used in _file_ignored_set and _is_excluded) and fails to match nested files on
Python ≤3.12; change matching to normalize the Path to a POSIX string and use
fnmatch.fnmatchcase(path.as_posix(), pattern) (or detect Python ≥3.13 and use
PurePath.full_match for that runtime) so that patterns like "tests/**" and
"migrations/**" behave as documented; update both _file_ignored_set and
_is_excluded to perform matching via the POSIX-string + fnmatch approach (or the
conditional full_match fallback) instead of Path.match.
---
Outside diff comments:
In `@CONFIGURATION.md`:
- Around line 87-101: The example output in CONFIGURATION.md is stale: update
the two example code blocks under "End-of-run summary" to match the current
_make_summary behavior (it now emits a generic "(N suppressed)" suffix rather
than "via # nosafe"); specifically, replace occurrences of " (3 suppressed via #
nosafe)" with " (3 suppressed)" and adjust both examples to show the generic "(N
suppressed)" text so the docs match the output generated by the _make_summary
function.
---
Nitpick comments:
In `@CONFIGURATION.md`:
- Around line 184-221: Update the CONFIGURATION.md per_file_ignores section to
(1) add a one-line note explaining the glob dialect used by per_file_ignores and
exclude_paths — state that patterns currently follow Python's
pathlib.PurePath.match (shell-glob-like) semantics where "**" matches any number
of path segments (or indicate intent to switch to a full_match behavior once
src/safelint/core/engine.py is changed), and (2) make the YAML example mirror
the TOML example by adding the missing "src/legacy/**" entry under
per_file_ignores so the two examples are symmetric; reference the
per_file_ignores and exclude_paths terminology and pathlib.PurePath.match when
editing the text.
In `@src/safelint/core/engine.py`:
- Around line 189-205: Update the check_file docstring to reflect that
suppression comes from both inline "# nosafe" directives and per_file_ignores:
state that violations filtered by either mechanism are counted in
LintResult.suppressed, and clarify that when fail_fast is enabled the loop stops
after the first rule that produces at least one unsuppressed violation after
applying per_file_ignores (i.e., after the second filtering pass). Mention the
names per_file_ignores and LintResult.suppressed in the text so readers can
locate the relevant behavior.
- Around line 170-171: Validate and normalize per-file ignores by first checking
that raw_pfi is a mapping and each entries value is an iterable of strings, then
iterate (not in a single comprehension) over raw_pfi and for each pattern build
(pattern, frozenset(names), frozenset(codes_upper)) but also check each entry
against the existing known_names and known_codes_upper sets and call
_log.warning when an entry matches neither (similar to the global ignore logic);
on type mismatches raise a clear ValueError/TypeError mentioning invalid
per_file_ignores so callers get a friendly config error instead of an
AttributeError deep in the constructor, and assign the validated list to
self.per_file_ignores.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 65756005-2541-4649-a146-d456c24c015a
📒 Files selected for processing (6)
.pre-commit-config.yamlCONFIGURATION.mdREADME.mdsrc/safelint/cli.pysrc/safelint/core/config.pysrc/safelint/core/engine.py
…lls, Python config validation, dead code Code-review findings 2, 4, 13, 14, 16 on the 2.13.0 taint work. #2 (security, all seven trackers): a property cleared by a property-typed sanitiser survived an intervening UNKNOWN call, so `htmlSink(unescape(escape(u)))` read clean - an unknown call may transform or DECODE its input and cannot be trusted to carry a safety property through. The worklist now carries a per-node property: `_taint_step` / `_classify_call` return `(bool, list[(node, property)])`, propagating and template children keep the incoming property, and the unknown-call branch descends with `None` so any taint reaching those children re-taints the result for every property. Raw taint propagation is unchanged. Regression tests at tracker level (Python) and end-to-end (JavaScript). #4: `_resolve_core_lists` gained a `defaults` parameter so the Python path gets the same `_validated_string_list` typo/scalar guard as the other languages without losing its ClassVar fallback lists. #13: `PropertyContract.all_properties` unioned the sanitiser properties and the sink properties, so a property no sink ever requires still cost a full extra tree traversal per assignment. Intersect instead - only properties that are both established and required can change a verdict. #14: restore the `combined == frozenset()` short-circuit in the Go tracker's `_propagate` (a value already safe for nothing cannot be changed by further ORs). #16: drop the dead `ANNOTATED_ASSIGNMENT` dispatch branch and `_visit_ann_assignment` - tree-sitter-python has no such node kind (annotated assignments parse as `assignment`), so the branch was unreachable. The unused constant goes with it. Full gate green (pytest 2158 / 97.18%).
…lls, Python config validation, dead code Code-review findings 2, 4, 13, 14, 16 on the 2.13.0 taint work. #2 (security, all seven trackers): a property cleared by a property-typed sanitiser survived an intervening UNKNOWN call, so `htmlSink(unescape(escape(u)))` read clean - an unknown call may transform or DECODE its input and cannot be trusted to carry a safety property through. The worklist now carries a per-node property: `_taint_step` / `_classify_call` return `(bool, list[(node, property)])`, propagating and template children keep the incoming property, and the unknown-call branch descends with `None` so any taint reaching those children re-taints the result for every property. Raw taint propagation is unchanged. Regression tests at tracker level (Python) and end-to-end (JavaScript). #4: `_resolve_core_lists` gained a `defaults` parameter so the Python path gets the same `_validated_string_list` typo/scalar guard as the other languages without losing its ClassVar fallback lists. #13: `PropertyContract.all_properties` unioned the sanitiser properties and the sink properties, so a property no sink ever requires still cost a full extra tree traversal per assignment. Intersect instead - only properties that are both established and required can change a verdict. #14: restore the `combined == frozenset()` short-circuit in the Go tracker's `_propagate` (a value already safe for nothing cannot be changed by further ORs). #16: drop the dead `ANNOTATED_ASSIGNMENT` dispatch branch and `_visit_ann_assignment` - tree-sitter-python has no such node kind (annotated assignments parse as `assignment`), so the branch was unreachable. The unused constant goes with it. Full gate green (pytest 2158 / 97.18%).
This pull request introduces support for per-file rule suppression in safelint, allowing specific rules to be ignored for files matching given glob patterns. It updates the configuration schema, engine logic, and documentation to explain and enable this feature. There are also minor improvements to suppression reporting and documentation clarity.
Per-file ignore feature:
per_file_ignoreskey in the safelint configuration, allowing specific rules to be suppressed for files matching glob patterns. This is implemented in the engine and documented in both TOML and YAML formats. [1] [2] [3] [4] [5] [6]Documentation updates:
CONFIGURATION.mdandREADME.mdto explain the new per-file ignore mechanism, including configuration examples, comparison with other suppression methods, and references. [1] [2] [3]Suppression reporting:
# nosafeand per-file ignores, making suppression counts more accurate and general.Tooling update:
Summary by CodeRabbit
New Features
per_file_ignoresconfiguration option to suppress specific rules for files matching glob patterns.Documentation
Chores