Skip to content

Commit e875641

Browse files
committed
clippy_dev: Keep a source map when parsing files.
1 parent be6d81a commit e875641

File tree

8 files changed

+209
-88
lines changed

8 files changed

+209
-88
lines changed

clippy_dev/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ chrono = { version = "0.4.38", default-features = false, features = ["clock"] }
99
clap = { version = "4.4", features = ["derive"] }
1010
indoc = "1.0"
1111
itertools = "0.12"
12+
memchr = "2.7.5"
1213
opener = "0.7"
1314
walkdir = "2.3"
1415

clippy_dev/src/deprecate_lint.rs

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
1-
use crate::parse::{ActiveLint, DeprecatedLint, Lint, LintList};
1+
use crate::parse::{ActiveLint, DeprecatedLint, Lint, ParsedData};
22
use crate::update_lints::generate_lint_files;
33
use crate::utils::{FileUpdater, UpdateMode, UpdateStatus, Version, delete_dir_if_exists, delete_file_if_exists};
44
use core::mem;
5-
use rustc_data_structures::fx::FxHashMap;
65
use std::ffi::OsStr;
76
use std::path::Path;
87

@@ -20,8 +19,8 @@ pub fn deprecate(clippy_version: Version, name: &str, reason: &str) {
2019
panic!("`{name}` should not contain the `{prefix}` prefix");
2120
}
2221

23-
let mut list = LintList::collect();
24-
let Some(entry) = list.lints.get_mut(name) else {
22+
let mut data = ParsedData::collect();
23+
let Some(entry) = data.lints.get_mut(name) else {
2524
eprintln!("error: failed to find lint `{name}`");
2625
return;
2726
};
@@ -36,13 +35,13 @@ pub fn deprecate(clippy_version: Version, name: &str, reason: &str) {
3635
return;
3736
};
3837

39-
remove_lint_declaration(name, &lint, &list.lints);
40-
generate_lint_files(UpdateMode::Change, &list);
38+
remove_lint_declaration(name, &lint, &data);
39+
generate_lint_files(UpdateMode::Change, &data);
4140
println!("info: `{name}` has successfully been deprecated");
4241
println!("note: you must run `cargo uitest` to update the test results");
4342
}
4443

