-
Notifications
You must be signed in to change notification settings - Fork 13.4k
Add new tidy check to ensure that rustdoc DOM IDs are all declared as expected #86178
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
GuillaumeGomez
wants to merge
3
commits into
rust-lang:master
from
GuillaumeGomez:rustdoc-id-tidy-check
Closed
Changes from all commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,173 @@ | ||
//! Checks that the rustdoc ID map is up-to-date. The goal here is to check a few things: | ||
//! | ||
//! * All IDs created by rustdoc (through JS or `.html` files generation) are declared in the | ||
//! ID map. | ||
//! * There are no unused IDs. | ||
|
||
use std::collections::HashMap; | ||
use std::ffi::OsStr; | ||
use std::fs::File; | ||
use std::io::{BufRead, BufReader}; | ||
use std::path::Path; | ||
|
||
use regex::Regex; | ||
|
||
const ID_MAP_PATH: &str = "librustdoc/html/markdown.rs"; | ||
GuillaumeGomez marked this conversation as resolved.
Show resolved
Hide resolved
|
||
const IDS_USED_IN_JS: &[&str] = &[ | ||
// This one is created in the JS and therefore cannot be found in rust files. | ||
"help", | ||
// This one is used when we need to use a "default" ID. | ||
"deref-methods", | ||
]; | ||
|
||
fn extract_ids(path: &Path, bad: &mut bool) -> HashMap<String, usize> { | ||
let file = File::open(path).expect("failed to open file to extract rustdoc IDs"); | ||
let buf_reader = BufReader::new(file); | ||
let mut iter = buf_reader.lines(); | ||
let mut ids = HashMap::new(); | ||
|
||
while let Some(Ok(line)) = iter.next() { | ||
if line.trim_start().starts_with("html_id_map!(") { | ||
break; | ||
} | ||
} | ||
// We're now in the function body, time to retrieve the IDs! | ||
while let Some(line) = iter.next() { | ||
let line = line.unwrap(); | ||
let line = line.trim_start(); | ||
if line.starts_with("// ") { | ||
// It's a comment, ignoring this line... | ||
continue; | ||
} else if line.starts_with(")") { | ||
// We reached the end of the IDs declaration list. | ||
break; | ||
} | ||
GuillaumeGomez marked this conversation as resolved.
Show resolved
Hide resolved
|
||
let id = line.split('"').skip(1).next().unwrap(); | ||
if ids.insert(id.to_owned(), 0).is_some() { | ||
eprintln!( | ||
"=> ID `{}` is defined more than once in the ID map in file `{}`", | ||
id, | ||
path.display(), | ||
); | ||
*bad = true; | ||
} | ||
} | ||
if ids.is_empty() { | ||
eprintln!("=> No IDs were found in rustdoc in file `{}`...", path.display()); | ||
*bad = true; | ||
} | ||
ids | ||
} | ||
|
||
fn check_id( | ||
path: &Path, | ||
id: &str, | ||
ids: &mut HashMap<String, usize>, | ||
line_nb: usize, | ||
bad: &mut bool, | ||
) { | ||
if id.contains('{') { | ||
// This is a formatted ID, no need to check it! | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: Is it that there's no need to check it, or we're incapable of checking it? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Incapable. |
||
return; | ||
} | ||
let id = id.to_owned(); | ||
GuillaumeGomez marked this conversation as resolved.
Show resolved
Hide resolved
|
||
match ids.get_mut(&id) { | ||
Some(nb) => *nb += 1, | ||
None => { | ||
eprintln!( | ||
"=> ID `{}` in file `{}` at line {} is missing from `init_id_map`", | ||
id, | ||
path.display(), | ||
line_nb + 1, | ||
); | ||
*bad = true; | ||
} | ||
} | ||
} | ||
|
||
fn check_ids( | ||
path: &Path, | ||
f: &str, | ||
ids: &mut HashMap<String, usize>, | ||
regex: &Regex, | ||
bad: &mut bool, | ||
small_section_header_checked: &mut usize, | ||
) { | ||
let mut is_checking_small_section_header = None; | ||
|
||
for (line_nb, line) in f.lines().enumerate() { | ||
let trimmed = line.trim_start(); | ||
// We're not interested in comments or doc comments. | ||
if trimmed.starts_with("//") { | ||
continue; | ||
} else if let Some(start_line) = is_checking_small_section_header { | ||
if line_nb == start_line + 2 { | ||
check_id(path, trimmed.split('"').skip(1).next().unwrap(), ids, line_nb, bad); | ||
is_checking_small_section_header = None; | ||
} | ||
} else if trimmed.contains("write_small_section_header(") | ||
&& !trimmed.contains("fn write_small_section_header(") | ||
{ | ||
// First we extract the arguments. | ||
let trimmed = trimmed.split("write_small_section_header(").skip(1).next().unwrap_or(""); | ||
// This function is used to create section: the second argument of the function is an | ||
// ID and we need to check it as well, hence this specific check... | ||
if trimmed.contains(',') { | ||
// This is a call made on one line, so we can simply check it! | ||
check_id(path, trimmed.split('"').skip(1).next().unwrap(), ids, line_nb, bad); | ||
} else { | ||
is_checking_small_section_header = Some(line_nb); | ||
} | ||
*small_section_header_checked += 1; | ||
continue; | ||
} | ||
for cap in regex.captures_iter(line) { | ||
check_id(path, &cap[1], ids, line_nb, bad); | ||
} | ||
} | ||
} | ||
|
||
pub fn check(path: &Path, bad: &mut bool) { | ||
// matches ` id="blabla"` | ||
let regex = Regex::new(r#"[\s"]id=\\?["']([^\s\\]+)\\?["'][\s\\>"{]"#).unwrap(); | ||
|
||
println!("Checking rustdoc IDs..."); | ||
let mut ids = extract_ids(&path.join(ID_MAP_PATH), bad); | ||
let mut small_section_header_checked = 0; | ||
if *bad { | ||
return; | ||
} | ||
super::walk( | ||
&path.join("librustdoc/html"), | ||
GuillaumeGomez marked this conversation as resolved.
Show resolved
Hide resolved
|
||
&mut |path| super::filter_dirs(path), | ||
&mut |entry, contents| { | ||
let path = entry.path(); | ||
let file_name = entry.file_name(); | ||
if path.extension() == Some(OsStr::new("html")) | ||
|| (path.extension() == Some(OsStr::new("rs")) && file_name != "tests.rs") | ||
{ | ||
check_ids(path, contents, &mut ids, ®ex, bad, &mut small_section_header_checked); | ||
} | ||
}, | ||
); | ||
if small_section_header_checked == 0 { | ||
eprintln!( | ||
"=> No call to the `write_small_section_header` function was found. Was it renamed?", | ||
); | ||
*bad = true; | ||
} | ||
for (id, nb) in ids { | ||
if IDS_USED_IN_JS.contains(&id.as_str()) { | ||
if nb != 0 { | ||
eprintln!("=> ID `{}` is not supposed to be used in Rust code but in the JS!", id); | ||
*bad = true; | ||
} | ||
} else if nb == 0 { | ||
eprintln!( | ||
"=> ID `{}` is unused, it should be removed from `init_id_map` in file `{}`", | ||
id, ID_MAP_PATH | ||
); | ||
*bad = true; | ||
} | ||
} | ||
} |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.