From 868eff627c24940871cee01546bc0ad97694901a Mon Sep 17 00:00:00 2001 From: Felix Williams Date: Tue, 24 Oct 2023 19:20:06 +0100 Subject: [PATCH 01/12] add extension mapping option to cli --- crates/ruff_cli/src/args.rs | 12 +++- crates/ruff_cli/src/diagnostics.rs | 83 ++++++++++++++-------- crates/ruff_linter/src/settings/mod.rs | 6 +- crates/ruff_linter/src/settings/types.rs | 63 ++++++++++++++++ crates/ruff_workspace/src/configuration.rs | 12 +++- 5 files changed, 141 insertions(+), 35 deletions(-) diff --git a/crates/ruff_cli/src/args.rs b/crates/ruff_cli/src/args.rs index c9bd65f80eede..2492e4a05ef7d 100644 --- a/crates/ruff_cli/src/args.rs +++ b/crates/ruff_cli/src/args.rs @@ -8,8 +8,8 @@ use ruff_linter::line_width::LineLength; use ruff_linter::logging::LogLevel; use ruff_linter::registry::Rule; use ruff_linter::settings::types::{ - FilePattern, PatternPrefixPair, PerFileIgnore, PreviewMode, PythonVersion, SerializationFormat, - UnsafeFixes, + ExtensionPair, FilePattern, PatternPrefixPair, PerFileIgnore, PreviewMode, PythonVersion, + SerializationFormat, UnsafeFixes, }; use ruff_linter::{RuleParser, RuleSelector, RuleSelectorParser}; use ruff_workspace::configuration::{Configuration, RuleSelection}; @@ -343,6 +343,9 @@ pub struct CheckCommand { conflicts_with = "watch", )] pub show_settings: bool, + + #[arg(long, hide = true)] + pub extension: Option>, /// Dev-only argument to show fixes #[arg(long, hide = true)] pub ecosystem_ci: bool, @@ -523,6 +526,7 @@ impl CheckCommand { force_exclude: resolve_bool_arg(self.force_exclude, self.no_force_exclude), output_format: self.output_format, show_fixes: resolve_bool_arg(self.show_fixes, self.no_show_fixes), + extension: self.extension, }, ) } @@ -633,6 +637,7 @@ pub struct CliOverrides { pub force_exclude: Option, pub output_format: Option, pub show_fixes: Option, + pub extension: Option>, } impl ConfigurationTransformer for CliOverrides { @@ -711,6 +716,9 @@ impl ConfigurationTransformer for CliOverrides { if let Some(target_version) = &self.target_version { config.target_version = Some(*target_version); } + if let Some(extension) = &self.extension { + config.lint.extension = Some(extension.clone()); + } config } diff --git a/crates/ruff_cli/src/diagnostics.rs b/crates/ruff_cli/src/diagnostics.rs index b2cf812e09bff..3ea46ef64680e 100644 --- a/crates/ruff_cli/src/diagnostics.rs +++ b/crates/ruff_cli/src/diagnostics.rs @@ -17,13 +17,13 @@ use ruff_linter::logging::DisplayParseError; use ruff_linter::message::Message; use ruff_linter::pyproject_toml::lint_pyproject_toml; use ruff_linter::registry::AsRule; -use ruff_linter::settings::types::UnsafeFixes; +use ruff_linter::settings::types::{Language, UnsafeFixes}; use ruff_linter::settings::{flags, LinterSettings}; use ruff_linter::source_kind::{SourceError, SourceKind}; use ruff_linter::{fs, IOError, SyntaxError}; use ruff_notebook::{Notebook, NotebookError, NotebookIndex}; use ruff_python_ast::imports::ImportMap; -use ruff_python_ast::{SourceType, TomlSourceType}; +use ruff_python_ast::{PySourceType, SourceType, TomlSourceType}; use ruff_source_file::{LineIndex, SourceCode, SourceFileBuilder}; use ruff_text_size::{TextRange, TextSize}; use ruff_workspace::Settings; @@ -177,6 +177,21 @@ impl AddAssign for FixMap { } } +fn get_override_source_type( + path: Option<&Path>, + extension: &FxHashMap, +) -> Option { + let Some(ext) = path.and_then(|p| p.extension()).and_then(|p| p.to_str()) else { + return None; + }; + match extension.get(ext) { + Some(Language::Python) => Some(PySourceType::Python), + Some(Language::Ipynb) => Some(PySourceType::Ipynb), + Some(Language::Pyi) => Some(PySourceType::Stub), + None => None, + } +} + /// Lint the source code at the given `Path`. pub(crate) fn lint_path( path: &Path, @@ -215,32 +230,35 @@ pub(crate) fn lint_path( }; debug!("Checking: {}", path.display()); - - let source_type = match SourceType::from(path) { - SourceType::Toml(TomlSourceType::Pyproject) => { - let messages = if settings - .rules - .iter_enabled() - .any(|rule_code| rule_code.lint_source().is_pyproject_toml()) - { - let contents = match std::fs::read_to_string(path).map_err(SourceError::from) { - Ok(contents) => contents, - Err(err) => { - return Ok(Diagnostics::from_source_error(&err, Some(path), settings)); - } + let source_type = match get_override_source_type(Some(path), &settings.extension) { + Some(source_type) => source_type, + None => match SourceType::from(path) { + SourceType::Toml(TomlSourceType::Pyproject) => { + let messages = if settings + .rules + .iter_enabled() + .any(|rule_code| rule_code.lint_source().is_pyproject_toml()) + { + let contents = match std::fs::read_to_string(path).map_err(SourceError::from) { + Ok(contents) => contents, + Err(err) => { + return Ok(Diagnostics::from_source_error(&err, Some(path), settings)); + } + }; + let source_file = + SourceFileBuilder::new(path.to_string_lossy(), contents).finish(); + lint_pyproject_toml(source_file, settings) + } else { + vec![] }; - let source_file = SourceFileBuilder::new(path.to_string_lossy(), contents).finish(); - lint_pyproject_toml(source_file, settings) - } else { - vec![] - }; - return Ok(Diagnostics { - messages, - ..Diagnostics::default() - }); - } - SourceType::Toml(_) => return Ok(Diagnostics::default()), - SourceType::Python(source_type) => source_type, + return Ok(Diagnostics { + messages, + ..Diagnostics::default() + }); + } + SourceType::Toml(_) => return Ok(Diagnostics::default()), + SourceType::Python(source_type) => source_type, + }, }; // Extract the sources from the file. @@ -355,8 +373,15 @@ pub(crate) fn lint_stdin( fix_mode: flags::FixMode, ) -> Result { // TODO(charlie): Support `pyproject.toml`. - let SourceType::Python(source_type) = path.map(SourceType::from).unwrap_or_default() else { - return Ok(Diagnostics::default()); + let source_type = if let Some(source_type) = + get_override_source_type(path, &settings.linter.extension) + { + source_type + } else { + let SourceType::Python(source_type) = path.map(SourceType::from).unwrap_or_default() else { + return Ok(Diagnostics::default()); + }; + source_type }; // Extract the sources from the file. diff --git a/crates/ruff_linter/src/settings/mod.rs b/crates/ruff_linter/src/settings/mod.rs index 32c9131883a7b..9ae191f1c805e 100644 --- a/crates/ruff_linter/src/settings/mod.rs +++ b/crates/ruff_linter/src/settings/mod.rs @@ -10,7 +10,7 @@ use globset::{Glob, GlobMatcher}; use once_cell::sync::Lazy; use path_absolutize::path_dedot; use regex::Regex; -use rustc_hash::FxHashSet; +use rustc_hash::{FxHashMap, FxHashSet}; use crate::codes::RuleCodePrefix; use ruff_macros::CacheKey; @@ -29,7 +29,7 @@ use crate::{codes, RuleSelector}; use super::line_width::IndentWidth; use self::rule_table::RuleTable; -use self::types::PreviewMode; +use self::types::{Language, PreviewMode}; use crate::rule_selector::PreviewOptions; pub mod flags; @@ -40,6 +40,7 @@ pub mod types; pub struct LinterSettings { pub exclude: FilePatternSet, pub project_root: PathBuf, + pub extension: FxHashMap, pub rules: RuleTable, pub per_file_ignores: Vec<(GlobMatcher, GlobMatcher, RuleSet)>, @@ -139,6 +140,7 @@ impl LinterSettings { .flat_map(|selector| selector.rules(&PreviewOptions::default())) .collect(), allowed_confusables: FxHashSet::from_iter([]), + extension: FxHashMap::from_iter([]), // Needs duplicating builtins: vec![], diff --git a/crates/ruff_linter/src/settings/types.rs b/crates/ruff_linter/src/settings/types.rs index f74bbc3555d05..f6bd2af5cd668 100644 --- a/crates/ruff_linter/src/settings/types.rs +++ b/crates/ruff_linter/src/settings/types.rs @@ -5,6 +5,7 @@ use std::str::FromStr; use std::string::ToString; use anyhow::{bail, Result}; +use clap::ValueEnum; use globset::{Glob, GlobSet, GlobSetBuilder}; use pep440_rs::{Version as Pep440Version, VersionSpecifiers}; use ruff_diagnostics::Applicability; @@ -289,6 +290,68 @@ impl FromStr for PatternPrefixPair { } } +#[derive( + Clone, + Copy, + Debug, + PartialOrd, + Ord, + PartialEq, + Eq, + Default, + Serialize, + Deserialize, + CacheKey, + EnumIter, +)] +#[cfg_attr(feature = "clap", derive(clap::ValueEnum))] +#[serde(rename_all = "lowercase")] +#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] +pub enum Language { + #[default] + Python, + Pyi, + Ipynb, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct ExtensionPair { + pub extension: String, + pub language: Language, +} + +impl ExtensionPair { + const EXPECTED_PATTERN: &'static str = ": pattern"; +} + +impl FromStr for ExtensionPair { + type Err = anyhow::Error; + + fn from_str(s: &str) -> std::result::Result { + let (extension_str, language_str) = { + let tokens = s.split('=').collect::>(); + if tokens.len() != 2 { + bail!("Expected {}", Self::EXPECTED_PATTERN); + } + (tokens[0].trim(), tokens[1].trim()) + }; + let extension = extension_str.into(); + let Ok(language) = Language::from_str(language_str, true) else { + bail!("Unrecognised language {}", language_str) + }; + Ok(Self { + extension, + language, + }) + } +} + +impl From for (String, Language) { + fn from(value: ExtensionPair) -> Self { + (value.extension, value.language) + } +} + #[derive(Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Debug, Hash)] #[cfg_attr(feature = "clap", derive(clap::ValueEnum))] #[serde(rename_all = "kebab-case")] diff --git a/crates/ruff_workspace/src/configuration.rs b/crates/ruff_workspace/src/configuration.rs index efe7d4d52016f..cee5ef2094f31 100644 --- a/crates/ruff_workspace/src/configuration.rs +++ b/crates/ruff_workspace/src/configuration.rs @@ -24,8 +24,8 @@ use ruff_linter::rule_selector::{PreviewOptions, Specificity}; use ruff_linter::rules::pycodestyle; use ruff_linter::settings::rule_table::RuleTable; use ruff_linter::settings::types::{ - FilePattern, FilePatternSet, PerFileIgnore, PreviewMode, PythonVersion, SerializationFormat, - UnsafeFixes, Version, + ExtensionPair, FilePattern, FilePatternSet, PerFileIgnore, PreviewMode, PythonVersion, + SerializationFormat, UnsafeFixes, Version, }; use ruff_linter::settings::{ resolve_per_file_ignores, LinterSettings, DEFAULT_SELECTORS, DUMMY_VARIABLE_RGX, TASK_TAGS, @@ -218,6 +218,11 @@ impl Configuration { preview: lint_preview, target_version, project_root: project_root.to_path_buf(), + extension: lint + .extension + .map(|x| x.into_iter().map(|y| (y.extension, y.language))) + .map(FxHashMap::from_iter) + .unwrap_or_default(), allowed_confusables: lint .allowed_confusables .map(FxHashSet::from_iter) @@ -533,6 +538,7 @@ impl Configuration { pub struct LintConfiguration { pub exclude: Option>, pub preview: Option, + pub extension: Option>, // Rule selection pub extend_per_file_ignores: Vec, @@ -678,6 +684,7 @@ impl LintConfiguration { pyflakes: options.common.pyflakes, pylint: options.common.pylint, pyupgrade: options.common.pyupgrade, + extension: None, }) } @@ -983,6 +990,7 @@ impl LintConfiguration { pyflakes: self.pyflakes.combine(config.pyflakes), pylint: self.pylint.combine(config.pylint), pyupgrade: self.pyupgrade.combine(config.pyupgrade), + extension: self.extension.or(config.extension), } } } From c01a883ed3f28ee48507ede45295b0f0850c83ae Mon Sep 17 00:00:00 2001 From: Felix Williams Date: Wed, 25 Oct 2023 13:05:58 +0100 Subject: [PATCH 02/12] add extension to configuration and add integration test --- crates/ruff_cli/src/args.rs | 4 +- crates/ruff_cli/tests/integration_test.rs | 113 +++++++++++++++++++++ crates/ruff_linter/src/settings/types.rs | 5 +- crates/ruff_workspace/src/configuration.rs | 10 +- crates/ruff_workspace/src/options.rs | 15 ++- ruff.schema.json | 28 +++++ 6 files changed, 170 insertions(+), 5 deletions(-) diff --git a/crates/ruff_cli/src/args.rs b/crates/ruff_cli/src/args.rs index 2492e4a05ef7d..7f05086cd6b8e 100644 --- a/crates/ruff_cli/src/args.rs +++ b/crates/ruff_cli/src/args.rs @@ -343,8 +343,8 @@ pub struct CheckCommand { conflicts_with = "watch", )] pub show_settings: bool, - - #[arg(long, hide = true)] + /// List of mappings from file extension to language. + #[arg(long, value_delimiter = ',', help_heading = "Miscellaneous")] pub extension: Option>, /// Dev-only argument to show fixes #[arg(long, hide = true)] diff --git a/crates/ruff_cli/tests/integration_test.rs b/crates/ruff_cli/tests/integration_test.rs index 27a1336c3ff63..2fd11e99b21ff 100644 --- a/crates/ruff_cli/tests/integration_test.rs +++ b/crates/ruff_cli/tests/integration_test.rs @@ -320,6 +320,119 @@ fn stdin_fix_jupyter() { Found 2 errors (2 fixed, 0 remaining). "###); } +#[test] +fn stdin_override_parser_ipynb() { + let args = ["--extension", "py=ipynb", "--stdin-filename", "Jupyter.py"]; + assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME)) + .args(STDIN_BASE_OPTIONS) + .args(args) + .pass_stdin(r#"{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "id": "dccc687c-96e2-4604-b957-a8a89b5bec06", + "metadata": {}, + "outputs": [], + "source": [ + "import os" + ] + }, + { + "cell_type": "markdown", + "id": "19e1b029-f516-4662-a9b9-623b93edac1a", + "metadata": {}, + "source": [ + "Foo" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "cdce7b92-b0fb-4c02-86f6-e233b26fa84f", + "metadata": {}, + "outputs": [], + "source": [ + "import sys" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "e40b33d2-7fe4-46c5-bdf0-8802f3052565", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "1\n" + ] + } + ], + "source": [ + "print(1)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a1899bc8-d46f-4ec0-b1d1-e1ca0f04bf60", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.2" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +}"#), @r###" + success: false + exit_code: 1 + ----- stdout ----- + Jupyter.py:cell 1:1:8: F401 [*] `os` imported but unused + Jupyter.py:cell 3:1:8: F401 [*] `sys` imported but unused + Found 2 errors. + [*] 2 fixable with the `--fix` option. + + ----- stderr ----- + "###); +} + +#[test] +fn stdin_override_parser_py() { + assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME)) + .args(STDIN_BASE_OPTIONS) + .args(["--extension", "ipynb=python", "--stdin-filename", "F401.ipynb"]) + .pass_stdin("import os\n"), @r###" + success: false + exit_code: 1 + ----- stdout ----- + F401.ipynb:1:8: F401 [*] `os` imported but unused + Found 1 error. + [*] 1 fixable with the `--fix` option. + + ----- stderr ----- + "###); +} #[test] fn stdin_fix_when_not_fixable_should_still_print_contents() { diff --git a/crates/ruff_linter/src/settings/types.rs b/crates/ruff_linter/src/settings/types.rs index f6bd2af5cd668..d83ce03bb0903 100644 --- a/crates/ruff_linter/src/settings/types.rs +++ b/crates/ruff_linter/src/settings/types.rs @@ -337,7 +337,10 @@ impl FromStr for ExtensionPair { }; let extension = extension_str.into(); let Ok(language) = Language::from_str(language_str, true) else { - bail!("Unrecognised language {}", language_str) + bail!( + "Unrecognised language {}. Must be one of python,pyi,ipynb", + language_str + ) }; Ok(Self { extension, diff --git a/crates/ruff_workspace/src/configuration.rs b/crates/ruff_workspace/src/configuration.rs index cee5ef2094f31..67601febdc044 100644 --- a/crates/ruff_workspace/src/configuration.rs +++ b/crates/ruff_workspace/src/configuration.rs @@ -684,7 +684,15 @@ impl LintConfiguration { pyflakes: options.common.pyflakes, pylint: options.common.pylint, pyupgrade: options.common.pyupgrade, - extension: None, + extension: options.common.extension.map(|extension| { + extension + .into_iter() + .map(|(extension, language)| ExtensionPair { + extension, + language, + }) + .collect() + }), }) } diff --git a/crates/ruff_workspace/src/options.rs b/crates/ruff_workspace/src/options.rs index 0189e93857cff..04acbe556833c 100644 --- a/crates/ruff_workspace/src/options.rs +++ b/crates/ruff_workspace/src/options.rs @@ -23,7 +23,7 @@ use ruff_linter::rules::{ pycodestyle, pydocstyle, pyflakes, pylint, pyupgrade, }; use ruff_linter::settings::types::{ - IdentifierPattern, PythonVersion, SerializationFormat, Version, + IdentifierPattern, Language, PythonVersion, SerializationFormat, Version, }; use ruff_linter::{warn_user_once, RuleSelector}; use ruff_macros::{CombineOptions, OptionsMetadata}; @@ -847,6 +847,19 @@ pub struct LintCommonOptions { "# )] pub extend_per_file_ignores: Option>>, + + /// A mapping from file extension to one of python,ipynb,pyi to + /// override default python file type inference + #[option( + default = "{}", + value_type = "dict[str, Language]", + example = r#" + # Treat .ipynb files as python files + [tool.ruff.extension] + "ipynb" = "python" + "# + )] + pub extension: Option>, } #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] diff --git a/ruff.schema.json b/ruff.schema.json index 2e422405377ee..a20388ba130e5 100644 --- a/ruff.schema.json +++ b/ruff.schema.json @@ -158,6 +158,16 @@ "$ref": "#/definitions/RuleSelector" } }, + "extension": { + "description": "A mapping from file extension to one of python,ipynb,pyi to override default python file type inference", + "type": [ + "object", + "null" + ], + "additionalProperties": { + "$ref": "#/definitions/Language" + } + }, "external": { "description": "A list of rule codes that are unsupported by Ruff, but should be preserved when (e.g.) validating `# noqa` directives. Useful for retaining `# noqa` directives that cover plugins not yet implemented by Ruff.", "type": [ @@ -1578,6 +1588,14 @@ }, "additionalProperties": false }, + "Language": { + "type": "string", + "enum": [ + "python", + "pyi", + "ipynb" + ] + }, "LineEnding": { "oneOf": [ { @@ -1732,6 +1750,16 @@ "$ref": "#/definitions/RuleSelector" } }, + "extension": { + "description": "A mapping from file extension to one of python,ipynb,pyi to override default python file type inference", + "type": [ + "object", + "null" + ], + "additionalProperties": { + "$ref": "#/definitions/Language" + } + }, "external": { "description": "A list of rule codes that are unsupported by Ruff, but should be preserved when (e.g.) validating `# noqa` directives. Useful for retaining `# noqa` directives that cover plugins not yet implemented by Ruff.", "type": [ From c05a21c1dd753ee970cf66818e192babaebd1578 Mon Sep 17 00:00:00 2001 From: Felix Williams Date: Wed, 25 Oct 2023 13:16:22 +0100 Subject: [PATCH 03/12] use colons instead of equals in extension mapping to match per file ignores --- crates/ruff_cli/src/args.rs | 4 ++-- crates/ruff_cli/tests/integration_test.rs | 4 ++-- crates/ruff_linter/src/settings/types.rs | 2 +- docs/configuration.md | 2 ++ 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/crates/ruff_cli/src/args.rs b/crates/ruff_cli/src/args.rs index 7f05086cd6b8e..22745b8546485 100644 --- a/crates/ruff_cli/src/args.rs +++ b/crates/ruff_cli/src/args.rs @@ -343,8 +343,8 @@ pub struct CheckCommand { conflicts_with = "watch", )] pub show_settings: bool, - /// List of mappings from file extension to language. - #[arg(long, value_delimiter = ',', help_heading = "Miscellaneous")] + /// List of mappings from file extension to language (one of ["python", "ipynb", "pyi"]). + #[arg(long, value_delimiter = ',')] pub extension: Option>, /// Dev-only argument to show fixes #[arg(long, hide = true)] diff --git a/crates/ruff_cli/tests/integration_test.rs b/crates/ruff_cli/tests/integration_test.rs index 2fd11e99b21ff..96a4fe3f0ed47 100644 --- a/crates/ruff_cli/tests/integration_test.rs +++ b/crates/ruff_cli/tests/integration_test.rs @@ -322,7 +322,7 @@ fn stdin_fix_jupyter() { } #[test] fn stdin_override_parser_ipynb() { - let args = ["--extension", "py=ipynb", "--stdin-filename", "Jupyter.py"]; + let args = ["--extension", "py:ipynb", "--stdin-filename", "Jupyter.py"]; assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME)) .args(STDIN_BASE_OPTIONS) .args(args) @@ -421,7 +421,7 @@ fn stdin_override_parser_ipynb() { fn stdin_override_parser_py() { assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME)) .args(STDIN_BASE_OPTIONS) - .args(["--extension", "ipynb=python", "--stdin-filename", "F401.ipynb"]) + .args(["--extension", "ipynb:python", "--stdin-filename", "F401.ipynb"]) .pass_stdin("import os\n"), @r###" success: false exit_code: 1 diff --git a/crates/ruff_linter/src/settings/types.rs b/crates/ruff_linter/src/settings/types.rs index d83ce03bb0903..7eb055e4742ef 100644 --- a/crates/ruff_linter/src/settings/types.rs +++ b/crates/ruff_linter/src/settings/types.rs @@ -329,7 +329,7 @@ impl FromStr for ExtensionPair { fn from_str(s: &str) -> std::result::Result { let (extension_str, language_str) = { - let tokens = s.split('=').collect::>(); + let tokens = s.split(':').collect::>(); if tokens.len() != 2 { bail!("Expected {}", Self::EXPECTED_PATTERN); } diff --git a/docs/configuration.md b/docs/configuration.md index 8676d079259fa..5662a05addcf1 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -316,6 +316,8 @@ Options: See the files Ruff will be run against with the current settings --show-settings See the settings Ruff will use to lint a given Python file + --extension + List of mappings from file extension to language (one of ["python", "ipynb", "pyi"]) -h, --help Print help From e6a48344dd31da3b14a109bf28c7d60c7d3307be Mon Sep 17 00:00:00 2001 From: Felix Williams Date: Wed, 25 Oct 2023 16:58:15 +0100 Subject: [PATCH 04/12] rename extension to extension_override --- crates/ruff_cli/src/args.rs | 10 +++++----- crates/ruff_cli/src/diagnostics.rs | 4 ++-- crates/ruff_cli/tests/integration_test.rs | 9 +++++++-- crates/ruff_linter/src/settings/mod.rs | 4 ++-- crates/ruff_workspace/src/configuration.rs | 10 +++++----- crates/ruff_workspace/src/options.rs | 4 ++-- docs/configuration.md | 2 +- ruff.schema.json | 4 ++-- 8 files changed, 26 insertions(+), 21 deletions(-) diff --git a/crates/ruff_cli/src/args.rs b/crates/ruff_cli/src/args.rs index 22745b8546485..3f0a319277679 100644 --- a/crates/ruff_cli/src/args.rs +++ b/crates/ruff_cli/src/args.rs @@ -345,7 +345,7 @@ pub struct CheckCommand { pub show_settings: bool, /// List of mappings from file extension to language (one of ["python", "ipynb", "pyi"]). #[arg(long, value_delimiter = ',')] - pub extension: Option>, + pub extension_override: Option>, /// Dev-only argument to show fixes #[arg(long, hide = true)] pub ecosystem_ci: bool, @@ -526,7 +526,7 @@ impl CheckCommand { force_exclude: resolve_bool_arg(self.force_exclude, self.no_force_exclude), output_format: self.output_format, show_fixes: resolve_bool_arg(self.show_fixes, self.no_show_fixes), - extension: self.extension, + extension_override: self.extension_override, }, ) } @@ -637,7 +637,7 @@ pub struct CliOverrides { pub force_exclude: Option, pub output_format: Option, pub show_fixes: Option, - pub extension: Option>, + pub extension_override: Option>, } impl ConfigurationTransformer for CliOverrides { @@ -716,8 +716,8 @@ impl ConfigurationTransformer for CliOverrides { if let Some(target_version) = &self.target_version { config.target_version = Some(*target_version); } - if let Some(extension) = &self.extension { - config.lint.extension = Some(extension.clone()); + if let Some(extension) = &self.extension_override { + config.lint.extension_override = Some(extension.clone()); } config diff --git a/crates/ruff_cli/src/diagnostics.rs b/crates/ruff_cli/src/diagnostics.rs index 3ea46ef64680e..5c0fa5fa3b000 100644 --- a/crates/ruff_cli/src/diagnostics.rs +++ b/crates/ruff_cli/src/diagnostics.rs @@ -230,7 +230,7 @@ pub(crate) fn lint_path( }; debug!("Checking: {}", path.display()); - let source_type = match get_override_source_type(Some(path), &settings.extension) { + let source_type = match get_override_source_type(Some(path), &settings.extension_override) { Some(source_type) => source_type, None => match SourceType::from(path) { SourceType::Toml(TomlSourceType::Pyproject) => { @@ -374,7 +374,7 @@ pub(crate) fn lint_stdin( ) -> Result { // TODO(charlie): Support `pyproject.toml`. let source_type = if let Some(source_type) = - get_override_source_type(path, &settings.linter.extension) + get_override_source_type(path, &settings.linter.extension_override) { source_type } else { diff --git a/crates/ruff_cli/tests/integration_test.rs b/crates/ruff_cli/tests/integration_test.rs index 96a4fe3f0ed47..e2aa1c86631ec 100644 --- a/crates/ruff_cli/tests/integration_test.rs +++ b/crates/ruff_cli/tests/integration_test.rs @@ -322,7 +322,12 @@ fn stdin_fix_jupyter() { } #[test] fn stdin_override_parser_ipynb() { - let args = ["--extension", "py:ipynb", "--stdin-filename", "Jupyter.py"]; + let args = [ + "--extension-override", + "py:ipynb", + "--stdin-filename", + "Jupyter.py", + ]; assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME)) .args(STDIN_BASE_OPTIONS) .args(args) @@ -421,7 +426,7 @@ fn stdin_override_parser_ipynb() { fn stdin_override_parser_py() { assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME)) .args(STDIN_BASE_OPTIONS) - .args(["--extension", "ipynb:python", "--stdin-filename", "F401.ipynb"]) + .args(["--extension-override", "ipynb:python", "--stdin-filename", "F401.ipynb"]) .pass_stdin("import os\n"), @r###" success: false exit_code: 1 diff --git a/crates/ruff_linter/src/settings/mod.rs b/crates/ruff_linter/src/settings/mod.rs index 9ae191f1c805e..9546348f1f10d 100644 --- a/crates/ruff_linter/src/settings/mod.rs +++ b/crates/ruff_linter/src/settings/mod.rs @@ -40,7 +40,7 @@ pub mod types; pub struct LinterSettings { pub exclude: FilePatternSet, pub project_root: PathBuf, - pub extension: FxHashMap, + pub extension_override: FxHashMap, pub rules: RuleTable, pub per_file_ignores: Vec<(GlobMatcher, GlobMatcher, RuleSet)>, @@ -140,7 +140,7 @@ impl LinterSettings { .flat_map(|selector| selector.rules(&PreviewOptions::default())) .collect(), allowed_confusables: FxHashSet::from_iter([]), - extension: FxHashMap::from_iter([]), + extension_override: FxHashMap::from_iter([]), // Needs duplicating builtins: vec![], diff --git a/crates/ruff_workspace/src/configuration.rs b/crates/ruff_workspace/src/configuration.rs index 67601febdc044..600fb34123fe3 100644 --- a/crates/ruff_workspace/src/configuration.rs +++ b/crates/ruff_workspace/src/configuration.rs @@ -218,8 +218,8 @@ impl Configuration { preview: lint_preview, target_version, project_root: project_root.to_path_buf(), - extension: lint - .extension + extension_override: lint + .extension_override .map(|x| x.into_iter().map(|y| (y.extension, y.language))) .map(FxHashMap::from_iter) .unwrap_or_default(), @@ -538,7 +538,7 @@ impl Configuration { pub struct LintConfiguration { pub exclude: Option>, pub preview: Option, - pub extension: Option>, + pub extension_override: Option>, // Rule selection pub extend_per_file_ignores: Vec, @@ -684,7 +684,7 @@ impl LintConfiguration { pyflakes: options.common.pyflakes, pylint: options.common.pylint, pyupgrade: options.common.pyupgrade, - extension: options.common.extension.map(|extension| { + extension_override: options.common.extension_override.map(|extension| { extension .into_iter() .map(|(extension, language)| ExtensionPair { @@ -998,7 +998,7 @@ impl LintConfiguration { pyflakes: self.pyflakes.combine(config.pyflakes), pylint: self.pylint.combine(config.pylint), pyupgrade: self.pyupgrade.combine(config.pyupgrade), - extension: self.extension.or(config.extension), + extension_override: self.extension_override.or(config.extension_override), } } } diff --git a/crates/ruff_workspace/src/options.rs b/crates/ruff_workspace/src/options.rs index 04acbe556833c..459ea58002e64 100644 --- a/crates/ruff_workspace/src/options.rs +++ b/crates/ruff_workspace/src/options.rs @@ -855,11 +855,11 @@ pub struct LintCommonOptions { value_type = "dict[str, Language]", example = r#" # Treat .ipynb files as python files - [tool.ruff.extension] + [tool.ruff.extension_override] "ipynb" = "python" "# )] - pub extension: Option>, + pub extension_override: Option>, } #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] diff --git a/docs/configuration.md b/docs/configuration.md index 5662a05addcf1..4421113cda696 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -316,7 +316,7 @@ Options: See the files Ruff will be run against with the current settings --show-settings See the settings Ruff will use to lint a given Python file - --extension + --extension-override List of mappings from file extension to language (one of ["python", "ipynb", "pyi"]) -h, --help Print help diff --git a/ruff.schema.json b/ruff.schema.json index a20388ba130e5..9d9566a5f6c00 100644 --- a/ruff.schema.json +++ b/ruff.schema.json @@ -158,7 +158,7 @@ "$ref": "#/definitions/RuleSelector" } }, - "extension": { + "extension-override": { "description": "A mapping from file extension to one of python,ipynb,pyi to override default python file type inference", "type": [ "object", @@ -1750,7 +1750,7 @@ "$ref": "#/definitions/RuleSelector" } }, - "extension": { + "extension-override": { "description": "A mapping from file extension to one of python,ipynb,pyi to override default python file type inference", "type": [ "object", From 4284044f2a3c3fd9cb067b05d31f6e7a59696da1 Mon Sep 17 00:00:00 2001 From: Felix Williams Date: Wed, 25 Oct 2023 17:41:18 +0100 Subject: [PATCH 05/12] make extension-override a linter-only option --- crates/ruff_cli/src/args.rs | 9 +++---- crates/ruff_cli/src/cache.rs | 4 +++ crates/ruff_cli/src/commands/check.rs | 19 ++++++++++++-- crates/ruff_cli/src/commands/check_stdin.rs | 4 +++ crates/ruff_cli/src/diagnostics.rs | 8 +++--- crates/ruff_cli/src/lib.rs | 10 ++++++++ crates/ruff_linter/src/settings/mod.rs | 6 ++--- crates/ruff_workspace/src/configuration.rs | 21 +++------------- crates/ruff_workspace/src/options.rs | 15 +---------- docs/configuration.md | 2 -- ruff.schema.json | 28 --------------------- 11 files changed, 49 insertions(+), 77 deletions(-) diff --git a/crates/ruff_cli/src/args.rs b/crates/ruff_cli/src/args.rs index 3f0a319277679..8189d9bd0b9a3 100644 --- a/crates/ruff_cli/src/args.rs +++ b/crates/ruff_cli/src/args.rs @@ -344,7 +344,7 @@ pub struct CheckCommand { )] pub show_settings: bool, /// List of mappings from file extension to language (one of ["python", "ipynb", "pyi"]). - #[arg(long, value_delimiter = ',')] + #[arg(long, value_delimiter = ',', hide = true)] pub extension_override: Option>, /// Dev-only argument to show fixes #[arg(long, hide = true)] @@ -495,6 +495,7 @@ impl CheckCommand { statistics: self.statistics, stdin_filename: self.stdin_filename, watch: self.watch, + extension_override: self.extension_override, }, CliOverrides { dummy_variable_rgx: self.dummy_variable_rgx, @@ -526,7 +527,6 @@ impl CheckCommand { force_exclude: resolve_bool_arg(self.force_exclude, self.no_force_exclude), output_format: self.output_format, show_fixes: resolve_bool_arg(self.show_fixes, self.no_show_fixes), - extension_override: self.extension_override, }, ) } @@ -593,6 +593,7 @@ pub struct CheckArguments { pub statistics: bool, pub stdin_filename: Option, pub watch: bool, + pub extension_override: Option>, } /// CLI settings that are distinct from configuration (commands, lists of files, @@ -637,7 +638,6 @@ pub struct CliOverrides { pub force_exclude: Option, pub output_format: Option, pub show_fixes: Option, - pub extension_override: Option>, } impl ConfigurationTransformer for CliOverrides { @@ -716,9 +716,6 @@ impl ConfigurationTransformer for CliOverrides { if let Some(target_version) = &self.target_version { config.target_version = Some(*target_version); } - if let Some(extension) = &self.extension_override { - config.lint.extension_override = Some(extension.clone()); - } config } diff --git a/crates/ruff_cli/src/cache.rs b/crates/ruff_cli/src/cache.rs index c9794a9c4b1ca..d4e23a466ec59 100644 --- a/crates/ruff_cli/src/cache.rs +++ b/crates/ruff_cli/src/cache.rs @@ -566,6 +566,7 @@ mod tests { use anyhow::Result; use filetime::{set_file_mtime, FileTime}; use itertools::Itertools; + use rustc_hash::FxHashMap; use test_case::test_case; use ruff_cache::CACHE_DIR_NAME; @@ -629,6 +630,7 @@ mod tests { flags::Noqa::Enabled, flags::FixMode::Generate, UnsafeFixes::Enabled, + &FxHashMap::default(), ) .unwrap(); if diagnostics @@ -675,6 +677,7 @@ mod tests { flags::Noqa::Enabled, flags::FixMode::Generate, UnsafeFixes::Enabled, + &FxHashMap::default(), ) .unwrap(); } @@ -1048,6 +1051,7 @@ mod tests { flags::Noqa::Enabled, flags::FixMode::Generate, UnsafeFixes::Enabled, + &FxHashMap::default(), ) } diff --git a/crates/ruff_cli/src/commands/check.rs b/crates/ruff_cli/src/commands/check.rs index 001cb0ae8449d..414e0601d4c3b 100644 --- a/crates/ruff_cli/src/commands/check.rs +++ b/crates/ruff_cli/src/commands/check.rs @@ -14,7 +14,7 @@ use rustc_hash::FxHashMap; use ruff_diagnostics::Diagnostic; use ruff_linter::message::Message; use ruff_linter::registry::Rule; -use ruff_linter::settings::types::UnsafeFixes; +use ruff_linter::settings::types::{Language, UnsafeFixes}; use ruff_linter::settings::{flags, LinterSettings}; use ruff_linter::{fs, warn_user_once, IOError}; use ruff_python_ast::imports::ImportMap; @@ -30,6 +30,7 @@ use crate::diagnostics::Diagnostics; use crate::panic::catch_unwind; /// Run the linter over a collection of files. +#[allow(clippy::too_many_arguments)] pub(crate) fn check( files: &[PathBuf], pyproject_config: &PyprojectConfig, @@ -38,6 +39,7 @@ pub(crate) fn check( noqa: flags::Noqa, fix_mode: flags::FixMode, unsafe_fixes: UnsafeFixes, + extension_override: &FxHashMap, ) -> Result { // Collect all the Python files to check. let start = Instant::now(); @@ -103,6 +105,7 @@ pub(crate) fn check( noqa, fix_mode, unsafe_fixes, + extension_override, ) .map_err(|e| { (Some(path.to_path_buf()), { @@ -184,6 +187,7 @@ pub(crate) fn check( /// Wraps [`lint_path`](crate::diagnostics::lint_path) in a [`catch_unwind`](std::panic::catch_unwind) and emits /// a diagnostic if the linting the file panics. +#[allow(clippy::too_many_arguments)] fn lint_path( path: &Path, package: Option<&Path>, @@ -192,9 +196,19 @@ fn lint_path( noqa: flags::Noqa, fix_mode: flags::FixMode, unsafe_fixes: UnsafeFixes, + extension_override: &FxHashMap, ) -> Result { let result = catch_unwind(|| { - crate::diagnostics::lint_path(path, package, settings, cache, noqa, fix_mode, unsafe_fixes) + crate::diagnostics::lint_path( + path, + package, + settings, + cache, + noqa, + fix_mode, + unsafe_fixes, + extension_override, + ) }); match result { @@ -280,6 +294,7 @@ mod test { flags::Noqa::Disabled, flags::FixMode::Generate, UnsafeFixes::Enabled, + &FxHashMap::default(), ) .unwrap(); let mut output = Vec::new(); diff --git a/crates/ruff_cli/src/commands/check_stdin.rs b/crates/ruff_cli/src/commands/check_stdin.rs index 67f01cd0e8d3a..dafe6ce977a4a 100644 --- a/crates/ruff_cli/src/commands/check_stdin.rs +++ b/crates/ruff_cli/src/commands/check_stdin.rs @@ -4,7 +4,9 @@ use anyhow::Result; use ruff_linter::packaging; use ruff_linter::settings::flags; +use ruff_linter::settings::types::Language; use ruff_workspace::resolver::{match_exclusion, python_file_at_path, PyprojectConfig}; +use rustc_hash::FxHashMap; use crate::args::CliOverrides; use crate::diagnostics::{lint_stdin, Diagnostics}; @@ -17,6 +19,7 @@ pub(crate) fn check_stdin( overrides: &CliOverrides, noqa: flags::Noqa, fix_mode: flags::FixMode, + extension_override: &FxHashMap, ) -> Result { if let Some(filename) = filename { if !python_file_at_path(filename, pyproject_config, overrides)? { @@ -42,6 +45,7 @@ pub(crate) fn check_stdin( &pyproject_config.settings, noqa, fix_mode, + extension_override, )?; diagnostics.messages.sort_unstable(); Ok(diagnostics) diff --git a/crates/ruff_cli/src/diagnostics.rs b/crates/ruff_cli/src/diagnostics.rs index 5c0fa5fa3b000..b559aa511d605 100644 --- a/crates/ruff_cli/src/diagnostics.rs +++ b/crates/ruff_cli/src/diagnostics.rs @@ -193,6 +193,7 @@ fn get_override_source_type( } /// Lint the source code at the given `Path`. +#[allow(clippy::too_many_arguments)] pub(crate) fn lint_path( path: &Path, package: Option<&Path>, @@ -201,6 +202,7 @@ pub(crate) fn lint_path( noqa: flags::Noqa, fix_mode: flags::FixMode, unsafe_fixes: UnsafeFixes, + extension_override: &FxHashMap, ) -> Result { // Check the cache. // TODO(charlie): `fixer::Mode::Apply` and `fixer::Mode::Diff` both have @@ -230,7 +232,7 @@ pub(crate) fn lint_path( }; debug!("Checking: {}", path.display()); - let source_type = match get_override_source_type(Some(path), &settings.extension_override) { + let source_type = match get_override_source_type(Some(path), extension_override) { Some(source_type) => source_type, None => match SourceType::from(path) { SourceType::Toml(TomlSourceType::Pyproject) => { @@ -371,10 +373,10 @@ pub(crate) fn lint_stdin( settings: &Settings, noqa: flags::Noqa, fix_mode: flags::FixMode, + extension_override: &FxHashMap, ) -> Result { // TODO(charlie): Support `pyproject.toml`. - let source_type = if let Some(source_type) = - get_override_source_type(path, &settings.linter.extension_override) + let source_type = if let Some(source_type) = get_override_source_type(path, extension_override) { source_type } else { diff --git a/crates/ruff_cli/src/lib.rs b/crates/ruff_cli/src/lib.rs index 4056315d67ca9..95fe632ad6fe3 100644 --- a/crates/ruff_cli/src/lib.rs +++ b/crates/ruff_cli/src/lib.rs @@ -17,6 +17,7 @@ use ruff_linter::settings::flags::FixMode; use ruff_linter::settings::types::SerializationFormat; use ruff_linter::{fs, warn_user, warn_user_once}; use ruff_workspace::Settings; +use rustc_hash::FxHashMap; use crate::args::{Args, CheckCommand, Command, FormatCommand}; use crate::printer::{Flags as PrinterFlags, Printer}; @@ -295,6 +296,11 @@ pub fn check(args: CheckCommand, log_level: LogLevel) -> Result { unsafe_fixes, printer_flags, ); + let extension_override = cli + .extension_override + .map(|eo| eo.into_iter().map(|y| (y.extension, y.language))) + .map(FxHashMap::from_iter) + .unwrap_or_default(); if cli.watch { if output_format != SerializationFormat::Text { @@ -323,6 +329,7 @@ pub fn check(args: CheckCommand, log_level: LogLevel) -> Result { noqa.into(), fix_mode, unsafe_fixes, + &extension_override, )?; printer.write_continuously(&mut writer, &messages)?; @@ -356,6 +363,7 @@ pub fn check(args: CheckCommand, log_level: LogLevel) -> Result { noqa.into(), fix_mode, unsafe_fixes, + &extension_override, )?; printer.write_continuously(&mut writer, &messages)?; } @@ -373,6 +381,7 @@ pub fn check(args: CheckCommand, log_level: LogLevel) -> Result { &overrides, noqa.into(), fix_mode, + &extension_override, )? } else { commands::check::check( @@ -383,6 +392,7 @@ pub fn check(args: CheckCommand, log_level: LogLevel) -> Result { noqa.into(), fix_mode, unsafe_fixes, + &extension_override, )? }; diff --git a/crates/ruff_linter/src/settings/mod.rs b/crates/ruff_linter/src/settings/mod.rs index 7b189cc55abba..199b4a490d607 100644 --- a/crates/ruff_linter/src/settings/mod.rs +++ b/crates/ruff_linter/src/settings/mod.rs @@ -9,7 +9,7 @@ use globset::{Glob, GlobMatcher}; use once_cell::sync::Lazy; use path_absolutize::path_dedot; use regex::Regex; -use rustc_hash::{FxHashMap, FxHashSet}; +use rustc_hash::FxHashSet; use crate::codes::RuleCodePrefix; use ruff_macros::CacheKey; @@ -28,7 +28,7 @@ use crate::{codes, RuleSelector}; use super::line_width::IndentWidth; use self::rule_table::RuleTable; -use self::types::{Language, PreviewMode}; +use self::types::PreviewMode; use crate::rule_selector::PreviewOptions; pub mod flags; @@ -39,7 +39,6 @@ pub mod types; pub struct LinterSettings { pub exclude: FilePatternSet, pub project_root: PathBuf, - pub extension_override: FxHashMap, pub rules: RuleTable, pub per_file_ignores: Vec<(GlobMatcher, GlobMatcher, RuleSet)>, @@ -139,7 +138,6 @@ impl LinterSettings { .flat_map(|selector| selector.rules(&PreviewOptions::default())) .collect(), allowed_confusables: FxHashSet::from_iter([]), - extension_override: FxHashMap::from_iter([]), // Needs duplicating builtins: vec![], diff --git a/crates/ruff_workspace/src/configuration.rs b/crates/ruff_workspace/src/configuration.rs index e254abf0f32b6..a11658e432900 100644 --- a/crates/ruff_workspace/src/configuration.rs +++ b/crates/ruff_workspace/src/configuration.rs @@ -24,8 +24,8 @@ use ruff_linter::rule_selector::{PreviewOptions, Specificity}; use ruff_linter::rules::pycodestyle; use ruff_linter::settings::rule_table::RuleTable; use ruff_linter::settings::types::{ - ExtensionPair, FilePattern, FilePatternSet, PerFileIgnore, PreviewMode, PythonVersion, - SerializationFormat, UnsafeFixes, Version, + FilePattern, FilePatternSet, PerFileIgnore, PreviewMode, PythonVersion, SerializationFormat, + UnsafeFixes, Version, }; use ruff_linter::settings::{ resolve_per_file_ignores, LinterSettings, DEFAULT_SELECTORS, DUMMY_VARIABLE_RGX, TASK_TAGS, @@ -218,11 +218,7 @@ impl Configuration { preview: lint_preview, target_version, project_root: project_root.to_path_buf(), - extension_override: lint - .extension_override - .map(|x| x.into_iter().map(|y| (y.extension, y.language))) - .map(FxHashMap::from_iter) - .unwrap_or_default(), + allowed_confusables: lint .allowed_confusables .map(FxHashSet::from_iter) @@ -538,7 +534,6 @@ impl Configuration { pub struct LintConfiguration { pub exclude: Option>, pub preview: Option, - pub extension_override: Option>, // Rule selection pub extend_per_file_ignores: Vec, @@ -684,15 +679,6 @@ impl LintConfiguration { pyflakes: options.common.pyflakes, pylint: options.common.pylint, pyupgrade: options.common.pyupgrade, - extension_override: options.common.extension_override.map(|extension| { - extension - .into_iter() - .map(|(extension, language)| ExtensionPair { - extension, - language, - }) - .collect() - }), }) } @@ -998,7 +984,6 @@ impl LintConfiguration { pyflakes: self.pyflakes.combine(config.pyflakes), pylint: self.pylint.combine(config.pylint), pyupgrade: self.pyupgrade.combine(config.pyupgrade), - extension_override: self.extension_override.or(config.extension_override), } } } diff --git a/crates/ruff_workspace/src/options.rs b/crates/ruff_workspace/src/options.rs index 374d916ced498..e895a444f2521 100644 --- a/crates/ruff_workspace/src/options.rs +++ b/crates/ruff_workspace/src/options.rs @@ -23,7 +23,7 @@ use ruff_linter::rules::{ pycodestyle, pydocstyle, pyflakes, pylint, pyupgrade, }; use ruff_linter::settings::types::{ - IdentifierPattern, Language, PythonVersion, SerializationFormat, Version, + IdentifierPattern, PythonVersion, SerializationFormat, Version, }; use ruff_linter::{warn_user_once, RuleSelector}; use ruff_macros::{CombineOptions, OptionsMetadata}; @@ -847,19 +847,6 @@ pub struct LintCommonOptions { "# )] pub extend_per_file_ignores: Option>>, - - /// A mapping from file extension to one of python,ipynb,pyi to - /// override default python file type inference - #[option( - default = "{}", - value_type = "dict[str, Language]", - example = r#" - # Treat .ipynb files as python files - [tool.ruff.extension_override] - "ipynb" = "python" - "# - )] - pub extension_override: Option>, } #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] diff --git a/docs/configuration.md b/docs/configuration.md index ee32358a1754c..ee991f0100a1a 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -316,8 +316,6 @@ Options: See the files Ruff will be run against with the current settings --show-settings See the settings Ruff will use to lint a given Python file - --extension-override - List of mappings from file extension to language (one of ["python", "ipynb", "pyi"]) -h, --help Print help diff --git a/ruff.schema.json b/ruff.schema.json index 6e88e3e98707a..117404e0c26cd 100644 --- a/ruff.schema.json +++ b/ruff.schema.json @@ -158,16 +158,6 @@ "$ref": "#/definitions/RuleSelector" } }, - "extension-override": { - "description": "A mapping from file extension to one of python,ipynb,pyi to override default python file type inference", - "type": [ - "object", - "null" - ], - "additionalProperties": { - "$ref": "#/definitions/Language" - } - }, "external": { "description": "A list of rule codes or prefixes that are unsupported by Ruff, but should be preserved when (e.g.) validating `# noqa` directives. Useful for retaining `# noqa` directives that cover plugins not yet implemented by Ruff.", "type": [ @@ -1588,14 +1578,6 @@ }, "additionalProperties": false }, - "Language": { - "type": "string", - "enum": [ - "python", - "pyi", - "ipynb" - ] - }, "LineEnding": { "oneOf": [ { @@ -1750,16 +1732,6 @@ "$ref": "#/definitions/RuleSelector" } }, - "extension-override": { - "description": "A mapping from file extension to one of python,ipynb,pyi to override default python file type inference", - "type": [ - "object", - "null" - ], - "additionalProperties": { - "$ref": "#/definitions/Language" - } - }, "external": { "description": "A list of rule codes or prefixes that are unsupported by Ruff, but should be preserved when (e.g.) validating `# noqa` directives. Useful for retaining `# noqa` directives that cover plugins not yet implemented by Ruff.", "type": [ From cf57f7682307f6d25999775deae9cba3e5c27ef8 Mon Sep 17 00:00:00 2001 From: Felix Williams Date: Thu, 26 Oct 2023 13:18:27 +0100 Subject: [PATCH 06/12] remove stray newline --- crates/ruff_workspace/src/configuration.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/ruff_workspace/src/configuration.rs b/crates/ruff_workspace/src/configuration.rs index 1527d4cc924c9..0f7924531a388 100644 --- a/crates/ruff_workspace/src/configuration.rs +++ b/crates/ruff_workspace/src/configuration.rs @@ -218,7 +218,6 @@ impl Configuration { preview: lint_preview, target_version, project_root: project_root.to_path_buf(), - allowed_confusables: lint .allowed_confusables .map(FxHashSet::from_iter) From 9a8fd171e77e58192f1955af1b528e1669022b4f Mon Sep 17 00:00:00 2001 From: Felix Williams Date: Tue, 31 Oct 2023 11:10:18 +0000 Subject: [PATCH 07/12] implement FromStr for Language instead of relying on clap::ValueEnum --- crates/ruff_linter/src/settings/types.rs | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/crates/ruff_linter/src/settings/types.rs b/crates/ruff_linter/src/settings/types.rs index 949c94cb38a99..67770ba9eb8b4 100644 --- a/crates/ruff_linter/src/settings/types.rs +++ b/crates/ruff_linter/src/settings/types.rs @@ -5,7 +5,6 @@ use std::str::FromStr; use std::string::ToString; use anyhow::{bail, Result}; -use clap::ValueEnum; use globset::{Glob, GlobSet, GlobSetBuilder}; use pep440_rs::{Version as Pep440Version, VersionSpecifiers}; use ruff_diagnostics::Applicability; @@ -314,6 +313,21 @@ pub enum Language { Ipynb, } +impl FromStr for Language { + type Err = anyhow::Error; + fn from_str(s: &str) -> std::result::Result { + match s.to_ascii_lowercase().as_str() { + "python" => Ok(Self::Python), + "pyi" => Ok(Self::Pyi), + "ipynb" => Ok(Self::Ipynb), + _ => bail!( + "Unrecognised language {}. Must be one of python,pyi,ipynb", + s + ), + } + } +} + #[derive(Clone, Debug, PartialEq, Eq)] pub struct ExtensionPair { pub extension: String, @@ -336,12 +350,7 @@ impl FromStr for ExtensionPair { (tokens[0].trim(), tokens[1].trim()) }; let extension = extension_str.into(); - let Ok(language) = Language::from_str(language_str, true) else { - bail!( - "Unrecognised language {}. Must be one of python,pyi,ipynb", - language_str - ) - }; + let language = Language::from_str(language_str)?; Ok(Self { extension, language, From b2c4caacbf54d1df66cc9e24ee5e3c80c87aa003 Mon Sep 17 00:00:00 2001 From: Felix Williams Date: Tue, 7 Nov 2023 10:13:44 +0000 Subject: [PATCH 08/12] make extension_override a private struct --- crates/ruff_cli/src/cache.rs | 8 ++-- crates/ruff_cli/src/commands/check.rs | 10 ++--- crates/ruff_cli/src/commands/check_stdin.rs | 5 +-- crates/ruff_cli/src/diagnostics.rs | 15 +++----- crates/ruff_cli/src/lib.rs | 4 +- crates/ruff_linter/src/settings/types.rs | 41 +++++++++++++++++++++ 6 files changed, 58 insertions(+), 25 deletions(-) diff --git a/crates/ruff_cli/src/cache.rs b/crates/ruff_cli/src/cache.rs index 3555a4c458287..73878ed5edade 100644 --- a/crates/ruff_cli/src/cache.rs +++ b/crates/ruff_cli/src/cache.rs @@ -564,7 +564,7 @@ mod tests { use anyhow::Result; use filetime::{set_file_mtime, FileTime}; use itertools::Itertools; - use rustc_hash::FxHashMap; + use ruff_linter::settings::types::ExtensionMapping; use test_case::test_case; use ruff_cache::CACHE_DIR_NAME; @@ -628,7 +628,7 @@ mod tests { flags::Noqa::Enabled, flags::FixMode::Generate, UnsafeFixes::Enabled, - &FxHashMap::default(), + &ExtensionMapping::default(), ) .unwrap(); if diagnostics @@ -675,7 +675,7 @@ mod tests { flags::Noqa::Enabled, flags::FixMode::Generate, UnsafeFixes::Enabled, - &FxHashMap::default(), + &ExtensionMapping::default(), ) .unwrap(); } @@ -1049,7 +1049,7 @@ mod tests { flags::Noqa::Enabled, flags::FixMode::Generate, UnsafeFixes::Enabled, - &FxHashMap::default(), + &ExtensionMapping::default(), ) } diff --git a/crates/ruff_cli/src/commands/check.rs b/crates/ruff_cli/src/commands/check.rs index 414e0601d4c3b..372489db72dfd 100644 --- a/crates/ruff_cli/src/commands/check.rs +++ b/crates/ruff_cli/src/commands/check.rs @@ -14,7 +14,7 @@ use rustc_hash::FxHashMap; use ruff_diagnostics::Diagnostic; use ruff_linter::message::Message; use ruff_linter::registry::Rule; -use ruff_linter::settings::types::{Language, UnsafeFixes}; +use ruff_linter::settings::types::{ExtensionMapping, UnsafeFixes}; use ruff_linter::settings::{flags, LinterSettings}; use ruff_linter::{fs, warn_user_once, IOError}; use ruff_python_ast::imports::ImportMap; @@ -39,7 +39,7 @@ pub(crate) fn check( noqa: flags::Noqa, fix_mode: flags::FixMode, unsafe_fixes: UnsafeFixes, - extension_override: &FxHashMap, + extension_override: &ExtensionMapping, ) -> Result { // Collect all the Python files to check. let start = Instant::now(); @@ -196,7 +196,7 @@ fn lint_path( noqa: flags::Noqa, fix_mode: flags::FixMode, unsafe_fixes: UnsafeFixes, - extension_override: &FxHashMap, + extension_override: &ExtensionMapping, ) -> Result { let result = catch_unwind(|| { crate::diagnostics::lint_path( @@ -245,7 +245,7 @@ mod test { use ruff_linter::message::{Emitter, EmitterContext, TextEmitter}; use ruff_linter::registry::Rule; - use ruff_linter::settings::types::UnsafeFixes; + use ruff_linter::settings::types::{ExtensionMapping, UnsafeFixes}; use ruff_linter::settings::{flags, LinterSettings}; use ruff_workspace::resolver::{PyprojectConfig, PyprojectDiscoveryStrategy}; use ruff_workspace::Settings; @@ -294,7 +294,7 @@ mod test { flags::Noqa::Disabled, flags::FixMode::Generate, UnsafeFixes::Enabled, - &FxHashMap::default(), + &ExtensionMapping::default(), ) .unwrap(); let mut output = Vec::new(); diff --git a/crates/ruff_cli/src/commands/check_stdin.rs b/crates/ruff_cli/src/commands/check_stdin.rs index dafe6ce977a4a..720ca3407cb37 100644 --- a/crates/ruff_cli/src/commands/check_stdin.rs +++ b/crates/ruff_cli/src/commands/check_stdin.rs @@ -4,9 +4,8 @@ use anyhow::Result; use ruff_linter::packaging; use ruff_linter::settings::flags; -use ruff_linter::settings::types::Language; +use ruff_linter::settings::types::ExtensionMapping; use ruff_workspace::resolver::{match_exclusion, python_file_at_path, PyprojectConfig}; -use rustc_hash::FxHashMap; use crate::args::CliOverrides; use crate::diagnostics::{lint_stdin, Diagnostics}; @@ -19,7 +18,7 @@ pub(crate) fn check_stdin( overrides: &CliOverrides, noqa: flags::Noqa, fix_mode: flags::FixMode, - extension_override: &FxHashMap, + extension_override: &ExtensionMapping, ) -> Result { if let Some(filename) = filename { if !python_file_at_path(filename, pyproject_config, overrides)? { diff --git a/crates/ruff_cli/src/diagnostics.rs b/crates/ruff_cli/src/diagnostics.rs index dc9d6a5ca0e9c..b579b5b257bba 100644 --- a/crates/ruff_cli/src/diagnostics.rs +++ b/crates/ruff_cli/src/diagnostics.rs @@ -17,7 +17,7 @@ use ruff_linter::logging::DisplayParseError; use ruff_linter::message::Message; use ruff_linter::pyproject_toml::lint_pyproject_toml; use ruff_linter::registry::AsRule; -use ruff_linter::settings::types::{Language, UnsafeFixes}; +use ruff_linter::settings::types::{ExtensionMapping, UnsafeFixes}; use ruff_linter::settings::{flags, LinterSettings}; use ruff_linter::source_kind::{SourceError, SourceKind}; use ruff_linter::{fs, IOError, SyntaxError}; @@ -179,17 +179,12 @@ impl AddAssign for FixMap { fn get_override_source_type( path: Option<&Path>, - extension: &FxHashMap, + extension: &ExtensionMapping, ) -> Option { let Some(ext) = path.and_then(|p| p.extension()).and_then(|p| p.to_str()) else { return None; }; - match extension.get(ext) { - Some(Language::Python) => Some(PySourceType::Python), - Some(Language::Ipynb) => Some(PySourceType::Ipynb), - Some(Language::Pyi) => Some(PySourceType::Stub), - None => None, - } + extension.map_ext(ext).map(PySourceType::from) } /// Lint the source code at the given `Path`. @@ -202,7 +197,7 @@ pub(crate) fn lint_path( noqa: flags::Noqa, fix_mode: flags::FixMode, unsafe_fixes: UnsafeFixes, - extension_override: &FxHashMap, + extension_override: &ExtensionMapping, ) -> Result { // Check the cache. let caching = match cache { @@ -388,7 +383,7 @@ pub(crate) fn lint_stdin( settings: &Settings, noqa: flags::Noqa, fix_mode: flags::FixMode, - extension_override: &FxHashMap, + extension_override: &ExtensionMapping, ) -> Result { // TODO(charlie): Support `pyproject.toml`. let source_type = if let Some(source_type) = get_override_source_type(path, extension_override) diff --git a/crates/ruff_cli/src/lib.rs b/crates/ruff_cli/src/lib.rs index a57a49a89c20d..cbd8fc08919f8 100644 --- a/crates/ruff_cli/src/lib.rs +++ b/crates/ruff_cli/src/lib.rs @@ -17,7 +17,6 @@ use ruff_linter::settings::flags::FixMode; use ruff_linter::settings::types::SerializationFormat; use ruff_linter::{fs, warn_user, warn_user_once}; use ruff_workspace::Settings; -use rustc_hash::FxHashMap; use crate::args::{Args, CheckCommand, Command, FormatCommand, HelpFormat}; use crate::printer::{Flags as PrinterFlags, Printer}; @@ -317,8 +316,7 @@ pub fn check(args: CheckCommand, log_level: LogLevel) -> Result { ); let extension_override = cli .extension_override - .map(|eo| eo.into_iter().map(|y| (y.extension, y.language))) - .map(FxHashMap::from_iter) + .map(|eo| eo.into_iter().collect()) .unwrap_or_default(); if cli.watch { diff --git a/crates/ruff_linter/src/settings/types.rs b/crates/ruff_linter/src/settings/types.rs index 67770ba9eb8b4..4f3551194e67c 100644 --- a/crates/ruff_linter/src/settings/types.rs +++ b/crates/ruff_linter/src/settings/types.rs @@ -1,3 +1,4 @@ +use rustc_hash::FxHashMap; use std::hash::{Hash, Hasher}; use std::ops::Deref; use std::path::{Path, PathBuf}; @@ -8,6 +9,7 @@ use anyhow::{bail, Result}; use globset::{Glob, GlobSet, GlobSetBuilder}; use pep440_rs::{Version as Pep440Version, VersionSpecifiers}; use ruff_diagnostics::Applicability; +use ruff_python_ast::PySourceType; use serde::{de, Deserialize, Deserializer, Serialize}; use strum::IntoEnumIterator; use strum_macros::EnumIter; @@ -328,6 +330,16 @@ impl FromStr for Language { } } +impl From for PySourceType { + fn from(value: Language) -> Self { + match value { + Language::Python => Self::Python, + Language::Ipynb => Self::Ipynb, + Language::Pyi => Self::Stub, + } + } +} + #[derive(Clone, Debug, PartialEq, Eq)] pub struct ExtensionPair { pub extension: String, @@ -363,6 +375,35 @@ impl From for (String, Language) { (value.extension, value.language) } } +#[derive(Debug, Clone)] +pub struct ExtensionMapping { + mapping: FxHashMap, +} + +impl ExtensionMapping { + pub fn map_ext(&self, ext: &str) -> Option { + self.mapping.get(ext).copied() + } +} + +impl Default for ExtensionMapping { + fn default() -> Self { + Self { + mapping: FxHashMap::from_iter([]), + } + } +} + +impl FromIterator for ExtensionMapping { + fn from_iter>(iter: T) -> Self { + Self { + mapping: iter + .into_iter() + .map(|y| (y.extension, y.language)) + .collect(), + } + } +} #[derive(Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Debug, Hash)] #[cfg_attr(feature = "clap", derive(clap::ValueEnum))] From 4fafe979c73eecd141392fc0f2aa466bd9486972 Mon Sep 17 00:00:00 2001 From: Felix Williams Date: Tue, 7 Nov 2023 11:44:42 +0000 Subject: [PATCH 09/12] rename extension_override to extension and move it to settings --- crates/ruff_cli/src/args.rs | 9 ++++--- crates/ruff_cli/src/cache.rs | 4 --- crates/ruff_cli/src/commands/check.rs | 19 +++----------- crates/ruff_cli/src/commands/check_stdin.rs | 3 --- crates/ruff_cli/src/diagnostics.rs | 8 +++--- crates/ruff_cli/src/lib.rs | 8 ------ crates/ruff_cli/tests/integration_test.rs | 9 ++----- crates/ruff_linter/src/settings/mod.rs | 4 ++- crates/ruff_linter/src/settings/types.rs | 7 +++++- crates/ruff_workspace/src/configuration.rs | 8 ++++-- crates/ruff_workspace/src/options.rs | 15 ++++++++++- ruff.schema.json | 28 +++++++++++++++++++++ 12 files changed, 72 insertions(+), 50 deletions(-) diff --git a/crates/ruff_cli/src/args.rs b/crates/ruff_cli/src/args.rs index 7dc9ee38c06d0..4da87e9ee8e4e 100644 --- a/crates/ruff_cli/src/args.rs +++ b/crates/ruff_cli/src/args.rs @@ -353,7 +353,7 @@ pub struct CheckCommand { pub show_settings: bool, /// List of mappings from file extension to language (one of ["python", "ipynb", "pyi"]). #[arg(long, value_delimiter = ',', hide = true)] - pub extension_override: Option>, + pub extension: Option>, /// Dev-only argument to show fixes #[arg(long, hide = true)] pub ecosystem_ci: bool, @@ -503,7 +503,6 @@ impl CheckCommand { statistics: self.statistics, stdin_filename: self.stdin_filename, watch: self.watch, - extension_override: self.extension_override, }, CliOverrides { dummy_variable_rgx: self.dummy_variable_rgx, @@ -536,6 +535,7 @@ impl CheckCommand { force_exclude: resolve_bool_arg(self.force_exclude, self.no_force_exclude), output_format: self.output_format, show_fixes: resolve_bool_arg(self.show_fixes, self.no_show_fixes), + extension: self.extension, }, ) } @@ -602,7 +602,6 @@ pub struct CheckArguments { pub statistics: bool, pub stdin_filename: Option, pub watch: bool, - pub extension_override: Option>, } /// CLI settings that are distinct from configuration (commands, lists of files, @@ -648,6 +647,7 @@ pub struct CliOverrides { pub force_exclude: Option, pub output_format: Option, pub show_fixes: Option, + pub extension: Option>, } impl ConfigurationTransformer for CliOverrides { @@ -732,6 +732,9 @@ impl ConfigurationTransformer for CliOverrides { if let Some(target_version) = &self.target_version { config.target_version = Some(*target_version); } + if let Some(extension) = &self.extension { + config.lint.extension = Some(extension.clone().into_iter().collect()); + } config } diff --git a/crates/ruff_cli/src/cache.rs b/crates/ruff_cli/src/cache.rs index 73878ed5edade..733d056ed5e60 100644 --- a/crates/ruff_cli/src/cache.rs +++ b/crates/ruff_cli/src/cache.rs @@ -564,7 +564,6 @@ mod tests { use anyhow::Result; use filetime::{set_file_mtime, FileTime}; use itertools::Itertools; - use ruff_linter::settings::types::ExtensionMapping; use test_case::test_case; use ruff_cache::CACHE_DIR_NAME; @@ -628,7 +627,6 @@ mod tests { flags::Noqa::Enabled, flags::FixMode::Generate, UnsafeFixes::Enabled, - &ExtensionMapping::default(), ) .unwrap(); if diagnostics @@ -675,7 +673,6 @@ mod tests { flags::Noqa::Enabled, flags::FixMode::Generate, UnsafeFixes::Enabled, - &ExtensionMapping::default(), ) .unwrap(); } @@ -1049,7 +1046,6 @@ mod tests { flags::Noqa::Enabled, flags::FixMode::Generate, UnsafeFixes::Enabled, - &ExtensionMapping::default(), ) } diff --git a/crates/ruff_cli/src/commands/check.rs b/crates/ruff_cli/src/commands/check.rs index 372489db72dfd..9dbc24b78bc69 100644 --- a/crates/ruff_cli/src/commands/check.rs +++ b/crates/ruff_cli/src/commands/check.rs @@ -14,7 +14,7 @@ use rustc_hash::FxHashMap; use ruff_diagnostics::Diagnostic; use ruff_linter::message::Message; use ruff_linter::registry::Rule; -use ruff_linter::settings::types::{ExtensionMapping, UnsafeFixes}; +use ruff_linter::settings::types::UnsafeFixes; use ruff_linter::settings::{flags, LinterSettings}; use ruff_linter::{fs, warn_user_once, IOError}; use ruff_python_ast::imports::ImportMap; @@ -39,7 +39,6 @@ pub(crate) fn check( noqa: flags::Noqa, fix_mode: flags::FixMode, unsafe_fixes: UnsafeFixes, - extension_override: &ExtensionMapping, ) -> Result { // Collect all the Python files to check. let start = Instant::now(); @@ -105,7 +104,6 @@ pub(crate) fn check( noqa, fix_mode, unsafe_fixes, - extension_override, ) .map_err(|e| { (Some(path.to_path_buf()), { @@ -196,19 +194,9 @@ fn lint_path( noqa: flags::Noqa, fix_mode: flags::FixMode, unsafe_fixes: UnsafeFixes, - extension_override: &ExtensionMapping, ) -> Result { let result = catch_unwind(|| { - crate::diagnostics::lint_path( - path, - package, - settings, - cache, - noqa, - fix_mode, - unsafe_fixes, - extension_override, - ) + crate::diagnostics::lint_path(path, package, settings, cache, noqa, fix_mode, unsafe_fixes) }); match result { @@ -245,7 +233,7 @@ mod test { use ruff_linter::message::{Emitter, EmitterContext, TextEmitter}; use ruff_linter::registry::Rule; - use ruff_linter::settings::types::{ExtensionMapping, UnsafeFixes}; + use ruff_linter::settings::types::UnsafeFixes; use ruff_linter::settings::{flags, LinterSettings}; use ruff_workspace::resolver::{PyprojectConfig, PyprojectDiscoveryStrategy}; use ruff_workspace::Settings; @@ -294,7 +282,6 @@ mod test { flags::Noqa::Disabled, flags::FixMode::Generate, UnsafeFixes::Enabled, - &ExtensionMapping::default(), ) .unwrap(); let mut output = Vec::new(); diff --git a/crates/ruff_cli/src/commands/check_stdin.rs b/crates/ruff_cli/src/commands/check_stdin.rs index 720ca3407cb37..67f01cd0e8d3a 100644 --- a/crates/ruff_cli/src/commands/check_stdin.rs +++ b/crates/ruff_cli/src/commands/check_stdin.rs @@ -4,7 +4,6 @@ use anyhow::Result; use ruff_linter::packaging; use ruff_linter::settings::flags; -use ruff_linter::settings::types::ExtensionMapping; use ruff_workspace::resolver::{match_exclusion, python_file_at_path, PyprojectConfig}; use crate::args::CliOverrides; @@ -18,7 +17,6 @@ pub(crate) fn check_stdin( overrides: &CliOverrides, noqa: flags::Noqa, fix_mode: flags::FixMode, - extension_override: &ExtensionMapping, ) -> Result { if let Some(filename) = filename { if !python_file_at_path(filename, pyproject_config, overrides)? { @@ -44,7 +42,6 @@ pub(crate) fn check_stdin( &pyproject_config.settings, noqa, fix_mode, - extension_override, )?; diagnostics.messages.sort_unstable(); Ok(diagnostics) diff --git a/crates/ruff_cli/src/diagnostics.rs b/crates/ruff_cli/src/diagnostics.rs index b579b5b257bba..a192120f2840e 100644 --- a/crates/ruff_cli/src/diagnostics.rs +++ b/crates/ruff_cli/src/diagnostics.rs @@ -197,7 +197,6 @@ pub(crate) fn lint_path( noqa: flags::Noqa, fix_mode: flags::FixMode, unsafe_fixes: UnsafeFixes, - extension_override: &ExtensionMapping, ) -> Result { // Check the cache. let caching = match cache { @@ -232,7 +231,8 @@ pub(crate) fn lint_path( }; debug!("Checking: {}", path.display()); - let source_type = match get_override_source_type(Some(path), extension_override) { + + let source_type = match get_override_source_type(Some(path), &settings.extension) { Some(source_type) => source_type, None => match SourceType::from(path) { SourceType::Toml(TomlSourceType::Pyproject) => { @@ -383,10 +383,10 @@ pub(crate) fn lint_stdin( settings: &Settings, noqa: flags::Noqa, fix_mode: flags::FixMode, - extension_override: &ExtensionMapping, ) -> Result { // TODO(charlie): Support `pyproject.toml`. - let source_type = if let Some(source_type) = get_override_source_type(path, extension_override) + let source_type = if let Some(source_type) = + get_override_source_type(path, &settings.linter.extension) { source_type } else { diff --git a/crates/ruff_cli/src/lib.rs b/crates/ruff_cli/src/lib.rs index cbd8fc08919f8..4cb0db0c8bb9e 100644 --- a/crates/ruff_cli/src/lib.rs +++ b/crates/ruff_cli/src/lib.rs @@ -314,10 +314,6 @@ pub fn check(args: CheckCommand, log_level: LogLevel) -> Result { unsafe_fixes, printer_flags, ); - let extension_override = cli - .extension_override - .map(|eo| eo.into_iter().collect()) - .unwrap_or_default(); if cli.watch { if output_format != SerializationFormat::Text { @@ -346,7 +342,6 @@ pub fn check(args: CheckCommand, log_level: LogLevel) -> Result { noqa.into(), fix_mode, unsafe_fixes, - &extension_override, )?; printer.write_continuously(&mut writer, &messages)?; @@ -380,7 +375,6 @@ pub fn check(args: CheckCommand, log_level: LogLevel) -> Result { noqa.into(), fix_mode, unsafe_fixes, - &extension_override, )?; printer.write_continuously(&mut writer, &messages)?; } @@ -398,7 +392,6 @@ pub fn check(args: CheckCommand, log_level: LogLevel) -> Result { &overrides, noqa.into(), fix_mode, - &extension_override, )? } else { commands::check::check( @@ -409,7 +402,6 @@ pub fn check(args: CheckCommand, log_level: LogLevel) -> Result { noqa.into(), fix_mode, unsafe_fixes, - &extension_override, )? }; diff --git a/crates/ruff_cli/tests/integration_test.rs b/crates/ruff_cli/tests/integration_test.rs index e2aa1c86631ec..96a4fe3f0ed47 100644 --- a/crates/ruff_cli/tests/integration_test.rs +++ b/crates/ruff_cli/tests/integration_test.rs @@ -322,12 +322,7 @@ fn stdin_fix_jupyter() { } #[test] fn stdin_override_parser_ipynb() { - let args = [ - "--extension-override", - "py:ipynb", - "--stdin-filename", - "Jupyter.py", - ]; + let args = ["--extension", "py:ipynb", "--stdin-filename", "Jupyter.py"]; assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME)) .args(STDIN_BASE_OPTIONS) .args(args) @@ -426,7 +421,7 @@ fn stdin_override_parser_ipynb() { fn stdin_override_parser_py() { assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME)) .args(STDIN_BASE_OPTIONS) - .args(["--extension-override", "ipynb:python", "--stdin-filename", "F401.ipynb"]) + .args(["--extension", "ipynb:python", "--stdin-filename", "F401.ipynb"]) .pass_stdin("import os\n"), @r###" success: false exit_code: 1 diff --git a/crates/ruff_linter/src/settings/mod.rs b/crates/ruff_linter/src/settings/mod.rs index 6f36a5280c871..20a5b317ac9a2 100644 --- a/crates/ruff_linter/src/settings/mod.rs +++ b/crates/ruff_linter/src/settings/mod.rs @@ -23,7 +23,7 @@ use crate::rules::{ flake8_tidy_imports, flake8_type_checking, flake8_unused_arguments, isort, mccabe, pep8_naming, pycodestyle, pydocstyle, pyflakes, pylint, pyupgrade, }; -use crate::settings::types::{FilePatternSet, PerFileIgnore, PythonVersion}; +use crate::settings::types::{ExtensionMapping, FilePatternSet, PerFileIgnore, PythonVersion}; use crate::{codes, RuleSelector}; use super::line_width::IndentWidth; @@ -49,6 +49,7 @@ pub struct LinterSettings { pub target_version: PythonVersion, pub preview: PreviewMode, pub explicit_preview_rules: bool, + pub extension: ExtensionMapping, // Rule-specific settings pub allowed_confusables: FxHashSet, @@ -187,6 +188,7 @@ impl LinterSettings { pyupgrade: pyupgrade::settings::Settings::default(), preview: PreviewMode::default(), explicit_preview_rules: false, + extension: ExtensionMapping::default(), } } diff --git a/crates/ruff_linter/src/settings/types.rs b/crates/ruff_linter/src/settings/types.rs index 4f3551194e67c..eb8bedee933f6 100644 --- a/crates/ruff_linter/src/settings/types.rs +++ b/crates/ruff_linter/src/settings/types.rs @@ -375,7 +375,7 @@ impl From for (String, Language) { (value.extension, value.language) } } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, CacheKey)] pub struct ExtensionMapping { mapping: FxHashMap, } @@ -393,6 +393,11 @@ impl Default for ExtensionMapping { } } } +impl From> for ExtensionMapping { + fn from(value: FxHashMap) -> Self { + Self { mapping: value } + } +} impl FromIterator for ExtensionMapping { fn from_iter>(iter: T) -> Self { diff --git a/crates/ruff_workspace/src/configuration.rs b/crates/ruff_workspace/src/configuration.rs index 1adcd7c68c995..f69c9042bfad2 100644 --- a/crates/ruff_workspace/src/configuration.rs +++ b/crates/ruff_workspace/src/configuration.rs @@ -24,8 +24,8 @@ use ruff_linter::rule_selector::{PreviewOptions, Specificity}; use ruff_linter::rules::pycodestyle; use ruff_linter::settings::rule_table::RuleTable; use ruff_linter::settings::types::{ - FilePattern, FilePatternSet, PerFileIgnore, PreviewMode, PythonVersion, SerializationFormat, - UnsafeFixes, Version, + ExtensionMapping, FilePattern, FilePatternSet, PerFileIgnore, PreviewMode, PythonVersion, + SerializationFormat, UnsafeFixes, Version, }; use ruff_linter::settings::{ resolve_per_file_ignores, LinterSettings, DEFAULT_SELECTORS, DUMMY_VARIABLE_RGX, TASK_TAGS, @@ -215,6 +215,7 @@ impl Configuration { linter: LinterSettings { rules: lint.as_rule_table(lint_preview), exclude: FilePatternSet::try_from_iter(lint.exclude.unwrap_or_default())?, + extension: lint.extension.unwrap_or_default(), preview: lint_preview, target_version, project_root: project_root.to_path_buf(), @@ -534,6 +535,7 @@ impl Configuration { pub struct LintConfiguration { pub exclude: Option>, pub preview: Option, + pub extension: Option, // Rule selection pub extend_per_file_ignores: Vec, @@ -610,6 +612,7 @@ impl LintConfiguration { .collect() }), preview: options.preview.map(PreviewMode::from), + extension: options.common.extension.map(ExtensionMapping::from), rule_selections: vec![RuleSelection { select: options.common.select, @@ -916,6 +919,7 @@ impl LintConfiguration { Self { exclude: self.exclude.or(config.exclude), preview: self.preview.or(config.preview), + extension: self.extension.or(config.extension), rule_selections: config .rule_selections .into_iter() diff --git a/crates/ruff_workspace/src/options.rs b/crates/ruff_workspace/src/options.rs index 5884bfa0a12b9..eb86b07657805 100644 --- a/crates/ruff_workspace/src/options.rs +++ b/crates/ruff_workspace/src/options.rs @@ -23,7 +23,7 @@ use ruff_linter::rules::{ pycodestyle, pydocstyle, pyflakes, pylint, pyupgrade, }; use ruff_linter::settings::types::{ - IdentifierPattern, PythonVersion, SerializationFormat, Version, + IdentifierPattern, Language, PythonVersion, SerializationFormat, Version, }; use ruff_linter::{warn_user_once, RuleSelector}; use ruff_macros::{CombineOptions, OptionsMetadata}; @@ -724,6 +724,19 @@ pub struct LintCommonOptions { )] pub unfixable: Option>, + /// A mapping from file extension to one of python,ipynb,pyi to + /// override default python file type inference + #[option( + default = "{}", + value_type = "dict[str, Language]", + example = r#" + # Treat .ipynb files as python files + [tool.ruff.extension] + "ipynb" = "python" + "# + )] + pub extension: Option>, + /// Options for the `flake8-annotations` plugin. #[option_group] pub flake8_annotations: Option, diff --git a/ruff.schema.json b/ruff.schema.json index 72a967d1b277c..3257f65580bdf 100644 --- a/ruff.schema.json +++ b/ruff.schema.json @@ -158,6 +158,16 @@ "$ref": "#/definitions/RuleSelector" } }, + "extension": { + "description": "A mapping from file extension to one of python,ipynb,pyi to override default python file type inference", + "type": [ + "object", + "null" + ], + "additionalProperties": { + "$ref": "#/definitions/Language" + } + }, "external": { "description": "A list of rule codes or prefixes that are unsupported by Ruff, but should be preserved when (e.g.) validating `# noqa` directives. Useful for retaining `# noqa` directives that cover plugins not yet implemented by Ruff.", "type": [ @@ -1578,6 +1588,14 @@ }, "additionalProperties": false }, + "Language": { + "type": "string", + "enum": [ + "python", + "pyi", + "ipynb" + ] + }, "LineEnding": { "oneOf": [ { @@ -1732,6 +1750,16 @@ "$ref": "#/definitions/RuleSelector" } }, + "extension": { + "description": "A mapping from file extension to one of python,ipynb,pyi to override default python file type inference", + "type": [ + "object", + "null" + ], + "additionalProperties": { + "$ref": "#/definitions/Language" + } + }, "external": { "description": "A list of rule codes or prefixes that are unsupported by Ruff, but should be preserved when (e.g.) validating `# noqa` directives. Useful for retaining `# noqa` directives that cover plugins not yet implemented by Ruff.", "type": [ From 24d1957ca047d79f1abe7da6bb8e1dca4d92e449 Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Tue, 7 Nov 2023 17:31:34 -0500 Subject: [PATCH 10/12] Nits --- crates/ruff_cli/src/diagnostics.rs | 15 ++++------- crates/ruff_linter/src/settings/types.rs | 34 ++++++++++-------------- 2 files changed, 19 insertions(+), 30 deletions(-) diff --git a/crates/ruff_cli/src/diagnostics.rs b/crates/ruff_cli/src/diagnostics.rs index a192120f2840e..6504cd0d51a4b 100644 --- a/crates/ruff_cli/src/diagnostics.rs +++ b/crates/ruff_cli/src/diagnostics.rs @@ -177,14 +177,9 @@ impl AddAssign for FixMap { } } -fn get_override_source_type( - path: Option<&Path>, - extension: &ExtensionMapping, -) -> Option { - let Some(ext) = path.and_then(|p| p.extension()).and_then(|p| p.to_str()) else { - return None; - }; - extension.map_ext(ext).map(PySourceType::from) +fn override_source_type(path: Option<&Path>, extension: &ExtensionMapping) -> Option { + let ext = path?.extension()?.to_str()?; + extension.get(ext).map(PySourceType::from) } /// Lint the source code at the given `Path`. @@ -232,7 +227,7 @@ pub(crate) fn lint_path( debug!("Checking: {}", path.display()); - let source_type = match get_override_source_type(Some(path), &settings.extension) { + let source_type = match override_source_type(Some(path), &settings.extension) { Some(source_type) => source_type, None => match SourceType::from(path) { SourceType::Toml(TomlSourceType::Pyproject) => { @@ -386,7 +381,7 @@ pub(crate) fn lint_stdin( ) -> Result { // TODO(charlie): Support `pyproject.toml`. let source_type = if let Some(source_type) = - get_override_source_type(path, &settings.linter.extension) + override_source_type(path, &settings.linter.extension) { source_type } else { diff --git a/crates/ruff_linter/src/settings/types.rs b/crates/ruff_linter/src/settings/types.rs index eb8bedee933f6..8c8d84efb7f34 100644 --- a/crates/ruff_linter/src/settings/types.rs +++ b/crates/ruff_linter/src/settings/types.rs @@ -1,4 +1,3 @@ -use rustc_hash::FxHashMap; use std::hash::{Hash, Hasher}; use std::ops::Deref; use std::path::{Path, PathBuf}; @@ -8,14 +7,15 @@ use std::string::ToString; use anyhow::{bail, Result}; use globset::{Glob, GlobSet, GlobSetBuilder}; use pep440_rs::{Version as Pep440Version, VersionSpecifiers}; -use ruff_diagnostics::Applicability; -use ruff_python_ast::PySourceType; +use rustc_hash::FxHashMap; use serde::{de, Deserialize, Deserializer, Serialize}; use strum::IntoEnumIterator; use strum_macros::EnumIter; use ruff_cache::{CacheKey, CacheKeyHasher}; +use ruff_diagnostics::Applicability; use ruff_macros::CacheKey; +use ruff_python_ast::PySourceType; use crate::fs; use crate::registry::RuleSet; @@ -305,8 +305,8 @@ impl FromStr for PatternPrefixPair { CacheKey, EnumIter, )] -#[cfg_attr(feature = "clap", derive(clap::ValueEnum))] #[serde(rename_all = "lowercase")] +#[cfg_attr(feature = "clap", derive(clap::ValueEnum))] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] pub enum Language { #[default] @@ -317,15 +317,15 @@ pub enum Language { impl FromStr for Language { type Err = anyhow::Error; - fn from_str(s: &str) -> std::result::Result { + + fn from_str(s: &str) -> Result { match s.to_ascii_lowercase().as_str() { "python" => Ok(Self::Python), "pyi" => Ok(Self::Pyi), "ipynb" => Ok(Self::Ipynb), - _ => bail!( - "Unrecognised language {}. Must be one of python,pyi,ipynb", - s - ), + _ => { + bail!("Unrecognized language: `{s}`. Expected one of `python`, `pyi`, or `ipynb`.") + } } } } @@ -375,24 +375,18 @@ impl From for (String, Language) { (value.extension, value.language) } } -#[derive(Debug, Clone, CacheKey)] +#[derive(Debug, Clone, Default, CacheKey)] pub struct ExtensionMapping { mapping: FxHashMap, } impl ExtensionMapping { - pub fn map_ext(&self, ext: &str) -> Option { - self.mapping.get(ext).copied() + /// Return the [`Language`] for the given extension. + pub fn get(&self, extension: &str) -> Option { + self.mapping.get(extension).copied() } } -impl Default for ExtensionMapping { - fn default() -> Self { - Self { - mapping: FxHashMap::from_iter([]), - } - } -} impl From> for ExtensionMapping { fn from(value: FxHashMap) -> Self { Self { mapping: value } @@ -404,7 +398,7 @@ impl FromIterator for ExtensionMapping { Self { mapping: iter .into_iter() - .map(|y| (y.extension, y.language)) + .map(|pair| (pair.extension, pair.language)) .collect(), } } From e879d0cea5a1f5ca1b85dfa88b6c31f874dfbd6f Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Tue, 7 Nov 2023 17:34:37 -0500 Subject: [PATCH 11/12] Remove from Options --- crates/ruff_workspace/src/configuration.rs | 4 +++- crates/ruff_workspace/src/options.rs | 15 +----------- ruff.schema.json | 28 ---------------------- 3 files changed, 4 insertions(+), 43 deletions(-) diff --git a/crates/ruff_workspace/src/configuration.rs b/crates/ruff_workspace/src/configuration.rs index f69c9042bfad2..a17d909dff4e7 100644 --- a/crates/ruff_workspace/src/configuration.rs +++ b/crates/ruff_workspace/src/configuration.rs @@ -602,6 +602,9 @@ impl LintConfiguration { .chain(options.common.extend_unfixable.into_iter().flatten()) .collect(); Ok(LintConfiguration { + // `--extension` is a hidden command-line argument that isn't supported in configuration + // files at present. + extension: None, exclude: options.exclude.map(|paths| { paths .into_iter() @@ -612,7 +615,6 @@ impl LintConfiguration { .collect() }), preview: options.preview.map(PreviewMode::from), - extension: options.common.extension.map(ExtensionMapping::from), rule_selections: vec![RuleSelection { select: options.common.select, diff --git a/crates/ruff_workspace/src/options.rs b/crates/ruff_workspace/src/options.rs index eb86b07657805..5884bfa0a12b9 100644 --- a/crates/ruff_workspace/src/options.rs +++ b/crates/ruff_workspace/src/options.rs @@ -23,7 +23,7 @@ use ruff_linter::rules::{ pycodestyle, pydocstyle, pyflakes, pylint, pyupgrade, }; use ruff_linter::settings::types::{ - IdentifierPattern, Language, PythonVersion, SerializationFormat, Version, + IdentifierPattern, PythonVersion, SerializationFormat, Version, }; use ruff_linter::{warn_user_once, RuleSelector}; use ruff_macros::{CombineOptions, OptionsMetadata}; @@ -724,19 +724,6 @@ pub struct LintCommonOptions { )] pub unfixable: Option>, - /// A mapping from file extension to one of python,ipynb,pyi to - /// override default python file type inference - #[option( - default = "{}", - value_type = "dict[str, Language]", - example = r#" - # Treat .ipynb files as python files - [tool.ruff.extension] - "ipynb" = "python" - "# - )] - pub extension: Option>, - /// Options for the `flake8-annotations` plugin. #[option_group] pub flake8_annotations: Option, diff --git a/ruff.schema.json b/ruff.schema.json index 3257f65580bdf..72a967d1b277c 100644 --- a/ruff.schema.json +++ b/ruff.schema.json @@ -158,16 +158,6 @@ "$ref": "#/definitions/RuleSelector" } }, - "extension": { - "description": "A mapping from file extension to one of python,ipynb,pyi to override default python file type inference", - "type": [ - "object", - "null" - ], - "additionalProperties": { - "$ref": "#/definitions/Language" - } - }, "external": { "description": "A list of rule codes or prefixes that are unsupported by Ruff, but should be preserved when (e.g.) validating `# noqa` directives. Useful for retaining `# noqa` directives that cover plugins not yet implemented by Ruff.", "type": [ @@ -1588,14 +1578,6 @@ }, "additionalProperties": false }, - "Language": { - "type": "string", - "enum": [ - "python", - "pyi", - "ipynb" - ] - }, "LineEnding": { "oneOf": [ { @@ -1750,16 +1732,6 @@ "$ref": "#/definitions/RuleSelector" } }, - "extension": { - "description": "A mapping from file extension to one of python,ipynb,pyi to override default python file type inference", - "type": [ - "object", - "null" - ], - "additionalProperties": { - "$ref": "#/definitions/Language" - } - }, "external": { "description": "A list of rule codes or prefixes that are unsupported by Ruff, but should be preserved when (e.g.) validating `# noqa` directives. Useful for retaining `# noqa` directives that cover plugins not yet implemented by Ruff.", "type": [ From 50d7875cd4e35cdaaa9ac6241f57b0b216c0a850 Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Tue, 7 Nov 2023 17:36:51 -0500 Subject: [PATCH 12/12] Remove allow --- crates/ruff_cli/src/diagnostics.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/ruff_cli/src/diagnostics.rs b/crates/ruff_cli/src/diagnostics.rs index 6504cd0d51a4b..076963fee53b1 100644 --- a/crates/ruff_cli/src/diagnostics.rs +++ b/crates/ruff_cli/src/diagnostics.rs @@ -183,7 +183,6 @@ fn override_source_type(path: Option<&Path>, extension: &ExtensionMapping) -> Op } /// Lint the source code at the given `Path`. -#[allow(clippy::too_many_arguments)] pub(crate) fn lint_path( path: &Path, package: Option<&Path>,