45-
fn remove_lint_declaration(name: &str, lint: &ActiveLint, lints: &FxHashMap<String, Lint>) {
44+
fn remove_lint_declaration(name: &str, lint: &ActiveLint, data: &ParsedData) {
4645
fn remove_test_assets(name: &str) {
4746
let test_file_stem = format!("tests/ui/{name}");
4847
let path = Path::new(&test_file_stem);
@@ -84,24 +83,26 @@ fn remove_lint_declaration(name: &str, lint: &ActiveLint, lints: &FxHashMap<Stri
8483
}
8584
}
8685

87-
if lints.values().any(|l| {
86+
let lint_file = &data.source_map.files[lint.span.file];
87+
if data.lints.values().any(|l| {
8888
if let Lint::Active(l) = l {
89-
l.krate == lint.krate && l.module.starts_with(&lint.module)
89+
let other_file = &data.source_map.files[l.span.file];
90+
other_file.krate == lint_file.krate && other_file.module.starts_with(&lint_file.module)
9091
} else {
9192
false
9293
}
9394
}) {
9495
// Try to delete a sub-module that matches the lint's name
95-
let removed_mod = if lint.path.file_name().map(OsStr::as_encoded_bytes) == Some(b"mod.rs") {
96-
let mut path = lint.path.to_path_buf();
96+
let removed_mod = if lint_file.path.file_name().map(OsStr::as_encoded_bytes) == Some(b"mod.rs") {
97+
let mut path = lint_file.path.to_path_buf();
9798
path.set_file_name(name);
9899
path.set_extension("rs");
99100
delete_file_if_exists(&path)
100101
} else {
101102
false
102103
};
103104

104-
FileUpdater::default().update_file(&lint.path, &mut |_, src, dst| {
105+
FileUpdater::default().update_file(&lint_file.path, &mut |_, src, dst| {
105106
let (a, b, c, d) = if removed_mod
106107
&& let mod_decl = format!("\nmod {name};")
107108
&& let Some(mod_start) = src.find(&mod_decl)
@@ -132,7 +133,7 @@ fn remove_lint_declaration(name: &str, lint: &ActiveLint, lints: &FxHashMap<Stri
132133
});
133134
} else {
134135
// No other lint in the same module or a sub-module.
135-
delete_file_if_exists(&lint.path);
136+
delete_file_if_exists(&lint_file.path);
136137
}
137138
remove_test_assets(name);
138139
}

clippy_dev/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
extern crate rustc_driver;
2222

2323
extern crate rustc_data_structures;
24+
extern crate rustc_index;
2425
extern crate rustc_lexer;
2526
extern crate rustc_literal_escaper;
2627

@@ -38,3 +39,4 @@ pub mod update_lints;
3839
pub mod utils;
3940

4041
mod parse;
42+
mod source_map;

clippy_dev/src/parse.rs

Lines changed: 37 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1-
use crate::utils::{ErrAction, File, expect_action, walk_dir_no_dot_or_target};
1+
use crate::source_map::{SourceFile, SourceMap, Span};
2+
use crate::utils::{ErrAction, expect_action, walk_dir_no_dot_or_target};
23
use core::ops::Range;
34
use core::slice;
45
use rustc_data_structures::fx::FxHashMap;
56
use rustc_lexer::{self as lexer, FrontmatterAllowed};
67
use std::fs;
7-
use std::path::{self, Path, PathBuf};
8+
use std::path::{self, Path};
89

910
#[derive(Clone, Copy)]
1011
pub enum Token<'a> {
@@ -184,11 +185,8 @@ impl<'txt> RustSearcher<'txt> {
184185
}
185186

186187
pub struct ActiveLint {
187-
pub krate: String,
188-
pub module: String,
189188
pub group: String,
190-
pub path: PathBuf,
191-
pub span: Range<u32>,
189+
pub span: Span,
192190
}
193191

194192
pub struct DeprecatedLint {
@@ -207,24 +205,26 @@ pub enum Lint {
207205
Renamed(RenamedLint),
208206
}
209207

210-
pub struct LintList {
208+
pub struct ParsedData {
209+
pub source_map: SourceMap,
211210
pub lints: FxHashMap<String, Lint>,
212211
pub deprecated_span: Range<u32>,
213212
pub renamed_span: Range<u32>,
214213
}
215-
impl LintList {
214+
impl ParsedData {
216215
pub fn collect() -> Self {
217216
// 2025-05: Initial capacities should fit everything without reallocating.
218217
let mut parser = Parser {
218+
source_map: SourceMap::with_capacity(8, 1000),
219219
lints: FxHashMap::with_capacity_and_hasher(1000, Default::default()),
220220
deprecated_span: 0..0,
221221
renamed_span: 0..0,
222-
contents: String::with_capacity(1024 * 128 * 3),
223222
};
224223
parser.parse_src_files();
225224
parser.parse_deprecated_lints();
226225

227-
LintList {
226+
ParsedData {
227+
source_map: parser.source_map,
228228
lints: parser.lints,
229229
deprecated_span: parser.deprecated_span,
230230
renamed_span: parser.renamed_span,
@@ -233,10 +233,10 @@ impl LintList {
233233
}
234234

235235
struct Parser {
236+
source_map: SourceMap,
236237
lints: FxHashMap<String, Lint>,
237238
deprecated_span: Range<u32>,
238239
renamed_span: Range<u32>,
239-
contents: String,
240240
}
241241
impl Parser {
242242
/// Parses all source files looking for lint declarations (`declare_clippy_lint! { .. }`).
@@ -250,8 +250,9 @@ impl Parser {
250250
continue;
251251
};
252252
if crate_path.starts_with("clippy_lints") && crate_path != "clippy_lints_internal" {
253-
crate_path.push_str("/src");
254-
let krate = &crate_path[..crate_path.len() - 4];
253+
let krate = self.source_map.add_new_crate(&crate_path);
254+
crate_path.push(path::MAIN_SEPARATOR);
255+
crate_path.push_str("src");
255256
for e in walk_dir_no_dot_or_target(&crate_path) {
256257
let e = expect_action(e, ErrAction::Read, &crate_path);
257258
if let Some(path) = e.path().to_str()
@@ -270,15 +271,16 @@ impl Parser {
270271
};
271272
path.replace(path::MAIN_SEPARATOR, "::")
272273
};
273-
self.parse_src_file(e.path(), krate, &module);
274+
let file = self.source_map.load_new_file(e.path(), krate, module);
275+
self.parse_src_file(file);
274276
}
275277
}
276278
}
277279
}
278280
}
279281

280282
/// Parse a source file looking for `declare_clippy_lint` macro invocations.
281-
fn parse_src_file(&mut self, path: &Path, krate: &str, module: &str) {
283+
fn parse_src_file(&mut self, file: SourceFile) {
282284
#[allow(clippy::enum_glob_use)]
283285
use Token::*;
284286
#[rustfmt::skip]
@@ -291,20 +293,20 @@ impl Parser {
291293
Ident("pub"), CaptureIdent, Comma, AnyComment, CaptureIdent, Comma,
292294
];
293295

294-
File::open_read_to_cleared_string(path, &mut self.contents);
295-
let mut searcher = RustSearcher::new(&self.contents);
296+
let mut searcher = RustSearcher::new(&self.source_map.files[file].contents);
296297
while searcher.find_token(Ident("declare_clippy_lint")) {
297298
let start = searcher.pos() - "declare_clippy_lint".len() as u32;
298299
let (mut name, mut group) = ("", "");
299300
if searcher.match_tokens(DECL_TOKENS, &mut [&mut name, &mut group]) && searcher.find_token(CloseBrace) {
300301
self.lints.insert(
301302
name.to_ascii_lowercase(),
302303
Lint::Active(ActiveLint {
303-
krate: krate.into(),
304-
module: module.into(),
305304
group: group.into(),
306-
path: path.into(),
307-
span: start..searcher.pos(),
305+
span: Span {
306+
file,
307+
start,
308+
end: searcher.pos(),
309+
},
308310
}),
309311
);
310312
}
@@ -332,10 +334,15 @@ impl Parser {
332334
Bang, OpenBrace, Ident("RENAMED"), OpenParen, Ident("RENAMED_VERSION"), CloseParen, Eq, OpenBracket,
333335
];
334336

335-
let path = Path::new("clippy_lints/src/deprecated_lints.rs");
336-
File::open_read_to_cleared_string(path, &mut self.contents);
337+
let krate = self.source_map.add_crate("clippy_lints");
338+
let file = self.source_map.load_file(
339+
Path::new("clippy_lints/src/deprecated_lints.rs"),
340+
krate,
341+
"deprecated_lints",
342+
);
343+
let file = &self.source_map.files[file];
337344

338-
let mut searcher = RustSearcher::new(&self.contents);
345+
let mut searcher = RustSearcher::new(&file.contents);
339346
// First instance is the macro definition.
340347
assert!(
341348
searcher.find_token(Ident("declare_with_version")),
@@ -350,10 +357,10 @@ impl Parser {
350357
let mut reason = "";
351358
while searcher.match_tokens(DECL_TOKENS, &mut [&mut version, &mut name, &mut reason]) {
352359
self.lints.insert(
353-
parse_clippy_lint_name(path, name),
360+
parse_clippy_lint_name(&file.path, name),
354361
Lint::Deprecated(DeprecatedLint {
355-
reason: parse_str_single_line(path, reason),
356-
version: parse_str_single_line(path, version),
362+
reason: parse_str_single_line(&file.path, reason),
363+
version: parse_str_single_line(&file.path, version),
357364
}),
358365
);
359366
end = searcher.pos();
@@ -371,10 +378,10 @@ impl Parser {
371378
let mut new_name = "";
372379
while searcher.match_tokens(DECL_TOKENS, &mut [&mut version, &mut old_name, &mut new_name]) {
373380
self.lints.insert(
374-
parse_clippy_lint_name(path, old_name),
381+
parse_clippy_lint_name(&file.path, old_name),
375382
Lint::Renamed(RenamedLint {
376-
new_name: parse_maybe_clippy_lint_name(path, new_name),
377-
version: parse_str_single_line(path, version),
383+
new_name: parse_maybe_clippy_lint_name(&file.path, new_name),
384+
version: parse_str_single_line(&file.path, version),
378385
}),
379386
);
380387
end = searcher.pos();

clippy_dev/src/rename_lint.rs

Lines changed: 24 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use crate::parse::{Lint, LintList, RenamedLint, RustSearcher, Token};
1+
use crate::parse::{Lint, ParsedData, RenamedLint, RustSearcher, Token};
22
use crate::update_lints::generate_lint_files;
33
use crate::utils::{
44
ErrAction, FileUpdater, UpdateMode, UpdateStatus, Version, delete_dir_if_exists, delete_file_if_exists,
@@ -36,15 +36,15 @@ pub fn rename(clippy_version: Version, old_name: &str, new_name: &str, uplift: b
3636
}
3737

3838
let mut updater = FileUpdater::default();
39-
let mut list = LintList::collect();
39+
let mut data = ParsedData::collect();
4040

4141
// Update any existing renames
4242
let new_name_prefixed = if uplift {
4343
new_name.to_owned()
4444
} else {
4545
String::from_iter(["clippy::", new_name])
4646
};
47-
for lint in list.lints.values_mut() {
47+
for lint in data.lints.values_mut() {
4848
if let Lint::Renamed(lint) = lint
4949
&& lint.new_name.strip_prefix("clippy::") == Some(old_name)
5050
{
@@ -53,11 +53,11 @@ pub fn rename(clippy_version: Version, old_name: &str, new_name: &str, uplift: b
5353
}
5454

5555
// Mark the lint as renamed
56-
let Some(old_entry) = list.lints.get_mut(old_name) else {
56+
let Some(old_entry) = data.lints.get_mut(old_name) else {
5757
eprintln!("error: failed to find lint `{old_name}`");
5858
return;
5959
};
60-
let Lint::Active(mut lint) = mem::replace(
60+
let Lint::Active(lint) = mem::replace(
6161
old_entry,
6262
Lint::Renamed(RenamedLint {
6363
new_name: new_name_prefixed,
@@ -70,19 +70,20 @@ pub fn rename(clippy_version: Version, old_name: &str, new_name: &str, uplift: b
7070

7171
let mut mod_edit = ModEdit::None;
7272
if uplift {
73-
let is_unique_mod = list.lints.values().any(|x| {
73+
let lint_file = &data.source_map.files[lint.span.file];
74+
let is_unique_mod = data.lints.values().any(|x| {
7475
if let Lint::Active(x) = x {
75-
x.module == lint.module
76+
data.source_map.files[x.span.file].module == lint_file.module
7677
} else {
7778
false
7879
}
7980
});
8081
if is_unique_mod {
81-
if delete_file_if_exists(lint.path.as_ref()) {
82+
if delete_file_if_exists(lint_file.path.as_ref()) {
8283
mod_edit = ModEdit::Delete;
8384
}
8485
} else {
85-
updater.update_file(&lint.path, &mut |_, src, dst| -> UpdateStatus {
86+
updater.update_file(&lint_file.path, &mut |_, src, dst| -> UpdateStatus {
8687
let mut start = &src[..lint.span.start as usize];
8788
if start.ends_with("\n\n") {
8889
start = &start[..start.len() - 1];
@@ -96,27 +97,28 @@ pub fn rename(clippy_version: Version, old_name: &str, new_name: &str, uplift: b
9697
UpdateStatus::Changed
9798
});
9899
}
99-
delete_test_files(old_name, &list.lints);
100-
} else if let StdEntry::Vacant(entry) = list.lints.entry(new_name.to_owned()) {
101-
if lint.module.ends_with(old_name)
102-
&& lint
100+
delete_test_files(old_name, &data.lints);
101+
} else if let StdEntry::Vacant(entry) = data.lints.entry(new_name.to_owned()) {
102+
let lint_file = &mut data.source_map.files[lint.span.file];
103+
if lint_file.module.ends_with(old_name)
104+
&& lint_file
103105
.path
104106
.file_stem()
105107
.is_some_and(|x| x.as_encoded_bytes() == old_name.as_bytes())
106108
{
107-
let mut new_path = lint.path.with_file_name(new_name).into_os_string();
109+
let mut new_path = lint_file.path.with_file_name(new_name).into_os_string();
108110
new_path.push(".rs");
109-
if try_rename_file(lint.path.as_ref(), new_path.as_ref()) {
110-
lint.path = new_path.into();
111+
if try_rename_file(lint_file.path.as_ref(), new_path.as_ref()) {
112+
lint_file.path = new_path.into();
111113
mod_edit = ModEdit::Rename;
112-
}
113114

114-
let mod_len = lint.module.len();
115-
lint.module.truncate(mod_len - old_name.len());
116-
lint.module.push_str(new_name);
115+
let mod_len = lint_file.module.len();
116+
lint_file.module.truncate(mod_len - old_name.len());
117+
lint_file.module.push_str(new_name);
118+
}
117119
}
118120
entry.insert(Lint::Active(lint));
119-
rename_test_files(old_name, new_name, &list.lints);
121+
rename_test_files(old_name, new_name, &data.lints);
120122
} else {
121123
println!("Renamed `clippy::{old_name}` to `clippy::{new_name}`");
122124
println!("Since `{new_name}` already exists the existing code has not been changed");
@@ -130,7 +132,7 @@ pub fn rename(clippy_version: Version, old_name: &str, new_name: &str, uplift: b
130132
updater.update_file(e.path(), &mut update_fn);
131133
}
132134
}
133-
generate_lint_files(UpdateMode::Change, &list);
135+
generate_lint_files(UpdateMode::Change, &data);
134136

135137
if uplift {
136138
println!("Uplifted `clippy::{old_name}` as `{new_name}`");

0 commit comments

Comments
 (0)