diff --git a/crates/rust-analyzer/src/cli/analysis_stats.rs b/crates/rust-analyzer/src/cli/analysis_stats.rs index 55a542c3c1..f52e1e7512 100644 --- a/crates/rust-analyzer/src/cli/analysis_stats.rs +++ b/crates/rust-analyzer/src/cli/analysis_stats.rs @@ -10,7 +10,11 @@ use hir::{ db::{AstDatabase, DefDatabase, HirDatabase}, AssocItem, Crate, Function, HasSource, HirDisplay, ModuleDef, }; -use hir_def::{body::BodySourceMap, expr::ExprId, FunctionId}; +use hir_def::{ + body::{BodySourceMap, SyntheticSyntax}, + expr::ExprId, + FunctionId, +}; use hir_ty::{TyExt, TypeWalk}; use ide::{Analysis, AnalysisHost, LineCol, RootDatabase}; use ide_db::base_db::{ @@ -28,7 +32,7 @@ use syntax::{AstNode, SyntaxNode}; use vfs::{AbsPathBuf, Vfs, VfsPath}; use crate::cli::{ - flags, + flags::{self, OutputFormat}, load_cargo::{load_workspace, LoadCargoConfig}, print_memory_usage, progress_report::ProgressReport, @@ -191,7 +195,7 @@ impl flags::AnalysisStats { ) { let mut bar = match verbosity { Verbosity::Quiet | Verbosity::Spammy => ProgressReport::hidden(), - _ if self.parallel => ProgressReport::hidden(), + _ if self.parallel || self.output.is_some() => ProgressReport::hidden(), _ => ProgressReport::new(funcs.len() as u64), }; @@ -252,7 +256,7 @@ impl flags::AnalysisStats { for (expr_id, _) in body.exprs.iter() { let ty = &inference_result[expr_id]; num_exprs += 1; - if ty.is_unknown() { + let unknown_or_partial = if ty.is_unknown() { num_exprs_unknown += 1; if verbosity.is_spammy() { if let Some((path, start, end)) = @@ -270,6 +274,7 @@ impl flags::AnalysisStats { bar.println(format!("{}: Unknown type", name,)); } } + true } else { let mut is_partially_unknown = false; ty.walk(&mut |ty| { @@ -280,7 +285,8 @@ impl flags::AnalysisStats { if is_partially_unknown { num_exprs_partially_unknown += 1; } - } + is_partially_unknown + }; if self.only.is_some() && verbosity.is_spammy() { // in super-verbose mode for just one function, we print every single expression if let Some((_, start, end)) = @@ -298,6 +304,13 @@ impl flags::AnalysisStats { bar.println(format!("unknown location: {}", ty.display(db))); } } + if unknown_or_partial && self.output == Some(OutputFormat::Csv) { + println!( + r#"{},type,"{}""#, + location_csv(db, &analysis, vfs, &sm, expr_id), + ty.display(db) + ); + } if let Some(mismatch) = inference_result.type_mismatch_for_expr(expr_id) { num_type_mismatches += 1; if verbosity.is_verbose() { @@ -323,6 +336,14 @@ impl flags::AnalysisStats { )); } } + if self.output == Some(OutputFormat::Csv) { + println!( + r#"{},mismatch,"{}","{}""#, + location_csv(db, &analysis, vfs, &sm, expr_id), + mismatch.expected.display(db), + mismatch.actual.display(db) + ); + } } } if verbosity.is_spammy() { @@ -358,6 +379,28 @@ impl flags::AnalysisStats { } } +fn location_csv( + db: &RootDatabase, + analysis: &Analysis, + vfs: &Vfs, + sm: &BodySourceMap, + expr_id: ExprId, +) -> String { + let src = match sm.expr_syntax(expr_id) { + Ok(s) => s, + Err(SyntheticSyntax) => return "synthetic,,".to_string(), + }; + let root = db.parse_or_expand(src.file_id).unwrap(); + let node = src.map(|e| e.to_node(&root).syntax().clone()); + let original_range = node.as_ref().original_file_range(db); + let path = vfs.file_path(original_range.file_id); + let line_index = analysis.file_line_index(original_range.file_id).unwrap(); + let text_range = original_range.range; + let (start, end) = + (line_index.line_col(text_range.start()), line_index.line_col(text_range.end())); + format!("{},{}:{},{}:{}", path, start.line + 1, start.col, end.line + 1, end.col) +} + fn expr_syntax_range( db: &RootDatabase, analysis: &Analysis, diff --git a/crates/rust-analyzer/src/cli/flags.rs b/crates/rust-analyzer/src/cli/flags.rs index b759d912c9..19907ebddb 100644 --- a/crates/rust-analyzer/src/cli/flags.rs +++ b/crates/rust-analyzer/src/cli/flags.rs @@ -1,6 +1,6 @@ //! Grammar for the command-line arguments. #![allow(unreachable_pub)] -use std::path::PathBuf; +use std::{path::PathBuf, str::FromStr}; use ide_ssr::{SsrPattern, SsrRule}; @@ -54,6 +54,8 @@ xflags::xflags! { /// Directory with Cargo.toml. required path: PathBuf { + optional --output format: OutputFormat + /// Randomize order in which crates, modules, and items are processed. optional --randomize /// Run type inference in parallel. @@ -160,6 +162,7 @@ pub struct Highlight { pub struct AnalysisStats { pub path: PathBuf, + pub output: Option, pub randomize: bool, pub parallel: bool, pub memory_usage: bool, @@ -215,6 +218,11 @@ impl RustAnalyzer { } // generated end +#[derive(Debug, PartialEq, Eq)] +pub enum OutputFormat { + Csv, +} + impl RustAnalyzer { pub fn verbosity(&self) -> Verbosity { if self.quiet { @@ -227,3 +235,14 @@ impl RustAnalyzer { } } } + +impl FromStr for OutputFormat { + type Err = String; + + fn from_str(s: &str) -> Result { + match s { + "csv" => Ok(Self::Csv), + _ => Err(format!("unknown output format `{}`", s)), + } + } +} diff --git a/xtask/src/flags.rs b/xtask/src/flags.rs index 69b3cb9c17..993c64ccea 100644 --- a/xtask/src/flags.rs +++ b/xtask/src/flags.rs @@ -113,9 +113,15 @@ pub struct Bb { impl Xtask { pub const HELP: &'static str = Self::HELP_; + #[allow(dead_code)] pub fn from_env() -> xflags::Result { Self::from_env_() } + + #[allow(dead_code)] + pub fn from_vec(args: Vec) -> xflags::Result { + Self::from_vec_(args) + } } // generated end