Skip to content

fix(nolint): skip unopened NOLINTEND blocks that specify a category#423

Merged
aaronliu0130 merged 2 commits intodevelopfrom
skiclan
Mar 1, 2026
Merged

fix(nolint): skip unopened NOLINTEND blocks that specify a category#423
aaronliu0130 merged 2 commits intodevelopfrom
skiclan

Conversation

@aaronliu0130
Copy link
Member

@aaronliu0130 aaronliu0130 commented Feb 28, 2026

these are often for external tools like clang-tidy instead

Should fix #298

Summary by CodeRabbit

  • Bug Fixes

    • Adjusted handling of NOLINT END blocks so an error is only reported when no category is specified; END with an explicit category is quietly ignored.
  • Tests

    • Added a test to confirm NOLINTBEGIN blocks that specify external tool categories do not suppress unrelated warnings, ensuring suppression behavior matches expectations.

these are often for external tools like clang-tidy instead
@coderabbitai
Copy link

coderabbitai bot commented Feb 28, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 66e2461 and 436b03c.

📒 Files selected for processing (1)
  • cpplint.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • cpplint.py

📝 Walkthrough

Walkthrough

The pull request updates cpplint's NOLINT END handling: END directives that include an external-tool category (e.g., clang-analyzer) are ignored instead of always emitting a "Not in a NOLINT block" error; errors still emit when no category is specified.

Changes

Cohort / File(s) Summary
NOLINT parsing logic
cpplint.py
Adjusted parsing so that an NOLINTEND(...) without a matching NOLINTBEGIN only triggers "Not in a NOLINT block" when no category is present; NOLINTEND with an external-tool category is ignored.
Test coverage
cpplint_unittest.py
Added a unit test confirming that NOLINTBEGIN/NOLINTEND using external-tool categories (e.g., clang-tidy/clang-analyzer) do not suppress errors and do not produce the block-mismatch error.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 I hopped through lines of code so neat,
Found an END with no start to meet,
If it bears a tool's own name, I wink and sigh —
I let it pass on by,
And nibble bugs beneath the byte-lit night.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically describes the main change: fixing NOLINTEND block handling to skip unopened blocks that specify a category.
Linked Issues check ✅ Passed The pull request directly addresses issue #298 by implementing logic to skip NOLINTEND blocks with specified categories, preventing false-positive 'Not in a NOLINT block' errors.
Out of Scope Changes check ✅ Passed All changes are scoped to fixing NOLINT block handling; the test addition validates the fix and a performance-related if/else reordering is directly related to the main change.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch skiclan

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
cpplint_unittest.py (1)

776-793: Add a direct unopened NOLINTEND(category) regression case.

This currently validates the behavior indirectly through NOLINTBEGIN(...) + NOLINTEND(...). The PR objective is specifically unopened NOLINTEND(category), so a standalone case would better lock the contract.

Proposed test addition
         assert (
             error_collector.Results()
             == "Use int16_t/int64_t/etc, rather than the C type long  [runtime/int] [4]"
         )
+        # unopened NOLINTEND(category) for external tools should be ignored
+        error_collector = ErrorCollector(self.assertTrue)
+        cpplint.ProcessFileData(
+            "test.cc",
+            "cc",
+            [
+                "// Copyright 2026 Space Dot",
+                "// NOLINTEND(clang-analyzer-core.uninitialized.UndefReturn)",
+                "long d = 1;",
+                "",
+            ],
+            error_collector,
+        )
+        assert (
+            error_collector.Results()
+            == "Use int16_t/int64_t/etc, rather than the C type long  [runtime/int] [4]"
+        )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cpplint_unittest.py` around lines 776 - 793, Add a new unit test case in
cpplint_unittest.py that directly exercises an unopened NOLINTEND(category):
create an ErrorCollector (ErrorCollector(self.assertTrue)) and call
cpplint.ProcessFileData with a file content list that contains a standalone "//
NOLINTEND(clang-analyzer-core.uninitialized.UndefReturn)" (no matching
NOLINTBEGIN) plus a line that would normally trigger the lint (e.g., "long c =
418;"), then assert the expected lint message is still emitted (using
error_collector.Results()). Place the test alongside the existing tests that
call cpplint.ProcessFileData so it uses the same helpers (ErrorCollector,
cpplint.ProcessFileData) and verifies behavior for unopened NOLINTEND(category).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@cpplint.py`:
- Around line 1159-1164: The current skip in the NOLINTEND handling is too
broad; instead of skipping when matched.group(2) is any non-empty category, only
skip when the category explicitly identifies an external tool (e.g.,
clang/clang-tidy families). Update the condition around
_error_suppressions.HasOpenBlock() and matched.group(2) to test the category for
external-tool identifiers (for example, check matched.group(2) startswith
"clang-" or matches "clang-tidy"/"clang_analyzer" etc.) and only suppress the
readability/nolint error() when that external-tool check passes; otherwise call
error(filename, linenum, "readability/nolint", 5, "Not in a NOLINT block") as
before.

---

Nitpick comments:
In `@cpplint_unittest.py`:
- Around line 776-793: Add a new unit test case in cpplint_unittest.py that
directly exercises an unopened NOLINTEND(category): create an ErrorCollector
(ErrorCollector(self.assertTrue)) and call cpplint.ProcessFileData with a file
content list that contains a standalone "//
NOLINTEND(clang-analyzer-core.uninitialized.UndefReturn)" (no matching
NOLINTBEGIN) plus a line that would normally trigger the lint (e.g., "long c =
418;"), then assert the expected lint message is still emitted (using
error_collector.Results()). Place the test alongside the existing tests that
call cpplint.ProcessFileData so it uses the same helpers (ErrorCollector,
cpplint.ProcessFileData) and verifies behavior for unopened NOLINTEND(category).

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c72387f and 66e2461.

