Introduce `tab-size` to correcly calculate the line length with tabulations (#4167)

This commit is contained in:
Jonathan Plasse 2023-05-24 08:37:24 +02:00 committed by GitHub
parent 3644695bf2
commit c6a760e298
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
45 changed files with 1005 additions and 173 deletions

View File

@ -86,9 +86,16 @@ while x > 0:
): ):
print("Bad module!") print("Bad module!")
# SIM102 # SIM102 (auto-fixable)
if node.module: if node.module012345678:
if node.module == "multiprocessing" or node.module.startswith( 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." "multiprocessing."
): ):
print("Bad module!") print("Bad module!")

View File

@ -80,17 +80,25 @@ else:
# SIM108 # SIM108
if a: if a:
b = cccccccccccccccccccccccccccccccccccc b = "cccccccccccccccccccccccccccccccccß"
else: else:
b = ddddddddddddddddddddddddddddddddddddd b = "ddddddddddddddddddddddddddddddddd💣"
# OK (too long) # OK (too long)
if True: if True:
if a: if a:
b = cccccccccccccccccccccccccccccccccccc b = ccccccccccccccccccccccccccccccccccc
else: 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) # SIM108 (without fix due to trailing comment)

View File

@ -155,3 +155,19 @@ def f():
if check(x): if check(x):
return False return False
return True return True
def f():
# SIM110
for x in "012ß9💣29012ß9💣29012ß9💣29012ß9💣29012ß9💣2":
if x.isdigit():
return True
return False
def f():
# OK (too long)
for x in "012ß9💣29012ß9💣29012ß9💣29012ß9💣29012ß9💣29":
if x.isdigit():
return True
return False

View File

@ -171,3 +171,19 @@ def f():
if x > y: if x > y:
return False return False
return True return True
def f():
# SIM111
for x in "012ß9💣29012ß9💣29012ß9💣29012ß9💣29012ß9":
if x.isdigit():
return False
return True
def f():
# OK (too long)
for x in "012ß9💣29012ß9💣29012ß9💣29012ß9💣29012ß90":
if x.isdigit():
return False
return True

View File

@ -90,3 +90,13 @@ with (
D() as d, D() as d,
): ):
print("hello") print("hello")
# SIM117 (auto-fixable)
with A("01ß9💣28901ß9💣28901ß9💣289") as a:
with B("01ß9💣28901ß9💣28901ß9💣289") as b:
print("hello")
# SIM117 (not auto-fixable too long)
with A("01ß9💣28901ß9💣28901ß9💣2890") as a:
with B("01ß9💣28901ß9💣28901ß9💣289") as b:
print("hello")

View File

@ -36,12 +36,18 @@ else:
if key in a_dict: if key in a_dict:
vars[idx] = a_dict[key] vars[idx] = a_dict[key]
else: else:
vars[idx] = "default" vars[idx] = "defaultß9💣26789ß9💣26789ß9💣26789ß9💣26789ß9💣26789"
### ###
# Negative cases # Negative cases
### ###
# OK (too long)
if key in a_dict:
vars[idx] = a_dict[key]
else:
vars[idx] = "defaultß9💣26789ß9💣26789ß9💣26789ß9💣26789ß9💣26789ß"
# OK (false negative) # OK (false negative)
if not key in a_dict: if not key in a_dict:
var = "default" var = "default"

View File

@ -2,3 +2,7 @@ import a
# Don't take this comment into account when determining whether the next import can fit on one line. # Don't take this comment into account when determining whether the next import can fit on one line.
from b import c 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. 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💣29012ß9💣29012ß9💣29012ß9💣29012ß9💣29012ß9💣29012ß9💣2
# The next import doesn't fit on one line.
from h import i # 012ß9💣29012ß9💣29012ß9💣29012ß9💣29012ß9💣29012ß9💣29012ß9💣29

View File

@ -0,0 +1,11 @@
a = """ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A6"""
a = """ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A6"""
b = """ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A6"""
b = """ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A6"""
c = """24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A6"""
c = """24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A6"""
d = """💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A6"""
d = """💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A6"""

View File

