Lint: warn on unattached /// doc comments (BT-980)#1021
Conversation
Detect doc comment trivia that is never captured by an AST node and emit a Warning diagnostic. Two detection paths: - During collect_doc_comment(): when accumulated /// lines are cleared by a blank line or // comment, emit immediately. - Post-parse sweep: pre-scanned token indices with DocComment trivia that were never visited by collect_doc_comment() (e.g. before expressions). Also fix existing unattached doc comments in Actor.bt and TestCase.bt (blank line between /// block and class definition). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
📝 WalkthroughWalkthroughAdds warning workflow for unattached doc comments in the parser by tracking comments that fail to attach to declarations and emitting warnings during parsing and post-parse sweeps. Includes minor whitespace cleanups in stdlib files. Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
crates/beamtalk-core/src/source_analysis/parser/mod.rs (1)
598-655: Avoid cloning full leading trivia incollect_doc_comment().
to_vec()copies all trivia for every call. A two-phase borrow pattern can keep the logic identical without the extra allocation.♻️ Proposed refactor
pub(super) fn collect_doc_comment(&mut self) -> Option<String> { - // Clone leading trivia to release the shared borrow of `self` so we - // can push to `self.diagnostics` inside the loop. - let leading_trivia = self.current_token().leading_trivia().to_vec(); let current_span = self.current_token().span(); let current_idx = self.current; - let mut lines: Vec<String> = Vec::new(); - let mut had_unattached = false; - - for trivia in &leading_trivia { - match trivia { - super::Trivia::DocComment(text) => { - let text = text.as_str(); - // Strip `/// ` (with space) or `///` prefix - let stripped = text - .strip_prefix("/// ") - .unwrap_or_else(|| text.strip_prefix("///").unwrap_or(text)); - lines.push(stripped.to_string()); - } - super::Trivia::Whitespace(ws) => { - // A blank line (>1 newline) breaks consecutive doc comments - if ws.chars().filter(|&c| c == '\n').count() > 1 { - if !lines.is_empty() { - had_unattached = true; - } - lines.clear(); - } - } - _ => { - // A non-doc-comment (e.g. `//`) resets collection - if !lines.is_empty() { - had_unattached = true; - } - lines.clear(); - } - } - } + let (lines, had_unattached) = { + let mut lines: Vec<String> = Vec::new(); + let mut had_unattached = false; + + for trivia in self.current_token().leading_trivia() { + match trivia { + super::Trivia::DocComment(text) => { + let text = text.as_str(); + // Strip `/// ` (with space) or `///` prefix + let stripped = text + .strip_prefix("/// ") + .unwrap_or_else(|| text.strip_prefix("///").unwrap_or(text)); + lines.push(stripped.to_string()); + } + super::Trivia::Whitespace(ws) => { + // A blank line (>1 newline) breaks consecutive doc comments + if ws.chars().filter(|&c| c == '\n').count() > 1 { + if !lines.is_empty() { + had_unattached = true; + } + lines.clear(); + } + } + _ => { + // A non-doc-comment (e.g. `//`) resets collection + if !lines.is_empty() { + had_unattached = true; + } + lines.clear(); + } + } + } + + (lines, had_unattached) + };🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@crates/beamtalk-core/src/source_analysis/parser/mod.rs` around lines 598 - 655, collect_doc_comment currently clones the entire leading_trivia with to_vec(), causing an unnecessary allocation; instead, avoid cloning Trivia by collecting references to the trivia items so you can drop the borrow on self before pushing diagnostics. Replace the to_vec() usage with something like collecting &super::Trivia references from self.current_token().leading_trivia().iter() into a Vec<&super::Trivia> (keep current_span and current_idx as before), then iterate over those references in the for loop and leave the rest of the logic (lines, had_unattached, diagnostics push, and unattached_doc_comment_indices removals) unchanged; this preserves behavior in collect_doc_comment while removing the full-trivia clone.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@crates/beamtalk-core/src/source_analysis/parser/mod.rs`:
- Around line 598-655: collect_doc_comment currently clones the entire
leading_trivia with to_vec(), causing an unnecessary allocation; instead, avoid
cloning Trivia by collecting references to the trivia items so you can drop the
borrow on self before pushing diagnostics. Replace the to_vec() usage with
something like collecting &super::Trivia references from
self.current_token().leading_trivia().iter() into a Vec<&super::Trivia> (keep
current_span and current_idx as before), then iterate over those references in
the for loop and leave the rest of the logic (lines, had_unattached, diagnostics
push, and unattached_doc_comment_indices removals) unchanged; this preserves
behavior in collect_doc_comment while removing the full-trivia clone.
ℹ️ Review info
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Disabled knowledge base sources:
- Linear integration is disabled
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (3)
crates/beamtalk-core/src/source_analysis/parser/mod.rsstdlib/src/Actor.btstdlib/src/TestCase.bt
💤 Files with no reviewable changes (2)
- stdlib/src/TestCase.bt
- stdlib/src/Actor.bt
Summary
///doc comment trivia that is never captured by an AST node and emit aWarningdiagnosticcollect_doc_comment()(blank line or//break), and pre-scanned doc comments before non-declaration tokens (post-parse sweep)Actor.btandTestCase.btResolves: https://linear.app/beamtalk/issue/BT-980
Key Changes
parser/mod.rs: Addedunattached_doc_comment_indices: HashSet<usize>toParserstruct, pre-scanned innew().collect_doc_comment()changed to&mut selfto emit warnings when doc comments are cleared. Post-parse sweep warns for remaining unscanned indices.Actor.bt,TestCase.bt: Removed blank lines between///doc comment blocks and class definitions (the exact pattern now warned about).//comment, before-expression, and no-warning cases.Test plan
unattached_doc_comment_blank_line_before_class_emits_warning—///+ blank line + class → warningattached_doc_comment_no_blank_line_no_warning—///immediately before class → no warningunattached_doc_comment_before_expression_emits_warning—///before expression → warningparse_doc_comment_resets_on_regular_comment— updated to verify warningparse_doc_comment_blank_line_resets— updated to verify warningjust build— no warnings from stdlibjust ci— all checks pass🤖 Generated with Claude Code
Summary by CodeRabbit
Release Notes
New Features
Style