[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <20250422125824.30525-1-contact@arnaud-lcm.com>
Date: Tue, 22 Apr 2025 14:58:24 +0200
From: Arnaud Lecomte <contact@...aud-lcm.com>
To: Andy Whitcroft <apw@...onical.com>,
Joe Perches <joe@...ches.com>,
Dwaipayan Ray <dwaipayanray1@...il.com>,
Lukas Bulwahn <lukas.bulwahn@...il.com>,
Miguel Ojeda <ojeda@...nel.org>,
Alex Gaynor <alex.gaynor@...il.com>,
Boqun Feng <boqun.feng@...il.com>,
Gary Guo <gary@...yguo.net>,
Björn Roy Baron <bjorn3_gh@...tonmail.com>,
Benno Lossin <benno.lossin@...ton.me>,
Andreas Hindborg <a.hindborg@...nel.org>,
Alice Ryhl <aliceryhl@...gle.com>,
Trevor Gross <tmgross@...ch.edu>,
Danilo Krummrich <dakr@...nel.org>,
Nathan Chancellor <nathan@...nel.org>,
Nick Desaulniers <nick.desaulniers+lkml@...il.com>,
Bill Wendling <morbo@...gle.com>,
Justin Stitt <justinstitt@...gle.com>
Cc: linux-kernel@...r.kernel.org,
rust-for-linux@...r.kernel.org,
llvm@...ts.linux.dev,
contact@...aud-lcm.com,
skhan@...uxfoundation.org
Subject: [PATCH v3 1/2] checkpatch.pl: warn about // comments on private Rust
items
Add a new checkpatch.pl warning to detect //-style comments above
private Rust items that were likely intended to be doc comments (///).
This helps catch documentation that won't appear in rustdoc output.
The detection uses multiple heuristics to identify likely doc comments:
- Comments containing markdown
- Comments starting with an imperative tone
- Comments with mention of params
- Comments with code block
- Comments with keywords: Returns, ...
- Block comments longer than 3 lines.
- Comments with references: @see, @link, ...
Comments are only flagged if they:
- Appear above private items
- Don't contain obvious non-doc patterns (TODO, FIXME)
- Score sufficiently on heuristics
The warning triggered then suggests to convert flagged comments to
doc comments.
Signed-off-by: Arnaud Lecomte <contact@...aud-lcm.com>
---
Changes in v3:
- Add heuristics to decide whether we should trigger the warning on comments
- Link to v2: https://lore.kernel.org/all/20250420194806.54354-1-contact@arnaud-lcm.com/
---
scripts/checkpatch.pl | 110 ++++++++++++++++++++++++++++++++++++++++++
1 file changed, 110 insertions(+)
diff --git a/scripts/checkpatch.pl b/scripts/checkpatch.pl
index 3d22bf863eec..eb564a119d68 100755
--- a/scripts/checkpatch.pl
+++ b/scripts/checkpatch.pl
@@ -1987,6 +1987,97 @@ sub ctx_locate_comment {
chomp($current_comment);
return($current_comment);
}
+
+sub ctx_detect_missing_doc_comment_behind_rust_private_item {
+ my ($linenr) = @_;
+ my $start_line = $linenr - 1;
+ my $idx = $start_line;
+
+ # Skip if it's a public Rust item (starts with 'pub')
+ return unless $rawlines[$start_line] !~ /^\+?\s*pub(\s|\([^)]*\)\s)/;
+
+ # Check if it's an empty line
+ return unless $rawlines[$start_line] !~ /^\s*\+?\s*$/;
+
+ # Check if it's a comment
+ return unless $rawlines[$start_line] !~ /^\+?\s*\/\/\/?\/?.*$/;
+
+ # Check if it is a private macro
+ if ($rawlines[$start_line] =~ /^\+?\s*macro_rules!\s+([a-zA-Z_][a-zA-Z0-9_]*|#?[a-zA-Z_][a-zA-Z0-9_]*)\s*(?:\{|$)/ && $idx > 0) {
+ return unless $rawlines[$idx -1] !~ /^\+?\s*#\s*\[\s*macro_export\s*\]\s*$/;
+ } else {
+ # Check if it's a Rust item
+ return unless $rawlines[$start_line] =~ /^\+?\s*\b(fn|struct|const|enum|trait|type|mod|impl|use)\b/;
+ }
+
+ # Walk backwards through non-empty lines
+ $idx--;
+ my @comment_lines;
+ while ($idx >= 0 && $rawlines[$idx] !~ /^\s*\+?\s*$/) {
+ # Stop if a doc comment is found
+ last if $rawlines[$idx] =~ m@^\+?\s*///@;
+ # Clean the line first (remove diff markers, trim spaces)
+ my $clean_line = $rawlines[$idx];
+ $clean_line =~ s/^\+?\s*//;
+ $clean_line =~ s/\s*$//;
+ if($clean_line =~ m@^\+?\s*//@) { # Only add comments
+ unshift @comment_lines, $clean_line;
+ }
+ $idx--;
+ }
+
+ # No comments to check
+ return unless @comment_lines;
+
+ # Heuristic: Check for documentation keywords
+ my $keywords = qr/Returns|Arguments|Examples?|Panics|Safety|Note|Errors|See\s+Also/i;
+ my $has_keywords = grep { /\/\/\s*$keywords/ } @comment_lines;
+
+ # Heuristic: Markdown syntax
+ my $markdown = qr{
+ `[^`]+` # Inline code
+ | \*\*[^*]+\*\* # Bold with proper boundaries
+ | \b_[^_\s][^_]*_\b # Italic with word boundaries
+ | \#[^#\s] # Headings like # Title
+ | ^\s*```[\w]*\s*$ # Code block marker line (allow optional language)
+ }x;
+
+ my $has_markdown = grep { /$markdown/ } @comment_lines;
+
+ # Heuristic: Parameter mentions
+ my $item_line = $rawlines[$start_line];
+ my @params = ($item_line =~ /(\b\w+)\s*:\s*\w+/g); # Extract param names
+ my $param_mentions = 0;
+ if (@params) {
+ $param_mentions = grep { my $line = $_; grep { $line =~ /\b$_\b/ } @params } @comment_lines;
+ }
+
+ # Heuristic: Start with imperative tones
+ my $first_line = $comment_lines[0];
+ my $imperative_tone = $first_line =~ /^\s*\/\/\s*(Returns?|Computes?|Checks?|Converts?|Creates?)\b/i;
+
+ # Heuristic: References
+ my $has_refs = grep { /\[`\w+`\]|\@see|\@link/i } @comment_lines;
+
+ # Heuristic: Code block
+ my $has_code_block = grep { /```|\buse\s+\w+::|^\s*\/\/\s*\w+\(.*\)/ } @comment_lines;
+ # Combine heuristics
+ my $score = 0;
+ $score += 1 if $has_keywords;
+ $score += 2 if $has_markdown;
+ $score += 1 if $param_mentions;
+ $score += 2 if $imperative_tone;
+ $score += 2 if $has_refs;
+ $score += 2 if scalar(@comment_lines) >= 3;
+ $score += 2 if $has_code_block;
+
+ # Trigger if score meets threshold
+ return unless $score >= 2;
+
+ return ($idx+1, $start_line);
+}
+
+
sub ctx_has_comment {
my ($first_line, $end_line) = @_;
my $cmt = ctx_locate_comment($first_line, $end_line);
@@ -2950,6 +3041,25 @@ sub process {
}
}
+# check for incorrect use of `//` instead of `\\\` for doc comments in Rust
+ if ($perl_version_ok && $realfile =~ /\.rs$/) {
+ my ($start_l, $end_l) = ctx_detect_missing_doc_comment_behind_rust_private_item($linenr);
+ if (defined($start_l) && defined($end_l)) {
+ for (my $i = $start_l; $i <= $end_l; $i++) {
+ my $comment_line = $rawlines[$i - 1];
+ next unless $comment_line =~ m@^\+//([^/].*)$@;
+ my $comment = $1;
+ # Skip obvious non-doc comments
+ next if $comment =~ m@^\s*(?:TODO|FIXME|XXX|NOTE|HACK|SAFETY):?@i;
+ next if $comment =~ m@^\s*[[:punct:]]{2,}@;
+ my $line_issue = $i - 3;
+ WARN("POSSIBLE_MISSING_RUST_DOC",
+ "Consider using `///` if the comment was intended as documentation (line $line_issue).\n" .
+ "$here\n$comment_line");
+ }
+ }
+ }
+
# Check the patch for a From:
if (decode("MIME-Header", $line) =~ /^From:\s*(.*)/) {
$author = $1;
--
2.43.0
Powered by blists - more mailing lists