@ -0,0 +1,31 @@
#!/usr/bin/env python3
"""Here's a top-level ß9💣2ing that's over theß9💣2."""
def f1():
"""Here's a ß9💣2ing 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💣2ing.")
"This is also considered a ß9💣2ing, and is over theß9💣2."
def f2():
"""Here's a multi-line ß9💣2ing.
It's over theß9💣2 on this line, which isn't the first line in the ß9💣2ing.
"""
def f3():
"""Here's a multi-line ß9💣2ing.
It's over theß9💣2 on this line, which isn't the first line in the ß9💣2ing."""

View File

@ -183,6 +183,7 @@ mod tests {
use ruff_python_ast::source_code::{Indexer, Locator, Stylist}; use ruff_python_ast::source_code::{Indexer, Locator, Stylist};
use crate::line_width::LineLength;
use crate::registry::Rule; use crate::registry::Rule;
use crate::settings::Settings; use crate::settings::Settings;
@ -196,7 +197,7 @@ mod tests {
let indexer = Indexer::from_tokens(&tokens, &locator); let indexer = Indexer::from_tokens(&tokens, &locator);
let stylist = Stylist::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( check_physical_lines(
Path::new("foo.py"), Path::new("foo.py"),
&locator, &locator,
@ -209,7 +210,8 @@ mod tests {
}, },
) )
}; };
assert_eq!(check_with_max_line_length(8), vec![]); let line_length = LineLength::from(8);
assert_eq!(check_with_max_line_length(8), vec![]); assert_eq!(check_with_max_line_length(line_length), vec![]);
assert_eq!(check_with_max_line_length(line_length), vec![]);
} }
} }

View File

@ -3,6 +3,7 @@ use std::collections::{HashMap, HashSet};
use anyhow::Result; use anyhow::Result;
use itertools::Itertools; use itertools::Itertools;
use crate::line_width::LineLength;
use crate::registry::Linter; use crate::registry::Linter;
use crate::rule_selector::RuleSelector; use crate::rule_selector::RuleSelector;
use crate::rules::flake8_pytest_style::types::{ use crate::rules::flake8_pytest_style::types::{
@ -120,7 +121,9 @@ pub fn convert(
options.builtins = Some(parser::parse_strings(value.as_ref())); options.builtins = Some(parser::parse_strings(value.as_ref()));
} }
"max-line-length" | "max_line_length" => match value.parse::<usize>() { "max-line-length" | "max_line_length" => match value.parse::<usize>() {
Ok(line_length) => options.line_length = Some(line_length), Ok(line_length) => {
options.line_length = Some(LineLength::from(line_length));
}
Err(e) => { Err(e) => {
warn_user!("Unable to parse '{key}' property: {e}"); warn_user!("Unable to parse '{key}' property: {e}");
} }
@ -403,7 +406,7 @@ pub fn convert(
// Extract any settings from the existing `pyproject.toml`. // Extract any settings from the existing `pyproject.toml`.
if let Some(black) = &external_config.black { if let Some(black) = &external_config.black {
if let Some(line_length) = &black.line_length { 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 { 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::converter::DEFAULT_SELECTORS;
use crate::flake8_to_ruff::pep621::Project; use crate::flake8_to_ruff::pep621::Project;
use crate::flake8_to_ruff::ExternalConfig; use crate::flake8_to_ruff::ExternalConfig;
use crate::line_width::LineLength;
use crate::registry::Linter; use crate::registry::Linter;
use crate::rule_selector::RuleSelector; use crate::rule_selector::RuleSelector;
use crate::rules::pydocstyle::settings::Convention; use crate::rules::pydocstyle::settings::Convention;
@ -510,7 +514,7 @@ mod tests {
Some(vec![]), Some(vec![]),
)?; )?;
let expected = Pyproject::new(Options { let expected = Pyproject::new(Options {
line_length: Some(100), line_length: Some(LineLength::from(100)),
..default_options([]) ..default_options([])
}); });
assert_eq!(actual, expected); assert_eq!(actual, expected);
@ -529,7 +533,7 @@ mod tests {
Some(vec![]), Some(vec![]),
)?; )?;
let expected = Pyproject::new(Options { let expected = Pyproject::new(Options {
line_length: Some(100), line_length: Some(LineLength::from(100)),
..default_options([]) ..default_options([])
}); });
assert_eq!(actual, expected); assert_eq!(actual, expected);

View File

@ -21,6 +21,7 @@ pub mod fs;
mod importer; mod importer;
pub mod jupyter; pub mod jupyter;
mod lex; mod lex;
pub mod line_width;
pub mod linter; pub mod linter;
pub mod logging; pub mod logging;
pub mod message; pub mod message;

View File

@ -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<usize> 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<std::cmp::Ordering> {
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<Item = char>) -> 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<LineLength> for LineWidth {
fn eq(&self, other: &LineLength) -> bool {
self.width == other.0
}
}
impl PartialOrd<LineLength> for LineWidth {
fn partial_cmp(&self, other: &LineLength) -> Option<std::cmp::Ordering> {
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<u8> for TabSize {
fn from(tab_size: u8) -> Self {
Self(tab_size)
}
}
impl From<TabSize> for usize {
fn from(tab_size: TabSize) -> Self {
tab_size.0 as usize
}
}

View File

@ -11,6 +11,7 @@ use ruff_text_size::{TextRange, TextSize};
use ruff_python_ast::source_code::{OneIndexed, SourceLocation}; use ruff_python_ast::source_code::{OneIndexed, SourceLocation};
use crate::fs::relativize_path; use crate::fs::relativize_path;
use crate::line_width::{LineWidth, TabSize};
use crate::message::diff::Diff; use crate::message::diff::Diff;
use crate::message::{Emitter, EmitterContext, Message}; use crate::message::{Emitter, EmitterContext, Message};
use crate::registry::AsRule; use crate::registry::AsRule;
@ -237,39 +238,35 @@ impl Display for MessageCodeFrame<'_> {
} }
fn replace_whitespace(source: &str, annotation_range: TextRange) -> SourceCode { 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 result = String::new();
let mut last_end = 0; let mut last_end = 0;
let mut range = annotation_range; let mut range = annotation_range;
let mut column = 0; let mut line_width = LineWidth::new(TAB_SIZE);
for (index, c) in source.chars().enumerate() { for (index, c) in source.char_indices() {
match c { let old_width = line_width.get();
'\t' => { line_width = line_width.add_char(c);
let tab_width = TAB_SIZE - column % TAB_SIZE;
column += tab_width;
if index < usize::from(annotation_range.start()) { if matches!(c, '\t') {
range += TextSize::new(tab_width - 1); // SAFETY: The difference is a value in the range [1..TAB_SIZE] which is guaranteed to be less than `u32`.
} else if index < usize::from(annotation_range.end()) { #[allow(clippy::cast_possible_truncation)]
range = range.add_end(TextSize::new(tab_width - 1)); let tab_width = (line_width.get() - old_width) as u32;
}
result.push_str(&source[last_end..index]); if index < usize::from(annotation_range.start()) {
range += TextSize::new(tab_width - 1);
for _ in 0..tab_width { } else if index < usize::from(annotation_range.end()) {
result.push(' '); range = range.add_end(TextSize::new(tab_width - 1));
}
last_end = index + 1;
} }
'\n' | '\r' => {
column = 0; result.push_str(&source[last_end..index]);
}
_ => { for _ in 0..tab_width {
column += 1; result.push(' ');
} }
last_end = index + 1;
} }
} }

View File

@ -2,7 +2,6 @@ use log::error;
use ruff_text_size::TextRange; use ruff_text_size::TextRange;
use rustc_hash::FxHashSet; use rustc_hash::FxHashSet;
use rustpython_parser::ast::{self, Cmpop, Constant, Expr, ExprContext, Ranged, Stmt}; 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_diagnostics::{AutofixKind, Diagnostic, Edit, Fix, Violation};
use ruff_macros::{derive_message_formats, 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 ruff_python_semantic::model::SemanticModel;
use crate::checkers::ast::Checker; use crate::checkers::ast::Checker;
use crate::line_width::LineWidth;
use crate::registry::AsRule; use crate::registry::AsRule;
use crate::rules::flake8_simplify::rules::fix_if; use crate::rules::flake8_simplify::rules::fix_if;
@ -288,7 +288,10 @@ pub(crate) fn nested_if_statements(
.content() .content()
.unwrap_or_default() .unwrap_or_default()
.universal_newlines() .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)] #[allow(deprecated)]
diagnostic.set_fix(Fix::unspecified(edit)); 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. // Don't flag if the resulting expression would exceed the maximum line length.
let line_start = checker.locator.line_start(stmt.start()); let line_start = checker.locator.line_start(stmt.start());
if checker.locator.contents()[TextRange::new(line_start, stmt.start())].width() if LineWidth::new(checker.settings.tab_size)
+ contents.width() .add_str(&checker.locator.contents()[TextRange::new(line_start, stmt.start())])
.add_str(&contents)
> checker.settings.line_length > checker.settings.line_length
{ {
return; 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. // Don't flag if the resulting expression would exceed the maximum line length.
let line_start = checker.locator.line_start(stmt.start()); let line_start = checker.locator.line_start(stmt.start());
if checker.locator.contents()[TextRange::new(line_start, stmt.start())].width() if LineWidth::new(checker.settings.tab_size)
+ contents.width() .add_str(&checker.locator.contents()[TextRange::new(line_start, stmt.start())])
.add_str(&contents)
> checker.settings.line_length > checker.settings.line_length
{ {
return; return;

View File

@ -1,7 +1,6 @@
use log::error; use log::error;
use ruff_text_size::TextRange; use ruff_text_size::TextRange;
use rustpython_parser::ast::{self, Ranged, Stmt, Withitem}; use rustpython_parser::ast::{self, Ranged, Stmt, Withitem};
use unicode_width::UnicodeWidthStr;
use ruff_diagnostics::{AutofixKind, Violation}; use ruff_diagnostics::{AutofixKind, Violation};
use ruff_diagnostics::{Diagnostic, Fix}; 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 ruff_python_ast::newlines::StrExt;
use crate::checkers::ast::Checker; use crate::checkers::ast::Checker;
use crate::line_width::LineWidth;
use crate::registry::AsRule; use crate::registry::AsRule;
use super::fix_with; use super::fix_with;
@ -111,7 +111,10 @@ pub(crate) fn multiple_with_statements(
.content() .content()
.unwrap_or_default() .unwrap_or_default()
.universal_newlines() .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)] #[allow(deprecated)]
diagnostic.set_fix(Fix::unspecified(edit)); diagnostic.set_fix(Fix::unspecified(edit));

View File

@ -2,13 +2,13 @@ use ruff_text_size::{TextRange, TextSize};
use rustpython_parser::ast::{ use rustpython_parser::ast::{
self, Cmpop, Comprehension, Constant, Expr, ExprContext, Ranged, Stmt, Unaryop, self, Cmpop, Comprehension, Constant, Expr, ExprContext, Ranged, Stmt, Unaryop,
}; };
use unicode_width::UnicodeWidthStr;
use ruff_diagnostics::{AutofixKind, Diagnostic, Edit, Fix, Violation}; use ruff_diagnostics::{AutofixKind, Diagnostic, Edit, Fix, Violation};
use ruff_macros::{derive_message_formats, violation}; use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::source_code::Generator; use ruff_python_ast::source_code::Generator;
use crate::checkers::ast::Checker; use crate::checkers::ast::Checker;
use crate::line_width::LineWidth;
use crate::registry::{AsRule, Rule}; use crate::registry::{AsRule, Rule};
#[violation] #[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. // Don't flag if the resulting expression would exceed the maximum line length.
let line_start = checker.locator.line_start(stmt.start()); let line_start = checker.locator.line_start(stmt.start());
if checker.locator.contents()[TextRange::new(line_start, stmt.start())].width() if LineWidth::new(checker.settings.tab_size)
+ contents.width() .add_str(&checker.locator.contents()[TextRange::new(line_start, stmt.start())])
.add_str(&contents)
> checker.settings.line_length > checker.settings.line_length
{ {
return; 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. // Don't flag if the resulting expression would exceed the maximum line length.
let line_start = checker.locator.line_start(stmt.start()); let line_start = checker.locator.line_start(stmt.start());
if checker.locator.contents()[TextRange::new(line_start, stmt.start())].width() if LineWidth::new(checker.settings.tab_size)
+ contents.width() .add_str(&checker.locator.contents()[TextRange::new(line_start, stmt.start())])
.add_str(&contents)
> checker.settings.line_length > checker.settings.line_length
{ {
return; return;

View File

@ -211,14 +211,14 @@ SIM102.py:83:5: SIM102 [*] Use a single `if` statement instead of nested `if` st
85 |+ )): 85 |+ )):
86 |+ print("Bad module!") 86 |+ print("Bad module!")
88 87 | 88 87 |
89 88 | # SIM102 89 88 | # SIM102 (auto-fixable)
90 89 | if node.module: 90 89 | if node.module012345678:
SIM102.py:90:1: SIM102 [*] Use a single `if` statement instead of nested `if` statements SIM102.py:90:1: SIM102 [*] Use a single `if` statement instead of nested `if` statements
| |
90 | # SIM102 90 | # SIM102 (auto-fixable)
91 | / if node.module: 91 | / if node.module012345678:
92 | | if node.module == "multiprocessing" or node.module.startswith( 92 | | if node.module == "multiprocß9💣2" or node.module.startswith(
93 | | "multiprocessing." 93 | | "multiprocessing."
94 | | ): 94 | | ):
| |______^ SIM102 | |______^ SIM102
@ -229,44 +229,56 @@ SIM102.py:90:1: SIM102 [*] Use a single `if` statement instead of nested `if` st
Suggested fix Suggested fix
87 87 | print("Bad module!") 87 87 | print("Bad module!")
88 88 | 88 88 |
89 89 | # SIM102 89 89 | # SIM102 (auto-fixable)
90 |-if node.module: 90 |-if node.module012345678:
91 |- if node.module == "multiprocessing" or node.module.startswith( 91 |- if node.module == "multiprocß9💣2" or node.module.startswith(
92 |- "multiprocessing." 92 |- "multiprocessing."
93 |- ): 93 |- ):
94 |- print("Bad module!") 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." 91 |+ "multiprocessing."
92 |+)): 92 |+)):
93 |+ print("Bad module!") 93 |+ print("Bad module!")
95 94 | 95 94 |
96 95 | 96 95 | # SIM102 (not auto-fixable)
97 96 | # OK 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: 97 | # SIM102 (not auto-fixable)
118 | # SIM 102 98 | / if node.module0123456789:
119 | if b: 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 | |_____________^ SIM102
121 | print("foo") 128 | print("foo")
122 | else: 129 | else:
| |
= help: Combine `if` statements using `and` = help: Combine `if` statements using `and`
Suggested fix Suggested fix
114 114 | # OK 121 121 | # OK
115 115 | if a: 122 122 | if a:
116 116 | # SIM 102 123 123 | # SIM 102
117 |- if b: 124 |- if b:
118 |- if c: 125 |- if c:
119 |- print("foo") 126 |- print("foo")
117 |+ if b and c: 124 |+ if b and c:
118 |+ print("foo") 125 |+ print("foo")
120 119 | else: 127 126 | else:
121 120 | print("bar") 128 127 | print("bar")
122 121 | 129 128 |

View File

@ -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` = 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 82 | # SIM108
83 | / if a: 83 | / if a:
84 | | b = cccccccccccccccccccccccccccccccccccc 84 | | b = "cccccccccccccccccccccccccccccccccß"
85 | | else: 85 | | else:
86 | | b = ddddddddddddddddddddddddddddddddddddd 86 | | b = "ddddddddddddddddddddddddddddddddd💣"
| |_____________________________________________^ SIM108 | |_____________________________________________^ 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 Suggested fix
79 79 | 79 79 |
80 80 | 80 80 |
81 81 | # SIM108 81 81 | # SIM108
82 |-if a: 82 |-if a:
83 |- b = cccccccccccccccccccccccccccccccccccc 83 |- b = "cccccccccccccccccccccccccccccccccß"
84 |-else: 84 |-else:
85 |- b = ddddddddddddddddddddddddddddddddddddd 85 |- b = "ddddddddddddddddddddddddddddddddd💣"
82 |+b = cccccccccccccccccccccccccccccccccccc if a else ddddddddddddddddddddddddddddddddddddd 82 |+b = "cccccccccccccccccccccccccccccccccß" if a else "ddddddddddddddddddddddddddddddddd💣"
86 83 | 86 83 |
87 84 | 87 84 |
88 85 | # OK (too long) 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) 105 | # SIM108 (without fix due to trailing comment)
98 | / if True: 106 | / if True:
99 | | exitcode = 0 107 | | exitcode = 0
100 | | else: 108 | | else:
101 | | exitcode = 1 # Trailing comment 109 | | exitcode = 1 # Trailing comment
| |________________^ SIM108 | |________________^ SIM108
| |
= help: Replace `if`-`else`-block with `exitcode = 0 if True else 1` = 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 112 | # SIM108
105 | / if True: x = 3 # Foo 113 | / if True: x = 3 # Foo
106 | | else: x = 5 114 | | else: x = 5
| |___________^ SIM108 | |___________^ SIM108
| |
= help: Replace `if`-`else`-block with `x = 3 if True else 5` = 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 117 | # SIM108
110 | / if True: # Foo 118 | / if True: # Foo
111 | | x = 3 119 | | x = 3
112 | | else: 120 | | else:
113 | | x = 5 121 | | x = 5
| |_________^ SIM108 | |_________^ SIM108
| |
= help: Replace `if`-`else`-block with `x = 3 if True else 5` = help: Replace `if`-`else`-block with `x = 3 if True else 5`

View File

@ -264,5 +264,34 @@ SIM110.py:154:5: SIM110 [*] Use `return all(not check(x) for x in iterable)` ins
156 |- return False 156 |- return False
157 |- return True 157 |- return True
154 |+ return all(not check(x) for x in iterable) 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💣29012ß9💣29012ß9💣29012ß9💣29012ß9💣2")` instead of `for` loop
|
162 | def f():
163 | # SIM110
164 | for x in "012ß9💣29012ß9💣29012ß9💣29012ß9💣29012ß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💣29012ß9💣29012ß9💣29012ß9💣29012ß9💣2")`
Suggested fix
159 159 |
160 160 | def f():
161 161 | # SIM110
162 |- for x in "012ß9💣29012ß9💣29012ß9💣29012ß9💣29012ß9💣2":
163 |- if x.isdigit():
164 |- return True
165 |- return False
162 |+ return any(x.isdigit() for x in "012ß9💣29012ß9💣29012ß9💣29012ß9💣29012ß9💣2")
166 163 |
167 164 |
168 165 | def f():

View File

@ -316,5 +316,34 @@ SIM111.py:170:5: SIM110 [*] Use `return all(x <= y for x in iterable)` instead o
172 |- return False 172 |- return False
173 |- return True 173 |- return True
170 |+ return all(x <= y for x in iterable) 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💣29012ß9💣29012ß9💣29012ß9💣29012ß9")` instead of `for` loop
|
178 | def f():
179 | # SIM111
180 | for x in "012ß9💣29012ß9💣29012ß9💣29012ß9💣29012ß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💣29012ß9💣29012ß9💣29012ß9💣29012ß9")`
Suggested fix
175 175 |
176 176 | def f():
177 177 | # SIM111
178 |- for x in "012ß9💣29012ß9💣29012ß9💣29012ß9💣29012ß9":
179 |- if x.isdigit():
180 |- return False
181 |- return True
178 |+ return all(not x.isdigit() for x in "012ß9💣29012ß9💣29012ß9💣29012ß9💣29012ß9")
182 179 |
183 180 |
184 181 | def f():

View File

@ -212,5 +212,41 @@ SIM117.py:84:1: SIM117 [*] Use a single `with` statement with multiple contexts
91 |- ): 91 |- ):
92 |- print("hello") 92 |- print("hello")
89 |+ print("hello") 89 |+ print("hello")
93 90 |
94 91 | # SIM117 (auto-fixable)
95 92 | with A("01ß9💣28901ß9💣28901ß9💣289") 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💣28901ß9💣28901ß9💣289") as a:
97 | | with B("01ß9💣28901ß9💣28901ß9💣289") 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💣28901ß9💣28901ß9💣289") as a:
96 |- with B("01ß9💣28901ß9💣28901ß9💣289") as b:
97 |- print("hello")
95 |+with A("01ß9💣28901ß9💣28901ß9💣289") as a, B("01ß9💣28901ß9💣28901ß9💣289") as b:
96 |+ print("hello")
98 97 |
99 98 | # SIM117 (not auto-fixable too long)
100 99 | with A("01ß9💣28901ß9💣28901ß9💣2890") 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💣28901ß9💣28901ß9💣2890") as a:
102 | | with B("01ß9💣28901ß9💣28901ß9💣289") as b:
| |__________________________________________________^ SIM117
103 | print("hello")
|
= help: Combine `with` statements

View File

@ -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) 35 32 | # SIM401 (complex expression in var)
36 33 | if key in a_dict: 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💣26789ß9💣26789ß9💣26789ß9💣26789ß9💣26789")` instead of an `if` block
| |
36 | # SIM401 (complex expression in var) 36 | # SIM401 (complex expression in var)
37 | / if key in a_dict: 37 | / if key in a_dict:
38 | | vars[idx] = a_dict[key] 38 | | vars[idx] = a_dict[key]
39 | | else: 39 | | else:
40 | | vars[idx] = "default" 40 | | vars[idx] = "defaultß9💣26789ß9💣26789ß9💣26789ß9💣26789ß9💣26789"
| |_________________________^ SIM401 | |___________________________________________________________________________^ SIM401
41 | 41 |
42 | ### 42 | ###
| |
= help: Replace with `vars[idx] = a_dict.get(key, "default")` = help: Replace with `vars[idx] = a_dict.get(key, "defaultß9💣26789ß9💣26789ß9💣26789ß9💣26789ß9💣26789")`
Suggested fix Suggested fix
33 33 | var = "default" 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: 36 |-if key in a_dict:
37 |- vars[idx] = a_dict[key] 37 |- vars[idx] = a_dict[key]
38 |-else: 38 |-else:
39 |- vars[idx] = "default" 39 |- vars[idx] = "defaultß9💣26789ß9💣26789ß9💣26789ß9💣26789ß9💣26789"
36 |+vars[idx] = a_dict.get(key, "default") 36 |+vars[idx] = a_dict.get(key, "defaultß9💣26789ß9💣26789ß9💣26789ß9💣26789ß9💣26789")
40 37 | 40 37 |
41 38 | ### 41 38 | ###
42 39 | # Negative cases 42 39 | # Negative cases

View File

@ -1,7 +1,7 @@
use unicode_width::UnicodeWidthStr;
use ruff_python_ast::source_code::Stylist; use ruff_python_ast::source_code::Stylist;
use crate::line_width::{LineLength, LineWidth};
use super::types::{AliasData, CommentSet, ImportFromData, Importable}; use super::types::{AliasData, CommentSet, ImportFromData, Importable};
// Guess a capacity to use for string allocation. // Guess a capacity to use for string allocation.
@ -45,7 +45,8 @@ pub(crate) fn format_import_from(
import_from: &ImportFromData, import_from: &ImportFromData,
comments: &CommentSet, comments: &CommentSet,
aliases: &[(AliasData, CommentSet)], aliases: &[(AliasData, CommentSet)],
line_length: usize, line_length: LineLength,
indentation_width: LineWidth,
stylist: &Stylist, stylist: &Stylist,
force_wrap_aliases: bool, force_wrap_aliases: bool,
is_first: bool, is_first: bool,
@ -56,8 +57,14 @@ pub(crate) fn format_import_from(
.iter() .iter()
.all(|(alias, _)| alias.name == "*" && alias.asname.is_none()) .all(|(alias, _)| alias.name == "*" && alias.asname.is_none())
{ {
let (single_line, ..) = let (single_line, ..) = format_single_line(
format_single_line(import_from, comments, aliases, is_first, stylist); import_from,
comments,
aliases,
is_first,
stylist,
indentation_width,
);
return single_line; return single_line;
} }
@ -71,8 +78,14 @@ pub(crate) fn format_import_from(
|| aliases.len() == 1 || aliases.len() == 1
|| aliases.iter().all(|(alias, _)| alias.asname.is_none())) || aliases.iter().all(|(alias, _)| alias.asname.is_none()))
{ {
let (single_line, import_width) = let (single_line, import_width) = format_single_line(
format_single_line(import_from, comments, aliases, is_first, stylist); import_from,
comments,
aliases,
is_first,
stylist,
indentation_width,
);
if import_width <= line_length || aliases.iter().any(|(alias, _)| alias.name == "*") { if import_width <= line_length || aliases.iter().any(|(alias, _)| alias.name == "*") {
return single_line; return single_line;
} }
@ -90,9 +103,10 @@ fn format_single_line(
aliases: &[(AliasData, CommentSet)], aliases: &[(AliasData, CommentSet)],
is_first: bool, is_first: bool,
stylist: &Stylist, stylist: &Stylist,
) -> (String, usize) { indentation_width: LineWidth,
) -> (String, LineWidth) {
let mut output = String::with_capacity(CAPACITY); 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() { if !is_first && !comments.atop.is_empty() {
output.push_str(&stylist.line_ending()); output.push_str(&stylist.line_ending());
@ -106,28 +120,28 @@ fn format_single_line(
output.push_str("from "); output.push_str("from ");
output.push_str(&module_name); output.push_str(&module_name);
output.push_str(" import "); 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() { for (index, (AliasData { name, asname }, comments)) in aliases.iter().enumerate() {
if let Some(asname) = asname { if let Some(asname) = asname {
output.push_str(name); output.push_str(name);
output.push_str(" as "); output.push_str(" as ");
output.push_str(asname); 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 { } else {
output.push_str(name); output.push_str(name);
line_width += name.width(); line_width = line_width.add_str(name);
} }
if index < aliases.len() - 1 { if index < aliases.len() - 1 {
output.push_str(", "); output.push_str(", ");
line_width += 2; line_width = line_width.add_width(2);
} }
for comment in &comments.inline { for comment in &comments.inline {
output.push(' '); output.push(' ');
output.push(' '); output.push(' ');
output.push_str(comment); 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(' '); output.push(' ');
output.push_str(comment); 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()); output.push_str(&stylist.line_ending());

View File

@ -17,6 +17,7 @@ use sorting::cmp_either_import;
use types::EitherImport::{Import, ImportFrom}; use types::EitherImport::{Import, ImportFrom};
use types::{AliasData, EitherImport, TrailingComma}; use types::{AliasData, EitherImport, TrailingComma};
use crate::line_width::{LineLength, LineWidth};
use crate::rules::isort::categorize::KnownModules; use crate::rules::isort::categorize::KnownModules;
use crate::rules::isort::types::ImportBlock; use crate::rules::isort::types::ImportBlock;
use crate::settings::types::PythonVersion; use crate::settings::types::PythonVersion;
@ -65,7 +66,8 @@ pub(crate) fn format_imports(
block: &Block, block: &Block,
comments: Vec<Comment>, comments: Vec<Comment>,
locator: &Locator, locator: &Locator,
line_length: usize, line_length: LineLength,
indentation_width: LineWidth,
stylist: &Stylist, stylist: &Stylist,
src: &[PathBuf], src: &[PathBuf],
package: Option<&Path>, package: Option<&Path>,
@ -107,6 +109,7 @@ pub(crate) fn format_imports(
let block_output = format_import_block( let block_output = format_import_block(
block, block,
line_length, line_length,
indentation_width,
stylist, stylist,
src, src,
package, package,
@ -162,7 +165,8 @@ pub(crate) fn format_imports(
#[allow(clippy::too_many_arguments, clippy::fn_params_excessive_bools)] #[allow(clippy::too_many_arguments, clippy::fn_params_excessive_bools)]
fn format_import_block( fn format_import_block(
block: ImportBlock, block: ImportBlock,
line_length: usize, line_length: LineLength,
indentation_width: LineWidth,
stylist: &Stylist, stylist: &Stylist,
src: &[PathBuf], src: &[PathBuf],
package: Option<&Path>, package: Option<&Path>,
@ -264,6 +268,7 @@ fn format_import_block(
&comments, &comments,
&aliases, &aliases,
line_length, line_length,
indentation_width,
stylist, stylist,
force_wrap_aliases, force_wrap_aliases,
is_first_statement, is_first_statement,

View File

@ -13,6 +13,7 @@ use ruff_python_ast::helpers::{
use ruff_python_ast::source_code::{Indexer, Locator, Stylist}; use ruff_python_ast::source_code::{Indexer, Locator, Stylist};
use ruff_python_ast::whitespace::leading_space; use ruff_python_ast::whitespace::leading_space;
use crate::line_width::LineWidth;
use crate::registry::AsRule; use crate::registry::AsRule;
use crate::settings::Settings; use crate::settings::Settings;
@ -116,7 +117,8 @@ pub(crate) fn organize_imports(
block, block,
comments, comments,
locator, locator,
settings.line_length - indentation.len(), settings.line_length,
LineWidth::new(settings.tab_size).add_str(indentation),
stylist, stylist,
&settings.src, &settings.src,
package, package,

View File

@ -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. 2 | | # Don't take this comment into account when determining whether the next import can fit on one line.
3 | | from b import c 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. 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💣29012ß9💣29012ß9💣29012ß9💣29012ß9💣29012ß9💣29012ß9💣2
7 | | # The next import doesn't fit on one line.
8 | | from h import i # 012ß9💣29012ß9💣29012ß9💣29012ß9💣29012ß9💣29012ß9💣29012ß9💣29
| |
= help: Organize imports = help: Organize imports
Suggested fix Suggested fix
1 1 | import a 1 1 | import a
2 |+ 2 |+
2 3 | # Don't take this comment into account when determining whether the next import can fit on one line. 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 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. 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 ( 5 |+from d import (
6 |+ e, # Do take this comment into account when determining whether the next import can fit on one line. 6 |+ e, # Do take this comment into account when determining whether the next import can fit on one line.
7 |+) 7 |+)
8 |+
5 9 | # The next import fits on one line.
6 10 | from f import g # 012ß9💣29012ß9💣29012ß9💣29012ß9💣29012ß9💣29012ß9💣29012ß9💣2
11 |+
7 12 | # The next import doesn't fit on one line.
8 |-from h import i # 012ß9💣29012ß9💣29012ß9💣29012ß9💣29012ß9💣29012ß9💣29012ß9💣29
13 |+from h import (
14 |+ i, # 012ß9💣29012ß9💣29012ß9💣29012ß9💣29012ß9💣29012ß9💣29012ß9💣29
15 |+)

View File

@ -1,10 +1,12 @@
use ruff_text_size::{TextLen, TextRange}; use ruff_text_size::{TextLen, TextRange};
use rustpython_parser::ast::{self, Cmpop, Expr}; 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::newlines::Line;
use ruff_python_ast::source_code::Generator; use ruff_python_ast::source_code::Generator;
use crate::line_width::{LineLength, LineWidth, TabSize};
pub(crate) fn is_ambiguous_name(name: &str) -> bool { pub(crate) fn is_ambiguous_name(name: &str) -> bool {
name == "l" || name == "I" || name == "O" name == "l" || name == "I" || name == "O"
} }
@ -26,19 +28,19 @@ pub(crate) fn compare(
pub(super) fn is_overlong( pub(super) fn is_overlong(
line: &Line, line: &Line,
limit: usize, limit: LineLength,
ignore_overlong_task_comments: bool, ignore_overlong_task_comments: bool,
task_tags: &[String], task_tags: &[String],
tab_size: TabSize,
) -> Option<Overlong> { ) -> Option<Overlong> {
let mut start_offset = line.start(); let mut start_offset = line.start();
let mut width = 0; let mut width = LineWidth::new(tab_size);
for c in line.chars() { for c in line.chars() {
if width < limit { if width < limit {
start_offset += c.text_len(); start_offset += c.text_len();
} }
width = width.add_char(c);
width += c.width().unwrap_or(0);
} }
if width <= limit { if width <= limit {
@ -64,14 +66,14 @@ pub(super) fn is_overlong(
// begins before the limit. // begins before the limit.
let last_chunk = chunks.last().unwrap_or(second_chunk); let last_chunk = chunks.last().unwrap_or(second_chunk);
if last_chunk.contains("://") { if last_chunk.contains("://") {
if width - last_chunk.width() <= limit { if width.get() - last_chunk.width() <= limit.get() {
return None; return None;
} }
} }
Some(Overlong { Some(Overlong {
range: TextRange::new(start_offset, line.end()), range: TextRange::new(start_offset, line.end()),
width, width: width.get(),
}) })
} }

View File

@ -11,6 +11,7 @@ mod tests {
use anyhow::Result; use anyhow::Result;
use test_case::test_case; use test_case::test_case;
use crate::line_width::LineLength;
use crate::registry::Rule; use crate::registry::Rule;
use crate::test::test_path; use crate::test::test_path;
use crate::{assert_messages, settings}; use crate::{assert_messages, settings};
@ -166,7 +167,7 @@ mod tests {
Path::new("pycodestyle/W505.py"), Path::new("pycodestyle/W505.py"),
&settings::Settings { &settings::Settings {
pycodestyle: Settings { pycodestyle: Settings {
max_doc_length: Some(50), max_doc_length: Some(LineLength::from(50)),
..Settings::default() ..Settings::default()
}, },
..settings::Settings::for_rule(Rule::DocLineTooLong) ..settings::Settings::for_rule(Rule::DocLineTooLong)
@ -175,4 +176,37 @@ mod tests {
assert_messages!(diagnostics); assert_messages!(diagnostics);
Ok(()) 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(())
}
} }

View File

@ -48,6 +48,12 @@ pub(crate) fn doc_line_too_long(line: &Line, settings: &Settings) -> Option<Diag
limit, limit,
settings.pycodestyle.ignore_overlong_task_comments, settings.pycodestyle.ignore_overlong_task_comments,
&settings.task_tags, &settings.task_tags,
settings.tab_size,
) )
.map(|overlong| Diagnostic::new(DocLineTooLong(overlong.width(), limit), overlong.range())) .map(|overlong| {
Diagnostic::new(
DocLineTooLong(overlong.width(), limit.get()),
overlong.range(),
)
})
} }

View File

@ -43,6 +43,7 @@ pub(crate) fn line_too_long(line: &Line, settings: &Settings) -> Option<Diagnost
limit, limit,
settings.pycodestyle.ignore_overlong_task_comments, settings.pycodestyle.ignore_overlong_task_comments,
&settings.task_tags, &settings.task_tags,
settings.tab_size,
) )
.map(|overlong| Diagnostic::new(LineTooLong(overlong.width(), limit), overlong.range())) .map(|overlong| Diagnostic::new(LineTooLong(overlong.width(), limit.get()), overlong.range()))
} }

View File

@ -4,6 +4,8 @@ use serde::{Deserialize, Serialize};
use ruff_macros::{CacheKey, CombineOptions, ConfigurationOptions}; use ruff_macros::{CacheKey, CombineOptions, ConfigurationOptions};
use crate::line_width::LineLength;
#[derive( #[derive(
Debug, PartialEq, Eq, Serialize, Deserialize, Default, ConfigurationOptions, CombineOptions, Debug, PartialEq, Eq, Serialize, Deserialize, Default, ConfigurationOptions, CombineOptions,
)] )]
@ -19,7 +21,7 @@ pub struct Options {
)] )]
/// The maximum line length to allow for line-length violations within /// The maximum line length to allow for line-length violations within
/// documentation (`W505`), including standalone comments. /// documentation (`W505`), including standalone comments.
pub max_doc_length: Option<usize>, pub max_doc_length: Option<LineLength>,
#[option( #[option(
default = "false", default = "false",
value_type = "bool", value_type = "bool",
@ -35,7 +37,7 @@ pub struct Options {
#[derive(Debug, Default, CacheKey)] #[derive(Debug, Default, CacheKey)]
pub struct Settings { pub struct Settings {
pub max_doc_length: Option<usize>, pub max_doc_length: Option<LineLength>,
pub ignore_overlong_task_comments: bool, pub ignore_overlong_task_comments: bool,
} }

View File

@ -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💣2ing 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💣2ing 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💣2ing.")
|
W505_utf_8.py:18:50: W505 Doc line too long (61 > 50 characters)
|
18 | "This is also considered a ß9💣2ing, 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💣2ing.
25 |
26 | It's over theß9💣2 on this line, which isn't the first line in the ß9💣2ing.
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ W505
27 | """
|
W505_utf_8.py:31:50: W505 Doc line too long (85 > 50 characters)
|
31 | """Here's a multi-line ß9💣2ing.
32 |
33 | It's over theß9💣2 on this line, which isn't the first line in the ß9💣2ing."""
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ W505
|

