summaryrefslogtreecommitdiffstats
path: root/src/tools/rust-analyzer/crates/sourcegen
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-17 12:02:58 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-17 12:02:58 +0000
commit698f8c2f01ea549d77d7dc3338a12e04c11057b9 (patch)
tree173a775858bd501c378080a10dca74132f05bc50 /src/tools/rust-analyzer/crates/sourcegen
parentInitial commit. (diff)
downloadrustc-698f8c2f01ea549d77d7dc3338a12e04c11057b9.tar.xz
rustc-698f8c2f01ea549d77d7dc3338a12e04c11057b9.zip
Adding upstream version 1.64.0+dfsg1.upstream/1.64.0+dfsg1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/tools/rust-analyzer/crates/sourcegen')
-rw-r--r--src/tools/rust-analyzer/crates/sourcegen/Cargo.toml16
-rw-r--r--src/tools/rust-analyzer/crates/sourcegen/src/lib.rs203
2 files changed, 219 insertions, 0 deletions
diff --git a/src/tools/rust-analyzer/crates/sourcegen/Cargo.toml b/src/tools/rust-analyzer/crates/sourcegen/Cargo.toml
new file mode 100644
index 000000000..a84110d94
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/sourcegen/Cargo.toml
@@ -0,0 +1,16 @@
+[package]
+name = "sourcegen"
+version = "0.0.0"
+description = "TBD"
+license = "MIT OR Apache-2.0"
+edition = "2021"
+rust-version = "1.57"
+
+[lib]
+doctest = false
+
+[dependencies]
+xshell = "0.2.2"
+
+[features]
+in-rust-tree = []
diff --git a/src/tools/rust-analyzer/crates/sourcegen/src/lib.rs b/src/tools/rust-analyzer/crates/sourcegen/src/lib.rs
new file mode 100644
index 000000000..ce0224ec7
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/sourcegen/src/lib.rs
@@ -0,0 +1,203 @@
+//! rust-analyzer relies heavily on source code generation.
+//!
+//! Things like feature documentation or assist tests are implemented by
+//! processing rust-analyzer's own source code and generating the appropriate
+//! output. See `sourcegen_` tests in various crates.
+//!
+//! This crate contains utilities to make this kind of source-gen easy.
+
+#![warn(rust_2018_idioms, unused_lifetimes, semicolon_in_expressions_from_macros)]
+
+use std::{
+ fmt, fs, mem,
+ path::{Path, PathBuf},
+};
+
+use xshell::{cmd, Shell};
+
+pub fn list_rust_files(dir: &Path) -> Vec<PathBuf> {
+ let mut res = list_files(dir);
+ res.retain(|it| {
+ it.file_name().unwrap_or_default().to_str().unwrap_or_default().ends_with(".rs")
+ });
+ res
+}
+
+pub fn list_files(dir: &Path) -> Vec<PathBuf> {
+ let mut res = Vec::new();
+ let mut work = vec![dir.to_path_buf()];
+ while let Some(dir) = work.pop() {
+ for entry in dir.read_dir().unwrap() {
+ let entry = entry.unwrap();
+ let file_type = entry.file_type().unwrap();
+ let path = entry.path();
+ let is_hidden =
+ path.file_name().unwrap_or_default().to_str().unwrap_or_default().starts_with('.');
+ if !is_hidden {
+ if file_type.is_dir() {
+ work.push(path);
+ } else if file_type.is_file() {
+ res.push(path);
+ }
+ }
+ }
+ }
+ res
+}
+
+#[derive(Clone)]
+pub struct CommentBlock {
+ pub id: String,
+ pub line: usize,
+ pub contents: Vec<String>,
+ is_doc: bool,
+}
+
+impl CommentBlock {
+ pub fn extract(tag: &str, text: &str) -> Vec<CommentBlock> {
+ assert!(tag.starts_with(char::is_uppercase));
+
+ let tag = format!("{}:", tag);
+ // Would be nice if we had `.retain_mut` here!
+ CommentBlock::extract_untagged(text)
+ .into_iter()
+ .filter_map(|mut block| {
+ let first = block.contents.remove(0);
+ first.strip_prefix(&tag).map(|id| {
+ if block.is_doc {
+ panic!(
+ "Use plain (non-doc) comments with tags like {}:\n {}",
+ tag, first
+ );
+ }
+
+ block.id = id.trim().to_string();
+ block
+ })
+ })
+ .collect()
+ }
+
+ pub fn extract_untagged(text: &str) -> Vec<CommentBlock> {
+ let mut res = Vec::new();
+
+ let lines = text.lines().map(str::trim_start);
+
+ let dummy_block =
+ CommentBlock { id: String::new(), line: 0, contents: Vec::new(), is_doc: false };
+ let mut block = dummy_block.clone();
+ for (line_num, line) in lines.enumerate() {
+ match line.strip_prefix("//") {
+ Some(mut contents) => {
+ if let Some('/' | '!') = contents.chars().next() {
+ contents = &contents[1..];
+ block.is_doc = true;
+ }
+ if let Some(' ') = contents.chars().next() {
+ contents = &contents[1..];
+ }
+ block.contents.push(contents.to_string());
+ }
+ None => {
+ if !block.contents.is_empty() {
+ let block = mem::replace(&mut block, dummy_block.clone());
+ res.push(block);
+ }
+ block.line = line_num + 2;
+ }
+ }
+ }
+ if !block.contents.is_empty() {
+ res.push(block);
+ }
+ res
+ }
+}
+
+#[derive(Debug)]
+pub struct Location {
+ pub file: PathBuf,
+ pub line: usize,
+}
+
+impl fmt::Display for Location {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ let path = self.file.strip_prefix(&project_root()).unwrap().display().to_string();
+ let path = path.replace('\\', "/");
+ let name = self.file.file_name().unwrap();
+ write!(
+ f,
+ "https://github.com/rust-lang/rust-analyzer/blob/master/{}#L{}[{}]",
+ path,
+ self.line,
+ name.to_str().unwrap()
+ )
+ }
+}
+
+fn ensure_rustfmt(sh: &Shell) {
+ let version = cmd!(sh, "rustfmt --version").read().unwrap_or_default();
+ if !version.contains("stable") {
+ panic!(
+ "Failed to run rustfmt from toolchain 'stable'. \
+ Please run `rustup component add rustfmt --toolchain stable` to install it.",
+ );
+ }
+}
+
+pub fn reformat(text: String) -> String {
+ let sh = Shell::new().unwrap();
+ sh.set_var("RUSTUP_TOOLCHAIN", "stable");
+ ensure_rustfmt(&sh);
+ let rustfmt_toml = project_root().join("rustfmt.toml");
+ let mut stdout = cmd!(sh, "rustfmt --config-path {rustfmt_toml} --config fn_single_line=true")
+ .stdin(text)
+ .read()
+ .unwrap();
+ if !stdout.ends_with('\n') {
+ stdout.push('\n');
+ }
+ stdout
+}
+
+pub fn add_preamble(generator: &'static str, mut text: String) -> String {
+ let preamble = format!("//! Generated by `{}`, do not edit by hand.\n\n", generator);
+ text.insert_str(0, &preamble);
+ text
+}
+
+/// Checks that the `file` has the specified `contents`. If that is not the
+/// case, updates the file and then fails the test.
+pub fn ensure_file_contents(file: &Path, contents: &str) {
+ if let Ok(old_contents) = fs::read_to_string(file) {
+ if normalize_newlines(&old_contents) == normalize_newlines(contents) {
+ // File is already up to date.
+ return;
+ }
+ }
+
+ let display_path = file.strip_prefix(&project_root()).unwrap_or(file);
+ eprintln!(
+ "\n\x1b[31;1merror\x1b[0m: {} was not up-to-date, updating\n",
+ display_path.display()
+ );
+ if std::env::var("CI").is_ok() {
+ eprintln!(" NOTE: run `cargo test` locally and commit the updated files\n");
+ }
+ if let Some(parent) = file.parent() {
+ let _ = fs::create_dir_all(parent);
+ }
+ fs::write(file, contents).unwrap();
+ panic!("some file was not up to date and has been updated, simply re-run the tests");
+}
+
+fn normalize_newlines(s: &str) -> String {
+ s.replace("\r\n", "\n")
+}
+
+pub fn project_root() -> PathBuf {
+ let dir = env!("CARGO_MANIFEST_DIR");
+ let res = PathBuf::from(dir).parent().unwrap().parent().unwrap().to_owned();
+ assert!(res.join("triagebot.toml").exists());
+ res
+}