/// Suggest a name from `existing_names` that is similar to `wrong_name`. pub(crate) fn did_you_mean, T: AsRef>( existing_names: impl Iterator, wrong_name: T, ) -> Option { if wrong_name.as_ref().len() < 3 { return None; } existing_names .filter(|ref id| id.as_ref().len() >= 2) .map(|ref id| { ( id.as_ref().to_string(), strsim::damerau_levenshtein( &id.as_ref().to_lowercase(), &wrong_name.as_ref().to_lowercase(), ), ) }) .min_by_key(|(_, dist)| *dist) // Heuristic to filter out bad matches .filter(|(_, dist)| *dist <= 3) .map(|(id, _)| id) } use crate::{Db, Program, PythonVersionWithSource}; use ruff_db::diagnostic::{Annotation, Diagnostic, SubDiagnostic, SubDiagnosticSeverity}; use std::fmt::Write; /// Add a subdiagnostic to `diagnostic` that explains why a certain Python version was inferred. /// /// ty can infer the Python version from various sources, such as command-line arguments, /// configuration files, or defaults. pub fn add_inferred_python_version_hint_to_diagnostic( db: &dyn Db, diagnostic: &mut Diagnostic, action: &str, ) { let program = Program::get(db); let PythonVersionWithSource { version, source } = program.python_version_with_source(db); match source { crate::PythonVersionSource::Cli => { diagnostic.info(format_args!( "Python {version} was assumed when {action} because it was specified on the command line", )); } crate::PythonVersionSource::ConfigFile(source) => { if let Some(span) = source.span(db) { let mut sub_diagnostic = SubDiagnostic::new( SubDiagnosticSeverity::Info, format_args!("Python {version} was assumed when {action}"), ); sub_diagnostic.annotate(Annotation::primary(span).message(format_args!( "Python {version} assumed due to this configuration setting" ))); diagnostic.sub(sub_diagnostic); } else { diagnostic.info(format_args!( "Python {version} was assumed when {action} because of your configuration file(s)", )); } } crate::PythonVersionSource::PyvenvCfgFile(source) => { if let Some(span) = source.span(db) { let mut sub_diagnostic = SubDiagnostic::new( SubDiagnosticSeverity::Info, format_args!( "Python {version} was assumed when {action} because of your virtual environment" ), ); sub_diagnostic.annotate( Annotation::primary(span) .message("Python version inferred from virtual environment metadata file"), ); // TODO: it would also be nice to tell them how we resolved their virtual environment... diagnostic.sub(sub_diagnostic); } else { diagnostic.info(format_args!( "Python {version} was assumed when {action} because \ your virtual environment's pyvenv.cfg file indicated \ it was the Python version being used", )); } diagnostic.info( "No Python version was specified on the command line \ or in a configuration file", ); } crate::PythonVersionSource::PythonVSCodeExtension => { diagnostic.info(format_args!( "Python {version} was assumed when {action} \ because it's the version of the selected Python interpreter in the VS Code Python extension", )); } crate::PythonVersionSource::InstallationDirectoryLayout { site_packages_parent_dir, } => { // TODO: it would also be nice to tell them how we resolved this Python installation... diagnostic.info(format_args!( "Python {version} was assumed when {action} \ because of the layout of your Python installation" )); diagnostic.info(format_args!( "The primary `site-packages` directory of your installation was found \ at `lib/{site_packages_parent_dir}/site-packages/`" )); diagnostic.info( "No Python version was specified on the command line \ or in a configuration file", ); } crate::PythonVersionSource::Default => { diagnostic.info(format_args!( "Python {version} was assumed when {action} \ because it is the newest Python version supported by ty, \ and neither a command-line argument nor a configuration setting was provided", )); } } } /// Format a list of elements as a human-readable enumeration. /// /// Encloses every element in backticks (`1`, `2` and `3`). pub(crate) fn format_enumeration(elements: I) -> String where I: IntoIterator, IT: ExactSizeIterator + DoubleEndedIterator, D: std::fmt::Display, { let mut elements = elements.into_iter(); debug_assert!(elements.len() >= 2); let final_element = elements.next_back().unwrap(); let penultimate_element = elements.next_back().unwrap(); let mut buffer = String::new(); for element in elements { write!(&mut buffer, "`{element}`, ").ok(); } write!(&mut buffer, "`{penultimate_element}` and `{final_element}`").ok(); buffer }