View File

@ -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💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A6"""
| ^ E501
2 | a = """ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A6"""
|
E501_2.py:2:81: E501 Line too long (89 > 88 characters)
|
2 | a = """ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A6"""
3 | a = """ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A6"""
| ^ E501
4 |
5 | b = """ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A6"""
|
E501_2.py:4:81: E501 Line too long (89 > 88 characters)
|
4 | a = """ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A6"""
5 |
6 | b = """ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A6"""
| ^ E501
7 | b = """ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A6"""
|
E501_2.py:5:81: E501 Line too long (89 > 88 characters)
|
5 | b = """ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A6"""
6 | b = """ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A6"""
| ^ E501
7 |
8 | c = """24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A6"""
|
E501_2.py:7:82: E501 Line too long (89 > 88 characters)
|
7 | b = """ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A6"""
8 |
9 | c = """24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A6"""
| ^ E501
10 | c = """24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A6"""
|
E501_2.py:10:82: E501 Line too long (89 > 88 characters)
|
10 | c = """24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A6"""
11 |
12 | d = """💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A6"""
| ^ E501
13 | d = """💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A6"""
|

View File

@ -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💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A6"""
| ^ E501
2 | a = """ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A6"""
|
E501_2.py:2:81: E501 Line too long (89 > 88 characters)
|
2 | a = """ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A6"""
3 | a = """ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A6"""
| ^ E501
4 |
5 | b = """ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A6"""
|
E501_2.py:4:81: E501 Line too long (89 > 88 characters)
|
4 | a = """ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A6"""
5 |
6 | b = """ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A6"""
| ^ E501
7 | b = """ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A6"""
|
E501_2.py:5:81: E501 Line too long (89 > 88 characters)
|
5 | b = """ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A6"""
6 | b = """ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A6"""
| ^ E501
7 |
8 | c = """24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A6"""
|
E501_2.py:7:82: E501 Line too long (89 > 88 characters)
|
7 | b = """ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A6"""
8 |
9 | c = """24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A6"""
| ^ E501
10 | c = """24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A6"""
|
E501_2.py:10:82: E501 Line too long (89 > 88 characters)
|
10 | c = """24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A6"""
11 |
12 | d = """💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A6"""
| ^ E501
13 | d = """💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A6"""
|

