From c6a760e298a9288f71519b2c2431375b07492739 Mon Sep 17 00:00:00 2001 From: Jonathan Plasse <13716151+JonathanPlasse@users.noreply.github.com> Date: Wed, 24 May 2023 08:37:24 +0200 Subject: [PATCH] Introduce `tab-size` to correcly calculate the line length with tabulations (#4167) --- .../test/fixtures/flake8_simplify/SIM102.py | 13 +- .../test/fixtures/flake8_simplify/SIM108.py | 16 +- .../test/fixtures/flake8_simplify/SIM110.py | 16 ++ .../test/fixtures/flake8_simplify/SIM111.py | 16 ++ .../test/fixtures/flake8_simplify/SIM117.py | 10 ++ .../test/fixtures/flake8_simplify/SIM401.py | 8 +- .../fixtures/isort/fit_line_length_comment.py | 4 + .../test/fixtures/pycodestyle/E501_2.py | 11 ++ .../test/fixtures/pycodestyle/W505_utf_8.py | 31 ++++ crates/ruff/src/checkers/physical_lines.rs | 8 +- crates/ruff/src/flake8_to_ruff/converter.rs | 12 +- crates/ruff/src/lib.rs | 1 + crates/ruff/src/line_width.rs | 165 ++++++++++++++++++ crates/ruff/src/message/text.rs | 45 +++-- .../src/rules/flake8_simplify/rules/ast_if.rs | 17 +- .../rules/flake8_simplify/rules/ast_with.rs | 7 +- .../rules/reimplemented_builtin.rs | 12 +- ...ke8_simplify__tests__SIM102_SIM102.py.snap | 70 +++++--- ...ke8_simplify__tests__SIM108_SIM108.py.snap | 46 ++--- ...ke8_simplify__tests__SIM110_SIM110.py.snap | 29 +++ ...ke8_simplify__tests__SIM110_SIM111.py.snap | 29 +++ ...ke8_simplify__tests__SIM117_SIM117.py.snap | 36 ++++ ...ke8_simplify__tests__SIM401_SIM401.py.snap | 12 +- crates/ruff/src/rules/isort/format.rs | 44 +++-- crates/ruff/src/rules/isort/mod.rs | 9 +- .../src/rules/isort/rules/organize_imports.rs | 4 +- ...rt__tests__fit_line_length_comment.py.snap | 29 ++- crates/ruff/src/rules/pycodestyle/helpers.rs | 16 +- crates/ruff/src/rules/pycodestyle/mod.rs | 36 +++- .../pycodestyle/rules/doc_line_too_long.rs | 8 +- .../rules/pycodestyle/rules/line_too_long.rs | 3 +- crates/ruff/src/rules/pycodestyle/settings.rs | 6 +- ...yle__tests__max_doc_length_with_utf_8.snap | 62 +++++++ ...rules__pycodestyle__tests__tab_size_1.snap | 56 ++++++ ...rules__pycodestyle__tests__tab_size_2.snap | 56 ++++++ ...rules__pycodestyle__tests__tab_size_4.snap | 65 +++++++ ...rules__pycodestyle__tests__tab_size_8.snap | 72 ++++++++ crates/ruff/src/settings/configuration.rs | 6 +- crates/ruff/src/settings/defaults.rs | 6 +- crates/ruff/src/settings/mod.rs | 7 +- crates/ruff/src/settings/options.rs | 16 +- crates/ruff/src/settings/pyproject.rs | 5 +- crates/ruff_cli/src/args.rs | 3 +- crates/ruff_wasm/src/lib.rs | 4 +- ruff.schema.json | 51 ++++-- 45 files changed, 1005 insertions(+), 173 deletions(-) create mode 100644 crates/ruff/resources/test/fixtures/pycodestyle/E501_2.py create mode 100644 crates/ruff/resources/test/fixtures/pycodestyle/W505_utf_8.py create mode 100644 crates/ruff/src/line_width.rs create mode 100644 crates/ruff/src/rules/pycodestyle/snapshots/ruff__rules__pycodestyle__tests__max_doc_length_with_utf_8.snap create mode 100644 crates/ruff/src/rules/pycodestyle/snapshots/ruff__rules__pycodestyle__tests__tab_size_1.snap create mode 100644 crates/ruff/src/rules/pycodestyle/snapshots/ruff__rules__pycodestyle__tests__tab_size_2.snap create mode 100644 crates/ruff/src/rules/pycodestyle/snapshots/ruff__rules__pycodestyle__tests__tab_size_4.snap create mode 100644 crates/ruff/src/rules/pycodestyle/snapshots/ruff__rules__pycodestyle__tests__tab_size_8.snap diff --git a/crates/ruff/resources/test/fixtures/flake8_simplify/SIM102.py b/crates/ruff/resources/test/fixtures/flake8_simplify/SIM102.py index c1a9c03cf1..3b47a3e335 100644 --- a/crates/ruff/resources/test/fixtures/flake8_simplify/SIM102.py +++ b/crates/ruff/resources/test/fixtures/flake8_simplify/SIM102.py @@ -86,9 +86,16 @@ while x > 0: ): print("Bad module!") -# SIM102 -if node.module: - if node.module == "multiprocessing" or node.module.startswith( +# SIM102 (auto-fixable) +if node.module012345678: + if node.module == "multiprocß9πŸ’£2ℝ" or node.module.startswith( + "multiprocessing." + ): + print("Bad module!") + +# SIM102 (not auto-fixable) +if node.module0123456789: + if node.module == "multiprocß9πŸ’£2ℝ" or node.module.startswith( "multiprocessing." ): print("Bad module!") diff --git a/crates/ruff/resources/test/fixtures/flake8_simplify/SIM108.py b/crates/ruff/resources/test/fixtures/flake8_simplify/SIM108.py index ab43e3f2b1..94b14f911a 100644 --- a/crates/ruff/resources/test/fixtures/flake8_simplify/SIM108.py +++ b/crates/ruff/resources/test/fixtures/flake8_simplify/SIM108.py @@ -80,17 +80,25 @@ else: # SIM108 if a: - b = cccccccccccccccccccccccccccccccccccc + b = "cccccccccccccccccccccccccccccccccß" else: - b = ddddddddddddddddddddddddddddddddddddd + b = "dddddddddddddddddddddddddddddddddπŸ’£" # OK (too long) if True: if a: - b = cccccccccccccccccccccccccccccccccccc + b = ccccccccccccccccccccccccccccccccccc else: - b = ddddddddddddddddddddddddddddddddddddd + b = ddddddddddddddddddddddddddddddddddd + + +# OK (too long with tabs) +if True: + if a: + b = ccccccccccccccccccccccccccccccccccc + else: + b = ddddddddddddddddddddddddddddddddddd # SIM108 (without fix due to trailing comment) diff --git a/crates/ruff/resources/test/fixtures/flake8_simplify/SIM110.py b/crates/ruff/resources/test/fixtures/flake8_simplify/SIM110.py index 30ce25bb0e..b02ac7c28c 100644 --- a/crates/ruff/resources/test/fixtures/flake8_simplify/SIM110.py +++ b/crates/ruff/resources/test/fixtures/flake8_simplify/SIM110.py @@ -155,3 +155,19 @@ def f(): if check(x): return False return True + + +def f(): + # SIM110 + for x in "012ß9πŸ’£2ℝ9012ß9πŸ’£2ℝ9012ß9πŸ’£2ℝ9012ß9πŸ’£2ℝ9012ß9πŸ’£2ℝ": + if x.isdigit(): + return True + return False + + +def f(): + # OK (too long) + for x in "012ß9πŸ’£2ℝ9012ß9πŸ’£2ℝ9012ß9πŸ’£2ℝ9012ß9πŸ’£2ℝ9012ß9πŸ’£2ℝ9": + if x.isdigit(): + return True + return False diff --git a/crates/ruff/resources/test/fixtures/flake8_simplify/SIM111.py b/crates/ruff/resources/test/fixtures/flake8_simplify/SIM111.py index f0afb793d4..d6908461fc 100644 --- a/crates/ruff/resources/test/fixtures/flake8_simplify/SIM111.py +++ b/crates/ruff/resources/test/fixtures/flake8_simplify/SIM111.py @@ -171,3 +171,19 @@ def f(): if x > y: return False return True + + +def f(): + # SIM111 + for x in "012ß9πŸ’£2ℝ9012ß9πŸ’£2ℝ9012ß9πŸ’£2ℝ9012ß9πŸ’£2ℝ9012ß9": + if x.isdigit(): + return False + return True + + +def f(): + # OK (too long) + for x in "012ß9πŸ’£2ℝ9012ß9πŸ’£2ℝ9012ß9πŸ’£2ℝ9012ß9πŸ’£2ℝ9012ß90": + if x.isdigit(): + return False + return True diff --git a/crates/ruff/resources/test/fixtures/flake8_simplify/SIM117.py b/crates/ruff/resources/test/fixtures/flake8_simplify/SIM117.py index 34dd47e361..3c99535e43 100644 --- a/crates/ruff/resources/test/fixtures/flake8_simplify/SIM117.py +++ b/crates/ruff/resources/test/fixtures/flake8_simplify/SIM117.py @@ -90,3 +90,13 @@ with ( D() as d, ): print("hello") + +# SIM117 (auto-fixable) +with A("01ß9πŸ’£2ℝ8901ß9πŸ’£2ℝ8901ß9πŸ’£2ℝ89") as a: + with B("01ß9πŸ’£2ℝ8901ß9πŸ’£2ℝ8901ß9πŸ’£2ℝ89") as b: + print("hello") + +# SIM117 (not auto-fixable too long) +with A("01ß9πŸ’£2ℝ8901ß9πŸ’£2ℝ8901ß9πŸ’£2ℝ890") as a: + with B("01ß9πŸ’£2ℝ8901ß9πŸ’£2ℝ8901ß9πŸ’£2ℝ89") as b: + print("hello") \ No newline at end of file diff --git a/crates/ruff/resources/test/fixtures/flake8_simplify/SIM401.py b/crates/ruff/resources/test/fixtures/flake8_simplify/SIM401.py index 404654465d..487f15a5f7 100644 --- a/crates/ruff/resources/test/fixtures/flake8_simplify/SIM401.py +++ b/crates/ruff/resources/test/fixtures/flake8_simplify/SIM401.py @@ -36,12 +36,18 @@ else: if key in a_dict: vars[idx] = a_dict[key] else: - vars[idx] = "default" + vars[idx] = "defaultß9πŸ’£2ℝ6789ß9πŸ’£2ℝ6789ß9πŸ’£2ℝ6789ß9πŸ’£2ℝ6789ß9πŸ’£2ℝ6789" ### # Negative cases ### +# OK (too long) +if key in a_dict: + vars[idx] = a_dict[key] +else: + vars[idx] = "defaultß9πŸ’£2ℝ6789ß9πŸ’£2ℝ6789ß9πŸ’£2ℝ6789ß9πŸ’£2ℝ6789ß9πŸ’£2ℝ6789ß" + # OK (false negative) if not key in a_dict: var = "default" diff --git a/crates/ruff/resources/test/fixtures/isort/fit_line_length_comment.py b/crates/ruff/resources/test/fixtures/isort/fit_line_length_comment.py index 11f1ee1f6e..94648e045e 100644 --- a/crates/ruff/resources/test/fixtures/isort/fit_line_length_comment.py +++ b/crates/ruff/resources/test/fixtures/isort/fit_line_length_comment.py @@ -2,3 +2,7 @@ import a # Don't take this comment into account when determining whether the next import can fit on one line. from b import c from d import e # Do take this comment into account when determining whether the next import can fit on one line. +# The next import fits on one line. +from f import g # 012ß9πŸ’£2ℝ9012ß9πŸ’£2ℝ9012ß9πŸ’£2ℝ9012ß9πŸ’£2ℝ9012ß9πŸ’£2ℝ9012ß9πŸ’£2ℝ9012ß9πŸ’£2ℝ +# The next import doesn't fit on one line. +from h import i # 012ß9πŸ’£2ℝ9012ß9πŸ’£2ℝ9012ß9πŸ’£2ℝ9012ß9πŸ’£2ℝ9012ß9πŸ’£2ℝ9012ß9πŸ’£2ℝ9012ß9πŸ’£2ℝ9 diff --git a/crates/ruff/resources/test/fixtures/pycodestyle/E501_2.py b/crates/ruff/resources/test/fixtures/pycodestyle/E501_2.py new file mode 100644 index 0000000000..18ea839392 --- /dev/null +++ b/crates/ruff/resources/test/fixtures/pycodestyle/E501_2.py @@ -0,0 +1,11 @@ +a = """ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A6""" +a = """ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A6""" + +b = """ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A6""" +b = """ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A6""" + +c = """2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A6""" +c = """2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A6""" + +d = """πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A6""" +d = """πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A6""" diff --git a/crates/ruff/resources/test/fixtures/pycodestyle/W505_utf_8.py b/crates/ruff/resources/test/fixtures/pycodestyle/W505_utf_8.py new file mode 100644 index 0000000000..6e177dad8f --- /dev/null +++ b/crates/ruff/resources/test/fixtures/pycodestyle/W505_utf_8.py @@ -0,0 +1,31 @@ +#!/usr/bin/env python3 +"""Here's a top-level ß9πŸ’£2ℝing that's over theß9πŸ’£2ℝ.""" + + +def f1(): + """Here's a ß9πŸ’£2ℝing that's also over theß9πŸ’£2ℝ.""" + + x = 1 # Here's a comment that's over theß9πŸ’£2ℝ, but it's not standalone. + + # Here's a standalone comment that's over theß9πŸ’£2ℝ. + + x = 2 + # Another standalone that is preceded by a newline and indent toke and is over theß9πŸ’£2ℝ. + + print("Here's a string that's over theß9πŸ’£2ℝ, but it's not a ß9πŸ’£2ℝing.") + + +"This is also considered a ß9πŸ’£2ℝing, and is over theß9πŸ’£2ℝ." + + +def f2(): + """Here's a multi-line ß9πŸ’£2ℝing. + + It's over theß9πŸ’£2ℝ on this line, which isn't the first line in the ß9πŸ’£2ℝing. + """ + + +def f3(): + """Here's a multi-line ß9πŸ’£2ℝing. + + It's over theß9πŸ’£2ℝ on this line, which isn't the first line in the ß9πŸ’£2ℝing.""" diff --git a/crates/ruff/src/checkers/physical_lines.rs b/crates/ruff/src/checkers/physical_lines.rs index 47b647677d..53b4704811 100644 --- a/crates/ruff/src/checkers/physical_lines.rs +++ b/crates/ruff/src/checkers/physical_lines.rs @@ -183,6 +183,7 @@ mod tests { use ruff_python_ast::source_code::{Indexer, Locator, Stylist}; + use crate::line_width::LineLength; use crate::registry::Rule; use crate::settings::Settings; @@ -196,7 +197,7 @@ mod tests { let indexer = Indexer::from_tokens(&tokens, &locator); let stylist = Stylist::from_tokens(&tokens, &locator); - let check_with_max_line_length = |line_length: usize| { + let check_with_max_line_length = |line_length: LineLength| { check_physical_lines( Path::new("foo.py"), &locator, @@ -209,7 +210,8 @@ mod tests { }, ) }; - assert_eq!(check_with_max_line_length(8), vec![]); - assert_eq!(check_with_max_line_length(8), vec![]); + let line_length = LineLength::from(8); + assert_eq!(check_with_max_line_length(line_length), vec![]); + assert_eq!(check_with_max_line_length(line_length), vec![]); } } diff --git a/crates/ruff/src/flake8_to_ruff/converter.rs b/crates/ruff/src/flake8_to_ruff/converter.rs index af6d53d680..f205ccaeb5 100644 --- a/crates/ruff/src/flake8_to_ruff/converter.rs +++ b/crates/ruff/src/flake8_to_ruff/converter.rs @@ -3,6 +3,7 @@ use std::collections::{HashMap, HashSet}; use anyhow::Result; use itertools::Itertools; +use crate::line_width::LineLength; use crate::registry::Linter; use crate::rule_selector::RuleSelector; use crate::rules::flake8_pytest_style::types::{ @@ -120,7 +121,9 @@ pub fn convert( options.builtins = Some(parser::parse_strings(value.as_ref())); } "max-line-length" | "max_line_length" => match value.parse::() { - Ok(line_length) => options.line_length = Some(line_length), + Ok(line_length) => { + options.line_length = Some(LineLength::from(line_length)); + } Err(e) => { warn_user!("Unable to parse '{key}' property: {e}"); } @@ -403,7 +406,7 @@ pub fn convert( // Extract any settings from the existing `pyproject.toml`. if let Some(black) = &external_config.black { if let Some(line_length) = &black.line_length { - options.line_length = Some(*line_length); + options.line_length = Some(LineLength::from(*line_length)); } if let Some(target_version) = &black.target_version { @@ -460,6 +463,7 @@ mod tests { use crate::flake8_to_ruff::converter::DEFAULT_SELECTORS; use crate::flake8_to_ruff::pep621::Project; use crate::flake8_to_ruff::ExternalConfig; + use crate::line_width::LineLength; use crate::registry::Linter; use crate::rule_selector::RuleSelector; use crate::rules::pydocstyle::settings::Convention; @@ -510,7 +514,7 @@ mod tests { Some(vec![]), )?; let expected = Pyproject::new(Options { - line_length: Some(100), + line_length: Some(LineLength::from(100)), ..default_options([]) }); assert_eq!(actual, expected); @@ -529,7 +533,7 @@ mod tests { Some(vec![]), )?; let expected = Pyproject::new(Options { - line_length: Some(100), + line_length: Some(LineLength::from(100)), ..default_options([]) }); assert_eq!(actual, expected); diff --git a/crates/ruff/src/lib.rs b/crates/ruff/src/lib.rs index e359ffdbd0..8c9e6772ce 100644 --- a/crates/ruff/src/lib.rs +++ b/crates/ruff/src/lib.rs @@ -21,6 +21,7 @@ pub mod fs; mod importer; pub mod jupyter; mod lex; +pub mod line_width; pub mod linter; pub mod logging; pub mod message; diff --git a/crates/ruff/src/line_width.rs b/crates/ruff/src/line_width.rs new file mode 100644 index 0000000000..8619b42aa3 --- /dev/null +++ b/crates/ruff/src/line_width.rs @@ -0,0 +1,165 @@ +use serde::{Deserialize, Serialize}; +use unicode_width::UnicodeWidthChar; + +use ruff_macros::CacheKey; + +/// The length of a line of text that is considered too long. +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, CacheKey)] +#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] +pub struct LineLength(usize); + +impl Default for LineLength { + /// The default line length. + fn default() -> Self { + Self(88) + } +} + +impl LineLength { + pub const fn get(&self) -> usize { + self.0 + } +} + +impl From for LineLength { + fn from(value: usize) -> Self { + Self(value) + } +} + +/// A measure of the width of a line of text. +/// +/// This is used to determine if a line is too long. +/// It should be compared to a [`LineLength`]. +#[derive(Clone, Copy, Debug)] +pub struct LineWidth { + /// The width of the line. + width: usize, + /// The column of the line. + /// 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, +} + +impl Default for LineWidth { + fn default() -> Self { + Self::new(TabSize::default()) + } +} + +impl PartialEq for LineWidth { + fn eq(&self, other: &Self) -> bool { + self.width == other.width + } +} + +impl Eq for LineWidth {} + +impl PartialOrd for LineWidth { + fn partial_cmp(&self, other: &Self) -> Option { + self.width.partial_cmp(&other.width) + } +} + +impl Ord for LineWidth { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.width.cmp(&other.width) + } +} + +impl LineWidth { + pub fn get(&self) -> usize { + self.width + } + + /// Creates a new `LineWidth` with the given tab size. + pub fn new(tab_size: TabSize) -> Self { + LineWidth { + width: 0, + column: 0, + tab_size, + } + } + + fn update(mut self, chars: impl Iterator) -> Self { + let tab_size: usize = self.tab_size.into(); + for c in chars { + match c { + '\t' => { + let tab_offset = tab_size - (self.column % tab_size); + self.width += tab_offset; + self.column += tab_offset; + } + '\n' | '\r' => { + self.width = 0; + self.column = 0; + } + _ => { + self.width += c.width().unwrap_or(0); + self.column += 1; + } + } + } + self + } + + /// Adds the given text to the line width. + #[must_use] + pub fn add_str(self, text: &str) -> Self { + self.update(text.chars()) + } + + /// Adds the given character to the line width. + #[must_use] + pub fn add_char(self, c: char) -> Self { + self.update(std::iter::once(c)) + } + + /// Adds the given width to the line width. + /// Also adds the given width to the column. + /// It is generally better to use [`LineWidth::add_str`] or [`LineWidth::add_char`]. + /// The width and column should be the same for the corresponding text. + /// Currently, this is only used to add spaces. + #[must_use] + pub fn add_width(mut self, width: usize) -> Self { + self.width += width; + self.column += width; + self + } +} + +impl PartialEq for LineWidth { + fn eq(&self, other: &LineLength) -> bool { + self.width == other.0 + } +} + +impl PartialOrd for LineWidth { + fn partial_cmp(&self, other: &LineLength) -> Option { + self.width.partial_cmp(&other.0) + } +} + +/// The size of a tab. +#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize, CacheKey)] +#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] +pub struct TabSize(pub u8); + +impl Default for TabSize { + fn default() -> Self { + Self(4) + } +} + +impl From for TabSize { + fn from(tab_size: u8) -> Self { + Self(tab_size) + } +} + +impl From for usize { + fn from(tab_size: TabSize) -> Self { + tab_size.0 as usize + } +} diff --git a/crates/ruff/src/message/text.rs b/crates/ruff/src/message/text.rs index f19b0f03ea..9f0a75db46 100644 --- a/crates/ruff/src/message/text.rs +++ b/crates/ruff/src/message/text.rs @@ -11,6 +11,7 @@ use ruff_text_size::{TextRange, TextSize}; use ruff_python_ast::source_code::{OneIndexed, SourceLocation}; use crate::fs::relativize_path; +use crate::line_width::{LineWidth, TabSize}; use crate::message::diff::Diff; use crate::message::{Emitter, EmitterContext, Message}; use crate::registry::AsRule; @@ -237,39 +238,35 @@ impl Display for MessageCodeFrame<'_> { } fn replace_whitespace(source: &str, annotation_range: TextRange) -> SourceCode { - static TAB_SIZE: u32 = 4; // TODO(jonathan): use `pycodestyle.tab-size` + static TAB_SIZE: TabSize = TabSize(4); // TODO(jonathan): use `tab-size` let mut result = String::new(); let mut last_end = 0; let mut range = annotation_range; - let mut column = 0; + let mut line_width = LineWidth::new(TAB_SIZE); - for (index, c) in source.chars().enumerate() { - match c { - '\t' => { - let tab_width = TAB_SIZE - column % TAB_SIZE; - column += tab_width; + for (index, c) in source.char_indices() { + let old_width = line_width.get(); + line_width = line_width.add_char(c); - if index < usize::from(annotation_range.start()) { - range += TextSize::new(tab_width - 1); - } else if index < usize::from(annotation_range.end()) { - range = range.add_end(TextSize::new(tab_width - 1)); - } + if matches!(c, '\t') { + // SAFETY: The difference is a value in the range [1..TAB_SIZE] which is guaranteed to be less than `u32`. + #[allow(clippy::cast_possible_truncation)] + let tab_width = (line_width.get() - old_width) as u32; - result.push_str(&source[last_end..index]); - - for _ in 0..tab_width { - result.push(' '); - } - - last_end = index + 1; + if index < usize::from(annotation_range.start()) { + range += TextSize::new(tab_width - 1); + } else if index < usize::from(annotation_range.end()) { + range = range.add_end(TextSize::new(tab_width - 1)); } - '\n' | '\r' => { - column = 0; - } - _ => { - column += 1; + + result.push_str(&source[last_end..index]); + + for _ in 0..tab_width { + result.push(' '); } + + last_end = index + 1; } } diff --git a/crates/ruff/src/rules/flake8_simplify/rules/ast_if.rs b/crates/ruff/src/rules/flake8_simplify/rules/ast_if.rs index 63f4ae67fa..a78d8f6df6 100644 --- a/crates/ruff/src/rules/flake8_simplify/rules/ast_if.rs +++ b/crates/ruff/src/rules/flake8_simplify/rules/ast_if.rs @@ -2,7 +2,6 @@ use log::error; use ruff_text_size::TextRange; use rustc_hash::FxHashSet; use rustpython_parser::ast::{self, Cmpop, Constant, Expr, ExprContext, Ranged, Stmt}; -use unicode_width::UnicodeWidthStr; use ruff_diagnostics::{AutofixKind, Diagnostic, Edit, Fix, Violation}; use ruff_macros::{derive_message_formats, violation}; @@ -14,6 +13,7 @@ use ruff_python_ast::newlines::StrExt; use ruff_python_semantic::model::SemanticModel; use crate::checkers::ast::Checker; +use crate::line_width::LineWidth; use crate::registry::AsRule; use crate::rules::flake8_simplify::rules::fix_if; @@ -288,7 +288,10 @@ pub(crate) fn nested_if_statements( .content() .unwrap_or_default() .universal_newlines() - .all(|line| line.width() <= checker.settings.line_length) + .all(|line| { + LineWidth::new(checker.settings.tab_size).add_str(&line) + <= checker.settings.line_length + }) { #[allow(deprecated)] diagnostic.set_fix(Fix::unspecified(edit)); @@ -508,8 +511,9 @@ pub(crate) fn use_ternary_operator(checker: &mut Checker, stmt: &Stmt, parent: O // Don't flag if the resulting expression would exceed the maximum line length. let line_start = checker.locator.line_start(stmt.start()); - if checker.locator.contents()[TextRange::new(line_start, stmt.start())].width() - + contents.width() + if LineWidth::new(checker.settings.tab_size) + .add_str(&checker.locator.contents()[TextRange::new(line_start, stmt.start())]) + .add_str(&contents) > checker.settings.line_length { return; @@ -863,8 +867,9 @@ pub(crate) fn use_dict_get_with_default( // Don't flag if the resulting expression would exceed the maximum line length. let line_start = checker.locator.line_start(stmt.start()); - if checker.locator.contents()[TextRange::new(line_start, stmt.start())].width() - + contents.width() + if LineWidth::new(checker.settings.tab_size) + .add_str(&checker.locator.contents()[TextRange::new(line_start, stmt.start())]) + .add_str(&contents) > checker.settings.line_length { return; diff --git a/crates/ruff/src/rules/flake8_simplify/rules/ast_with.rs b/crates/ruff/src/rules/flake8_simplify/rules/ast_with.rs index fa4cdb0497..5d8f3406d6 100644 --- a/crates/ruff/src/rules/flake8_simplify/rules/ast_with.rs +++ b/crates/ruff/src/rules/flake8_simplify/rules/ast_with.rs @@ -1,7 +1,6 @@ use log::error; use ruff_text_size::TextRange; use rustpython_parser::ast::{self, Ranged, Stmt, Withitem}; -use unicode_width::UnicodeWidthStr; use ruff_diagnostics::{AutofixKind, Violation}; use ruff_diagnostics::{Diagnostic, Fix}; @@ -10,6 +9,7 @@ use ruff_python_ast::helpers::{first_colon_range, has_comments_in}; use ruff_python_ast::newlines::StrExt; use crate::checkers::ast::Checker; +use crate::line_width::LineWidth; use crate::registry::AsRule; use super::fix_with; @@ -111,7 +111,10 @@ pub(crate) fn multiple_with_statements( .content() .unwrap_or_default() .universal_newlines() - .all(|line| line.width() <= checker.settings.line_length) + .all(|line| { + LineWidth::new(checker.settings.tab_size).add_str(&line) + <= checker.settings.line_length + }) { #[allow(deprecated)] diagnostic.set_fix(Fix::unspecified(edit)); diff --git a/crates/ruff/src/rules/flake8_simplify/rules/reimplemented_builtin.rs b/crates/ruff/src/rules/flake8_simplify/rules/reimplemented_builtin.rs index bc32ddabc1..915bfaa0f3 100644 --- a/crates/ruff/src/rules/flake8_simplify/rules/reimplemented_builtin.rs +++ b/crates/ruff/src/rules/flake8_simplify/rules/reimplemented_builtin.rs @@ -2,13 +2,13 @@ use ruff_text_size::{TextRange, TextSize}; use rustpython_parser::ast::{ self, Cmpop, Comprehension, Constant, Expr, ExprContext, Ranged, Stmt, Unaryop, }; -use unicode_width::UnicodeWidthStr; use ruff_diagnostics::{AutofixKind, Diagnostic, Edit, Fix, Violation}; use ruff_macros::{derive_message_formats, violation}; use ruff_python_ast::source_code::Generator; use crate::checkers::ast::Checker; +use crate::line_width::LineWidth; use crate::registry::{AsRule, Rule}; #[violation] @@ -224,8 +224,9 @@ pub(crate) fn convert_for_loop_to_any_all( // Don't flag if the resulting expression would exceed the maximum line length. let line_start = checker.locator.line_start(stmt.start()); - if checker.locator.contents()[TextRange::new(line_start, stmt.start())].width() - + contents.width() + if LineWidth::new(checker.settings.tab_size) + .add_str(&checker.locator.contents()[TextRange::new(line_start, stmt.start())]) + .add_str(&contents) > checker.settings.line_length { return; @@ -316,8 +317,9 @@ pub(crate) fn convert_for_loop_to_any_all( // Don't flag if the resulting expression would exceed the maximum line length. let line_start = checker.locator.line_start(stmt.start()); - if checker.locator.contents()[TextRange::new(line_start, stmt.start())].width() - + contents.width() + if LineWidth::new(checker.settings.tab_size) + .add_str(&checker.locator.contents()[TextRange::new(line_start, stmt.start())]) + .add_str(&contents) > checker.settings.line_length { return; diff --git a/crates/ruff/src/rules/flake8_simplify/snapshots/ruff__rules__flake8_simplify__tests__SIM102_SIM102.py.snap b/crates/ruff/src/rules/flake8_simplify/snapshots/ruff__rules__flake8_simplify__tests__SIM102_SIM102.py.snap index a3fd9f8f35..89c1a56862 100644 --- a/crates/ruff/src/rules/flake8_simplify/snapshots/ruff__rules__flake8_simplify__tests__SIM102_SIM102.py.snap +++ b/crates/ruff/src/rules/flake8_simplify/snapshots/ruff__rules__flake8_simplify__tests__SIM102_SIM102.py.snap @@ -211,14 +211,14 @@ SIM102.py:83:5: SIM102 [*] Use a single `if` statement instead of nested `if` st 85 |+ )): 86 |+ print("Bad module!") 88 87 | -89 88 | # SIM102 -90 89 | if node.module: +89 88 | # SIM102 (auto-fixable) +90 89 | if node.module012345678: SIM102.py:90:1: SIM102 [*] Use a single `if` statement instead of nested `if` statements | -90 | # SIM102 -91 | / if node.module: -92 | | if node.module == "multiprocessing" or node.module.startswith( +90 | # SIM102 (auto-fixable) +91 | / if node.module012345678: +92 | | if node.module == "multiprocß9πŸ’£2ℝ" or node.module.startswith( 93 | | "multiprocessing." 94 | | ): | |______^ SIM102 @@ -229,44 +229,56 @@ SIM102.py:90:1: SIM102 [*] Use a single `if` statement instead of nested `if` st β„Ή Suggested fix 87 87 | print("Bad module!") 88 88 | -89 89 | # SIM102 -90 |-if node.module: -91 |- if node.module == "multiprocessing" or node.module.startswith( +89 89 | # SIM102 (auto-fixable) +90 |-if node.module012345678: +91 |- if node.module == "multiprocß9πŸ’£2ℝ" or node.module.startswith( 92 |- "multiprocessing." 93 |- ): 94 |- print("Bad module!") - 90 |+if node.module and (node.module == "multiprocessing" or node.module.startswith( + 90 |+if node.module012345678 and (node.module == "multiprocß9πŸ’£2ℝ" or node.module.startswith( 91 |+ "multiprocessing." 92 |+)): 93 |+ print("Bad module!") 95 94 | -96 95 | -97 96 | # OK +96 95 | # SIM102 (not auto-fixable) +97 96 | if node.module0123456789: -SIM102.py:117:5: SIM102 [*] Use a single `if` statement instead of nested `if` statements +SIM102.py:97:1: SIM102 Use a single `if` statement instead of nested `if` statements | -117 | if a: -118 | # SIM 102 -119 | if b: + 97 | # SIM102 (not auto-fixable) + 98 | / if node.module0123456789: + 99 | | if node.module == "multiprocß9πŸ’£2ℝ" or node.module.startswith( +100 | | "multiprocessing." +101 | | ): + | |______^ SIM102 +102 | print("Bad module!") + | + = help: Combine `if` statements using `and` + +SIM102.py:124:5: SIM102 [*] Use a single `if` statement instead of nested `if` statements + | +124 | if a: +125 | # SIM 102 +126 | if b: | _____^ -120 | | if c: +127 | | if c: | |_____________^ SIM102 -121 | print("foo") -122 | else: +128 | print("foo") +129 | else: | = help: Combine `if` statements using `and` β„Ή Suggested fix -114 114 | # OK -115 115 | if a: -116 116 | # SIM 102 -117 |- if b: -118 |- if c: -119 |- print("foo") - 117 |+ if b and c: - 118 |+ print("foo") -120 119 | else: -121 120 | print("bar") -122 121 | +121 121 | # OK +122 122 | if a: +123 123 | # SIM 102 +124 |- if b: +125 |- if c: +126 |- print("foo") + 124 |+ if b and c: + 125 |+ print("foo") +127 126 | else: +128 127 | print("bar") +129 128 | diff --git a/crates/ruff/src/rules/flake8_simplify/snapshots/ruff__rules__flake8_simplify__tests__SIM108_SIM108.py.snap b/crates/ruff/src/rules/flake8_simplify/snapshots/ruff__rules__flake8_simplify__tests__SIM108_SIM108.py.snap index 520f6f1c2e..91a83a03c6 100644 --- a/crates/ruff/src/rules/flake8_simplify/snapshots/ruff__rules__flake8_simplify__tests__SIM108_SIM108.py.snap +++ b/crates/ruff/src/rules/flake8_simplify/snapshots/ruff__rules__flake8_simplify__tests__SIM108_SIM108.py.snap @@ -38,57 +38,57 @@ SIM108.py:58:1: SIM108 Use ternary operator `abc = x if x > 0 else -x` instead o | = help: Replace `if`-`else`-block with `abc = x if x > 0 else -x` -SIM108.py:82:1: SIM108 [*] Use ternary operator `b = cccccccccccccccccccccccccccccccccccc if a else ddddddddddddddddddddddddddddddddddddd` instead of `if`-`else`-block +SIM108.py:82:1: SIM108 [*] Use ternary operator `b = "cccccccccccccccccccccccccccccccccß" if a else "dddddddddddddddddddddddddddddddddπŸ’£"` instead of `if`-`else`-block | 82 | # SIM108 83 | / if a: -84 | | b = cccccccccccccccccccccccccccccccccccc +84 | | b = "cccccccccccccccccccccccccccccccccß" 85 | | else: -86 | | b = ddddddddddddddddddddddddddddddddddddd +86 | | b = "dddddddddddddddddddddddddddddddddπŸ’£" | |_____________________________________________^ SIM108 | - = help: Replace `if`-`else`-block with `b = cccccccccccccccccccccccccccccccccccc if a else ddddddddddddddddddddddddddddddddddddd` + = help: Replace `if`-`else`-block with `b = "cccccccccccccccccccccccccccccccccß" if a else "dddddddddddddddddddddddddddddddddπŸ’£"` β„Ή Suggested fix 79 79 | 80 80 | 81 81 | # SIM108 82 |-if a: -83 |- b = cccccccccccccccccccccccccccccccccccc +83 |- b = "cccccccccccccccccccccccccccccccccß" 84 |-else: -85 |- b = ddddddddddddddddddddddddddddddddddddd - 82 |+b = cccccccccccccccccccccccccccccccccccc if a else ddddddddddddddddddddddddddddddddddddd +85 |- b = "dddddddddddddddddddddddddddddddddπŸ’£" + 82 |+b = "cccccccccccccccccccccccccccccccccß" if a else "dddddddddddddddddddddddddddddddddπŸ’£" 86 83 | 87 84 | 88 85 | # OK (too long) -SIM108.py:97:1: SIM108 Use ternary operator `exitcode = 0 if True else 1` instead of `if`-`else`-block +SIM108.py:105:1: SIM108 Use ternary operator `exitcode = 0 if True else 1` instead of `if`-`else`-block | - 97 | # SIM108 (without fix due to trailing comment) - 98 | / if True: - 99 | | exitcode = 0 -100 | | else: -101 | | exitcode = 1 # Trailing comment +105 | # SIM108 (without fix due to trailing comment) +106 | / if True: +107 | | exitcode = 0 +108 | | else: +109 | | exitcode = 1 # Trailing comment | |________________^ SIM108 | = help: Replace `if`-`else`-block with `exitcode = 0 if True else 1` -SIM108.py:104:1: SIM108 Use ternary operator `x = 3 if True else 5` instead of `if`-`else`-block +SIM108.py:112:1: SIM108 Use ternary operator `x = 3 if True else 5` instead of `if`-`else`-block | -104 | # SIM108 -105 | / if True: x = 3 # Foo -106 | | else: x = 5 +112 | # SIM108 +113 | / if True: x = 3 # Foo +114 | | else: x = 5 | |___________^ SIM108 | = help: Replace `if`-`else`-block with `x = 3 if True else 5` -SIM108.py:109:1: SIM108 Use ternary operator `x = 3 if True else 5` instead of `if`-`else`-block +SIM108.py:117:1: SIM108 Use ternary operator `x = 3 if True else 5` instead of `if`-`else`-block | -109 | # SIM108 -110 | / if True: # Foo -111 | | x = 3 -112 | | else: -113 | | x = 5 +117 | # SIM108 +118 | / if True: # Foo +119 | | x = 3 +120 | | else: +121 | | x = 5 | |_________^ SIM108 | = help: Replace `if`-`else`-block with `x = 3 if True else 5` diff --git a/crates/ruff/src/rules/flake8_simplify/snapshots/ruff__rules__flake8_simplify__tests__SIM110_SIM110.py.snap b/crates/ruff/src/rules/flake8_simplify/snapshots/ruff__rules__flake8_simplify__tests__SIM110_SIM110.py.snap index b958d00ed4..c23a745c0d 100644 --- a/crates/ruff/src/rules/flake8_simplify/snapshots/ruff__rules__flake8_simplify__tests__SIM110_SIM110.py.snap +++ b/crates/ruff/src/rules/flake8_simplify/snapshots/ruff__rules__flake8_simplify__tests__SIM110_SIM110.py.snap @@ -264,5 +264,34 @@ SIM110.py:154:5: SIM110 [*] Use `return all(not check(x) for x in iterable)` ins 156 |- return False 157 |- return True 154 |+ return all(not check(x) for x in iterable) +158 155 | +159 156 | +160 157 | def f(): + +SIM110.py:162:5: SIM110 [*] Use `return any(x.isdigit() for x in "012ß9πŸ’£2ℝ9012ß9πŸ’£2ℝ9012ß9πŸ’£2ℝ9012ß9πŸ’£2ℝ9012ß9πŸ’£2ℝ")` instead of `for` loop + | +162 | def f(): +163 | # SIM110 +164 | for x in "012ß9πŸ’£2ℝ9012ß9πŸ’£2ℝ9012ß9πŸ’£2ℝ9012ß9πŸ’£2ℝ9012ß9πŸ’£2ℝ": + | _____^ +165 | | if x.isdigit(): +166 | | return True +167 | | return False + | |________________^ SIM110 + | + = help: Replace with `return any(x.isdigit() for x in "012ß9πŸ’£2ℝ9012ß9πŸ’£2ℝ9012ß9πŸ’£2ℝ9012ß9πŸ’£2ℝ9012ß9πŸ’£2ℝ")` + +β„Ή Suggested fix +159 159 | +160 160 | def f(): +161 161 | # SIM110 +162 |- for x in "012ß9πŸ’£2ℝ9012ß9πŸ’£2ℝ9012ß9πŸ’£2ℝ9012ß9πŸ’£2ℝ9012ß9πŸ’£2ℝ": +163 |- if x.isdigit(): +164 |- return True +165 |- return False + 162 |+ return any(x.isdigit() for x in "012ß9πŸ’£2ℝ9012ß9πŸ’£2ℝ9012ß9πŸ’£2ℝ9012ß9πŸ’£2ℝ9012ß9πŸ’£2ℝ") +166 163 | +167 164 | +168 165 | def f(): diff --git a/crates/ruff/src/rules/flake8_simplify/snapshots/ruff__rules__flake8_simplify__tests__SIM110_SIM111.py.snap b/crates/ruff/src/rules/flake8_simplify/snapshots/ruff__rules__flake8_simplify__tests__SIM110_SIM111.py.snap index 7355c92fcc..c4e2e48998 100644 --- a/crates/ruff/src/rules/flake8_simplify/snapshots/ruff__rules__flake8_simplify__tests__SIM110_SIM111.py.snap +++ b/crates/ruff/src/rules/flake8_simplify/snapshots/ruff__rules__flake8_simplify__tests__SIM110_SIM111.py.snap @@ -316,5 +316,34 @@ SIM111.py:170:5: SIM110 [*] Use `return all(x <= y for x in iterable)` instead o 172 |- return False 173 |- return True 170 |+ return all(x <= y for x in iterable) +174 171 | +175 172 | +176 173 | def f(): + +SIM111.py:178:5: SIM110 [*] Use `return all(not x.isdigit() for x in "012ß9πŸ’£2ℝ9012ß9πŸ’£2ℝ9012ß9πŸ’£2ℝ9012ß9πŸ’£2ℝ9012ß9")` instead of `for` loop + | +178 | def f(): +179 | # SIM111 +180 | for x in "012ß9πŸ’£2ℝ9012ß9πŸ’£2ℝ9012ß9πŸ’£2ℝ9012ß9πŸ’£2ℝ9012ß9": + | _____^ +181 | | if x.isdigit(): +182 | | return False +183 | | return True + | |_______________^ SIM110 + | + = help: Replace with `return all(not x.isdigit() for x in "012ß9πŸ’£2ℝ9012ß9πŸ’£2ℝ9012ß9πŸ’£2ℝ9012ß9πŸ’£2ℝ9012ß9")` + +β„Ή Suggested fix +175 175 | +176 176 | def f(): +177 177 | # SIM111 +178 |- for x in "012ß9πŸ’£2ℝ9012ß9πŸ’£2ℝ9012ß9πŸ’£2ℝ9012ß9πŸ’£2ℝ9012ß9": +179 |- if x.isdigit(): +180 |- return False +181 |- return True + 178 |+ return all(not x.isdigit() for x in "012ß9πŸ’£2ℝ9012ß9πŸ’£2ℝ9012ß9πŸ’£2ℝ9012ß9πŸ’£2ℝ9012ß9") +182 179 | +183 180 | +184 181 | def f(): diff --git a/crates/ruff/src/rules/flake8_simplify/snapshots/ruff__rules__flake8_simplify__tests__SIM117_SIM117.py.snap b/crates/ruff/src/rules/flake8_simplify/snapshots/ruff__rules__flake8_simplify__tests__SIM117_SIM117.py.snap index 0cb4bd04bf..94ddb2c326 100644 --- a/crates/ruff/src/rules/flake8_simplify/snapshots/ruff__rules__flake8_simplify__tests__SIM117_SIM117.py.snap +++ b/crates/ruff/src/rules/flake8_simplify/snapshots/ruff__rules__flake8_simplify__tests__SIM117_SIM117.py.snap @@ -212,5 +212,41 @@ SIM117.py:84:1: SIM117 [*] Use a single `with` statement with multiple contexts 91 |- ): 92 |- print("hello") 89 |+ print("hello") +93 90 | +94 91 | # SIM117 (auto-fixable) +95 92 | with A("01ß9πŸ’£2ℝ8901ß9πŸ’£2ℝ8901ß9πŸ’£2ℝ89") as a: + +SIM117.py:95:1: SIM117 [*] Use a single `with` statement with multiple contexts instead of nested `with` statements + | +95 | # SIM117 (auto-fixable) +96 | / with A("01ß9πŸ’£2ℝ8901ß9πŸ’£2ℝ8901ß9πŸ’£2ℝ89") as a: +97 | | with B("01ß9πŸ’£2ℝ8901ß9πŸ’£2ℝ8901ß9πŸ’£2ℝ89") as b: + | |__________________________________________________^ SIM117 +98 | print("hello") + | + = help: Combine `with` statements + +β„Ή Suggested fix +92 92 | print("hello") +93 93 | +94 94 | # SIM117 (auto-fixable) +95 |-with A("01ß9πŸ’£2ℝ8901ß9πŸ’£2ℝ8901ß9πŸ’£2ℝ89") as a: +96 |- with B("01ß9πŸ’£2ℝ8901ß9πŸ’£2ℝ8901ß9πŸ’£2ℝ89") as b: +97 |- print("hello") + 95 |+with A("01ß9πŸ’£2ℝ8901ß9πŸ’£2ℝ8901ß9πŸ’£2ℝ89") as a, B("01ß9πŸ’£2ℝ8901ß9πŸ’£2ℝ8901ß9πŸ’£2ℝ89") as b: + 96 |+ print("hello") +98 97 | +99 98 | # SIM117 (not auto-fixable too long) +100 99 | with A("01ß9πŸ’£2ℝ8901ß9πŸ’£2ℝ8901ß9πŸ’£2ℝ890") as a: + +SIM117.py:100:1: SIM117 Use a single `with` statement with multiple contexts instead of nested `with` statements + | +100 | # SIM117 (not auto-fixable too long) +101 | / with A("01ß9πŸ’£2ℝ8901ß9πŸ’£2ℝ8901ß9πŸ’£2ℝ890") as a: +102 | | with B("01ß9πŸ’£2ℝ8901ß9πŸ’£2ℝ8901ß9πŸ’£2ℝ89") as b: + | |__________________________________________________^ SIM117 +103 | print("hello") + | + = help: Combine `with` statements diff --git a/crates/ruff/src/rules/flake8_simplify/snapshots/ruff__rules__flake8_simplify__tests__SIM401_SIM401.py.snap b/crates/ruff/src/rules/flake8_simplify/snapshots/ruff__rules__flake8_simplify__tests__SIM401_SIM401.py.snap index 9a750e7e49..830ba3a2e7 100644 --- a/crates/ruff/src/rules/flake8_simplify/snapshots/ruff__rules__flake8_simplify__tests__SIM401_SIM401.py.snap +++ b/crates/ruff/src/rules/flake8_simplify/snapshots/ruff__rules__flake8_simplify__tests__SIM401_SIM401.py.snap @@ -105,18 +105,18 @@ SIM401.py:30:1: SIM401 [*] Use `var = dicts[idx].get(key, "default")` instead of 35 32 | # SIM401 (complex expression in var) 36 33 | if key in a_dict: -SIM401.py:36:1: SIM401 [*] Use `vars[idx] = a_dict.get(key, "default")` instead of an `if` block +SIM401.py:36:1: SIM401 [*] Use `vars[idx] = a_dict.get(key, "defaultß9πŸ’£2ℝ6789ß9πŸ’£2ℝ6789ß9πŸ’£2ℝ6789ß9πŸ’£2ℝ6789ß9πŸ’£2ℝ6789")` instead of an `if` block | 36 | # SIM401 (complex expression in var) 37 | / if key in a_dict: 38 | | vars[idx] = a_dict[key] 39 | | else: -40 | | vars[idx] = "default" - | |_________________________^ SIM401 +40 | | vars[idx] = "defaultß9πŸ’£2ℝ6789ß9πŸ’£2ℝ6789ß9πŸ’£2ℝ6789ß9πŸ’£2ℝ6789ß9πŸ’£2ℝ6789" + | |___________________________________________________________________________^ SIM401 41 | 42 | ### | - = help: Replace with `vars[idx] = a_dict.get(key, "default")` + = help: Replace with `vars[idx] = a_dict.get(key, "defaultß9πŸ’£2ℝ6789ß9πŸ’£2ℝ6789ß9πŸ’£2ℝ6789ß9πŸ’£2ℝ6789ß9πŸ’£2ℝ6789")` β„Ή Suggested fix 33 33 | var = "default" @@ -125,8 +125,8 @@ SIM401.py:36:1: SIM401 [*] Use `vars[idx] = a_dict.get(key, "default")` instead 36 |-if key in a_dict: 37 |- vars[idx] = a_dict[key] 38 |-else: -39 |- vars[idx] = "default" - 36 |+vars[idx] = a_dict.get(key, "default") +39 |- vars[idx] = "defaultß9πŸ’£2ℝ6789ß9πŸ’£2ℝ6789ß9πŸ’£2ℝ6789ß9πŸ’£2ℝ6789ß9πŸ’£2ℝ6789" + 36 |+vars[idx] = a_dict.get(key, "defaultß9πŸ’£2ℝ6789ß9πŸ’£2ℝ6789ß9πŸ’£2ℝ6789ß9πŸ’£2ℝ6789ß9πŸ’£2ℝ6789") 40 37 | 41 38 | ### 42 39 | # Negative cases diff --git a/crates/ruff/src/rules/isort/format.rs b/crates/ruff/src/rules/isort/format.rs index 0194dc4fa1..ece938b2ac 100644 --- a/crates/ruff/src/rules/isort/format.rs +++ b/crates/ruff/src/rules/isort/format.rs @@ -1,7 +1,7 @@ -use unicode_width::UnicodeWidthStr; - use ruff_python_ast::source_code::Stylist; +use crate::line_width::{LineLength, LineWidth}; + use super::types::{AliasData, CommentSet, ImportFromData, Importable}; // Guess a capacity to use for string allocation. @@ -45,7 +45,8 @@ pub(crate) fn format_import_from( import_from: &ImportFromData, comments: &CommentSet, aliases: &[(AliasData, CommentSet)], - line_length: usize, + line_length: LineLength, + indentation_width: LineWidth, stylist: &Stylist, force_wrap_aliases: bool, is_first: bool, @@ -56,8 +57,14 @@ pub(crate) fn format_import_from( .iter() .all(|(alias, _)| alias.name == "*" && alias.asname.is_none()) { - let (single_line, ..) = - format_single_line(import_from, comments, aliases, is_first, stylist); + let (single_line, ..) = format_single_line( + import_from, + comments, + aliases, + is_first, + stylist, + indentation_width, + ); return single_line; } @@ -71,8 +78,14 @@ pub(crate) fn format_import_from( || aliases.len() == 1 || aliases.iter().all(|(alias, _)| alias.asname.is_none())) { - let (single_line, import_width) = - format_single_line(import_from, comments, aliases, is_first, stylist); + let (single_line, import_width) = format_single_line( + import_from, + comments, + aliases, + is_first, + stylist, + indentation_width, + ); if import_width <= line_length || aliases.iter().any(|(alias, _)| alias.name == "*") { return single_line; } @@ -90,9 +103,10 @@ fn format_single_line( aliases: &[(AliasData, CommentSet)], is_first: bool, stylist: &Stylist, -) -> (String, usize) { + indentation_width: LineWidth, +) -> (String, LineWidth) { let mut output = String::with_capacity(CAPACITY); - let mut line_width = 0; + let mut line_width = indentation_width; if !is_first && !comments.atop.is_empty() { output.push_str(&stylist.line_ending()); @@ -106,28 +120,28 @@ fn format_single_line( output.push_str("from "); output.push_str(&module_name); output.push_str(" import "); - line_width += 5 + module_name.width() + 8; + line_width = line_width.add_width(5).add_str(&module_name).add_width(8); for (index, (AliasData { name, asname }, comments)) in aliases.iter().enumerate() { if let Some(asname) = asname { output.push_str(name); output.push_str(" as "); output.push_str(asname); - line_width += name.width() + 4 + asname.width(); + line_width = line_width.add_str(name).add_width(4).add_str(asname); } else { output.push_str(name); - line_width += name.width(); + line_width = line_width.add_str(name); } if index < aliases.len() - 1 { output.push_str(", "); - line_width += 2; + line_width = line_width.add_width(2); } for comment in &comments.inline { output.push(' '); output.push(' '); output.push_str(comment); - line_width += 2 + comment.width(); + line_width = line_width.add_width(2).add_str(comment); } } @@ -135,7 +149,7 @@ fn format_single_line( output.push(' '); output.push(' '); output.push_str(comment); - line_width += 2 + comment.width(); + line_width = line_width.add_width(2).add_str(comment); } output.push_str(&stylist.line_ending()); diff --git a/crates/ruff/src/rules/isort/mod.rs b/crates/ruff/src/rules/isort/mod.rs index a40e177073..251b69f6e4 100644 --- a/crates/ruff/src/rules/isort/mod.rs +++ b/crates/ruff/src/rules/isort/mod.rs @@ -17,6 +17,7 @@ use sorting::cmp_either_import; use types::EitherImport::{Import, ImportFrom}; use types::{AliasData, EitherImport, TrailingComma}; +use crate::line_width::{LineLength, LineWidth}; use crate::rules::isort::categorize::KnownModules; use crate::rules::isort::types::ImportBlock; use crate::settings::types::PythonVersion; @@ -65,7 +66,8 @@ pub(crate) fn format_imports( block: &Block, comments: Vec, locator: &Locator, - line_length: usize, + line_length: LineLength, + indentation_width: LineWidth, stylist: &Stylist, src: &[PathBuf], package: Option<&Path>, @@ -107,6 +109,7 @@ pub(crate) fn format_imports( let block_output = format_import_block( block, line_length, + indentation_width, stylist, src, package, @@ -162,7 +165,8 @@ pub(crate) fn format_imports( #[allow(clippy::too_many_arguments, clippy::fn_params_excessive_bools)] fn format_import_block( block: ImportBlock, - line_length: usize, + line_length: LineLength, + indentation_width: LineWidth, stylist: &Stylist, src: &[PathBuf], package: Option<&Path>, @@ -264,6 +268,7 @@ fn format_import_block( &comments, &aliases, line_length, + indentation_width, stylist, force_wrap_aliases, is_first_statement, diff --git a/crates/ruff/src/rules/isort/rules/organize_imports.rs b/crates/ruff/src/rules/isort/rules/organize_imports.rs index 2fd96c6d9a..ab79ecbda0 100644 --- a/crates/ruff/src/rules/isort/rules/organize_imports.rs +++ b/crates/ruff/src/rules/isort/rules/organize_imports.rs @@ -13,6 +13,7 @@ use ruff_python_ast::helpers::{ use ruff_python_ast::source_code::{Indexer, Locator, Stylist}; use ruff_python_ast::whitespace::leading_space; +use crate::line_width::LineWidth; use crate::registry::AsRule; use crate::settings::Settings; @@ -116,7 +117,8 @@ pub(crate) fn organize_imports( block, comments, locator, - settings.line_length - indentation.len(), + settings.line_length, + LineWidth::new(settings.tab_size).add_str(indentation), stylist, &settings.src, package, diff --git a/crates/ruff/src/rules/isort/snapshots/ruff__rules__isort__tests__fit_line_length_comment.py.snap b/crates/ruff/src/rules/isort/snapshots/ruff__rules__isort__tests__fit_line_length_comment.py.snap index 2dfbed7978..20c420373d 100644 --- a/crates/ruff/src/rules/isort/snapshots/ruff__rules__isort__tests__fit_line_length_comment.py.snap +++ b/crates/ruff/src/rules/isort/snapshots/ruff__rules__isort__tests__fit_line_length_comment.py.snap @@ -7,17 +7,30 @@ fit_line_length_comment.py:1:1: I001 [*] Import block is un-sorted or un-formatt 2 | | # Don't take this comment into account when determining whether the next import can fit on one line. 3 | | from b import c 4 | | from d import e # Do take this comment into account when determining whether the next import can fit on one line. +5 | | # The next import fits on one line. +6 | | from f import g # 012ß9πŸ’£2ℝ9012ß9πŸ’£2ℝ9012ß9πŸ’£2ℝ9012ß9πŸ’£2ℝ9012ß9πŸ’£2ℝ9012ß9πŸ’£2ℝ9012ß9πŸ’£2ℝ +7 | | # The next import doesn't fit on one line. +8 | | from h import i # 012ß9πŸ’£2ℝ9012ß9πŸ’£2ℝ9012ß9πŸ’£2ℝ9012ß9πŸ’£2ℝ9012ß9πŸ’£2ℝ9012ß9πŸ’£2ℝ9012ß9πŸ’£2ℝ9 | = help: Organize imports β„Ή Suggested fix -1 1 | import a - 2 |+ -2 3 | # Don't take this comment into account when determining whether the next import can fit on one line. -3 4 | from b import c -4 |-from d import e # Do take this comment into account when determining whether the next import can fit on one line. - 5 |+from d import ( - 6 |+ e, # Do take this comment into account when determining whether the next import can fit on one line. - 7 |+) +1 1 | import a + 2 |+ +2 3 | # Don't take this comment into account when determining whether the next import can fit on one line. +3 4 | from b import c +4 |-from d import e # Do take this comment into account when determining whether the next import can fit on one line. + 5 |+from d import ( + 6 |+ e, # Do take this comment into account when determining whether the next import can fit on one line. + 7 |+) + 8 |+ +5 9 | # The next import fits on one line. +6 10 | from f import g # 012ß9πŸ’£2ℝ9012ß9πŸ’£2ℝ9012ß9πŸ’£2ℝ9012ß9πŸ’£2ℝ9012ß9πŸ’£2ℝ9012ß9πŸ’£2ℝ9012ß9πŸ’£2ℝ + 11 |+ +7 12 | # The next import doesn't fit on one line. +8 |-from h import i # 012ß9πŸ’£2ℝ9012ß9πŸ’£2ℝ9012ß9πŸ’£2ℝ9012ß9πŸ’£2ℝ9012ß9πŸ’£2ℝ9012ß9πŸ’£2ℝ9012ß9πŸ’£2ℝ9 + 13 |+from h import ( + 14 |+ i, # 012ß9πŸ’£2ℝ9012ß9πŸ’£2ℝ9012ß9πŸ’£2ℝ9012ß9πŸ’£2ℝ9012ß9πŸ’£2ℝ9012ß9πŸ’£2ℝ9012ß9πŸ’£2ℝ9 + 15 |+) diff --git a/crates/ruff/src/rules/pycodestyle/helpers.rs b/crates/ruff/src/rules/pycodestyle/helpers.rs index 40b0314e9a..519ec3040e 100644 --- a/crates/ruff/src/rules/pycodestyle/helpers.rs +++ b/crates/ruff/src/rules/pycodestyle/helpers.rs @@ -1,10 +1,12 @@ use ruff_text_size::{TextLen, TextRange}; use rustpython_parser::ast::{self, Cmpop, Expr}; -use unicode_width::{UnicodeWidthChar, UnicodeWidthStr}; +use unicode_width::UnicodeWidthStr; use ruff_python_ast::newlines::Line; use ruff_python_ast::source_code::Generator; +use crate::line_width::{LineLength, LineWidth, TabSize}; + pub(crate) fn is_ambiguous_name(name: &str) -> bool { name == "l" || name == "I" || name == "O" } @@ -26,19 +28,19 @@ pub(crate) fn compare( pub(super) fn is_overlong( line: &Line, - limit: usize, + limit: LineLength, ignore_overlong_task_comments: bool, task_tags: &[String], + tab_size: TabSize, ) -> Option { let mut start_offset = line.start(); - let mut width = 0; + let mut width = LineWidth::new(tab_size); for c in line.chars() { if width < limit { start_offset += c.text_len(); } - - width += c.width().unwrap_or(0); + width = width.add_char(c); } if width <= limit { @@ -64,14 +66,14 @@ pub(super) fn is_overlong( // begins before the limit. let last_chunk = chunks.last().unwrap_or(second_chunk); if last_chunk.contains("://") { - if width - last_chunk.width() <= limit { + if width.get() - last_chunk.width() <= limit.get() { return None; } } Some(Overlong { range: TextRange::new(start_offset, line.end()), - width, + width: width.get(), }) } diff --git a/crates/ruff/src/rules/pycodestyle/mod.rs b/crates/ruff/src/rules/pycodestyle/mod.rs index 85999ec70e..cc25708246 100644 --- a/crates/ruff/src/rules/pycodestyle/mod.rs +++ b/crates/ruff/src/rules/pycodestyle/mod.rs @@ -11,6 +11,7 @@ mod tests { use anyhow::Result; use test_case::test_case; + use crate::line_width::LineLength; use crate::registry::Rule; use crate::test::test_path; use crate::{assert_messages, settings}; @@ -166,7 +167,7 @@ mod tests { Path::new("pycodestyle/W505.py"), &settings::Settings { pycodestyle: Settings { - max_doc_length: Some(50), + max_doc_length: Some(LineLength::from(50)), ..Settings::default() }, ..settings::Settings::for_rule(Rule::DocLineTooLong) @@ -175,4 +176,37 @@ mod tests { assert_messages!(diagnostics); Ok(()) } + + #[test] + fn max_doc_length_with_utf_8() -> Result<()> { + let diagnostics = test_path( + Path::new("pycodestyle/W505_utf_8.py"), + &settings::Settings { + pycodestyle: Settings { + max_doc_length: Some(LineLength::from(50)), + ..Settings::default() + }, + ..settings::Settings::for_rule(Rule::DocLineTooLong) + }, + )?; + assert_messages!(diagnostics); + Ok(()) + } + + #[test_case(1)] + #[test_case(2)] + #[test_case(4)] + #[test_case(8)] + fn tab_size(tab_size: u8) -> Result<()> { + let snapshot = format!("tab_size_{tab_size}"); + let diagnostics = test_path( + Path::new("pycodestyle/E501_2.py"), + &settings::Settings { + tab_size: tab_size.into(), + ..settings::Settings::for_rule(Rule::LineTooLong) + }, + )?; + assert_messages!(snapshot, diagnostics); + Ok(()) + } } diff --git a/crates/ruff/src/rules/pycodestyle/rules/doc_line_too_long.rs b/crates/ruff/src/rules/pycodestyle/rules/doc_line_too_long.rs index 99b663a733..74e9480163 100644 --- a/crates/ruff/src/rules/pycodestyle/rules/doc_line_too_long.rs +++ b/crates/ruff/src/rules/pycodestyle/rules/doc_line_too_long.rs @@ -48,6 +48,12 @@ pub(crate) fn doc_line_too_long(line: &Line, settings: &Settings) -> Option Option, + pub max_doc_length: Option, #[option( default = "false", value_type = "bool", @@ -35,7 +37,7 @@ pub struct Options { #[derive(Debug, Default, CacheKey)] pub struct Settings { - pub max_doc_length: Option, + pub max_doc_length: Option, pub ignore_overlong_task_comments: bool, } diff --git a/crates/ruff/src/rules/pycodestyle/snapshots/ruff__rules__pycodestyle__tests__max_doc_length_with_utf_8.snap b/crates/ruff/src/rules/pycodestyle/snapshots/ruff__rules__pycodestyle__tests__max_doc_length_with_utf_8.snap new file mode 100644 index 0000000000..17d2ed3c77 --- /dev/null +++ b/crates/ruff/src/rules/pycodestyle/snapshots/ruff__rules__pycodestyle__tests__max_doc_length_with_utf_8.snap @@ -0,0 +1,62 @@ +--- +source: crates/ruff/src/rules/pycodestyle/mod.rs +--- +W505_utf_8.py:2:50: W505 Doc line too long (57 > 50 characters) + | +2 | #!/usr/bin/env python3 +3 | """Here's a top-level ß9πŸ’£2ℝing that's over theß9πŸ’£2ℝ.""" + | ^^^^^^ W505 + | + +W505_utf_8.py:6:49: W505 Doc line too long (56 > 50 characters) + | +6 | def f1(): +7 | """Here's a ß9πŸ’£2ℝing that's also over theß9πŸ’£2ℝ.""" + | ^^^^^^ W505 +8 | +9 | x = 1 # Here's a comment that's over theß9πŸ’£2ℝ, but it's not standalone. + | + +W505_utf_8.py:10:51: W505 Doc line too long (56 > 50 characters) + | +10 | x = 1 # Here's a comment that's over theß9πŸ’£2ℝ, but it's not standalone. +11 | +12 | # Here's a standalone comment that's over theß9πŸ’£2ℝ. + | ^^^^^^ W505 +13 | +14 | x = 2 + | + +W505_utf_8.py:13:51: W505 Doc line too long (93 > 50 characters) + | +13 | x = 2 +14 | # Another standalone that is preceded by a newline and indent toke and is over theß9πŸ’£2ℝ. + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ W505 +15 | +16 | print("Here's a string that's over theß9πŸ’£2ℝ, but it's not a ß9πŸ’£2ℝing.") + | + +W505_utf_8.py:18:50: W505 Doc line too long (61 > 50 characters) + | +18 | "This is also considered a ß9πŸ’£2ℝing, and is over theß9πŸ’£2ℝ." + | ^^^^^^^^^^^ W505 + | + +W505_utf_8.py:24:50: W505 Doc line too long (82 > 50 characters) + | +24 | """Here's a multi-line ß9πŸ’£2ℝing. +25 | +26 | It's over theß9πŸ’£2ℝ on this line, which isn't the first line in the ß9πŸ’£2ℝing. + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ W505 +27 | """ + | + +W505_utf_8.py:31:50: W505 Doc line too long (85 > 50 characters) + | +31 | """Here's a multi-line ß9πŸ’£2ℝing. +32 | +33 | It's over theß9πŸ’£2ℝ on this line, which isn't the first line in the ß9πŸ’£2ℝing.""" + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ W505 + | + + diff --git a/crates/ruff/src/rules/pycodestyle/snapshots/ruff__rules__pycodestyle__tests__tab_size_1.snap b/crates/ruff/src/rules/pycodestyle/snapshots/ruff__rules__pycodestyle__tests__tab_size_1.snap new file mode 100644 index 0000000000..674ac41bd6 --- /dev/null +++ b/crates/ruff/src/rules/pycodestyle/snapshots/ruff__rules__pycodestyle__tests__tab_size_1.snap @@ -0,0 +1,56 @@ +--- +source: crates/ruff/src/rules/pycodestyle/mod.rs +--- +E501_2.py:1:81: E501 Line too long (89 > 88 characters) + | +1 | a = """ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A6""" + | ^ E501 +2 | a = """ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A6""" + | + +E501_2.py:2:81: E501 Line too long (89 > 88 characters) + | +2 | a = """ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A6""" +3 | a = """ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A6""" + | ^ E501 +4 | +5 | b = """ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A6""" + | + +E501_2.py:4:81: E501 Line too long (89 > 88 characters) + | +4 | a = """ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A6""" +5 | +6 | b = """ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A6""" + | ^ E501 +7 | b = """ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A6""" + | + +E501_2.py:5:81: E501 Line too long (89 > 88 characters) + | +5 | b = """ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A6""" +6 | b = """ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A6""" + | ^ E501 +7 | +8 | c = """2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A6""" + | + +E501_2.py:7:82: E501 Line too long (89 > 88 characters) + | + 7 | b = """ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A6""" + 8 | + 9 | c = """2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A6""" + | ^ E501 +10 | c = """2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A6""" + | + +E501_2.py:10:82: E501 Line too long (89 > 88 characters) + | +10 | c = """2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A6""" +11 | +12 | d = """πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A6""" + | ^ E501 +13 | d = """πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A6""" + | + + diff --git a/crates/ruff/src/rules/pycodestyle/snapshots/ruff__rules__pycodestyle__tests__tab_size_2.snap b/crates/ruff/src/rules/pycodestyle/snapshots/ruff__rules__pycodestyle__tests__tab_size_2.snap new file mode 100644 index 0000000000..674ac41bd6 --- /dev/null +++ b/crates/ruff/src/rules/pycodestyle/snapshots/ruff__rules__pycodestyle__tests__tab_size_2.snap @@ -0,0 +1,56 @@ +--- +source: crates/ruff/src/rules/pycodestyle/mod.rs +--- +E501_2.py:1:81: E501 Line too long (89 > 88 characters) + | +1 | a = """ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A6""" + | ^ E501 +2 | a = """ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A6""" + | + +E501_2.py:2:81: E501 Line too long (89 > 88 characters) + | +2 | a = """ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A6""" +3 | a = """ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A6""" + | ^ E501 +4 | +5 | b = """ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A6""" + | + +E501_2.py:4:81: E501 Line too long (89 > 88 characters) + | +4 | a = """ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A6""" +5 | +6 | b = """ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A6""" + | ^ E501 +7 | b = """ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A6""" + | + +E501_2.py:5:81: E501 Line too long (89 > 88 characters) + | +5 | b = """ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A6""" +6 | b = """ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A6""" + | ^ E501 +7 | +8 | c = """2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A6""" + | + +E501_2.py:7:82: E501 Line too long (89 > 88 characters) + | + 7 | b = """ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A6""" + 8 | + 9 | c = """2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A6""" + | ^ E501 +10 | c = """2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A6""" + | + +E501_2.py:10:82: E501 Line too long (89 > 88 characters) + | +10 | c = """2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A6""" +11 | +12 | d = """πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A6""" + | ^ E501 +13 | d = """πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A6""" + | + + diff --git a/crates/ruff/src/rules/pycodestyle/snapshots/ruff__rules__pycodestyle__tests__tab_size_4.snap b/crates/ruff/src/rules/pycodestyle/snapshots/ruff__rules__pycodestyle__tests__tab_size_4.snap new file mode 100644 index 0000000000..ef6c75a66d --- /dev/null +++ b/crates/ruff/src/rules/pycodestyle/snapshots/ruff__rules__pycodestyle__tests__tab_size_4.snap @@ -0,0 +1,65 @@ +--- +source: crates/ruff/src/rules/pycodestyle/mod.rs +--- +E501_2.py:1:81: E501 Line too long (89 > 88 characters) + | +1 | a = """ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A6""" + | ^ E501 +2 | a = """ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A6""" + | + +E501_2.py:2:77: E501 Line too long (93 > 88 characters) + | +2 | a = """ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A6""" +3 | a = """ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A6""" + | ^^^^^ E501 +4 | +5 | b = """ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A6""" + | + +E501_2.py:4:81: E501 Line too long (89 > 88 characters) + | +4 | a = """ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A6""" +5 | +6 | b = """ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A6""" + | ^ E501 +7 | b = """ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A6""" + | + +E501_2.py:5:77: E501 Line too long (93 > 88 characters) + | +5 | b = """ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A6""" +6 | b = """ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A6""" + | ^^^^^ E501 +7 | +8 | c = """2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A6""" + | + +E501_2.py:7:82: E501 Line too long (89 > 88 characters) + | + 7 | b = """ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A6""" + 8 | + 9 | c = """2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A6""" + | ^ E501 +10 | c = """2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A6""" + | + +E501_2.py:8:78: E501 Line too long (89 > 88 characters) + | + 8 | c = """2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A6""" + 9 | c = """2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A6""" + | ^ E501 +10 | +11 | d = """πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A6""" + | + +E501_2.py:10:82: E501 Line too long (89 > 88 characters) + | +10 | c = """2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A6""" +11 | +12 | d = """πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A6""" + | ^ E501 +13 | d = """πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A6""" + | + + diff --git a/crates/ruff/src/rules/pycodestyle/snapshots/ruff__rules__pycodestyle__tests__tab_size_8.snap b/crates/ruff/src/rules/pycodestyle/snapshots/ruff__rules__pycodestyle__tests__tab_size_8.snap new file mode 100644 index 0000000000..139405c993 --- /dev/null +++ b/crates/ruff/src/rules/pycodestyle/snapshots/ruff__rules__pycodestyle__tests__tab_size_8.snap @@ -0,0 +1,72 @@ +--- +source: crates/ruff/src/rules/pycodestyle/mod.rs +--- +E501_2.py:1:81: E501 Line too long (89 > 88 characters) + | +1 | a = """ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A6""" + | ^ E501 +2 | a = """ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A6""" + | + +E501_2.py:2:70: E501 Line too long (101 > 88 characters) + | +2 | a = """ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A6""" +3 | a = """ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A6""" + | ^^^^^^^^^^^^^ E501 +4 | +5 | b = """ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A6""" + | + +E501_2.py:4:81: E501 Line too long (89 > 88 characters) + | +4 | a = """ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A6""" +5 | +6 | b = """ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A6""" + | ^ E501 +7 | b = """ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A6""" + | + +E501_2.py:5:70: E501 Line too long (101 > 88 characters) + | +5 | b = """ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A6""" +6 | b = """ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A6""" + | ^^^^^^^^^^^^^ E501 +7 | +8 | c = """2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A6""" + | + +E501_2.py:7:82: E501 Line too long (89 > 88 characters) + | + 7 | b = """ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A6""" + 8 | + 9 | c = """2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A6""" + | ^ E501 +10 | c = """2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A6""" + | + +E501_2.py:8:71: E501 Line too long (97 > 88 characters) + | + 8 | c = """2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A6""" + 9 | c = """2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A6""" + | ^^^^^^^^ E501 +10 | +11 | d = """πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A6""" + | + +E501_2.py:10:82: E501 Line too long (89 > 88 characters) + | +10 | c = """2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A6""" +11 | +12 | d = """πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A6""" + | ^ E501 +13 | d = """πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A6""" + | + +E501_2.py:11:70: E501 Line too long (89 > 88 characters) + | +11 | d = """πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A6""" +12 | d = """πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A67ß9πŸ’£2ℝ4A6""" + | ^ E501 + | + + diff --git a/crates/ruff/src/settings/configuration.rs b/crates/ruff/src/settings/configuration.rs index 1c7d3b2982..86212fd45e 100644 --- a/crates/ruff/src/settings/configuration.rs +++ b/crates/ruff/src/settings/configuration.rs @@ -13,6 +13,7 @@ use shellexpand; use shellexpand::LookupError; use crate::fs; +use crate::line_width::{LineLength, TabSize}; use crate::rule_selector::RuleSelector; use crate::rules::{ flake8_annotations, flake8_bandit, flake8_bugbear, flake8_builtins, flake8_comprehensions, @@ -56,7 +57,8 @@ pub struct Configuration { pub format: Option, pub ignore_init_module_imports: Option, pub include: Option>, - pub line_length: Option, + pub line_length: Option, + pub tab_size: Option, pub namespace_packages: Option>, pub required_version: Option, pub respect_gitignore: Option, @@ -194,6 +196,7 @@ impl Configuration { .collect() }), line_length: options.line_length, + tab_size: options.tab_size, namespace_packages: options .namespace_packages .map(|namespace_package| resolve_src(&namespace_package, project_root)) @@ -281,6 +284,7 @@ impl Configuration { .ignore_init_module_imports .or(config.ignore_init_module_imports), line_length: self.line_length.or(config.line_length), + tab_size: self.tab_size.or(config.tab_size), namespace_packages: self.namespace_packages.or(config.namespace_packages), per_file_ignores: self.per_file_ignores.or(config.per_file_ignores), required_version: self.required_version.or(config.required_version), diff --git a/crates/ruff/src/settings/defaults.rs b/crates/ruff/src/settings/defaults.rs index 5d468784f4..2aac4fa44f 100644 --- a/crates/ruff/src/settings/defaults.rs +++ b/crates/ruff/src/settings/defaults.rs @@ -6,6 +6,7 @@ use regex::Regex; use rustc_hash::FxHashSet; use crate::codes::{self, RuleCodePrefix}; +use crate::line_width::{LineLength, TabSize}; use crate::registry::Linter; use crate::rule_selector::{prefix_to_selector, RuleSelector}; use crate::rules::{ @@ -26,8 +27,6 @@ pub const PREFIXES: &[RuleSelector] = &[ pub const TARGET_VERSION: PythonVersion = PythonVersion::Py310; -pub const LINE_LENGTH: usize = 88; - pub const TASK_TAGS: &[&str] = &["TODO", "FIXME", "XXX"]; pub static DUMMY_VARIABLE_RGX: Lazy = @@ -76,7 +75,8 @@ impl Default for Settings { force_exclude: false, ignore_init_module_imports: false, include: FilePatternSet::try_from_vec(INCLUDE.clone()).unwrap(), - line_length: LINE_LENGTH, + line_length: LineLength::default(), + tab_size: TabSize::default(), namespace_packages: vec![], per_file_ignores: vec![], respect_gitignore: true, diff --git a/crates/ruff/src/settings/mod.rs b/crates/ruff/src/settings/mod.rs index ed53334060..f604098fd4 100644 --- a/crates/ruff/src/settings/mod.rs +++ b/crates/ruff/src/settings/mod.rs @@ -26,6 +26,7 @@ use crate::settings::types::{FilePatternSet, PerFileIgnore, PythonVersion, Seria use crate::warn_user_once_by_id; use self::rule_table::RuleTable; +use super::line_width::{LineLength, TabSize}; pub mod configuration; pub mod defaults; @@ -98,7 +99,8 @@ pub struct Settings { pub dummy_variable_rgx: Regex, pub external: FxHashSet, pub ignore_init_module_imports: bool, - pub line_length: usize, + pub line_length: LineLength, + pub tab_size: TabSize, pub namespace_packages: Vec, pub src: Vec, pub task_tags: Vec, @@ -160,7 +162,8 @@ impl Settings { config.include.unwrap_or_else(|| defaults::INCLUDE.clone()), )?, ignore_init_module_imports: config.ignore_init_module_imports.unwrap_or_default(), - line_length: config.line_length.unwrap_or(defaults::LINE_LENGTH), + line_length: config.line_length.unwrap_or_default(), + tab_size: config.tab_size.unwrap_or_default(), namespace_packages: config.namespace_packages.unwrap_or_default(), per_file_ignores: resolve_per_file_ignores( config diff --git a/crates/ruff/src/settings/options.rs b/crates/ruff/src/settings/options.rs index 6cad16d2b9..5904adcc39 100644 --- a/crates/ruff/src/settings/options.rs +++ b/crates/ruff/src/settings/options.rs @@ -5,6 +5,7 @@ use serde::{Deserialize, Serialize}; use ruff_macros::ConfigurationOptions; +use crate::line_width::{LineLength, TabSize}; use crate::rule_selector::RuleSelector; use crate::rules::{ flake8_annotations, flake8_bandit, flake8_bugbear, flake8_builtins, flake8_comprehensions, @@ -303,13 +304,22 @@ pub struct Options { default = "88", value_type = "int", example = r#" - # Allow lines to be as long as 120 characters. - line-length = 120 + # Allow lines to be as long as 120 characters. + line-length = 120 "# )] /// The line length to use when enforcing long-lines violations (like /// `E501`). - pub line_length: Option, + pub line_length: Option, + #[option( + default = "4", + value_type = "int", + example = r#" + tab_size = 8 + "# + )] + /// The tabulation size to calculate line length. + pub tab_size: Option, #[option( default = "None", value_type = "str", diff --git a/crates/ruff/src/settings/pyproject.rs b/crates/ruff/src/settings/pyproject.rs index 929d5bb541..76337502cf 100644 --- a/crates/ruff/src/settings/pyproject.rs +++ b/crates/ruff/src/settings/pyproject.rs @@ -151,6 +151,7 @@ mod tests { use rustc_hash::FxHashMap; use crate::codes::{self, RuleCodePrefix}; + use crate::line_width::LineLength; use crate::rules::flake8_quotes::settings::Quote; use crate::rules::flake8_tidy_imports::banned_api::ApiBan; use crate::rules::flake8_tidy_imports::relative_imports::Strictness; @@ -200,7 +201,7 @@ line-length = 79 pyproject.tool, Some(Tools { ruff: Some(Options { - line_length: Some(79), + line_length: Some(LineLength::from(79)), ..Options::default() }) }) @@ -301,7 +302,7 @@ other-attribute = 1 config, Options { allowed_confusables: Some(vec!['βˆ’', 'ρ', 'βˆ—']), - line_length: Some(88), + line_length: Some(LineLength::from(88)), extend_exclude: Some(vec![ "excluded_file.py".to_string(), "migrations".to_string(), diff --git a/crates/ruff_cli/src/args.rs b/crates/ruff_cli/src/args.rs index b6f8d096eb..fee99706a4 100644 --- a/crates/ruff_cli/src/args.rs +++ b/crates/ruff_cli/src/args.rs @@ -5,6 +5,7 @@ use clap::{command, Parser}; use regex::Regex; use rustc_hash::FxHashMap; +use ruff::line_width::LineLength; use ruff::logging::LogLevel; use ruff::registry::Rule; use ruff::resolver::ConfigProcessor; @@ -545,7 +546,7 @@ impl ConfigProcessor for &Overrides { config.force_exclude = Some(*force_exclude); } if let Some(line_length) = &self.line_length { - config.line_length = Some(*line_length); + config.line_length = Some(LineLength::from(*line_length)); } if let Some(per_file_ignores) = &self.per_file_ignores { config.per_file_ignores = Some(collect_per_file_ignores(per_file_ignores.clone())); diff --git a/crates/ruff_wasm/src/lib.rs b/crates/ruff_wasm/src/lib.rs index 388ea19bf2..41a9feef5b 100644 --- a/crates/ruff_wasm/src/lib.rs +++ b/crates/ruff_wasm/src/lib.rs @@ -5,6 +5,7 @@ use serde::{Deserialize, Serialize}; use wasm_bindgen::prelude::*; use ruff::directives; +use ruff::line_width::{LineLength, TabSize}; use ruff::linter::{check_path, LinterResult}; use ruff::registry::AsRule; use ruff::rules::{ @@ -102,7 +103,8 @@ pub fn defaultSettings() -> Result { extend_unfixable: Some(Vec::default()), external: Some(Vec::default()), ignore: Some(Vec::default()), - line_length: Some(defaults::LINE_LENGTH), + line_length: Some(LineLength::default()), + tab_size: Some(TabSize::default()), select: Some(defaults::PREFIXES.to_vec()), target_version: Some(defaults::TARGET_VERSION), // Ignore a bunch of options that don't make sense in a single-file editor. diff --git a/ruff.schema.json b/ruff.schema.json index ae7af604e0..1bcfb5fb08 100644 --- a/ruff.schema.json +++ b/ruff.schema.json @@ -366,12 +366,14 @@ }, "line-length": { "description": "The line length to use when enforcing long-lines violations (like `E501`).", - "type": [ - "integer", - "null" - ], - "format": "uint", - "minimum": 0.0 + "anyOf": [ + { + "$ref": "#/definitions/LineLength" + }, + { + "type": "null" + } + ] }, "mccabe": { "description": "Options for the `mccabe` plugin.", @@ -503,6 +505,17 @@ "type": "string" } }, + "tab-size": { + "description": "The tabulation size to calculate line length.", + "anyOf": [ + { + "$ref": "#/definitions/TabSize" + }, + { + "type": "null" + } + ] + }, "target-version": { "description": "The Python version to target, e.g., when considering automatic code upgrades, like rewriting type annotations.\n\nIf omitted, the target version will be inferred from the `project.requires-python` field in the relevant `pyproject.toml` (e.g., `requires-python = \">=3.8\"`), if present.", "anyOf": [ @@ -1276,6 +1289,12 @@ }, "additionalProperties": false }, + "LineLength": { + "description": "The length of a line of text that is considered too long.", + "type": "integer", + "format": "uint", + "minimum": 0.0 + }, "McCabeOptions": { "type": "object", "properties": { @@ -1361,12 +1380,14 @@ }, "max-doc-length": { "description": "The maximum line length to allow for line-length violations within documentation (`W505`), including standalone comments.", - "type": [ - "integer", - "null" - ], - "format": "uint", - "minimum": 0.0 + "anyOf": [ + { + "$ref": "#/definitions/LineLength" + }, + { + "type": "null" + } + ] } }, "additionalProperties": false @@ -2489,6 +2510,12 @@ } ] }, + "TabSize": { + "description": "The size of a tab.", + "type": "integer", + "format": "uint8", + "minimum": 0.0 + }, "Version": { "type": "string" }