Skip to content

Commit ab1f092

Browse files
committed
clippy_dev: parse lint pass macro calls
1 parent 7bc7fe5 commit ab1f092

File tree

2 files changed

+145
-39
lines changed

2 files changed

+145
-39
lines changed

clippy_dev/src/new_lint.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -524,7 +524,7 @@ fn parse_mod_file(path: &Path, contents: &str) -> (&'static str, usize) {
524524
let mut decl_end = None;
525525
let mut searcher = RustSearcher::new(contents);
526526
let mut captures = [Capture::EMPTY];
527-
while let Some(name) = searcher.find_capture_token(CaptureIdent) {
527+
while let Some(name) = searcher.find_any_ident() {
528528
match name {
529529
"declare_clippy_lint" => {
530530
if searcher.match_tokens(&[Bang, OpenBrace], &mut []) && searcher.find_token(CloseBrace) {

clippy_dev/src/parse.rs

Lines changed: 144 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use crate::utils::{ErrAction, expect_action, walk_dir_no_dot_or_target};
33
use core::ops::Range;
44
use core::slice;
55
use rustc_data_structures::fx::FxHashMap;
6+
use rustc_index::{IndexVec, newtype_index};
67
use rustc_lexer::{self as lexer, FrontmatterAllowed};
78
use std::collections::hash_map::{Entry, VacantEntry};
89
use std::panic::Location;
@@ -27,13 +28,15 @@ pub enum Token<'a> {
2728
DoubleColon,
2829
Comma,
2930
Eq,
31+
FatArrow,
3032
Lifetime,
3133
Literal,
3234
Lt,
3335
Gt,
3436
OpenBrace,
3537
OpenBracket,
3638
OpenParen,
39+
OptLifetimeArgs,
3740
Pound,
3841
Semi,
3942
}
@@ -134,6 +137,7 @@ impl<'txt> RustSearcher<'txt> {
134137

135138
/// Consumes the next token if it matches the requested value and captures the value if
136139
/// requested. Returns `true` if a token was matched.
140+
#[expect(clippy::too_many_lines)]
137141
fn read_token(&mut self, token: Token<'_>, captures: &mut slice::IterMut<'_, Capture>) -> bool {
138142
loop {
139143
match (token, self.next_token.kind) {
@@ -202,6 +206,25 @@ impl<'txt> RustSearcher<'txt> {
202206
}
203207
return false;
204208
},
209+
(Token::FatArrow, lexer::TokenKind::Eq) => {
210+
self.step();
211+
if matches!(self.next_token.kind, lexer::TokenKind::Gt) {
212+
self.step();
213+
return true;
214+
}
215+
return false;
216+
},
217+
(Token::OptLifetimeArgs, lexer::TokenKind::Lt) => {
218+
self.step();
219+
while self.read_token(Token::Lifetime, captures) {
220+
if !self.read_token(Token::Comma, captures) {
221+
break;
222+
}
223+
}
224+
return self.read_token(Token::Gt, captures);
225+
},
226+
#[expect(clippy::match_same_arms)]
227+
(Token::OptLifetimeArgs, _) => return true,
205228
#[rustfmt::skip]
206229
(
207230
Token::CaptureLitStr,
@@ -235,16 +258,28 @@ impl<'txt> RustSearcher<'txt> {
235258
}
236259

237260
#[must_use]
238-
pub fn find_capture_token(&mut self, token: Token<'_>) -> Option<&'txt str> {
239-
let mut capture = Capture::EMPTY;
240-
let mut captures = slice::from_mut(&mut capture).iter_mut();
241-
while !self.read_token(token, &mut captures) {
242-
self.step();
243-
if self.at_end() {
244-
return None;
261+
pub fn find_any_ident(&mut self) -> Option<&'txt str> {
262+
loop {
263+
match self.next_token.kind {
264+
lexer::TokenKind::Ident => {
265+
let res = self.peek_text();
266+
self.step();
267+
return Some(res);
268+
},
269+
lexer::TokenKind::Eof => return None,
270+
_ => self.step(),
245271
}
246272
}
247-
Some(&self.text[capture.to_index()])
273+
}
274+
275+
#[must_use]
276+
pub fn find_ident(&mut self, s: &str) -> bool {
277+
while let Some(x) = self.find_any_ident() {
278+
if x == s {
279+
return true;
280+
}
281+
}
282+
false
248283
}
249284

250285
#[must_use]
@@ -286,9 +321,27 @@ pub struct Lint {
286321
pub name_span: Span,
287322
}
288323

324+
pub struct LintPassData {
325+
pub name: String,
326+
/// Span of the `impl_lint_pass` or `declare_lint_pass` macro call.
327+
pub mac_span: Span,
328+
}
329+
330+
newtype_index! {
331+
#[orderable]
332+
pub struct LintPass {}
333+
}
334+
335+
pub struct LintRegistration {
336+
pub name: String,
337+
pub pass: LintPass,
338+
}
339+
289340
pub struct ParsedData {
290341
pub source_map: SourceMap,
291342
pub lints: FxHashMap<String, Lint>,
343+
pub lint_passes: IndexVec<LintPass, LintPassData>,
344+
pub lint_registrations: Vec<LintRegistration>,
292345
pub deprecated_span: Range<u32>,
293346
pub renamed_span: Range<u32>,
294347
}
@@ -299,6 +352,8 @@ impl ParsedData {
299352
let mut parser = Parser {
300353
source_map: SourceMap::with_capacity(8, 1000),
301354
lints: FxHashMap::with_capacity_and_hasher(1000, Default::default()),
355+
lint_passes: IndexVec::with_capacity(400),
356+
lint_registrations: Vec::with_capacity(1000),
302357
deprecated_span: 0..0,
303358
renamed_span: 0..0,
304359
errors: Vec::new(),
@@ -355,6 +410,8 @@ impl ParsedData {
355410
ParsedData {
356411
source_map: parser.source_map,
357412
lints: parser.lints,
413+
lint_passes: parser.lint_passes,
414+
lint_registrations: parser.lint_registrations,
358415
deprecated_span: parser.deprecated_span,
359416
renamed_span: parser.renamed_span,
360417
}
@@ -399,6 +456,8 @@ impl From<ErrorKind> for Error {
399456
struct Parser {
400457
source_map: SourceMap,
401458
lints: FxHashMap<String, Lint>,
459+
lint_passes: IndexVec<LintPass, LintPassData>,
460+
lint_registrations: Vec<LintRegistration>,
402461
deprecated_span: Range<u32>,
403462
renamed_span: Range<u32>,
404463
errors: Vec<Error>,
@@ -449,7 +508,7 @@ impl Parser {
449508
#[allow(clippy::enum_glob_use)]
450509
use Token::*;
451510
#[rustfmt::skip]
452-
static DECL_TOKENS: &[Token<'_>] = &[
511+
static LINT_DECL_TOKENS: &[Token<'_>] = &[
453512
// { /// docs
454513
OpenBrace, AnyComment,
455514
// #[clippy::version = "version"]
@@ -458,42 +517,89 @@ impl Parser {
458517
Ident("pub"), CaptureIdent, Comma, AnyComment, CaptureIdent, Comma, AnyComment, LitStr,
459518
];
460519
#[rustfmt::skip]
461-
static EXTRA_TOKENS: &[Token<'_>] = &[
520+
static LINT_DECL_EXTRA_TOKENS: &[Token<'_>] = &[
462521
// , @option = value
463522
Comma, AnyComment, At, AnyIdent, Eq, Literal,
464523
];
524+
#[rustfmt::skip]
525+
static LINT_PASS_TOKENS: &[Token<'_>] = &[
526+
// ( name <'lt> => [
527+
OpenParen, AnyComment, CaptureIdent, OptLifetimeArgs, FatArrow, OpenBracket,
528+
];
465529

466530
let mut searcher = RustSearcher::new(&self.source_map.files[file].contents);
467531
let mut captures = [Capture::EMPTY; 2];
468-
#[expect(clippy::cast_possible_truncation)]
469-
while searcher.find_token(Ident("declare_clippy_lint")) {
470-
let start = searcher.pos() - "declare_clippy_lint".len() as u32;
532+
while let Some(ident) = searcher.find_any_ident() {
533+
#[expect(clippy::cast_possible_truncation)]
534+
let start = searcher.pos - ident.len() as u32;
471535
if searcher.match_token(Bang) {
472-
if !searcher.match_tokens(DECL_TOKENS, &mut captures) {
473-
self.errors.push(searcher.get_unexpected_err(file));
474-
return;
475-
}
476-
let name_span = captures[0].to_span(file);
477-
let name = searcher.get_capture(captures[0]).to_ascii_lowercase();
478-
if let Some(e) = get_vacant_lint(name, name_span, &mut self.lints, &mut self.errors) {
479-
e.insert(Lint {
480-
kind: LintKind::Active(ActiveLint {
481-
group: searcher.get_capture(captures[1]).into(),
482-
decl_span: Span {
536+
match ident {
537+
"declare_clippy_lint" => {
538+
if !searcher.match_tokens(LINT_DECL_TOKENS, &mut captures) {
539+
self.errors.push(searcher.get_unexpected_err(file));
540+
return;
541+
}
542+
while searcher.match_tokens(LINT_DECL_EXTRA_TOKENS, &mut []) {
543+
// nothing
544+
}
545+
if !searcher.match_token(CloseBrace) {
546+
self.errors.push(searcher.get_unexpected_err(file));
547+
return;
548+
}
549+
let name_span = captures[0].to_span(file);
550+
let name = searcher.get_capture(captures[0]).to_ascii_lowercase();
551+
if let Some(e) = get_vacant_lint(name, name_span, &mut self.lints, &mut self.errors) {
552+
e.insert(Lint {
553+
kind: LintKind::Active(ActiveLint {
554+
group: searcher.get_capture(captures[1]).into(),
555+
decl_span: Span {
556+
file,
557+
start,
558+
end: searcher.pos(),
559+
},
560+
}),
561+
name_span,
562+
});
563+
}
564+
},
565+
"impl_lint_pass" | "declare_lint_pass" => {
566+
if !searcher.match_tokens(LINT_PASS_TOKENS, &mut captures) {
567+
self.errors.push(searcher.get_unexpected_err(file));
568+
return;
569+
}
570+
let pass = self.lint_passes.next_index();
571+
let pass_name = captures[0];
572+
while searcher.match_tokens(&[AnyComment, CaptureIdent], &mut captures) {
573+
// Read a path expression.
574+
while searcher.match_token(DoubleColon) {
575+
// Overwrite the previous capture. The last segment is the lint name we want.
576+
if !searcher.match_tokens(&[CaptureIdent], &mut captures) {
577+
self.errors.push(searcher.get_unexpected_err(file));
578+
return;
579+
}
580+
}
581+
self.lint_registrations.push(LintRegistration {
582+
name: searcher.get_capture(captures[0]).to_ascii_lowercase(),
583+
pass,
584+
});
585+
if !searcher.match_token(Comma) {
586+
break;
587+
}
588+
}
589+
if !searcher.match_tokens(&[CloseBracket, CloseParen], &mut []) {
590+
self.errors.push(searcher.get_unexpected_err(file));
591+
return;
592+
}
593+
self.lint_passes.push(LintPassData {
594+
name: searcher.get_capture(pass_name).to_owned(),
595+
mac_span: Span {
483596
file,
484597
start,
485598
end: searcher.pos(),
486599
},
487-
}),
488-
name_span,
489-
});
490-
}
491-
while searcher.match_tokens(EXTRA_TOKENS, &mut []) {
492-
// nothing
493-
}
494-
if !searcher.match_token(CloseBrace) {
495-
self.errors.push(searcher.get_unexpected_err(file));
496-
return;
600+
});
601+
},
602+
_ => {},
497603
}
498604
}
499605
}
@@ -537,11 +643,11 @@ impl Parser {
537643
let mut searcher = RustSearcher::new(&file_data.contents);
538644
// First instance is the macro definition.
539645
assert!(
540-
searcher.find_token(Ident("declare_with_version")),
541-
"error parsing `clippy_lints/src/deprecated_lints.rs`"
646+
searcher.find_ident("declare_with_version"),
647+
"error parsing `clippy_lints/src/deprecated_lints.rs`",
542648
);
543649

544-
if !searcher.find_token(Ident("declare_with_version")) || !searcher.match_tokens(DEPRECATED_TOKENS, &mut []) {
650+
if !searcher.find_ident("declare_with_version") || !searcher.match_tokens(DEPRECATED_TOKENS, &mut []) {
545651
self.errors.push(searcher.get_unexpected_err(file));
546652
return;
547653
}
@@ -570,7 +676,7 @@ impl Parser {
570676
return;
571677
}
572678

573-
if !searcher.find_token(Ident("declare_with_version")) || !searcher.match_tokens(RENAMED_TOKENS, &mut []) {
679+
if !searcher.find_ident("declare_with_version") || !searcher.match_tokens(RENAMED_TOKENS, &mut []) {
574680
self.errors.push(searcher.get_unexpected_err(file));
575681
return;
576682
}

0 commit comments

Comments
 (0)