View File

@ -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💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A6"""
| ^ E501
2 | a = """ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A6"""
|
E501_2.py:2:77: E501 Line too long (93 > 88 characters)
|
2 | a = """ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A6"""
3 | a = """ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A6"""
| ^^^^^ E501
4 |
5 | b = """ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A6"""
|
E501_2.py:4:81: E501 Line too long (89 > 88 characters)
|
4 | a = """ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A6"""
5 |
6 | b = """ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A6"""
| ^ E501
7 | b = """ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A6"""
|
E501_2.py:5:77: E501 Line too long (93 > 88 characters)
|
5 | b = """ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A6"""
6 | b = """ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A6"""
| ^^^^^ E501
7 |
8 | c = """24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A6"""
|
E501_2.py:7:82: E501 Line too long (89 > 88 characters)
|
7 | b = """ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A6"""
8 |
9 | c = """24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A6"""
| ^ E501
10 | c = """24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A6"""
|
E501_2.py:8:78: E501 Line too long (89 > 88 characters)
|
8 | c = """24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A6"""
9 | c = """24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A6"""
| ^ E501
10 |
11 | d = """💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A6"""
|
E501_2.py:10:82: E501 Line too long (89 > 88 characters)
|
10 | c = """24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A6"""
11 |
12 | d = """💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A6"""
| ^ E501
13 | d = """💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A6"""
|

View File

@ -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💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A6"""
| ^ E501
2 | a = """ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A6"""
|
E501_2.py:2:70: E501 Line too long (101 > 88 characters)
|
2 | a = """ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A6"""
3 | a = """ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A6"""
| ^^^^^^^^^^^^^ E501
4 |
5 | b = """ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A6"""
|
E501_2.py:4:81: E501 Line too long (89 > 88 characters)
|
4 | a = """ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A6"""
5 |
6 | b = """ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A6"""
| ^ E501
7 | b = """ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A6"""
|
E501_2.py:5:70: E501 Line too long (101 > 88 characters)
|
5 | b = """ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A6"""
6 | b = """ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A6"""
| ^^^^^^^^^^^^^ E501
7 |
8 | c = """24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A6"""
|
E501_2.py:7:82: E501 Line too long (89 > 88 characters)
|
7 | b = """ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A6"""
8 |
9 | c = """24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A6"""
| ^ E501
10 | c = """24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A6"""
|
E501_2.py:8:71: E501 Line too long (97 > 88 characters)
|
8 | c = """24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A6"""
9 | c = """24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A6"""
| ^^^^^^^^ E501
10 |
11 | d = """💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A6"""
|
E501_2.py:10:82: E501 Line too long (89 > 88 characters)
|
10 | c = """24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A6"""
11 |
12 | d = """💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A6"""
| ^ E501
13 | d = """💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A6"""
|
E501_2.py:11:70: E501 Line too long (89 > 88 characters)
|
11 | d = """💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A6"""
12 | d = """💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A6"""
| ^ E501
|

