diff --git a/crates/ruff_cli/tests/format.rs b/crates/ruff_cli/tests/format.rs index 4892896f7b7de5..d0036d9ac94957 100644 --- a/crates/ruff_cli/tests/format.rs +++ b/crates/ruff_cli/tests/format.rs @@ -50,7 +50,7 @@ fn format_options() -> Result<()> { fs::write( &ruff_toml, r#" -tab-size = 8 +indent-width = 8 line-length = 84 [format] @@ -278,6 +278,41 @@ if condition: Ok(()) } +#[test] +fn deprecated_options() -> Result<()> { + let tempdir = TempDir::new()?; + let ruff_toml = tempdir.path().join("ruff.toml"); + fs::write( + &ruff_toml, + r#" +tab-size = 2 +"#, + )?; + + insta::with_settings!({filters => vec![ + (&*regex::escape(ruff_toml.to_str().unwrap()), "[RUFF-TOML-PATH]"), + ]}, { + assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME)) + .args(["format", "--config"]) + .arg(&ruff_toml) + .arg("-") + .pass_stdin(r#" +if True: + pass + "#), @r###" + success: true + exit_code: 0 + ----- stdout ----- + if True: + pass + + ----- stderr ----- + warning: The `tab-size` option has been renamed to `indent-width` to emphasize that it configures the indentation used by the formatter as well as the tab width. Please update your configuration to use `indent-width = ` instead. + "###); + }); + Ok(()) +} + /// Since 0.1.0 the legacy format option is no longer supported #[test] fn legacy_format_option() -> Result<()> { diff --git a/crates/ruff_linter/src/fix/edits.rs b/crates/ruff_linter/src/fix/edits.rs index 0a23555497faa9..76257f7f5967b3 100644 --- a/crates/ruff_linter/src/fix/edits.rs +++ b/crates/ruff_linter/src/fix/edits.rs @@ -14,7 +14,7 @@ use ruff_source_file::{Locator, NewlineWithTrailingNewline, UniversalNewlines}; use ruff_text_size::{Ranged, TextLen, TextRange, TextSize}; use crate::fix::codemods; -use crate::line_width::{LineLength, LineWidthBuilder, TabSize}; +use crate::line_width::{IndentWidth, LineLength, LineWidthBuilder}; /// Return the `Fix` to use when deleting a `Stmt`. /// @@ -293,7 +293,7 @@ pub(crate) fn fits( node: AnyNodeRef, locator: &Locator, line_length: LineLength, - tab_size: TabSize, + tab_size: IndentWidth, ) -> bool { all_lines_fit(fix, node, locator, line_length.value() as usize, tab_size) } @@ -305,7 +305,7 @@ pub(crate) fn fits_or_shrinks( node: AnyNodeRef, locator: &Locator, line_length: LineLength, - tab_size: TabSize, + tab_size: IndentWidth, ) -> bool { // Use the larger of the line length limit, or the longest line in the existing AST node. let line_length = std::iter::once(line_length.value() as usize) @@ -327,7 +327,7 @@ fn all_lines_fit( node: AnyNodeRef, locator: &Locator, line_length: usize, - tab_size: TabSize, + tab_size: IndentWidth, ) -> bool { let prefix = locator.slice(TextRange::new( locator.line_start(node.start()), diff --git a/crates/ruff_linter/src/line_width.rs b/crates/ruff_linter/src/line_width.rs index fe8fc31849452e..06fe47c94464b8 100644 --- a/crates/ruff_linter/src/line_width.rs +++ b/crates/ruff_linter/src/line_width.rs @@ -129,12 +129,12 @@ pub struct LineWidthBuilder { /// This is used to calculate the width of tabs. column: usize, /// The tab size to use when calculating the width of tabs. - tab_size: TabSize, + tab_size: IndentWidth, } impl Default for LineWidthBuilder { fn default() -> Self { - Self::new(TabSize::default()) + Self::new(IndentWidth::default()) } } @@ -164,7 +164,7 @@ impl LineWidthBuilder { } /// Creates a new `LineWidth` with the given tab size. - pub fn new(tab_size: TabSize) -> Self { + pub fn new(tab_size: IndentWidth) -> Self { LineWidthBuilder { width: 0, column: 0, @@ -234,28 +234,28 @@ impl PartialOrd for LineWidthBuilder { /// The size of a tab. #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize, CacheKey)] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] -pub struct TabSize(NonZeroU8); +pub struct IndentWidth(NonZeroU8); -impl TabSize { +impl IndentWidth { pub(crate) fn as_usize(self) -> usize { self.0.get() as usize } } -impl Default for TabSize { +impl Default for IndentWidth { fn default() -> Self { Self(NonZeroU8::new(4).unwrap()) } } -impl From for TabSize { +impl From for IndentWidth { fn from(tab_size: NonZeroU8) -> Self { Self(tab_size) } } -impl From for NonZeroU8 { - fn from(value: TabSize) -> Self { +impl From for NonZeroU8 { + fn from(value: IndentWidth) -> Self { value.0 } } diff --git a/crates/ruff_linter/src/message/text.rs b/crates/ruff_linter/src/message/text.rs index d43104d97dacec..833deab46c8182 100644 --- a/crates/ruff_linter/src/message/text.rs +++ b/crates/ruff_linter/src/message/text.rs @@ -12,7 +12,7 @@ use ruff_source_file::{OneIndexed, SourceLocation}; use ruff_text_size::{Ranged, TextRange, TextSize}; use crate::fs::relativize_path; -use crate::line_width::{LineWidthBuilder, TabSize}; +use crate::line_width::{IndentWidth, LineWidthBuilder}; use crate::message::diff::Diff; use crate::message::{Emitter, EmitterContext, Message}; use crate::registry::AsRule; @@ -300,7 +300,7 @@ fn replace_whitespace(source: &str, annotation_range: TextRange) -> SourceCode { let mut result = String::new(); let mut last_end = 0; let mut range = annotation_range; - let mut line_width = LineWidthBuilder::new(TabSize::default()); + let mut line_width = LineWidthBuilder::new(IndentWidth::default()); for (index, c) in source.char_indices() { let old_width = line_width.get(); diff --git a/crates/ruff_linter/src/rules/pycodestyle/overlong.rs b/crates/ruff_linter/src/rules/pycodestyle/overlong.rs index a91655892d4692..950bafa0554a56 100644 --- a/crates/ruff_linter/src/rules/pycodestyle/overlong.rs +++ b/crates/ruff_linter/src/rules/pycodestyle/overlong.rs @@ -7,7 +7,7 @@ use ruff_python_trivia::is_pragma_comment; use ruff_source_file::Line; use ruff_text_size::{TextLen, TextRange}; -use crate::line_width::{LineLength, LineWidthBuilder, TabSize}; +use crate::line_width::{IndentWidth, LineLength, LineWidthBuilder}; #[derive(Debug)] pub(super) struct Overlong { @@ -23,7 +23,7 @@ impl Overlong { indexer: &Indexer, limit: LineLength, task_tags: &[String], - tab_size: TabSize, + tab_size: IndentWidth, ) -> Option { // The maximum width of the line is the number of bytes multiplied by the tab size (the // worst-case scenario is that the line is all tabs). If the maximum width is less than the @@ -158,7 +158,7 @@ impl<'a> Deref for StrippedLine<'a> { } /// Returns the width of a given string, accounting for the tab size. -fn measure(s: &str, tab_size: TabSize) -> LineWidthBuilder { +fn measure(s: &str, tab_size: IndentWidth) -> LineWidthBuilder { let mut width = LineWidthBuilder::new(tab_size); width = width.add_str(s); width diff --git a/crates/ruff_linter/src/settings/mod.rs b/crates/ruff_linter/src/settings/mod.rs index 699288a4e07677..64bb91a746c36b 100644 --- a/crates/ruff_linter/src/settings/mod.rs +++ b/crates/ruff_linter/src/settings/mod.rs @@ -26,7 +26,7 @@ use crate::rules::{ use crate::settings::types::{FilePatternSet, PerFileIgnore, PythonVersion}; use crate::{codes, RuleSelector}; -use super::line_width::{LineLength, TabSize}; +use super::line_width::{IndentWidth, LineLength}; use self::rule_table::RuleTable; use self::types::PreviewMode; @@ -60,7 +60,7 @@ pub struct LinterSettings { pub logger_objects: Vec, pub namespace_packages: Vec, pub src: Vec, - pub tab_size: TabSize, + pub tab_size: IndentWidth, pub task_tags: Vec, pub typing_modules: Vec, @@ -157,7 +157,7 @@ impl LinterSettings { src: vec![path_dedot::CWD.clone()], // Needs duplicating - tab_size: TabSize::default(), + tab_size: IndentWidth::default(), task_tags: TASK_TAGS.iter().map(ToString::to_string).collect(), typing_modules: vec![], diff --git a/crates/ruff_wasm/src/lib.rs b/crates/ruff_wasm/src/lib.rs index f2fcc8055263fc..a067cde03e1689 100644 --- a/crates/ruff_wasm/src/lib.rs +++ b/crates/ruff_wasm/src/lib.rs @@ -6,7 +6,7 @@ use wasm_bindgen::prelude::*; use ruff_formatter::{FormatResult, Formatted, IndentStyle}; use ruff_linter::directives; -use ruff_linter::line_width::{LineLength, TabSize}; +use ruff_linter::line_width::{IndentWidth, LineLength}; use ruff_linter::linter::{check_path, LinterResult}; use ruff_linter::registry::AsRule; use ruff_linter::settings::types::PythonVersion; @@ -126,7 +126,7 @@ impl Workspace { line_length: Some(LineLength::default()), - tab_size: Some(TabSize::default()), + indent_width: Some(IndentWidth::default()), target_version: Some(PythonVersion::default()), lint: Some(LintOptions { diff --git a/crates/ruff_workspace/src/configuration.rs b/crates/ruff_workspace/src/configuration.rs index 8f208da039139c..bc20ab57ed9d06 100644 --- a/crates/ruff_workspace/src/configuration.rs +++ b/crates/ruff_workspace/src/configuration.rs @@ -16,8 +16,8 @@ use shellexpand::LookupError; use strum::IntoEnumIterator; use ruff_cache::cache_dir; -use ruff_formatter::{IndentStyle, IndentWidth, LineWidth}; -use ruff_linter::line_width::{LineLength, TabSize}; +use ruff_formatter::IndentStyle; +use ruff_linter::line_width::{IndentWidth, LineLength}; use ruff_linter::registry::RuleNamespace; use ruff_linter::registry::{Rule, RuleSet, INCOMPATIBLE_CODES}; use ruff_linter::rule_selector::{PreviewOptions, Specificity}; @@ -132,7 +132,7 @@ pub struct Configuration { // Global formatting options pub line_length: Option, - pub tab_size: Option, + pub indent_width: Option, pub lint: LintConfiguration, pub format: FormatConfiguration, @@ -165,14 +165,14 @@ impl Configuration { line_width: self .line_length .map_or(format_defaults.line_width, |length| { - LineWidth::from(NonZeroU16::from(length)) + ruff_formatter::LineWidth::from(NonZeroU16::from(length)) }), line_ending: format.line_ending.unwrap_or(format_defaults.line_ending), indent_style: format.indent_style.unwrap_or(format_defaults.indent_style), indent_width: self - .tab_size + .indent_width .map_or(format_defaults.indent_width, |tab_size| { - IndentWidth::from(NonZeroU8::from(tab_size)) + ruff_formatter::IndentWidth::from(NonZeroU8::from(tab_size)) }), quote_style: format.quote_style.unwrap_or(format_defaults.quote_style), magic_trailing_comma: format @@ -226,7 +226,7 @@ impl Configuration { external: FxHashSet::from_iter(lint.external.unwrap_or_default()), ignore_init_module_imports: lint.ignore_init_module_imports.unwrap_or_default(), line_length: self.line_length.unwrap_or_default(), - tab_size: self.tab_size.unwrap_or_default(), + tab_size: self.indent_width.unwrap_or_default(), namespace_packages: self.namespace_packages.unwrap_or_default(), per_file_ignores: resolve_per_file_ignores( lint.per_file_ignores @@ -383,6 +383,15 @@ impl Configuration { } }; + #[allow(deprecated)] + let indent_width = { + if options.tab_size.is_some() { + warn_user_once!("The `tab-size` option has been renamed to `indent-width` to emphasize that it configures the indentation used by the formatter as well as the tab width. Please update your configuration to use `indent-width = ` instead."); + } + + options.indent_width.or(options.tab_size) + }; + Ok(Self { builtins: options.builtins, cache_dir: options @@ -450,7 +459,7 @@ impl Configuration { output_format: options.output_format, force_exclude: options.force_exclude, line_length: options.line_length, - tab_size: options.tab_size, + indent_width, namespace_packages: options .namespace_packages .map(|namespace_package| resolve_src(&namespace_package, project_root)) @@ -498,7 +507,7 @@ impl Configuration { output_format: self.output_format.or(config.output_format), force_exclude: self.force_exclude.or(config.force_exclude), line_length: self.line_length.or(config.line_length), - tab_size: self.tab_size.or(config.tab_size), + indent_width: self.indent_width.or(config.indent_width), namespace_packages: self.namespace_packages.or(config.namespace_packages), required_version: self.required_version.or(config.required_version), respect_gitignore: self.respect_gitignore.or(config.respect_gitignore), diff --git a/crates/ruff_workspace/src/options.rs b/crates/ruff_workspace/src/options.rs index 2d0d98a4524c6b..e90dff714092c8 100644 --- a/crates/ruff_workspace/src/options.rs +++ b/crates/ruff_workspace/src/options.rs @@ -7,7 +7,7 @@ use serde::{Deserialize, Serialize}; use strum::IntoEnumIterator; use ruff_formatter::IndentStyle; -use ruff_linter::line_width::{LineLength, TabSize}; +use ruff_linter::line_width::{IndentWidth, LineLength}; use ruff_linter::rules::flake8_pytest_style::settings::SettingsError; use ruff_linter::rules::flake8_pytest_style::types; use ruff_linter::rules::flake8_quotes::settings::Quote; @@ -372,6 +372,24 @@ pub struct Options { )] pub line_length: Option, + /// The number of spaces per indentation level (tab). + /// + /// Used by the formatter and when enforcing long-line violations (like `E501`) to determine the visual + /// width of a tab. + /// + /// This option changes the number of spaces the formatter inserts when + /// using soft-tabs (`indent-style = space`). + /// + /// PEP 8 recommends using 4 spaces per [indentation level](https://peps.python.org/pep-0008/#indentation). + #[option( + default = "4", + value_type = "int", + example = r#" + indent-width = 2 + "# + )] + pub indent_width: Option, + /// The number of spaces a tab is equal to when enforcing long-line violations (like `E501`) /// or formatting code with the formatter. /// @@ -381,10 +399,14 @@ pub struct Options { default = "4", value_type = "int", example = r#" - tab-size = 8 + tab-size = 2 "# )] - pub tab_size: Option, + #[deprecated( + since = "0.1.2", + note = "The `tab-size` option has been renamed to `indent-width` to emphasize that it configures the indentation used by the formatter as well as the tab width. Please update your configuration to use `indent-width = ` instead." + )] + pub tab_size: Option, pub lint: Option, @@ -2292,7 +2314,7 @@ impl PycodestyleOptions { #[serde(deny_unknown_fields, rename_all = "kebab-case")] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] pub struct PydocstyleOptions { - /// Whether to use Google-style or NumPy-style conventions or the PEP257 + /// Whether to use Google-style or NumPy-style conventions or the [PEP 257](https://peps.python.org/pep-0257/) /// defaults when analyzing docstring sections. /// /// Enabling a convention will force-disable any rules that are not @@ -2564,10 +2586,26 @@ pub struct FormatOptions { )] pub preview: Option, - /// Whether to use 4 spaces or hard tabs for indenting code. + /// Whether to use spaces or tabs for indentation. + /// + /// `indent-style = "space"` (default): + /// + /// ```python + /// def f(): + /// print("Hello") # Spaces indent the `print` statement. + /// ``` + /// + /// `indent-style = "tab""`: + /// + /// ```python + /// def f(): + /// print("Hello") # A tab `\t` indents the `print` statement. + /// ``` + /// + /// PEP 8 recommends using spaces for [indentation](https://peps.python.org/pep-0008/#indentation). + /// We care about accessibility; if you do not need tabs for accessibility, we do not recommend you use them. /// - /// Defaults to 4 spaces. We care about accessibility; if you do not need tabs for - /// accessibility, we do not recommend you use them. + /// See [`indent-width`](#indent-width) to configure the number of spaces per indentation and the tab width. #[option( default = "space", value_type = r#""space" | "tab""#, diff --git a/ruff.schema.json b/ruff.schema.json index ae49d4c5c81568..23062bd8d1fb45 100644 --- a/ruff.schema.json +++ b/ruff.schema.json @@ -413,6 +413,17 @@ "type": "string" } }, + "indent-width": { + "description": "The number of spaces per indentation level (tab).\n\nUsed by the formatter and when enforcing long-line violations (like `E501`) to determine the visual width of a tab.\n\nThis option changes the number of spaces the formatter inserts when using soft-tabs (`indent-style = space`).\n\nPEP 8 recommends using 4 spaces per [indentation level](https://peps.python.org/pep-0008/#indentation).", + "anyOf": [ + { + "$ref": "#/definitions/IndentWidth" + }, + { + "type": "null" + } + ] + }, "isort": { "description": "Options for the `isort` plugin.", "anyOf": [ @@ -627,9 +638,10 @@ }, "tab-size": { "description": "The number of spaces a tab is equal to when enforcing long-line violations (like `E501`) or formatting code with the formatter.\n\nThis option changes the number of spaces inserted by the formatter when using soft-tabs (`indent-style = space`).", + "deprecated": true, "anyOf": [ { - "$ref": "#/definitions/TabSize" + "$ref": "#/definitions/IndentWidth" }, { "type": "null" @@ -1240,7 +1252,7 @@ } }, "indent-style": { - "description": "Whether to use 4 spaces or hard tabs for indenting code.\n\nDefaults to 4 spaces. We care about accessibility; if you do not need tabs for accessibility, we do not recommend you use them.", + "description": "Whether to use spaces or tabs for indentation.\n\n`indent-style = \"space\"` (default):\n\n```python def f(): print(\"Hello\") # Spaces indent the `print` statement. ```\n\n`indent-style = \"tab\"\"`:\n\n```python def f(): print(\"Hello\") # A tab `\\t` indents the `print` statement. ```\n\nPEP 8 recommends using spaces for [indentation](https://peps.python.org/pep-0008/#indentation). We care about accessibility; if you do not need tabs for accessibility, we do not recommend you use them.\n\nSee [`indent-width`](#indent-width) to configure the number of spaces per indentation and the tab width.", "anyOf": [ { "$ref": "#/definitions/IndentStyle" @@ -1327,6 +1339,12 @@ } ] }, + "IndentWidth": { + "description": "The size of a tab.", + "type": "integer", + "format": "uint8", + "minimum": 1.0 + }, "IsortOptions": { "type": "object", "properties": { @@ -2212,7 +2230,7 @@ "type": "object", "properties": { "convention": { - "description": "Whether to use Google-style or NumPy-style conventions or the PEP257 defaults when analyzing docstring sections.\n\nEnabling a convention will force-disable any rules that are not included in the specified convention. As such, the intended use is to enable a convention and then selectively disable any additional rules on top of it.\n\nFor example, to use Google-style conventions but avoid requiring documentation for every function parameter:\n\n```toml [tool.ruff] # Enable all `pydocstyle` rules, limiting to those that adhere to the # Google convention via `convention = \"google\"`, below. select = [\"D\"]\n\n# On top of the Google convention, disable `D417`, which requires # documentation for every function parameter. ignore = [\"D417\"]\n\n[tool.ruff.pydocstyle] convention = \"google\" ```\n\nAs conventions force-disable all rules not included in the convention, enabling _additional_ rules on top of a convention is currently unsupported.", + "description": "Whether to use Google-style or NumPy-style conventions or the [PEP 257](https://peps.python.org/pep-0257/) defaults when analyzing docstring sections.\n\nEnabling a convention will force-disable any rules that are not included in the specified convention. As such, the intended use is to enable a convention and then selectively disable any additional rules on top of it.\n\nFor example, to use Google-style conventions but avoid requiring documentation for every function parameter:\n\n```toml [tool.ruff] # Enable all `pydocstyle` rules, limiting to those that adhere to the # Google convention via `convention = \"google\"`, below. select = [\"D\"]\n\n# On top of the Google convention, disable `D417`, which requires # documentation for every function parameter. ignore = [\"D417\"]\n\n[tool.ruff.pydocstyle] convention = \"google\" ```\n\nAs conventions force-disable all rules not included in the convention, enabling _additional_ rules on top of a convention is currently unsupported.", "anyOf": [ { "$ref": "#/definitions/Convention" @@ -3559,12 +3577,6 @@ } ] }, - "TabSize": { - "description": "The size of a tab.", - "type": "integer", - "format": "uint8", - "minimum": 1.0 - }, "Version": { "type": "string" }