refactor(document links): use Lexer for unified directive argument scanning - #421
Conversation
📝 WalkthroughWalkthroughRemoved storing include filename source ranges from internal Include records; added lexer-driven directive-argument discovery and a lexer header-name mode; document link discovery now uses the lexer; updated directive handling; added tests for macro/includes/__/embed argument detection. Changes
Sequence DiagramsequenceDiagram
participant DL as DocumentLink Handler
participant KW as Directive Classifier
participant LXR as Lexer
participant FS as File Resolver
DL->>KW: examine preprocessor token
KW-->>DL: classify (include / include_next / embed / __has_*)
alt recognized directive
DL->>LXR: call find_directive_argument(content, offset, LangOptions)
alt requires header-name mode
DL->>LXR: set_header_name_mode()
end
LXR-->>DL: return LocalSourceRange (or none)
alt range found
DL->>FS: resolve filename -> target path
DL->>DL: create DocumentLink(range, target)
else no range
DL->>DL: skip link creation
end
else not a directive
DL->>DL: skip
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 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: 1
🤖 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/feature/document_links.cpp`:
- Around line 44-45: The Lexer is being created without the translation unit's
language options, causing dialect-sensitive tokenization mismatches; update the
Lexer construction in document_links.cpp from using the default opts to pass the
TU's options by calling Lexer with the second parameter &unit.lang_options()
(i.e., replace the current Lexer lexer(line) with a constructor that forwards
unit.lang_options()), ensuring the lambda's capture of unit is used so re-lexing
respects the original clang::LangOptions.
🪄 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: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 98378ec5-3b87-4273-a8ed-9d771acf2d09
📒 Files selected for processing (6)
src/compile/directive.cppsrc/compile/directive.hsrc/feature/document_links.cppsrc/syntax/lexer.cppsrc/syntax/lexer.htests/unit/feature/document_link_tests.cpp
e487d1d to
31e8c26
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
tests/unit/syntax/lexer_tests.cpp (1)
87-151: Add regressions for the newinclude_nextbranches.
src/syntax/lexer.cppnow has dedicated handling for bothinclude_nextand__has_include_next, but this suite never exercises either path. A small quoted/angled case for each would keep the newly added branches from drifting.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@tests/unit/syntax/lexer_tests.cpp` around lines 87 - 151, Add unit tests exercising the newly added include_next branches by adding four cases analogous to the existing ones: an IncludeNextQuoted and IncludeNextAngled that call EXPECT_RANGE(src, 0, R"("...")") / "<...>" on "#include_next \"foo.h\"" and "#include_next <iostream>", and HasIncludeNextQuoted and HasIncludeNextAngled that locate "__has_include_next" in "#if __has_include_next(\"foo.h\")" and "#if __has_include_next(<vector>)" and call EXPECT_RANGE with the found offset; use the same helper EXPECT_RANGE/find_directive_argument pattern as the other tests so the new branches in find_directive_argument are exercised.
🤖 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/syntax/lexer.cpp`:
- Around line 125-155: The loop currently recognizes __has_* identifiers and the
following '(' before checking abs_begin < offset, causing argument matches even
when offset is at the directive start; to fix, move the "if(abs_begin < offset)
continue;" check to immediately after abs_begin/abs_end are computed and before
any logic that sets or tests after_has_keyword (i.e. before the
tok.is_identifier() block and before the tok.kind == clang::tok::l_paren check),
so that you only set after_has_keyword, call lexer.set_header_name_mode(), or
return a LocalSourceRange for header/identifier tokens when the token lies at or
after the provided offset; keep the rest of the conditions
(tok.is_header_name(), tok.kind == string_literal, is_directive_keyword check)
unchanged.
---
Nitpick comments:
In `@tests/unit/syntax/lexer_tests.cpp`:
- Around line 87-151: Add unit tests exercising the newly added include_next
branches by adding four cases analogous to the existing ones: an
IncludeNextQuoted and IncludeNextAngled that call EXPECT_RANGE(src, 0,
R"("...")") / "<...>" on "#include_next \"foo.h\"" and "#include_next
<iostream>", and HasIncludeNextQuoted and HasIncludeNextAngled that locate
"__has_include_next" in "#if __has_include_next(\"foo.h\")" and "#if
__has_include_next(<vector>)" and call EXPECT_RANGE with the found offset; use
the same helper EXPECT_RANGE/find_directive_argument pattern as the other tests
so the new branches in find_directive_argument are exercised.
🪄 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: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 1a7fec79-5f0b-4df9-a9d3-f70e20c5545e
📒 Files selected for processing (7)
src/compile/directive.cppsrc/compile/directive.hsrc/feature/document_links.cppsrc/syntax/lexer.cppsrc/syntax/lexer.htests/unit/feature/document_link_tests.cpptests/unit/syntax/lexer_tests.cpp
🚧 Files skipped from review as they are similar to previous changes (2)
- tests/unit/feature/document_link_tests.cpp
- src/feature/document_links.cpp
…anning Replace hand-written character scanning with the project's Lexer class to find filename arguments in preprocessor directives. Extend the Lexer to activate header_name mode for #embed and expose set_header_name_mode() for __has_include/__has_embed contexts. Remove unused Include::filename_range field which had a latent assert crash on macro-expanded includes. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
31e8c26 to
9583294
Compare
There was a problem hiding this comment.
🧹 Nitpick comments (1)
src/syntax/lexer.cpp (1)
134-143: Consider centralizing the directive keyword checks.The include/embed keyword list is now hard-coded here and also in
Lexer::lex(). Pulling that into a shared helper would reduce drift the next time another directive variant is added.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/syntax/lexer.cpp` around lines 134 - 143, This code duplicates directive keyword checks (the hard-coded lists in lexer.cpp and in Lexer::lex()); extract the include/embed keyword set into a shared helper (e.g., IsDirectiveKeyword or a static const container) and use it here instead of comparing text against "__has_include", "__has_include_next", "__has_embed", "include", "include_next", "embed"; update the logic that sets after_has_keyword and ready to call that helper (referencing tok.is_identifier(), tok.text(line), after_has_keyword, ready, and Lexer::lex() so both places use the same helper) to avoid future drift.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@src/syntax/lexer.cpp`:
- Around line 134-143: This code duplicates directive keyword checks (the
hard-coded lists in lexer.cpp and in Lexer::lex()); extract the include/embed
keyword set into a shared helper (e.g., IsDirectiveKeyword or a static const
container) and use it here instead of comparing text against "__has_include",
"__has_include_next", "__has_embed", "include", "include_next", "embed"; update
the logic that sets after_has_keyword and ready to call that helper (referencing
tok.is_identifier(), tok.text(line), after_has_keyword, ready, and Lexer::lex()
so both places use the same helper) to avoid future drift.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 0627da46-bec5-42d2-a9d6-b9e65d416eca
📒 Files selected for processing (7)
src/compile/directive.cppsrc/compile/directive.hsrc/feature/document_links.cppsrc/syntax/lexer.cppsrc/syntax/lexer.htests/unit/feature/document_link_tests.cpptests/unit/syntax/lexer_tests.cpp
✅ Files skipped from review due to trivial changes (2)
- src/compile/directive.h
- tests/unit/syntax/lexer_tests.cpp
🚧 Files skipped from review as they are similar to previous changes (2)
- tests/unit/feature/document_link_tests.cpp
- src/syntax/lexer.h
Summary
document_links.cppwith the project'sLexerclass for finding filename arguments in preprocessor directivesLexerto activateheader_namemode for#embed/#include_next, and exposeset_header_name_mode()for__has_include/__has_embedcontextsInclude::filename_rangefield (had a latent assert crash on macro-expanded includes)MacroIncludeunit test covering#include MACROscenarioTest plan
MacroIncludetest)🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Refactor
Tests