View File

@ -13,6 +13,7 @@ use shellexpand;
use shellexpand::LookupError; use shellexpand::LookupError;
use crate::fs; use crate::fs;
use crate::line_width::{LineLength, TabSize};
use crate::rule_selector::RuleSelector; use crate::rule_selector::RuleSelector;
use crate::rules::{ use crate::rules::{
flake8_annotations, flake8_bandit, flake8_bugbear, flake8_builtins, flake8_comprehensions, flake8_annotations, flake8_bandit, flake8_bugbear, flake8_builtins, flake8_comprehensions,
@ -56,7 +57,8 @@ pub struct Configuration {
pub format: Option<SerializationFormat>, pub format: Option<SerializationFormat>,
pub ignore_init_module_imports: Option<bool>, pub ignore_init_module_imports: Option<bool>,
pub include: Option<Vec<FilePattern>>, pub include: Option<Vec<FilePattern>>,
pub line_length: Option<usize>, pub line_length: Option<LineLength>,
pub tab_size: Option<TabSize>,
pub namespace_packages: Option<Vec<PathBuf>>, pub namespace_packages: Option<Vec<PathBuf>>,
pub required_version: Option<Version>, pub required_version: Option<Version>,
pub respect_gitignore: Option<bool>, pub respect_gitignore: Option<bool>,
@ -194,6 +196,7 @@ impl Configuration {
.collect() .collect()
}), }),
line_length: options.line_length, line_length: options.line_length,
tab_size: options.tab_size,
namespace_packages: options namespace_packages: options
.namespace_packages .namespace_packages
.map(|namespace_package| resolve_src(&namespace_package, project_root)) .map(|namespace_package| resolve_src(&namespace_package, project_root))
@ -281,6 +284,7 @@ impl Configuration {
.ignore_init_module_imports .ignore_init_module_imports
.or(config.ignore_init_module_imports), .or(config.ignore_init_module_imports),
line_length: self.line_length.or(config.line_length), 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), namespace_packages: self.namespace_packages.or(config.namespace_packages),
per_file_ignores: self.per_file_ignores.or(config.per_file_ignores), per_file_ignores: self.per_file_ignores.or(config.per_file_ignores),
required_version: self.required_version.or(config.required_version), required_version: self.required_version.or(config.required_version),

