|
| 1 | +//! Generates descriptors structure for unstable feature from Unstable Book |
| 2 | +use std::{borrow::Cow, fs, path::Path}; |
| 3 | + |
| 4 | +use stdx::format_to; |
| 5 | +use test_utils::project_root; |
| 6 | +use xshell::cmd; |
| 7 | + |
| 8 | +/// This clones rustc repo, and so is not worth to keep up-to-date. We update |
| 9 | +/// manually by un-ignoring the test from time to time. |
| 10 | +#[test] |
| 11 | +#[ignore] |
| 12 | +fn sourcegen_lint_completions() { |
| 13 | + let rust_repo = project_root().join("./target/rust"); |
| 14 | + if !rust_repo.exists() { |
| 15 | + cmd!("git clone --depth=1 https://github.com/rust-lang/rust {rust_repo}").run().unwrap(); |
| 16 | + } |
| 17 | + |
| 18 | + let mut contents = r" |
| 19 | +pub struct Lint { |
| 20 | + pub label: &'static str, |
| 21 | + pub description: &'static str, |
| 22 | +} |
| 23 | +" |
| 24 | + .to_string(); |
| 25 | + generate_lint_descriptor(&mut contents); |
| 26 | + contents.push('\n'); |
| 27 | + |
| 28 | + generate_feature_descriptor(&mut contents, &rust_repo.join("src/doc/unstable-book/src")); |
| 29 | + contents.push('\n'); |
| 30 | + |
| 31 | + let lints_json = project_root().join("./target/clippy_lints.json"); |
| 32 | + cmd!("curl https://rust-lang.github.io/rust-clippy/master/lints.json --output {lints_json}") |
| 33 | + .run() |
| 34 | + .unwrap(); |
| 35 | + generate_descriptor_clippy(&mut contents, &lints_json); |
| 36 | + |
| 37 | + let contents = |
| 38 | + sourcegen::add_preamble("sourcegen_lint_completions", sourcegen::reformat(contents)); |
| 39 | + |
| 40 | + let destination = project_root().join("crates/ide_db/src/helpers/generated_lints.rs"); |
| 41 | + sourcegen::ensure_file_contents(destination.as_path(), &contents); |
| 42 | +} |
| 43 | + |
| 44 | +fn generate_lint_descriptor(buf: &mut String) { |
| 45 | + let stdout = cmd!("rustc -W help").read().unwrap(); |
| 46 | + let start_lints = stdout.find("---- ------- -------").unwrap(); |
| 47 | + let start_lint_groups = stdout.find("---- ---------").unwrap(); |
| 48 | + let end_lints = stdout.find("Lint groups provided by rustc:").unwrap(); |
| 49 | + let end_lint_groups = stdout |
| 50 | + .find("Lint tools like Clippy can provide additional lints and lint groups.") |
| 51 | + .unwrap(); |
| 52 | + buf.push_str(r#"pub const DEFAULT_LINTS: &[Lint] = &["#); |
| 53 | + buf.push('\n'); |
| 54 | + let mut lints = stdout[start_lints..end_lints] |
| 55 | + .lines() |
| 56 | + .skip(1) |
| 57 | + .filter(|l| !l.is_empty()) |
| 58 | + .map(|line| { |
| 59 | + let (name, rest) = line.trim().split_once(char::is_whitespace).unwrap(); |
| 60 | + let (_default_level, description) = |
| 61 | + rest.trim().split_once(char::is_whitespace).unwrap(); |
| 62 | + (name.trim(), Cow::Borrowed(description.trim())) |
| 63 | + }) |
| 64 | + .collect::<Vec<_>>(); |
| 65 | + lints.extend( |
| 66 | + stdout[start_lint_groups..end_lint_groups].lines().skip(1).filter(|l| !l.is_empty()).map( |
| 67 | + |line| { |
| 68 | + let (name, lints) = line.trim().split_once(char::is_whitespace).unwrap(); |
| 69 | + (name.trim(), format!("lint group for: {}", lints.trim()).into()) |
| 70 | + }, |
| 71 | + ), |
| 72 | + ); |
| 73 | + |
| 74 | + lints.sort_by(|(ident, _), (ident2, _)| ident.cmp(ident2)); |
| 75 | + lints.into_iter().for_each(|(name, description)| { |
| 76 | + push_lint_completion(buf, &name.replace("-", "_"), &description) |
| 77 | + }); |
| 78 | + buf.push_str("];\n"); |
| 79 | +} |
| 80 | + |
| 81 | +fn generate_feature_descriptor(buf: &mut String, src_dir: &Path) { |
| 82 | + let mut features = ["language-features", "library-features"] |
| 83 | + .iter() |
| 84 | + .flat_map(|it| sourcegen::list_files(&src_dir.join(it))) |
| 85 | + .filter(|path| { |
| 86 | + // Get all `.md ` files |
| 87 | + path.extension().unwrap_or_default().to_str().unwrap_or_default() == "md" |
| 88 | + }) |
| 89 | + .map(|path| { |
| 90 | + let feature_ident = path.file_stem().unwrap().to_str().unwrap().replace("-", "_"); |
| 91 | + let doc = fs::read_to_string(path).unwrap(); |
| 92 | + (feature_ident, doc) |
| 93 | + }) |
| 94 | + .collect::<Vec<_>>(); |
| 95 | + features.sort_by(|(feature_ident, _), (feature_ident2, _)| feature_ident.cmp(feature_ident2)); |
| 96 | + |
| 97 | + buf.push_str(r#"pub const FEATURES: &[Lint] = &["#); |
| 98 | + for (feature_ident, doc) in features.into_iter() { |
| 99 | + push_lint_completion(buf, &feature_ident, &doc) |
| 100 | + } |
| 101 | + buf.push('\n'); |
| 102 | + buf.push_str("];\n"); |
| 103 | +} |
| 104 | + |
| 105 | +#[derive(Default)] |
| 106 | +struct ClippyLint { |
| 107 | + help: String, |
| 108 | + id: String, |
| 109 | +} |
| 110 | + |
| 111 | +fn unescape(s: &str) -> String { |
| 112 | + s.replace(r#"\""#, "").replace(r#"\n"#, "\n").replace(r#"\r"#, "") |
| 113 | +} |
| 114 | + |
| 115 | +fn generate_descriptor_clippy(buf: &mut String, path: &Path) { |
| 116 | + let file_content = std::fs::read_to_string(path).unwrap(); |
| 117 | + let mut clippy_lints: Vec<ClippyLint> = Vec::new(); |
| 118 | + |
| 119 | + for line in file_content.lines().map(|line| line.trim()) { |
| 120 | + if line.starts_with(r#""id":"#) { |
| 121 | + let clippy_lint = ClippyLint { |
| 122 | + id: line |
| 123 | + .strip_prefix(r#""id": ""#) |
| 124 | + .expect("should be prefixed by id") |
| 125 | + .strip_suffix(r#"","#) |
| 126 | + .expect("should be suffixed by comma") |
| 127 | + .into(), |
| 128 | + help: String::new(), |
| 129 | + }; |
| 130 | + clippy_lints.push(clippy_lint) |
| 131 | + } else if line.starts_with(r#""What it does":"#) { |
| 132 | + // Typical line to strip: "What is doest": "Here is my useful content", |
| 133 | + let prefix_to_strip = r#""What it does": ""#; |
| 134 | + let suffix_to_strip = r#"","#; |
| 135 | + |
| 136 | + let clippy_lint = clippy_lints.last_mut().expect("clippy lint must already exist"); |
| 137 | + clippy_lint.help = line |
| 138 | + .strip_prefix(prefix_to_strip) |
| 139 | + .expect("should be prefixed by what it does") |
| 140 | + .strip_suffix(suffix_to_strip) |
| 141 | + .map(unescape) |
| 142 | + .expect("should be suffixed by comma"); |
| 143 | + } |
| 144 | + } |
| 145 | + clippy_lints.sort_by(|lint, lint2| lint.id.cmp(&lint2.id)); |
| 146 | + |
| 147 | + buf.push_str(r#"pub const CLIPPY_LINTS: &[Lint] = &["#); |
| 148 | + buf.push('\n'); |
| 149 | + for clippy_lint in clippy_lints.into_iter() { |
| 150 | + let lint_ident = format!("clippy::{}", clippy_lint.id); |
| 151 | + let doc = clippy_lint.help; |
| 152 | + push_lint_completion(buf, &lint_ident, &doc); |
| 153 | + } |
| 154 | + buf.push_str("];\n"); |
| 155 | +} |
| 156 | + |
| 157 | +fn push_lint_completion(buf: &mut String, label: &str, description: &str) { |
| 158 | + format_to!( |
| 159 | + buf, |
| 160 | + r###" Lint {{ |
| 161 | + label: "{}", |
| 162 | + description: r##"{}"## |
| 163 | + }},"###, |
| 164 | + label, |
| 165 | + description |
| 166 | + ); |
| 167 | +} |
0 commit comments