Add a subcommand to generate dependency graphs (#13402)

## Summary

This PR adds an experimental Ruff subcommand to generate dependency
graphs based on module resolution.

A few highlights:

- You can generate either dependency or dependent graphs via the
`--direction` command-line argument.
- Like Pants, we also provide an option to identify imports from string
literals (`--detect-string-imports`).
- Users can also provide additional dependency data via the
`include-dependencies` key under `[tool.ruff.import-map]`. This map uses
file paths as keys, and lists of strings as values. Those strings can be
file paths or globs.

The dependency resolution uses the red-knot module resolver which is
intended to be fully spec compliant, so it's also a chance to expose the
module resolver in a real-world setting.

The CLI is, e.g., `ruff graph build ../autobot`, which will output a
JSON map from file to files it depends on for the `autobot` project.
This commit is contained in:
Charlie Marsh 2024-09-19 21:06:32 -04:00 committed by GitHub
parent 260c2ecd15
commit 4e935f7d7d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
30 changed files with 1339 additions and 45 deletions

View file

@ -1,11 +1,14 @@
use regex::Regex;
use rustc_hash::{FxBuildHasher, FxHashMap, FxHashSet};
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
use std::path::PathBuf;
use strum::IntoEnumIterator;
use crate::options_base::{OptionsMetadata, Visit};
use crate::settings::LineEnding;
use ruff_formatter::IndentStyle;
use ruff_graph::Direction;
use ruff_linter::line_width::{IndentWidth, LineLength};
use ruff_linter::rules::flake8_import_conventions::settings::BannedAliases;
use ruff_linter::rules::flake8_pytest_style::settings::SettingsError;
@ -433,6 +436,10 @@ pub struct Options {
/// Options to configure code formatting.
#[option_group]
pub format: Option<FormatOptions>,
/// Options to configure import map generation.
#[option_group]
pub analyze: Option<AnalyzeOptions>,
}
/// Configures how Ruff checks your code.
@ -3306,6 +3313,59 @@ pub struct FormatOptions {
pub docstring_code_line_length: Option<DocstringCodeLineWidth>,
}
/// 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 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<bool>,
/// Whether to generate a map from file to files that it depends on (dependencies) or files that
/// depend on it (dependents).
#[option(
default = r#"\"dependencies\""#,
value_type = "\"dependents\" | \"dependencies\"",
example = r#"
direction = "dependencies"
"#
)]
pub direction: Option<Direction>,
/// 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.
#[option(
default = "false",
value_type = "bool",
example = r#"
detect-string-imports = true
"#
)]
pub detect_string_imports: Option<bool>,
/// 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.
#[option(
default = "{}",
value_type = "dict[str, list[str]]",
example = r#"
include-dependencies = {
"foo/bar.py": ["foo/baz/*.py"],
}
"#
)]
pub include_dependencies: Option<BTreeMap<PathBuf, Vec<String>>>,
}
#[cfg(test)]
mod tests {
use crate::options::Flake8SelfOptions;