View File

@ -6,6 +6,7 @@ use regex::Regex;
use rustc_hash::FxHashSet; use rustc_hash::FxHashSet;
use crate::codes::{self, RuleCodePrefix}; use crate::codes::{self, RuleCodePrefix};
use crate::line_width::{LineLength, TabSize};
use crate::registry::Linter; use crate::registry::Linter;
use crate::rule_selector::{prefix_to_selector, RuleSelector}; use crate::rule_selector::{prefix_to_selector, RuleSelector};
use crate::rules::{ use crate::rules::{
@ -26,8 +27,6 @@ pub const PREFIXES: &[RuleSelector] = &[
pub const TARGET_VERSION: PythonVersion = PythonVersion::Py310; pub const TARGET_VERSION: PythonVersion = PythonVersion::Py310;
pub const LINE_LENGTH: usize = 88;
pub const TASK_TAGS: &[&str] = &["TODO", "FIXME", "XXX"]; pub const TASK_TAGS: &[&str] = &["TODO", "FIXME", "XXX"];
pub static DUMMY_VARIABLE_RGX: Lazy<Regex> = pub static DUMMY_VARIABLE_RGX: Lazy<Regex> =
@ -76,7 +75,8 @@ impl Default for Settings {
force_exclude: false, force_exclude: false,
ignore_init_module_imports: false, ignore_init_module_imports: false,
include: FilePatternSet::try_from_vec(INCLUDE.clone()).unwrap(), include: FilePatternSet::try_from_vec(INCLUDE.clone()).unwrap(),
line_length: LINE_LENGTH, line_length: LineLength::default(),
tab_size: TabSize::default(),
namespace_packages: vec![], namespace_packages: vec![],
per_file_ignores: vec![], per_file_ignores: vec![],
respect_gitignore: true, respect_gitignore: true,

View File

@ -26,6 +26,7 @@ use crate::settings::types::{FilePatternSet, PerFileIgnore, PythonVersion, Seria
use crate::warn_user_once_by_id; use crate::warn_user_once_by_id;
use self::rule_table::RuleTable; use self::rule_table::RuleTable;
use super::line_width::{LineLength, TabSize};
pub mod configuration; pub mod configuration;
pub mod defaults; pub mod defaults;
@ -98,7 +99,8 @@ pub struct Settings {
pub dummy_variable_rgx: Regex, pub dummy_variable_rgx: Regex,
pub external: FxHashSet<String>, pub external: FxHashSet<String>,
pub ignore_init_module_imports: bool, pub ignore_init_module_imports: bool,
pub line_length: usize, pub line_length: LineLength,
pub tab_size: TabSize,
pub namespace_packages: Vec<PathBuf>, pub namespace_packages: Vec<PathBuf>,
pub src: Vec<PathBuf>, pub src: Vec<PathBuf>,
pub task_tags: Vec<String>, pub task_tags: Vec<String>,
@ -160,7 +162,8 @@ impl Settings {
config.include.unwrap_or_else(|| defaults::INCLUDE.clone()), config.include.unwrap_or_else(|| defaults::INCLUDE.clone()),
)?, )?,
ignore_init_module_imports: config.ignore_init_module_imports.unwrap_or_default(), 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(), namespace_packages: config.namespace_packages.unwrap_or_default(),
per_file_ignores: resolve_per_file_ignores( per_file_ignores: resolve_per_file_ignores(
config config

View File

@ -5,6 +5,7 @@ use serde::{Deserialize, Serialize};
use ruff_macros::ConfigurationOptions; use ruff_macros::ConfigurationOptions;
use crate::line_width::{LineLength, TabSize};
use crate::rule_selector::RuleSelector; use crate::rule_selector::RuleSelector;
use crate::rules::{ use crate::rules::{
flake8_annotations, flake8_bandit, flake8_bugbear, flake8_builtins, flake8_comprehensions, flake8_annotations, flake8_bandit, flake8_bugbear, flake8_builtins, flake8_comprehensions,
@ -303,13 +304,22 @@ pub struct Options {
default = "88", default = "88",
value_type = "int", value_type = "int",
example = r#" example = r#"
# Allow lines to be as long as 120 characters. # Allow lines to be as long as 120 characters.
line-length = 120 line-length = 120
"# "#
)] )]
/// The line length to use when enforcing long-lines violations (like /// The line length to use when enforcing long-lines violations (like
/// `E501`). /// `E501`).
pub line_length: Option<usize>, pub line_length: Option<LineLength>,
#[option(
default = "4",
value_type = "int",
example = r#"
tab_size = 8
"#
)]
/// The tabulation size to calculate line length.
pub tab_size: Option<TabSize>,
#[option( #[option(
default = "None", default = "None",
value_type = "str", value_type = "str",

View File

@ -151,6 +151,7 @@ mod tests {
use rustc_hash::FxHashMap; use rustc_hash::FxHashMap;
use crate::codes::{self, RuleCodePrefix}; use crate::codes::{self, RuleCodePrefix};
use crate::line_width::LineLength;
use crate::rules::flake8_quotes::settings::Quote; use crate::rules::flake8_quotes::settings::Quote;
use crate::rules::flake8_tidy_imports::banned_api::ApiBan; use crate::rules::flake8_tidy_imports::banned_api::ApiBan;
use crate::rules::flake8_tidy_imports::relative_imports::Strictness; use crate::rules::flake8_tidy_imports::relative_imports::Strictness;
@ -200,7 +201,7 @@ line-length = 79
pyproject.tool, pyproject.tool,
Some(Tools { Some(Tools {
ruff: Some(Options { ruff: Some(Options {
line_length: Some(79), line_length: Some(LineLength::from(79)),
..Options::default() ..Options::default()
}) })
}) })
@ -301,7 +302,7 @@ other-attribute = 1
config, config,
Options { Options {
allowed_confusables: Some(vec!['', 'ρ', '']), allowed_confusables: Some(vec!['', 'ρ', '']),
line_length: Some(88), line_length: Some(LineLength::from(88)),
extend_exclude: Some(vec![ extend_exclude: Some(vec![
"excluded_file.py".to_string(), "excluded_file.py".to_string(),
"migrations".to_string(), "migrations".to_string(),

View File

@ -5,6 +5,7 @@ use clap::{command, Parser};
use regex::Regex; use regex::Regex;
use rustc_hash::FxHashMap; use rustc_hash::FxHashMap;
use ruff::line_width::LineLength;
use ruff::logging::LogLevel; use ruff::logging::LogLevel;
use ruff::registry::Rule; use ruff::registry::Rule;
use ruff::resolver::ConfigProcessor; use ruff::resolver::ConfigProcessor;
@ -545,7 +546,7 @@ impl ConfigProcessor for &Overrides {
config.force_exclude = Some(*force_exclude); config.force_exclude = Some(*force_exclude);
} }
if let Some(line_length) = &self.line_length { 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 { if let Some(per_file_ignores) = &self.per_file_ignores {
config.per_file_ignores = Some(collect_per_file_ignores(per_file_ignores.clone())); config.per_file_ignores = Some(collect_per_file_ignores(per_file_ignores.clone()));

View File

@ -5,6 +5,7 @@ use serde::{Deserialize, Serialize};
use wasm_bindgen::prelude::*; use wasm_bindgen::prelude::*;
use ruff::directives; use ruff::directives;
use ruff::line_width::{LineLength, TabSize};
use ruff::linter::{check_path, LinterResult}; use ruff::linter::{check_path, LinterResult};
use ruff::registry::AsRule; use ruff::registry::AsRule;
use ruff::rules::{ use ruff::rules::{
@ -102,7 +103,8 @@ pub fn defaultSettings() -> Result<JsValue, JsValue> {
extend_unfixable: Some(Vec::default()), extend_unfixable: Some(Vec::default()),
external: Some(Vec::default()), external: Some(Vec::default()),
ignore: 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()), select: Some(defaults::PREFIXES.to_vec()),
target_version: Some(defaults::TARGET_VERSION), target_version: Some(defaults::TARGET_VERSION),
// Ignore a bunch of options that don't make sense in a single-file editor. // Ignore a bunch of options that don't make sense in a single-file editor.

51
ruff.schema.json generated
View File

@ -366,12 +366,14 @@
}, },
"line-length": { "line-length": {
"description": "The line length to use when enforcing long-lines violations (like `E501`).", "description": "The line length to use when enforcing long-lines violations (like `E501`).",
"type": [ "anyOf": [
"integer", {
"null" "$ref": "#/definitions/LineLength"
], },
"format": "uint", {
"minimum": 0.0 "type": "null"
}
]
}, },
"mccabe": { "mccabe": {
"description": "Options for the `mccabe` plugin.", "description": "Options for the `mccabe` plugin.",
@ -503,6 +505,17 @@
"type": "string" "type": "string"
} }
}, },
"tab-size": {
"description": "The tabulation size to calculate line length.",
"anyOf": [
{
"$ref": "#/definitions/TabSize"
},
{
"type": "null"
}
]
},
"target-version": { "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.", "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": [ "anyOf": [
@ -1276,6 +1289,12 @@
}, },
"additionalProperties": false "additionalProperties": false
}, },
"LineLength": {
"description": "The length of a line of text that is considered too long.",
"type": "integer",
"format": "uint",
"minimum": 0.0
},
"McCabeOptions": { "McCabeOptions": {
"type": "object", "type": "object",
"properties": { "properties": {
@ -1361,12 +1380,14 @@
}, },
"max-doc-length": { "max-doc-length": {
"description": "The maximum line length to allow for line-length violations within documentation (`W505`), including standalone comments.", "description": "The maximum line length to allow for line-length violations within documentation (`W505`), including standalone comments.",
"type": [ "anyOf": [
"integer", {
"null" "$ref": "#/definitions/LineLength"
], },
"format": "uint", {
"minimum": 0.0 "type": "null"
}
]
} }
}, },
"additionalProperties": false "additionalProperties": false
@ -2489,6 +2510,12 @@
} }
] ]
}, },
"TabSize": {
"description": "The size of a tab.",
"type": "integer",
"format": "uint8",
"minimum": 0.0
},
"Version": { "Version": {
"type": "string" "type": "string"
} }