From 0c9df89209c9f871d3cca20cd89d9e84fb0a1ac8 Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Thu, 19 Sep 2024 17:41:18 -0400 Subject: [PATCH] Gate behind preview; rename to analyze --- crates/ruff/src/args.rs | 29 +++-- .../commands/{graph.rs => analyze_graph.rs} | 20 ++-- crates/ruff/src/commands/mod.rs | 2 +- crates/ruff/src/lib.rs | 10 +- crates/ruff_graph/src/collector.rs | 47 ++++++-- crates/ruff_graph/src/lib.rs | 7 +- crates/ruff_graph/src/resolver.rs | 49 +------- crates/ruff_graph/src/settings.rs | 12 +- crates/ruff_workspace/src/configuration.rs | 51 +++++---- crates/ruff_workspace/src/options.rs | 17 ++- crates/ruff_workspace/src/resolver.rs | 1 - crates/ruff_workspace/src/settings.rs | 8 +- docs/configuration.md | 2 +- ruff.schema.json | 105 ++++++++++-------- 14 files changed, 191 insertions(+), 169 deletions(-) rename crates/ruff/src/commands/{graph.rs => analyze_graph.rs} (91%) diff --git a/crates/ruff/src/args.rs b/crates/ruff/src/args.rs index 2fb3131b5cfb05..abd6d1a4f1ab82 100644 --- a/crates/ruff/src/args.rs +++ b/crates/ruff/src/args.rs @@ -132,9 +132,9 @@ pub enum Command { Format(FormatCommand), /// Run the language server. Server(ServerCommand), - /// Analyze the import graph of the given files or directories. + /// Run analysis over Python source code. #[clap(subcommand)] - Graph(GraphCommand), + Analyze(AnalyzeCommand), /// Display Ruff's version Version { #[arg(long, value_enum, default_value = "text")] @@ -143,13 +143,13 @@ pub enum Command { } #[derive(Debug, Subcommand)] -pub enum GraphCommand { - /// Generate a map of Python file dependencies. - Build(GraphBuildCommand), +pub enum AnalyzeCommand { + /// Generate a map of Python file dependencies or dependents. + Graph(AnalyzeGraphCommand), } #[derive(Clone, Debug, clap::Parser)] -pub struct GraphBuildCommand { +pub struct AnalyzeGraphCommand { /// List of files or directories to include. #[clap(help = "List of files or directories to include [default: .]")] pub files: Vec, @@ -161,6 +161,11 @@ pub struct GraphBuildCommand { /// Attempt to detect imports from string literals. #[clap(long)] pub detect_string_imports: bool, + /// Enable preview mode. Use `--no-preview` to disable. + #[arg(long, overrides_with("no_preview"))] + preview: bool, + #[clap(long, overrides_with("preview"), hide = true)] + no_preview: bool, } // The `Parser` derive is for ruff_dev, for ruff `Args` would be sufficient @@ -765,14 +770,14 @@ impl FormatCommand { } } -impl GraphBuildCommand { +impl AnalyzeGraphCommand { /// Partition the CLI into command-line arguments and configuration /// overrides. pub fn partition( self, global_options: GlobalConfigArgs, - ) -> anyhow::Result<(GraphArgs, ConfigArguments)> { - let format_arguments = GraphArgs { + ) -> anyhow::Result<(AnalyzeGraphArgs, ConfigArguments)> { + let format_arguments = AnalyzeGraphArgs { files: self.files, direction: self.direction, }; @@ -783,6 +788,7 @@ impl GraphBuildCommand { } else { None }, + preview: resolve_bool_arg(self.preview, self.no_preview).map(PreviewMode::from), ..ExplicitConfigOverrides::default() }; @@ -1206,7 +1212,8 @@ impl LineColumnParseError { } /// CLI settings that are distinct from configuration (commands, lists of files, etc.). -pub struct GraphArgs { +#[derive(Clone, Debug)] +pub struct AnalyzeGraphArgs { pub files: Vec, pub direction: Direction, } @@ -1328,7 +1335,7 @@ impl ConfigurationTransformer for ExplicitConfigOverrides { config.extension = Some(extension.iter().cloned().collect()); } if let Some(detect_string_imports) = &self.detect_string_imports { - config.graph.detect_string_imports = Some(*detect_string_imports); + config.analyze.detect_string_imports = Some(*detect_string_imports); } config diff --git a/crates/ruff/src/commands/graph.rs b/crates/ruff/src/commands/analyze_graph.rs similarity index 91% rename from crates/ruff/src/commands/graph.rs rename to crates/ruff/src/commands/analyze_graph.rs index 2c46fbe8793ee0..187035f002dc94 100644 --- a/crates/ruff/src/commands/graph.rs +++ b/crates/ruff/src/commands/analyze_graph.rs @@ -1,11 +1,11 @@ -use crate::args::{ConfigArguments, GraphArgs}; +use crate::args::{AnalyzeGraphArgs, ConfigArguments}; use crate::resolve::resolve; use crate::{resolve_default_files, ExitStatus}; use anyhow::Result; use log::{debug, warn}; -use ruff_db::system::{SystemPath, SystemPathBuf}; +use ruff_db::system::SystemPathBuf; use ruff_graph::{Direction, ImportMap, ModuleDb, ModuleImports}; -use ruff_linter::warn_user_once; +use ruff_linter::{warn_user, warn_user_once}; use ruff_python_ast::{PySourceType, SourceType}; use ruff_workspace::resolver::{python_files_in_path, ResolvedFile}; use rustc_hash::FxHashMap; @@ -14,10 +14,16 @@ use std::path::Path; use std::sync::Arc; /// Generate an import map. -pub(crate) fn graph(args: GraphArgs, config_arguments: &ConfigArguments) -> Result { +pub(crate) fn analyze_graph( + args: AnalyzeGraphArgs, + config_arguments: &ConfigArguments, +) -> Result { // Construct the "default" settings. These are used when no `pyproject.toml` // files are present, or files are injected from outside the hierarchy. let pyproject_config = resolve(config_arguments, None)?; + if pyproject_config.settings.analyze.preview.is_disabled() { + warn_user!("`ruff analyze graph` is experimental and may change without warning"); + } // Find all Python files. let files = resolve_default_files(args.files, false); @@ -79,11 +85,11 @@ pub(crate) fn graph(args: GraphArgs, config_arguments: &ConfigArguments) -> Resu // Resolve the per-file settings. let settings = resolver.resolve(&path); - let string_imports = settings.graph.detect_string_imports; - let include_dependencies = settings.graph.include_dependencies.get(&path).cloned(); + let string_imports = settings.analyze.detect_string_imports; + let include_dependencies = settings.analyze.include_dependencies.get(&path).cloned(); // Ignore non-Python files. - let source_type = match settings.graph.extension.get(&path) { + let source_type = match settings.analyze.extension.get(&path) { None => match SourceType::from(&path) { SourceType::Python(source_type) => source_type, SourceType::Toml(_) => { diff --git a/crates/ruff/src/commands/mod.rs b/crates/ruff/src/commands/mod.rs index d7793098c86242..4d463a4ef5d150 100644 --- a/crates/ruff/src/commands/mod.rs +++ b/crates/ruff/src/commands/mod.rs @@ -1,11 +1,11 @@ pub(crate) mod add_noqa; +pub(crate) mod analyze_graph; pub(crate) mod check; pub(crate) mod check_stdin; pub(crate) mod clean; pub(crate) mod config; pub(crate) mod format; pub(crate) mod format_stdin; -pub(crate) mod graph; pub(crate) mod linter; pub(crate) mod rule; pub(crate) mod server; diff --git a/crates/ruff/src/lib.rs b/crates/ruff/src/lib.rs index 738c037bb643bd..bda58d4a8a8336 100644 --- a/crates/ruff/src/lib.rs +++ b/crates/ruff/src/lib.rs @@ -20,7 +20,9 @@ use ruff_linter::settings::types::OutputFormat; use ruff_linter::{fs, warn_user, warn_user_once}; use ruff_workspace::Settings; -use crate::args::{Args, CheckCommand, Command, FormatCommand, GraphBuildCommand, GraphCommand}; +use crate::args::{ + AnalyzeCommand, AnalyzeGraphCommand, Args, CheckCommand, Command, FormatCommand, +}; use crate::printer::{Flags as PrinterFlags, Printer}; pub mod args; @@ -186,7 +188,7 @@ pub fn run( Command::Check(args) => check(args, global_options), Command::Format(args) => format(args, global_options), Command::Server(args) => server(args), - Command::Graph(GraphCommand::Build(args)) => graph_build(args, global_options), + Command::Analyze(AnalyzeCommand::Graph(args)) => graph_build(args, global_options), } } @@ -200,10 +202,10 @@ fn format(args: FormatCommand, global_options: GlobalConfigArgs) -> Result Result { +fn graph_build(args: AnalyzeGraphCommand, global_options: GlobalConfigArgs) -> Result { let (cli, config_arguments) = args.partition(global_options)?; - commands::graph::graph(cli, &config_arguments) + commands::analyze_graph::analyze_graph(cli, &config_arguments) } fn server(args: ServerCommand) -> Result { diff --git a/crates/ruff_graph/src/collector.rs b/crates/ruff_graph/src/collector.rs index 5e633ab2a79d90..2ce801c4d4d199 100644 --- a/crates/ruff_graph/src/collector.rs +++ b/crates/ruff_graph/src/collector.rs @@ -1,20 +1,22 @@ use red_knot_python_semantic::ModuleName; -use ruff_python_ast::helpers::collect_import_from_member; use ruff_python_ast::visitor::source_order::{walk_body, walk_expr, walk_stmt, SourceOrderVisitor}; use ruff_python_ast::{self as ast, Expr, ModModule, Stmt}; /// Collect all imports for a given Python file. #[derive(Default, Debug)] -pub(crate) struct Collector { +pub(crate) struct Collector<'a> { + /// The path to the current module. + module_path: Option<&'a [String]>, /// Whether to detect imports from string literals. string_imports: bool, /// The collected imports from the Python AST. imports: Vec, } -impl Collector { - pub(crate) fn new(string_imports: bool) -> Self { +impl<'a> Collector<'a> { + pub(crate) fn new(module_path: Option<&'a [String]>, string_imports: bool) -> Self { Self { + module_path, string_imports, imports: Vec::new(), } @@ -27,7 +29,7 @@ impl Collector { } } -impl<'ast> SourceOrderVisitor<'ast> for Collector { +impl<'ast> SourceOrderVisitor<'ast> for Collector<'_> { fn visit_stmt(&mut self, stmt: &'ast Stmt) { match stmt { Stmt::ImportFrom(ast::StmtImportFrom { @@ -39,12 +41,35 @@ impl<'ast> SourceOrderVisitor<'ast> for Collector { let module = module.as_deref(); let level = *level; for alias in names { - if let Some(module_name) = ModuleName::from_components( - collect_import_from_member(level, module, &alias.name) - .segments() - .iter() - .cloned(), - ) { + let mut components = vec![]; + + if level > 0 { + // If we're resolving a relative import, we must have a module path. + let Some(module_path) = self.module_path else { + return; + }; + + // Start with the containing module. + components.extend(module_path.iter().map(String::as_str)); + + // Remove segments based on the number of dots. + for _ in 0..level { + if components.is_empty() { + return; + } + components.pop(); + } + } + + // Add the module path. + if let Some(module) = module { + components.extend(module.split('.')); + } + + // Add the alias name. + components.push(alias.name.as_str()); + + if let Some(module_name) = ModuleName::from_components(components) { self.imports.push(CollectedImport::ImportFrom(module_name)); } } diff --git a/crates/ruff_graph/src/lib.rs b/crates/ruff_graph/src/lib.rs index 747d039532872a..e75e082a34e1d4 100644 --- a/crates/ruff_graph/src/lib.rs +++ b/crates/ruff_graph/src/lib.rs @@ -1,7 +1,7 @@ use crate::collector::Collector; pub use crate::db::ModuleDb; use crate::resolver::Resolver; -pub use crate::settings::{Direction, GraphSettings}; +pub use crate::settings::{AnalyzeSettings, Direction}; use anyhow::Result; use red_knot_python_semantic::SemanticModel; use ruff_db::files::system_path_to_file; @@ -62,6 +62,7 @@ impl ImportMap { for import in imports.0 { reverse.0.entry(import).or_default().insert(path.clone()); } + reverse.0.entry(path).or_default(); } reverse } @@ -92,12 +93,12 @@ pub fn generate( let model = SemanticModel::new(db, file); // Collect the imports. - let imports = Collector::new(string_imports).collect(parsed.syntax()); + let imports = Collector::new(module_path.as_deref(), string_imports).collect(parsed.syntax()); // Resolve the imports. let mut resolved_imports = ModuleImports::default(); for import in imports { - let Some(resolved) = Resolver::new(&model, module_path.as_deref()).resolve(import) else { + let Some(resolved) = Resolver::new(&model).resolve(import) else { continue; }; let Some(path) = resolved.as_system_path() else { diff --git a/crates/ruff_graph/src/resolver.rs b/crates/ruff_graph/src/resolver.rs index 1105b29a7d0558..6fe463b5094dc8 100644 --- a/crates/ruff_graph/src/resolver.rs +++ b/crates/ruff_graph/src/resolver.rs @@ -1,4 +1,4 @@ -use red_knot_python_semantic::{ModuleName, SemanticModel}; +use red_knot_python_semantic::SemanticModel; use ruff_db::files::FilePath; use crate::collector::CollectedImport; @@ -6,15 +6,11 @@ use crate::collector::CollectedImport; /// Collect all imports for a given Python file. pub(crate) struct Resolver<'a> { semantic: &'a SemanticModel<'a>, - module_path: Option<&'a [String]>, } impl<'a> Resolver<'a> { - pub(crate) fn new(semantic: &'a SemanticModel<'a>, module_path: Option<&'a [String]>) -> Self { - Self { - semantic, - module_path, - } + pub(crate) fn new(semantic: &'a SemanticModel<'a>) -> Self { + Self { semantic } } pub(crate) fn resolve(&self, import: CollectedImport) -> Option<&'a FilePath> { @@ -24,17 +20,6 @@ impl<'a> Resolver<'a> { .resolve_module(import) .map(|module| module.file().path(self.semantic.db())), CollectedImport::ImportFrom(import) => { - // If the import is relative, resolve it relative to the current module. - let import = if import - .components() - .next() - .is_some_and(|segment| segment == ".") - { - from_relative_import(self.module_path?, import)? - } else { - import - }; - // Attempt to resolve the member (e.g., given `from foo import bar`, look for `foo.bar`). let parent = import.parent(); self.semantic @@ -50,31 +35,3 @@ impl<'a> Resolver<'a> { } } } - -/// Format the call path for a relative import, or `None` if the relative import extends beyond -/// the root module. -fn from_relative_import( - // The path from which the import is relative. - module: &[String], - // The path of the import itself (e.g., given `from ..foo import bar`, `[".", ".", "foo", "bar]`). - import: ModuleName, -) -> Option { - let mut components = Vec::with_capacity(module.len() + import.components().count()); - - // Start with the module path. - components.extend(module.iter().map(String::as_str)); - - // Remove segments based on the number of dots. - for segment in import.components() { - if segment == "." { - if components.is_empty() { - return None; - } - components.pop(); - } else { - components.push(segment); - } - } - - ModuleName::from_components(components) -} diff --git a/crates/ruff_graph/src/settings.rs b/crates/ruff_graph/src/settings.rs index 60147fcd8c29f4..03025b1dc63b48 100644 --- a/crates/ruff_graph/src/settings.rs +++ b/crates/ruff_graph/src/settings.rs @@ -1,24 +1,26 @@ use ruff_linter::display_settings; -use ruff_linter::settings::types::ExtensionMapping; +use ruff_linter::settings::types::{ExtensionMapping, PreviewMode}; use ruff_macros::CacheKey; use std::collections::BTreeMap; use std::fmt; use std::path::PathBuf; #[derive(Debug, Default, Clone, CacheKey)] -pub struct GraphSettings { +pub struct AnalyzeSettings { + pub preview: PreviewMode, pub detect_string_imports: bool, pub include_dependencies: BTreeMap)>, pub extension: ExtensionMapping, } -impl fmt::Display for GraphSettings { +impl fmt::Display for AnalyzeSettings { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - writeln!(f, "\n# Import Map Settings")?; + writeln!(f, "\n# Analyze Settings")?; display_settings! { formatter = f, - namespace = "import_map", + namespace = "analyze", fields = [ + self.preview, self.detect_string_imports, self.extension | debug, self.include_dependencies | debug, diff --git a/crates/ruff_workspace/src/configuration.rs b/crates/ruff_workspace/src/configuration.rs index b26acefdf7668b..4657784b68beb4 100644 --- a/crates/ruff_workspace/src/configuration.rs +++ b/crates/ruff_workspace/src/configuration.rs @@ -20,7 +20,7 @@ use strum::IntoEnumIterator; use ruff_cache::cache_dir; use ruff_formatter::IndentStyle; -use ruff_graph::{Direction, GraphSettings}; +use ruff_graph::{AnalyzeSettings, Direction}; use ruff_linter::line_width::{IndentWidth, LineLength}; use ruff_linter::registry::RuleNamespace; use ruff_linter::registry::{Rule, RuleSet, INCOMPATIBLE_CODES}; @@ -42,13 +42,13 @@ use ruff_python_formatter::{ }; use crate::options::{ - Flake8AnnotationsOptions, Flake8BanditOptions, Flake8BooleanTrapOptions, Flake8BugbearOptions, - Flake8BuiltinsOptions, Flake8ComprehensionsOptions, Flake8CopyrightOptions, - Flake8ErrMsgOptions, Flake8GetTextOptions, Flake8ImplicitStrConcatOptions, - Flake8ImportConventionsOptions, Flake8PytestStyleOptions, Flake8QuotesOptions, - Flake8SelfOptions, Flake8TidyImportsOptions, Flake8TypeCheckingOptions, - Flake8UnusedArgumentsOptions, FormatOptions, GraphOptions, IsortOptions, LintCommonOptions, - LintOptions, McCabeOptions, Options, Pep8NamingOptions, PyUpgradeOptions, PycodestyleOptions, + AnalyzeOptions, Flake8AnnotationsOptions, Flake8BanditOptions, Flake8BooleanTrapOptions, + Flake8BugbearOptions, Flake8BuiltinsOptions, Flake8ComprehensionsOptions, + Flake8CopyrightOptions, Flake8ErrMsgOptions, Flake8GetTextOptions, + Flake8ImplicitStrConcatOptions, Flake8ImportConventionsOptions, Flake8PytestStyleOptions, + Flake8QuotesOptions, Flake8SelfOptions, Flake8TidyImportsOptions, Flake8TypeCheckingOptions, + Flake8UnusedArgumentsOptions, FormatOptions, IsortOptions, LintCommonOptions, LintOptions, + McCabeOptions, Options, Pep8NamingOptions, PyUpgradeOptions, PycodestyleOptions, PydocstyleOptions, PyflakesOptions, PylintOptions, RuffOptions, }; use crate::settings::{ @@ -144,7 +144,7 @@ pub struct Configuration { pub lint: LintConfiguration, pub format: FormatConfiguration, - pub graph: ImportMapConfiguration, + pub analyze: AnalyzeConfiguration, } impl Configuration { @@ -210,17 +210,19 @@ impl Configuration { .unwrap_or(format_defaults.docstring_code_line_width), }; - let graph = self.graph; - let graph_defaults = GraphSettings::default(); + let analyze = self.analyze; + let analyze_preview = analyze.preview.unwrap_or(global_preview); + let analyze_defaults = AnalyzeSettings::default(); - let graph = GraphSettings { + let analyze = AnalyzeSettings { + preview: analyze_preview, extension: self.extension.clone().unwrap_or_default(), - detect_string_imports: graph + detect_string_imports: analyze .detect_string_imports - .unwrap_or(graph_defaults.detect_string_imports), - include_dependencies: graph + .unwrap_or(analyze_defaults.detect_string_imports), + include_dependencies: analyze .include_dependencies - .unwrap_or(graph_defaults.include_dependencies), + .unwrap_or(analyze_defaults.include_dependencies), }; let lint = self.lint; @@ -417,7 +419,7 @@ impl Configuration { }, formatter, - graph, + analyze, }) } @@ -551,8 +553,8 @@ impl Configuration { options.format.unwrap_or_default(), project_root, )?, - graph: ImportMapConfiguration::from_options( - options.graph.unwrap_or_default(), + analyze: AnalyzeConfiguration::from_options( + options.analyze.unwrap_or_default(), project_root, )?, }) @@ -594,7 +596,7 @@ impl Configuration { lint: self.lint.combine(config.lint), format: self.format.combine(config.format), - graph: self.graph.combine(config.graph), + analyze: self.analyze.combine(config.analyze), } } } @@ -1215,16 +1217,18 @@ impl FormatConfiguration { } #[derive(Clone, Debug, Default)] -pub struct ImportMapConfiguration { +pub struct AnalyzeConfiguration { + pub preview: Option, pub direction: Option, pub detect_string_imports: Option, pub include_dependencies: Option)>>, } -impl ImportMapConfiguration { +impl AnalyzeConfiguration { #[allow(clippy::needless_pass_by_value)] - pub fn from_options(options: GraphOptions, project_root: &Path) -> Result { + pub fn from_options(options: AnalyzeOptions, project_root: &Path) -> Result { Ok(Self { + preview: options.preview.map(PreviewMode::from), direction: options.direction, detect_string_imports: options.detect_string_imports, include_dependencies: options.include_dependencies.map(|dependencies| { @@ -1242,6 +1246,7 @@ impl ImportMapConfiguration { #[allow(clippy::needless_pass_by_value)] pub fn combine(self, config: Self) -> Self { Self { + preview: self.preview.or(config.preview), direction: self.direction.or(config.direction), detect_string_imports: self.detect_string_imports.or(config.detect_string_imports), include_dependencies: self.include_dependencies.or(config.include_dependencies), diff --git a/crates/ruff_workspace/src/options.rs b/crates/ruff_workspace/src/options.rs index 934de2c0f91db3..dc8f4dd9a06fd9 100644 --- a/crates/ruff_workspace/src/options.rs +++ b/crates/ruff_workspace/src/options.rs @@ -439,7 +439,7 @@ pub struct Options { /// Options to configure import map generation. #[option_group] - pub graph: Option, + pub analyze: Option, } /// Configures how Ruff checks your code. @@ -3313,13 +3313,24 @@ pub struct FormatOptions { pub docstring_code_line_length: Option, } -/// Configures Ruff's import map generation. +/// Configures Ruff's `analyze` command. #[derive( Clone, Debug, PartialEq, Eq, Default, Deserialize, Serialize, OptionsMetadata, CombineOptions, )] #[serde(deny_unknown_fields, rename_all = "kebab-case")] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] -pub struct GraphOptions { +pub struct AnalyzeOptions { + /// Whether to enable preview mode. When preview mode is enabled, Ruff will expose unstable + /// commands. + #[option( + default = "false", + value_type = "bool", + example = r#" + # Enable preview features. + preview = true + "# + )] + pub preview: Option, /// Whether to generate a map from file to files that it depends on (dependencies) or files that /// depend on it (dependents). #[option( diff --git a/crates/ruff_workspace/src/resolver.rs b/crates/ruff_workspace/src/resolver.rs index 42687be4d5c546..04750c8509362e 100644 --- a/crates/ruff_workspace/src/resolver.rs +++ b/crates/ruff_workspace/src/resolver.rs @@ -395,7 +395,6 @@ pub fn python_files_in_path<'a>( let walker = builder.build_parallel(); // Run the `WalkParallel` to collect all Python files. - let state = WalkPythonFilesState::new(resolver); let mut visitor = PythonFilesVisitorBuilder::new(transformer, &state); walker.visit(&mut visitor); diff --git a/crates/ruff_workspace/src/settings.rs b/crates/ruff_workspace/src/settings.rs index 2f8dfc527c2926..451d9d8a104ae9 100644 --- a/crates/ruff_workspace/src/settings.rs +++ b/crates/ruff_workspace/src/settings.rs @@ -1,7 +1,7 @@ use path_absolutize::path_dedot; use ruff_cache::cache_dir; use ruff_formatter::{FormatOptions, IndentStyle, IndentWidth, LineWidth}; -use ruff_graph::GraphSettings; +use ruff_graph::AnalyzeSettings; use ruff_linter::display_settings; use ruff_linter::settings::types::{ ExtensionMapping, FilePattern, FilePatternSet, OutputFormat, UnsafeFixes, @@ -36,7 +36,7 @@ pub struct Settings { pub file_resolver: FileResolverSettings, pub linter: LinterSettings, pub formatter: FormatterSettings, - pub graph: GraphSettings, + pub analyze: AnalyzeSettings, } impl Default for Settings { @@ -52,7 +52,7 @@ impl Default for Settings { linter: LinterSettings::new(project_root), file_resolver: FileResolverSettings::new(project_root), formatter: FormatterSettings::default(), - graph: GraphSettings::default(), + analyze: AnalyzeSettings::default(), } } } @@ -72,7 +72,7 @@ impl fmt::Display for Settings { self.file_resolver | nested, self.linter | nested, self.formatter | nested, - self.graph | nested, + self.analyze | nested, ] } Ok(()) diff --git a/docs/configuration.md b/docs/configuration.md index 7940f275d6814a..94b53c3ae662f2 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -522,7 +522,7 @@ Commands: clean Clear any caches in the current directory and any subdirectories format Run the Ruff formatter on the given files or directories server Run the language server - graph Analyze the import graph of the given files or directories + analyze Run analysis over Python source code version Display Ruff's version help Print this message or the help of the given subcommand(s) diff --git a/ruff.schema.json b/ruff.schema.json index f3c1cb09b1dd2a..c4adb82957e41e 100644 --- a/ruff.schema.json +++ b/ruff.schema.json @@ -16,6 +16,17 @@ "minLength": 1 } }, + "analyze": { + "description": "Options to configure import map generation.", + "anyOf": [ + { + "$ref": "#/definitions/AnalyzeOptions" + }, + { + "type": "null" + } + ] + }, "builtins": { "description": "A list of builtins to treat as defined references, in addition to the system builtins.", "type": [ @@ -424,17 +435,6 @@ } ] }, - "graph": { - "description": "Options to configure import map generation.", - "anyOf": [ - { - "$ref": "#/definitions/GraphOptions" - }, - { - "type": "null" - } - ] - }, "ignore": { "description": "A list of rule codes or prefixes to ignore. Prefixes can specify exact rules (like `F841`), entire categories (like `F`), or anything in between.\n\nWhen breaking ties between enabled and disabled rules (via `select` and `ignore`, respectively), more specific prefixes override less specific prefixes.", "deprecated": true, @@ -757,6 +757,51 @@ }, "additionalProperties": false, "definitions": { + "AnalyzeOptions": { + "description": "Configures Ruff's `analyze` command.", + "type": "object", + "properties": { + "detect-string-imports": { + "description": "Whether to detect imports from string literals. When enabled, Ruff will search for string literals that \"look like\" import paths, and include them in the import map, if they resolve to valid Python modules.", + "type": [ + "boolean", + "null" + ] + }, + "direction": { + "description": "Whether to generate a map from file to files that it depends on (dependencies) or files that depend on it (dependents).", + "anyOf": [ + { + "$ref": "#/definitions/Direction" + }, + { + "type": "null" + } + ] + }, + "include-dependencies": { + "description": "A map from file path to the list of file paths or globs that should be considered dependencies of that file, regardless of whether relevant imports are detected.", + "type": [ + "object", + "null" + ], + "additionalProperties": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "preview": { + "description": "Whether to enable preview mode. When preview mode is enabled, Ruff will expose unstable commands.", + "type": [ + "boolean", + "null" + ] + } + }, + "additionalProperties": false + }, "ApiBan": { "type": "object", "required": [ @@ -1455,44 +1500,6 @@ }, "additionalProperties": false }, - "GraphOptions": { - "description": "Configures Ruff's import map generation.", - "type": "object", - "properties": { - "detect-string-imports": { - "description": "Whether to detect imports from string literals. When enabled, Ruff will search for string literals that \"look like\" import paths, and include them in the import map, if they resolve to valid Python modules.", - "type": [ - "boolean", - "null" - ] - }, - "direction": { - "description": "Whether to generate a map from file to files that it depends on (dependencies) or files that depend on it (dependents).", - "anyOf": [ - { - "$ref": "#/definitions/Direction" - }, - { - "type": "null" - } - ] - }, - "include-dependencies": { - "description": "A map from file path to the list of file paths or globs that should be considered dependencies of that file, regardless of whether relevant imports are detected.", - "type": [ - "object", - "null" - ], - "additionalProperties": { - "type": "array", - "items": { - "type": "string" - } - } - } - }, - "additionalProperties": false - }, "ImportSection": { "anyOf": [ {