📒 Files selected for processing (2)
  • cpplint.py
  • cpplint_unittest.py

cpplint.py Outdated
Comment on lines +1159 to +1164
if not _error_suppressions.HasOpenBlock():
error(filename, linenum, "readability/nolint", 5, "Not in a NOLINT block")
if matched.group(2) not in (None, "(*)"):
# Has category; probably a clang-tidy rule. Safer to ignore.
pass
else:
error(filename, linenum, "readability/nolint", 5, "Not in a NOLINT block")
Copy link

@coderabbitai coderabbitai bot Feb 28, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Restrict this skip path to external-tool categories only.

The new check (matched.group(2) not in (None, "(*)")) skips the block-mismatch error for all categorized NOLINTEND(...) when no block is open. That can hide malformed cpplint/legacy directives (not just clang-* external ones), which is broader than the PR objective.

🔧 Proposed fix
-            if not _error_suppressions.HasOpenBlock():
-                if matched.group(2) not in (None, "(*)"):
-                    # Has category; probably a clang-tidy rule. Safer to ignore.
-                    pass
-                else:
-                    error(filename, linenum, "readability/nolint", 5, "Not in a NOLINT block")
+            categories = matched.group(2)
+            parsed_categories = (
+                {c.strip() for c in categories[1:-1].split(",")}
+                if categories and categories.startswith("(") and categories.endswith(")")
+                else set()
+            )
+            has_only_other_tool_categories = bool(parsed_categories) and all(
+                any(category.startswith(prefix) for prefix in _OTHER_NOLINT_CATEGORY_PREFIXES)
+                for category in parsed_categories
+            )
+
+            if not _error_suppressions.HasOpenBlock() and not has_only_other_tool_categories:
+                error(filename, linenum, "readability/nolint", 5, "Not in a NOLINT block")
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if not _error_suppressions.HasOpenBlock():
error(filename, linenum, "readability/nolint", 5, "Not in a NOLINT block")
if matched.group(2) not in (None, "(*)"):
# Has category; probably a clang-tidy rule. Safer to ignore.
pass
else:
error(filename, linenum, "readability/nolint", 5, "Not in a NOLINT block")
categories = matched.group(2)
parsed_categories = (
{c.strip() for c in categories[1:-1].split(",")}
if categories and categories.startswith("(") and categories.endswith(")")
else set()
)
has_only_other_tool_categories = bool(parsed_categories) and all(
any(category.startswith(prefix) for prefix in _OTHER_NOLINT_CATEGORY_PREFIXES)
for category in parsed_categories
)
if not _error_suppressions.HasOpenBlock() and not has_only_other_tool_categories:
error(filename, linenum, "readability/nolint", 5, "Not in a NOLINT block")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cpplint.py` around lines 1159 - 1164, The current skip in the NOLINTEND
handling is too broad; instead of skipping when matched.group(2) is any
non-empty category, only skip when the category explicitly identifies an
external tool (e.g., clang/clang-tidy families). Update the condition around
_error_suppressions.HasOpenBlock() and matched.group(2) to test the category for
external-tool identifiers (for example, check matched.group(2) startswith
"clang-" or matches "clang-tidy"/"clang_analyzer" etc.) and only suppress the
readability/nolint error() when that external-tool check passes; otherwise call
error(filename, linenum, "readability/nolint", 5, "Not in a NOLINT block") as
before.

Copy link
Member Author

Choose a reason for hiding this comment

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

I find this too complicated/less performant, and believe false positives would be niche.

This comment was marked as outdated.

Copy link
Member Author

@aaronliu0130 aaronliu0130 Feb 28, 2026

Choose a reason for hiding this comment

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

That's the exact concern I have. It's too uncommon a scenario/of too limited utility to use that much performance for.

I'm willing to see what others think about this.

Copy link

Choose a reason for hiding this comment

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

@aaronliu0130 Understood. Let’s keep the simpler, fast path.

To make the intent explicit (at zero runtime cost) and guard against regressions, I suggest:

  • Add a short comment where END is handled to note we intentionally ignore unmatched NOLINTEND(category) because many such categories are for external tools (e.g., clang-tidy/analyzer).
  • Add two small tests:
    • Unopened NOLINTEND(clang-analyzer-...) does not emit readability/nolint.
    • Unopened NOLINTEND(readability/...) still surfaces an error (currently “NOLINT categories not supported in block END: …”).

Example comment you could drop in:

Has category; many are external-tool directives (e.g., clang-tidy). Intentionally ignore unmatched END to avoid false positives (see #298).

If you’d like, I can add the tests in this PR.

seems to be slightly more performant due to branch prediction (former is
 more likely)
Copy link
Member

@cclauss cclauss left a comment

Choose a reason for hiding this comment

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

I do not love else: pass but I can live with it.

@aaronliu0130 aaronliu0130 merged commit 34c11ad into develop Mar 1, 2026
12 checks passed
@cclauss cclauss deleted the skiclan branch March 1, 2026 12:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Not in a NOLINT block when using NOLINTBEGIN/NOLINTEND with clang-analyzer rules

2 participants