mirror of
https://github.com/astral-sh/ruff.git
synced 2025-07-27 06:53:54 +00:00

## 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.
297 lines
9.9 KiB
Rust
297 lines
9.9 KiB
Rust
use path_absolutize::path_dedot;
|
|
use ruff_cache::cache_dir;
|
|
use ruff_formatter::{FormatOptions, IndentStyle, IndentWidth, LineWidth};
|
|
use ruff_graph::AnalyzeSettings;
|
|
use ruff_linter::display_settings;
|
|
use ruff_linter::settings::types::{
|
|
ExtensionMapping, FilePattern, FilePatternSet, OutputFormat, UnsafeFixes,
|
|
};
|
|
use ruff_linter::settings::LinterSettings;
|
|
use ruff_macros::CacheKey;
|
|
use ruff_python_ast::PySourceType;
|
|
use ruff_python_formatter::{
|
|
DocstringCode, DocstringCodeLineWidth, MagicTrailingComma, PreviewMode, PyFormatOptions,
|
|
QuoteStyle,
|
|
};
|
|
use ruff_source_file::find_newline;
|
|
use std::fmt;
|
|
use std::path::{Path, PathBuf};
|
|
|
|
#[derive(Debug, CacheKey)]
|
|
#[allow(clippy::struct_excessive_bools)]
|
|
pub struct Settings {
|
|
#[cache_key(ignore)]
|
|
pub cache_dir: PathBuf,
|
|
#[cache_key(ignore)]
|
|
pub fix: bool,
|
|
#[cache_key(ignore)]
|
|
pub fix_only: bool,
|
|
#[cache_key(ignore)]
|
|
pub unsafe_fixes: UnsafeFixes,
|
|
#[cache_key(ignore)]
|
|
pub output_format: OutputFormat,
|
|
#[cache_key(ignore)]
|
|
pub show_fixes: bool,
|
|
|
|
pub file_resolver: FileResolverSettings,
|
|
pub linter: LinterSettings,
|
|
pub formatter: FormatterSettings,
|
|
pub analyze: AnalyzeSettings,
|
|
}
|
|
|
|
impl Default for Settings {
|
|
fn default() -> Self {
|
|
let project_root = path_dedot::CWD.as_path();
|
|
Self {
|
|
cache_dir: cache_dir(project_root),
|
|
fix: false,
|
|
fix_only: false,
|
|
output_format: OutputFormat::default(),
|
|
show_fixes: false,
|
|
unsafe_fixes: UnsafeFixes::default(),
|
|
linter: LinterSettings::new(project_root),
|
|
file_resolver: FileResolverSettings::new(project_root),
|
|
formatter: FormatterSettings::default(),
|
|
analyze: AnalyzeSettings::default(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl fmt::Display for Settings {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
writeln!(f, "\n# General Settings")?;
|
|
display_settings! {
|
|
formatter = f,
|
|
fields = [
|
|
self.cache_dir | path,
|
|
self.fix,
|
|
self.fix_only,
|
|
self.output_format,
|
|
self.show_fixes,
|
|
self.unsafe_fixes,
|
|
self.file_resolver | nested,
|
|
self.linter | nested,
|
|
self.formatter | nested,
|
|
self.analyze | nested,
|
|
]
|
|
}
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, CacheKey)]
|
|
pub struct FileResolverSettings {
|
|
pub exclude: FilePatternSet,
|
|
pub extend_exclude: FilePatternSet,
|
|
pub force_exclude: bool,
|
|
pub include: FilePatternSet,
|
|
pub extend_include: FilePatternSet,
|
|
pub respect_gitignore: bool,
|
|
pub project_root: PathBuf,
|
|
}
|
|
|
|
impl fmt::Display for FileResolverSettings {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
writeln!(f, "\n# File Resolver Settings")?;
|
|
display_settings! {
|
|
formatter = f,
|
|
namespace = "file_resolver",
|
|
fields = [
|
|
self.exclude,
|
|
self.extend_exclude,
|
|
self.force_exclude,
|
|
self.include,
|
|
self.extend_include,
|
|
self.respect_gitignore,
|
|
self.project_root | path,
|
|
]
|
|
}
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
pub(crate) static EXCLUDE: &[FilePattern] = &[
|
|
FilePattern::Builtin(".bzr"),
|
|
FilePattern::Builtin(".direnv"),
|
|
FilePattern::Builtin(".eggs"),
|
|
FilePattern::Builtin(".git"),
|
|
FilePattern::Builtin(".git-rewrite"),
|
|
FilePattern::Builtin(".hg"),
|
|
FilePattern::Builtin(".ipynb_checkpoints"),
|
|
FilePattern::Builtin(".mypy_cache"),
|
|
FilePattern::Builtin(".nox"),
|
|
FilePattern::Builtin(".pants.d"),
|
|
FilePattern::Builtin(".pyenv"),
|
|
FilePattern::Builtin(".pytest_cache"),
|
|
FilePattern::Builtin(".pytype"),
|
|
FilePattern::Builtin(".ruff_cache"),
|
|
FilePattern::Builtin(".svn"),
|
|
FilePattern::Builtin(".tox"),
|
|
FilePattern::Builtin(".venv"),
|
|
FilePattern::Builtin(".vscode"),
|
|
FilePattern::Builtin("__pypackages__"),
|
|
FilePattern::Builtin("_build"),
|
|
FilePattern::Builtin("buck-out"),
|
|
FilePattern::Builtin("dist"),
|
|
FilePattern::Builtin("node_modules"),
|
|
FilePattern::Builtin("site-packages"),
|
|
FilePattern::Builtin("venv"),
|
|
];
|
|
|
|
pub(crate) static INCLUDE: &[FilePattern] = &[
|
|
FilePattern::Builtin("*.py"),
|
|
FilePattern::Builtin("*.pyi"),
|
|
FilePattern::Builtin("*.ipynb"),
|
|
FilePattern::Builtin("**/pyproject.toml"),
|
|
];
|
|
|
|
impl FileResolverSettings {
|
|
fn new(project_root: &Path) -> Self {
|
|
Self {
|
|
project_root: project_root.to_path_buf(),
|
|
exclude: FilePatternSet::try_from_iter(EXCLUDE.iter().cloned()).unwrap(),
|
|
extend_exclude: FilePatternSet::default(),
|
|
extend_include: FilePatternSet::default(),
|
|
force_exclude: false,
|
|
respect_gitignore: true,
|
|
include: FilePatternSet::try_from_iter(INCLUDE.iter().cloned()).unwrap(),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(CacheKey, Clone, Debug)]
|
|
pub struct FormatterSettings {
|
|
pub exclude: FilePatternSet,
|
|
pub extension: ExtensionMapping,
|
|
pub preview: PreviewMode,
|
|
pub target_version: ruff_python_formatter::PythonVersion,
|
|
|
|
pub line_width: LineWidth,
|
|
|
|
pub indent_style: IndentStyle,
|
|
pub indent_width: IndentWidth,
|
|
|
|
pub quote_style: QuoteStyle,
|
|
|
|
pub magic_trailing_comma: MagicTrailingComma,
|
|
|
|
pub line_ending: LineEnding,
|
|
|
|
pub docstring_code_format: DocstringCode,
|
|
pub docstring_code_line_width: DocstringCodeLineWidth,
|
|
}
|
|
|
|
impl FormatterSettings {
|
|
pub fn to_format_options(&self, source_type: PySourceType, source: &str) -> PyFormatOptions {
|
|
let line_ending = match self.line_ending {
|
|
LineEnding::Lf => ruff_formatter::printer::LineEnding::LineFeed,
|
|
LineEnding::CrLf => ruff_formatter::printer::LineEnding::CarriageReturnLineFeed,
|
|
#[cfg(target_os = "windows")]
|
|
LineEnding::Native => ruff_formatter::printer::LineEnding::CarriageReturnLineFeed,
|
|
#[cfg(not(target_os = "windows"))]
|
|
LineEnding::Native => ruff_formatter::printer::LineEnding::LineFeed,
|
|
LineEnding::Auto => match find_newline(source) {
|
|
Some((_, ruff_source_file::LineEnding::Lf)) => {
|
|
ruff_formatter::printer::LineEnding::LineFeed
|
|
}
|
|
Some((_, ruff_source_file::LineEnding::CrLf)) => {
|
|
ruff_formatter::printer::LineEnding::CarriageReturnLineFeed
|
|
}
|
|
Some((_, ruff_source_file::LineEnding::Cr)) => {
|
|
ruff_formatter::printer::LineEnding::CarriageReturn
|
|
}
|
|
None => ruff_formatter::printer::LineEnding::LineFeed,
|
|
},
|
|
};
|
|
|
|
PyFormatOptions::from_source_type(source_type)
|
|
.with_target_version(self.target_version)
|
|
.with_indent_style(self.indent_style)
|
|
.with_indent_width(self.indent_width)
|
|
.with_quote_style(self.quote_style)
|
|
.with_magic_trailing_comma(self.magic_trailing_comma)
|
|
.with_preview(self.preview)
|
|
.with_line_ending(line_ending)
|
|
.with_line_width(self.line_width)
|
|
.with_docstring_code(self.docstring_code_format)
|
|
.with_docstring_code_line_width(self.docstring_code_line_width)
|
|
}
|
|
}
|
|
|
|
impl Default for FormatterSettings {
|
|
fn default() -> Self {
|
|
let default_options = PyFormatOptions::default();
|
|
|
|
Self {
|
|
exclude: FilePatternSet::default(),
|
|
extension: ExtensionMapping::default(),
|
|
target_version: default_options.target_version(),
|
|
preview: PreviewMode::Disabled,
|
|
line_width: default_options.line_width(),
|
|
line_ending: LineEnding::Auto,
|
|
indent_style: default_options.indent_style(),
|
|
indent_width: default_options.indent_width(),
|
|
quote_style: default_options.quote_style(),
|
|
magic_trailing_comma: default_options.magic_trailing_comma(),
|
|
docstring_code_format: default_options.docstring_code(),
|
|
docstring_code_line_width: default_options.docstring_code_line_width(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl fmt::Display for FormatterSettings {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
writeln!(f, "\n# Formatter Settings")?;
|
|
display_settings! {
|
|
formatter = f,
|
|
namespace = "formatter",
|
|
fields = [
|
|
self.exclude,
|
|
self.target_version | debug,
|
|
self.preview,
|
|
self.line_width,
|
|
self.line_ending,
|
|
self.indent_style,
|
|
self.indent_width,
|
|
self.quote_style,
|
|
self.magic_trailing_comma,
|
|
self.docstring_code_format,
|
|
self.docstring_code_line_width,
|
|
]
|
|
}
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
#[derive(
|
|
Copy, Clone, Debug, Eq, PartialEq, Default, CacheKey, serde::Serialize, serde::Deserialize,
|
|
)]
|
|
#[serde(rename_all = "kebab-case")]
|
|
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
|
pub enum LineEnding {
|
|
/// The newline style is detected automatically on a file per file basis.
|
|
/// Files with mixed line endings will be converted to the first detected line ending.
|
|
/// Defaults to [`LineEnding::Lf`] for a files that contain no line endings.
|
|
#[default]
|
|
Auto,
|
|
|
|
/// Line endings will be converted to `\n` as is common on Unix.
|
|
Lf,
|
|
|
|
/// Line endings will be converted to `\r\n` as is common on Windows.
|
|
CrLf,
|
|
|
|
/// Line endings will be converted to `\n` on Unix and `\r\n` on Windows.
|
|
Native,
|
|
}
|
|
|
|
impl fmt::Display for LineEnding {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
match self {
|
|
Self::Auto => write!(f, "auto"),
|
|
Self::Lf => write!(f, "lf"),
|
|
Self::CrLf => write!(f, "crlf"),
|
|
Self::Native => write!(f, "native"),
|
|
}
|
|
}
|
|
}
|