diff --git a/crates/ruff/src/commands/add_noqa.rs b/crates/ruff/src/commands/add_noqa.rs index 928c5ce03e5b3..f46deeecf1e55 100644 --- a/crates/ruff/src/commands/add_noqa.rs +++ b/crates/ruff/src/commands/add_noqa.rs @@ -11,7 +11,7 @@ use ruff_linter::source_kind::SourceKind; use ruff_linter::warn_user_once; use ruff_python_ast::{PySourceType, SourceType}; use ruff_workspace::resolver::{ - PyprojectConfig, ResolvedFile, match_exclusion, python_files_in_path, + PyprojectConfig, ResolvedFile, match_exclusion, project_files_in_path, }; use crate::args::ConfigArguments; @@ -25,10 +25,22 @@ pub(crate) fn add_noqa( ) -> Result { // Collect all the files to check. let start = Instant::now(); - let (paths, resolver) = python_files_in_path(files, pyproject_config, config_arguments)?; + let (mut paths, resolver) = project_files_in_path(files, pyproject_config, config_arguments)?; let duration = start.elapsed(); debug!("Identified files to lint in: {duration:?}"); + // Filter out paths for file types not supported for linting + paths.retain(|path| { + if let Ok(ResolvedFile::Root(path) | ResolvedFile::Nested(path)) = path { + matches!( + SourceType::from(path), + SourceType::Python(PySourceType::Python | PySourceType::Stub) + ) + } else { + true + } + }); + if paths.is_empty() { warn_user_once!("No Python files found under the given path(s)"); return Ok(0); @@ -48,11 +60,7 @@ pub(crate) fn add_noqa( .par_iter() .flatten() .filter_map(|resolved_file| { - let SourceType::Python(source_type @ (PySourceType::Python | PySourceType::Stub)) = - SourceType::from(resolved_file.path()) - else { - return None; - }; + let source_type = SourceType::from(resolved_file.path()); let path = resolved_file.path(); let package = resolved_file .path() @@ -69,7 +77,7 @@ pub(crate) fn add_noqa( { return None; } - let source_kind = match SourceKind::from_path(path, SourceType::Python(source_type)) { + let source_kind = match SourceKind::from_path(path, source_type) { Ok(Some(source_kind)) => source_kind, Ok(None) => return None, Err(e) => { @@ -81,7 +89,7 @@ pub(crate) fn add_noqa( path, package, &source_kind, - source_type, + source_type.expect_python(), &settings.linter, reason, ) { diff --git a/crates/ruff/src/commands/analyze_graph.rs b/crates/ruff/src/commands/analyze_graph.rs index e36173c129715..968dac4c355a2 100644 --- a/crates/ruff/src/commands/analyze_graph.rs +++ b/crates/ruff/src/commands/analyze_graph.rs @@ -11,7 +11,7 @@ use ruff_linter::package::PackageRoot; use ruff_linter::source_kind::SourceKind; use ruff_linter::{warn_user, warn_user_once}; use ruff_python_ast::SourceType; -use ruff_workspace::resolver::{ResolvedFile, match_exclusion, python_files_in_path}; +use ruff_workspace::resolver::{ResolvedFile, match_exclusion, project_files_in_path}; use rustc_hash::{FxBuildHasher, FxHashMap}; use std::io::Write; use std::path::{Path, PathBuf}; @@ -35,7 +35,16 @@ pub(crate) fn analyze_graph( // Find all Python files. let files = resolve_default_files(args.files, false); - let (paths, resolver) = python_files_in_path(&files, &pyproject_config, config_arguments)?; + let (mut paths, resolver) = project_files_in_path(&files, &pyproject_config, config_arguments)?; + + // Filter to only Python files + paths.retain(|path| { + if let Ok(ResolvedFile::Root(path) | ResolvedFile::Nested(path)) = path { + matches!(SourceType::from(path), SourceType::Python(_)) + } else { + true + } + }); if paths.is_empty() { warn_user_once!("No Python files found under the given path(s)"); @@ -124,6 +133,7 @@ pub(crate) fn analyze_graph( let string_imports = settings.analyze.string_imports; let include_dependencies = settings.analyze.include_dependencies.get(path).cloned(); let type_checking_imports = settings.analyze.type_checking_imports; + let source_type = settings.analyze.extension.get_source_type(path); // Skip excluded files. if (settings.file_resolver.force_exclude || !resolved_file.is_root()) @@ -136,19 +146,6 @@ pub(crate) fn analyze_graph( continue; } - // Ignore non-Python files. - let source_type = match settings.analyze.extension.get_source_type(path) { - SourceType::Python(source_type) => source_type, - SourceType::Toml(_) => { - debug!("Ignoring TOML file: {}", path.display()); - continue; - } - SourceType::Markdown => { - debug!("Ignoring Markdown file: {}", path.display()); - continue; - } - }; - // Convert to system paths. let Ok(package) = package.map(SystemPathBuf::from_path_buf).transpose() else { warn!("Failed to convert package to system path"); @@ -165,10 +162,7 @@ pub(crate) fn analyze_graph( let result = inner_result.clone(); scope.spawn(move |_| { // Extract source code (handles both .py and .ipynb files) - let source_kind = match SourceKind::from_path( - path.as_std_path(), - SourceType::Python(source_type), - ) { + let source_kind = match SourceKind::from_path(path.as_std_path(), source_type) { Ok(Some(source_kind)) => source_kind, Ok(None) => { debug!("Skipping non-Python notebook: {path}"); @@ -186,7 +180,7 @@ pub(crate) fn analyze_graph( let mut imports = ModuleImports::detect( &db, source_code, - source_type, + source_type.expect_python(), &path, package.as_deref(), string_imports, diff --git a/crates/ruff/src/commands/check.rs b/crates/ruff/src/commands/check.rs index 694b1eb8f2ad7..5a80e4f5ac3b2 100644 --- a/crates/ruff/src/commands/check.rs +++ b/crates/ruff/src/commands/check.rs @@ -10,6 +10,7 @@ use log::{debug, warn}; #[cfg(not(target_family = "wasm"))] use rayon::prelude::*; use ruff_linter::message::create_panic_diagnostic; +use ruff_python_ast::{SourceType, TomlSourceType}; use rustc_hash::FxHashMap; use ruff_db::diagnostic::Diagnostic; @@ -22,7 +23,7 @@ use ruff_linter::{IOError, Violation, fs, warn_user_once}; use ruff_source_file::SourceFileBuilder; use ruff_text_size::TextRange; use ruff_workspace::resolver::{ - PyprojectConfig, ResolvedFile, match_exclusion, python_files_in_path, + PyprojectConfig, ResolvedFile, match_exclusion, project_files_in_path, }; use crate::args::ConfigArguments; @@ -41,9 +42,21 @@ pub(crate) fn check( ) -> Result { // Collect all the Python files to check. let start = Instant::now(); - let (paths, resolver) = python_files_in_path(files, pyproject_config, config_arguments)?; + let (mut paths, resolver) = project_files_in_path(files, pyproject_config, config_arguments)?; debug!("Identified files to lint in: {:?}", start.elapsed()); + // Filter out paths for file types not supported for linting + paths.retain(|path| { + if let Ok(ResolvedFile::Root(path) | ResolvedFile::Nested(path)) = path { + matches!( + SourceType::from(path), + SourceType::Python(_) | SourceType::Toml(TomlSourceType::Pyproject) + ) + } else { + true + } + }); + if paths.is_empty() { warn_user_once!("No Python files found under the given path(s)"); return Ok(Diagnostics::default()); diff --git a/crates/ruff/src/commands/check_stdin.rs b/crates/ruff/src/commands/check_stdin.rs index f76b3ab2df69e..0df58a806031b 100644 --- a/crates/ruff/src/commands/check_stdin.rs +++ b/crates/ruff/src/commands/check_stdin.rs @@ -5,7 +5,7 @@ use ruff_db::diagnostic::Diagnostic; use ruff_linter::package::PackageRoot; use ruff_linter::packaging; use ruff_linter::settings::flags; -use ruff_workspace::resolver::{PyprojectConfig, Resolver, match_exclusion, python_file_at_path}; +use ruff_workspace::resolver::{PyprojectConfig, Resolver, match_exclusion, project_file_at_path}; use crate::args::ConfigArguments; use crate::diagnostics::{Diagnostics, lint_stdin}; @@ -23,7 +23,7 @@ pub(crate) fn check_stdin( if resolver.force_exclude() { if let Some(filename) = filename { - if !python_file_at_path(filename, &mut resolver, overrides)? { + if !project_file_at_path(filename, &mut resolver, overrides)? { if fix_mode.is_apply() { parrot_stdin()?; } diff --git a/crates/ruff/src/commands/format.rs b/crates/ruff/src/commands/format.rs index 868493caf6bf6..fdf593d0c5393 100644 --- a/crates/ruff/src/commands/format.rs +++ b/crates/ruff/src/commands/format.rs @@ -38,7 +38,7 @@ use ruff_source_file::{LineIndex, LineRanges, OneIndexed, SourceFileBuilder}; use ruff_text_size::{TextLen, TextRange, TextSize}; use ruff_workspace::FormatterSettings; use ruff_workspace::resolver::{ - PyprojectConfig, ResolvedFile, Resolver, match_exclusion, python_files_in_path, + PyprojectConfig, ResolvedFile, Resolver, match_exclusion, project_files_in_path, }; use crate::args::{ConfigArguments, FormatArguments, FormatRange}; @@ -75,7 +75,7 @@ pub(crate) fn format( ) -> Result { let mode = FormatMode::from_cli(&cli); let files = resolve_default_files(cli.files, false); - let (paths, resolver) = python_files_in_path(&files, pyproject_config, config_arguments)?; + let (paths, resolver) = project_files_in_path(&files, pyproject_config, config_arguments)?; let output_format = pyproject_config.settings.output_format; let preview = pyproject_config.settings.formatter.preview; diff --git a/crates/ruff/src/commands/format_stdin.rs b/crates/ruff/src/commands/format_stdin.rs index aba5e31cbc26b..e75dd64b1fb79 100644 --- a/crates/ruff/src/commands/format_stdin.rs +++ b/crates/ruff/src/commands/format_stdin.rs @@ -7,7 +7,7 @@ use log::error; use ruff_linter::source_kind::{SourceError, SourceKind}; use ruff_python_ast::SourceType; use ruff_workspace::FormatterSettings; -use ruff_workspace::resolver::{PyprojectConfig, Resolver, match_exclusion, python_file_at_path}; +use ruff_workspace::resolver::{PyprojectConfig, Resolver, match_exclusion, project_file_at_path}; use crate::ExitStatus; use crate::args::{ConfigArguments, FormatArguments, FormatRange}; @@ -30,7 +30,7 @@ pub(crate) fn format_stdin( if resolver.force_exclude() { if let Some(filename) = cli.stdin_filename.as_deref() { - if !python_file_at_path(filename, &mut resolver, config_arguments)? { + if !project_file_at_path(filename, &mut resolver, config_arguments)? { if mode.is_write() { parrot_stdin()?; } diff --git a/crates/ruff/src/commands/show_files.rs b/crates/ruff/src/commands/show_files.rs index 7c74837fd3495..22826dbd0d5b9 100644 --- a/crates/ruff/src/commands/show_files.rs +++ b/crates/ruff/src/commands/show_files.rs @@ -5,7 +5,8 @@ use anyhow::Result; use itertools::Itertools; use ruff_linter::warn_user_once; -use ruff_workspace::resolver::{PyprojectConfig, ResolvedFile, python_files_in_path}; +use ruff_python_ast::{SourceType, TomlSourceType}; +use ruff_workspace::resolver::{PyprojectConfig, ResolvedFile, project_files_in_path}; use crate::args::ConfigArguments; @@ -17,7 +18,19 @@ pub(crate) fn show_files( writer: &mut impl Write, ) -> Result<()> { // Collect all files in the hierarchy. - let (paths, _resolver) = python_files_in_path(files, pyproject_config, config_arguments)?; + let (mut paths, _resolver) = project_files_in_path(files, pyproject_config, config_arguments)?; + + // Filter out paths for file types not supported for linting + paths.retain(|path| { + if let Ok(ResolvedFile::Root(path) | ResolvedFile::Nested(path)) = path { + matches!( + SourceType::from(path), + SourceType::Python(_) | SourceType::Toml(TomlSourceType::Pyproject) + ) + } else { + true + } + }); if paths.is_empty() { warn_user_once!("No Python files found under the given path(s)"); diff --git a/crates/ruff/src/commands/show_settings.rs b/crates/ruff/src/commands/show_settings.rs index 8c38285955e7f..ccea749ae20a7 100644 --- a/crates/ruff/src/commands/show_settings.rs +++ b/crates/ruff/src/commands/show_settings.rs @@ -4,7 +4,7 @@ use std::path::PathBuf; use anyhow::{Result, bail}; use itertools::Itertools; -use ruff_workspace::resolver::{PyprojectConfig, ResolvedFile, python_files_in_path}; +use ruff_workspace::resolver::{PyprojectConfig, ResolvedFile, project_files_in_path}; use crate::args::ConfigArguments; @@ -16,7 +16,7 @@ pub(crate) fn show_settings( writer: &mut impl Write, ) -> Result<()> { // Collect all files in the hierarchy. - let (paths, resolver) = python_files_in_path(files, pyproject_config, config_arguments)?; + let (paths, resolver) = project_files_in_path(files, pyproject_config, config_arguments)?; // Print the list of files. let Some(path) = paths diff --git a/crates/ruff/tests/cli/snapshots/cli__lint__requires_python_no_tool_preview_enabled.snap b/crates/ruff/tests/cli/snapshots/cli__lint__requires_python_no_tool_preview_enabled.snap index 885ea0a77fa49..3ada1e2365f76 100644 --- a/crates/ruff/tests/cli/snapshots/cli__lint__requires_python_no_tool_preview_enabled.snap +++ b/crates/ruff/tests/cli/snapshots/cli__lint__requires_python_no_tool_preview_enabled.snap @@ -63,6 +63,7 @@ file_resolver.include = [ "*.pyw", "*.ipynb", "**/pyproject.toml", + "*.md", ] file_resolver.extend_include = [] file_resolver.respect_gitignore = true diff --git a/crates/ruff_dev/src/format_dev.rs b/crates/ruff_dev/src/format_dev.rs index 82932e76fc7e3..0f25e87bc3553 100644 --- a/crates/ruff_dev/src/format_dev.rs +++ b/crates/ruff_dev/src/format_dev.rs @@ -36,7 +36,7 @@ use ruff_python_formatter::{ FormatModuleError, MagicTrailingComma, PreviewMode, PyFormatOptions, format_module_source, }; use ruff_python_parser::ParseError; -use ruff_workspace::resolver::{PyprojectConfig, ResolvedFile, Resolver, python_files_in_path}; +use ruff_workspace::resolver::{PyprojectConfig, ResolvedFile, Resolver, project_files_in_path}; fn parse_cli(dirs: &[PathBuf]) -> anyhow::Result<(FormatArguments, ConfigArguments)> { let args_matches = FormatCommand::command() @@ -68,7 +68,7 @@ fn ruff_check_paths<'a>( cli: &FormatArguments, config_arguments: &ConfigArguments, ) -> anyhow::Result<(Vec>, Resolver<'a>)> { - let (paths, resolver) = python_files_in_path(&cli.files, pyproject_config, config_arguments)?; + let (paths, resolver) = project_files_in_path(&cli.files, pyproject_config, config_arguments)?; Ok((paths, resolver)) } diff --git a/crates/ruff_workspace/src/options.rs b/crates/ruff_workspace/src/options.rs index 0f8697445ec9d..bc6339c5c4a7b 100644 --- a/crates/ruff_workspace/src/options.rs +++ b/crates/ruff_workspace/src/options.rs @@ -262,7 +262,7 @@ pub struct Options { /// /// For more information on the glob syntax, refer to the [`globset` documentation](https://docs.rs/globset/latest/globset/#syntax). #[option( - default = r#"["*.py", "*.pyi", "*.pyw", "*.ipynb", "**/pyproject.toml"]"#, + default = r#"["*.py", "*.pyi", "*.pyw", "*.ipynb", "*.md", "**/pyproject.toml"]"#, value_type = "list[str]", example = r#" include = ["*.py"] diff --git a/crates/ruff_workspace/src/resolver.rs b/crates/ruff_workspace/src/resolver.rs index c253d3fc83e2b..0dfb203aad2ff 100644 --- a/crates/ruff_workspace/src/resolver.rs +++ b/crates/ruff_workspace/src/resolver.rs @@ -432,8 +432,8 @@ impl From for Relativity { } } -/// Find all Python (`.py`, `.pyi`, `.pyw`, and `.ipynb` files) in a set of paths. -pub fn python_files_in_path<'a>( +/// Find all project files in a set of paths, following configured include/exclude settings. +pub fn project_files_in_path<'a>( paths: &[PathBuf], pyproject_config: &'a PyprojectConfig, transformer: &(dyn ConfigurationTransformer + Sync), @@ -504,7 +504,7 @@ pub fn python_files_in_path<'a>( let walker = builder.build_parallel(); - // Run the `WalkParallel` to collect all Python files. + // Run the `WalkParallel` to collect all files. let state = WalkPythonFilesState::new(resolver); let mut visitor = PythonFilesVisitorBuilder::new(transformer, &state); walker.visit(&mut visitor); @@ -762,7 +762,7 @@ impl ResolvedFile { } /// Return `true` if the Python file at [`Path`] is _not_ excluded. -pub fn python_file_at_path( +pub fn project_file_at_path( path: &Path, resolver: &mut Resolver, transformer: &dyn ConfigurationTransformer, @@ -962,7 +962,7 @@ mod tests { use crate::pyproject::find_settings_toml; use crate::resolver::{ ConfigurationOrigin, ConfigurationTransformer, PyprojectConfig, PyprojectDiscoveryStrategy, - ResolvedFile, Resolver, is_file_excluded, match_exclusion, python_files_in_path, + ResolvedFile, Resolver, is_file_excluded, match_exclusion, project_files_in_path, resolve_root_settings, }; use crate::settings::Settings; @@ -1024,7 +1024,7 @@ mod tests { File::create(&file2)?; create_dir(dir2)?; - let (paths, _) = python_files_in_path( + let (paths, _) = project_files_in_path( &[root.to_path_buf()], &PyprojectConfig::new(PyprojectDiscoveryStrategy::Fixed, Settings::default(), None), &NoOpTransformer, diff --git a/crates/ruff_workspace/src/settings.rs b/crates/ruff_workspace/src/settings.rs index 6e3159e5266dc..98befe4b775fd 100644 --- a/crates/ruff_workspace/src/settings.rs +++ b/crates/ruff_workspace/src/settings.rs @@ -150,6 +150,7 @@ pub(crate) static INCLUDE_PREVIEW: &[FilePattern] = &[ FilePattern::Builtin("*.pyw"), FilePattern::Builtin("*.ipynb"), FilePattern::Builtin("**/pyproject.toml"), + FilePattern::Builtin("*.md"), ]; impl FileResolverSettings { diff --git a/docs/formatter.md b/docs/formatter.md index ebf09308aa5ad..4bd968d7f1c3b 100644 --- a/docs/formatter.md +++ b/docs/formatter.md @@ -285,43 +285,6 @@ to `` and `` respectively. [blacken-docs]: https://github.com/adamchainz/blacken-docs/ -Ruff will not automatically discover or format Markdown files in your project, -but will format any Markdown files explicitly passed with a `.md` extension: - -```shell-session -$ ruff format --preview --check docs/ -warning: No Python files found under the given path(s) - -$ ruff format --preview --check docs/*.md -13 files already formatted -``` - -This is likely to change in a future release when the feature is stabilized. -Including Markdown files without also enabling [preview mode](preview.md#preview) -will result in an error message and non-zero [exit code](#exit-codes). - -To include Markdown files by default when running Ruff on your project, add them -with [`extend-include`](settings.md#extend-include) in your project settings: - -=== "pyproject.toml" - - ```toml - [tool.ruff] - # Find and format code blocks in Markdown files - extend-include = ["*.md"] - # OR - extend-include = ["docs/*.md"] - ``` - -=== "ruff.toml" - - ```toml - # Find and format code blocks in Markdown files - extend-include = ["*.md"] - # OR - extend-include = ["docs/*.md"] - ``` - To format Markdown files with extensions other than `.md`, configure custom [`extension`](settings.md#extension) mappings. Ruff will automatically include these mapped extensions in file discovery: @@ -353,6 +316,24 @@ repos: types_or: [python, pyi, jupyter, markdown] ``` +To *disable* formatting of Markdown files, add them to +[`extend-exclude`](settings.md#extend-exclude) in your project settings: + +=== "pyproject.toml" + + ```toml + [tool.ruff] + # Disable formatting in Markdown files + extend-exclude = ["*.md"] + ``` + +=== "ruff.toml" + + ```toml + # Disable formatting in Markdown files + extend-exclude = ["*.md"] + ``` + ## Format suppression Like Black, Ruff supports `# fmt: on`, `# fmt: off`, and `# fmt: skip` pragma comments, which can