mirror of https://github.com/astral-sh/ruff
Implement pycodestyle's logical line detection (#1130)
Along with the logical line detection, this adds 14 of the missing `pycodestyle` rules. For now, this is all gated behind a `logical_lines` feature that's off-by-default, which will let us implement all rules prior to shipping, since we want to couple the release of these rules with new defaults and instructions.
This commit is contained in:
parent
f03c8fff14
commit
e3dfa2e04e
|
|
@ -69,13 +69,13 @@ jobs:
|
|||
- name: "Run tests (Ubuntu)"
|
||||
if: ${{ matrix.os == 'ubuntu-latest' }}
|
||||
run: |
|
||||
cargo insta test --all --delete-unreferenced-snapshots
|
||||
cargo insta test --all --all-features --delete-unreferenced-snapshots
|
||||
git diff --exit-code
|
||||
- name: "Run tests (Windows)"
|
||||
if: ${{ matrix.os == 'windows-latest' }}
|
||||
shell: bash
|
||||
run: |
|
||||
cargo insta test --all
|
||||
cargo insta test --all --all-features
|
||||
git diff --exit-code
|
||||
- run: cargo test --package ruff_cli --test black_compatibility_test -- --ignored
|
||||
# Check for broken links in the documentation.
|
||||
|
|
|
|||
|
|
@ -126,6 +126,12 @@ dependencies = [
|
|||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bisection"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "021e079a1bab0ecce6cf4b4b74c0c37afa4a697136eb3b127875c84a8f04a8c3"
|
||||
|
||||
[[package]]
|
||||
name = "bit-set"
|
||||
version = "0.5.3"
|
||||
|
|
@ -1887,6 +1893,7 @@ name = "ruff"
|
|||
version = "0.0.241"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bisection",
|
||||
"bitflags",
|
||||
"cfg-if",
|
||||
"chrono",
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ doctest = false
|
|||
|
||||
[dependencies]
|
||||
anyhow = { version = "1.0.66" }
|
||||
bisection = { version = "0.1.0" }
|
||||
bitflags = { version = "1.3.2" }
|
||||
cfg-if = { version = "1.0.0" }
|
||||
chrono = { version = "0.4.21", default-features = false, features = ["clock"] }
|
||||
|
|
@ -63,6 +64,10 @@ thiserror = { version = "1.0" }
|
|||
titlecase = { version = "2.2.1" }
|
||||
toml = { version = "0.6.0" }
|
||||
|
||||
[features]
|
||||
default = []
|
||||
logical_lines = []
|
||||
|
||||
# https://docs.rs/getrandom/0.2.7/getrandom/#webassembly-support
|
||||
# For (future) wasm-pack support
|
||||
[target.'cfg(all(target_family = "wasm", target_os = "unknown"))'.dependencies]
|
||||
|
|
|
|||
|
|
@ -0,0 +1,42 @@
|
|||
#: E111
|
||||
if x > 2:
|
||||
print(x)
|
||||
#: E111 E117
|
||||
if True:
|
||||
print()
|
||||
#: E112
|
||||
if False:
|
||||
print()
|
||||
#: E113
|
||||
print()
|
||||
print()
|
||||
#: E114 E116
|
||||
mimetype = 'application/x-directory'
|
||||
# 'httpd/unix-directory'
|
||||
create_date = False
|
||||
#: E116 E116 E116
|
||||
def start(self):
|
||||
if True:
|
||||
self.master.start()
|
||||
# try:
|
||||
# self.master.start()
|
||||
# except MasterExit:
|
||||
# self.shutdown()
|
||||
# finally:
|
||||
# sys.exit()
|
||||
#: E115 E115 E115 E115 E115 E115
|
||||
def start(self):
|
||||
if True:
|
||||
# try:
|
||||
# self.master.start()
|
||||
# except MasterExit:
|
||||
# self.shutdown()
|
||||
# finally:
|
||||
# sys.exit()
|
||||
self.master.start()
|
||||
#: E117
|
||||
def start():
|
||||
print()
|
||||
#: E117 W191
|
||||
def start():
|
||||
print()
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
#: E201:1:6
|
||||
spam( ham[1], {eggs: 2})
|
||||
#: E201:1:10
|
||||
spam(ham[ 1], {eggs: 2})
|
||||
#: E201:1:15
|
||||
spam(ham[1], { eggs: 2})
|
||||
#: E201:1:6
|
||||
spam( ham[1], {eggs: 2})
|
||||
#: E201:1:10
|
||||
spam(ham[ 1], {eggs: 2})
|
||||
#: E201:1:15
|
||||
spam(ham[1], { eggs: 2})
|
||||
#: Okay
|
||||
spam(ham[1], {eggs: 2})
|
||||
#:
|
||||
|
||||
|
||||
#: E202:1:23
|
||||
spam(ham[1], {eggs: 2} )
|
||||
#: E202:1:22
|
||||
spam(ham[1], {eggs: 2 })
|
||||
#: E202:1:11
|
||||
spam(ham[1 ], {eggs: 2})
|
||||
#: E202:1:23
|
||||
spam(ham[1], {eggs: 2} )
|
||||
#: E202:1:22
|
||||
spam(ham[1], {eggs: 2 })
|
||||
#: E202:1:11
|
||||
spam(ham[1 ], {eggs: 2})
|
||||
#: Okay
|
||||
spam(ham[1], {eggs: 2})
|
||||
|
||||
result = func(
|
||||
arg1='some value',
|
||||
arg2='another value',
|
||||
)
|
||||
|
||||
result = func(
|
||||
arg1='some value',
|
||||
arg2='another value'
|
||||
)
|
||||
|
||||
result = [
|
||||
item for item in items
|
||||
if item > 5
|
||||
]
|
||||
#:
|
||||
|
||||
|
||||
#: E203:1:10
|
||||
if x == 4 :
|
||||
print x, y
|
||||
x, y = y, x
|
||||
#: E203:1:10
|
||||
if x == 4 :
|
||||
print x, y
|
||||
x, y = y, x
|
||||
#: E203:2:15 E702:2:16
|
||||
if x == 4:
|
||||
print x, y ; x, y = y, x
|
||||
#: E203:2:15 E702:2:16
|
||||
if x == 4:
|
||||
print x, y ; x, y = y, x
|
||||
#: E203:3:13
|
||||
if x == 4:
|
||||
print x, y
|
||||
x, y = y , x
|
||||
#: E203:3:13
|
||||
if x == 4:
|
||||
print x, y
|
||||
x, y = y , x
|
||||
#: Okay
|
||||
if x == 4:
|
||||
print x, y
|
||||
x, y = y, x
|
||||
a[b1, :] == a[b1, ...]
|
||||
b = a[:, b1]
|
||||
#:
|
||||
|
|
@ -0,0 +1,171 @@
|
|||
#: E221
|
||||
a = 12 + 3
|
||||
b = 4 + 5
|
||||
#: E221 E221
|
||||
x = 1
|
||||
y = 2
|
||||
long_variable = 3
|
||||
#: E221 E221
|
||||
x[0] = 1
|
||||
x[1] = 2
|
||||
long_variable = 3
|
||||
#: E221 E221
|
||||
x = f(x) + 1
|
||||
y = long_variable + 2
|
||||
z = x[0] + 3
|
||||
#: E221:3:14
|
||||
text = """
|
||||
bar
|
||||
foo %s""" % rofl
|
||||
#: Okay
|
||||
x = 1
|
||||
y = 2
|
||||
long_variable = 3
|
||||
#:
|
||||
|
||||
|
||||
#: E222
|
||||
a = a + 1
|
||||
b = b + 10
|
||||
#: E222 E222
|
||||
x = -1
|
||||
y = -2
|
||||
long_variable = 3
|
||||
#: E222 E222
|
||||
x[0] = 1
|
||||
x[1] = 2
|
||||
long_variable = 3
|
||||
#:
|
||||
|
||||
|
||||
#: E223
|
||||
foobart = 4
|
||||
a = 3 # aligned with tab
|
||||
#:
|
||||
|
||||
|
||||
#: E224
|
||||
a += 1
|
||||
b += 1000
|
||||
#:
|
||||
|
||||
|
||||
#: E225
|
||||
submitted +=1
|
||||
#: E225
|
||||
submitted+= 1
|
||||
#: E225
|
||||
c =-1
|
||||
#: E225
|
||||
x = x /2 - 1
|
||||
#: E225
|
||||
c = alpha -4
|
||||
#: E225
|
||||
c = alpha- 4
|
||||
#: E225
|
||||
z = x **y
|
||||
#: E225
|
||||
z = (x + 1) **y
|
||||
#: E225
|
||||
z = (x + 1)** y
|
||||
#: E225
|
||||
_1kB = _1MB >>10
|
||||
#: E225
|
||||
_1kB = _1MB>> 10
|
||||
#: E225 E225
|
||||
i=i+ 1
|
||||
#: E225 E225
|
||||
i=i +1
|
||||
#: E225
|
||||
i = 1and 1
|
||||
#: E225
|
||||
i = 1or 0
|
||||
#: E225
|
||||
1is 1
|
||||
#: E225
|
||||
1in []
|
||||
#: E225
|
||||
i = 1 @2
|
||||
#: E225
|
||||
i = 1@ 2
|
||||
#: E225 E226
|
||||
i=i+1
|
||||
#: E225 E226
|
||||
i =i+1
|
||||
#: E225 E226
|
||||
i= i+1
|
||||
#: E225 E226
|
||||
c = (a +b)*(a - b)
|
||||
#: E225 E226
|
||||
c = (a+ b)*(a - b)
|
||||
#:
|
||||
|
||||
#: E226
|
||||
z = 2//30
|
||||
#: E226 E226
|
||||
c = (a+b) * (a-b)
|
||||
#: E226
|
||||
norman = True+False
|
||||
#: E226
|
||||
x = x*2 - 1
|
||||
#: E226
|
||||
x = x/2 - 1
|
||||
#: E226 E226
|
||||
hypot2 = x*x + y*y
|
||||
#: E226
|
||||
c = (a + b)*(a - b)
|
||||
#: E226
|
||||
def halves(n):
|
||||
return (i//2 for i in range(n))
|
||||
#: E227
|
||||
_1kB = _1MB>>10
|
||||
#: E227
|
||||
_1MB = _1kB<<10
|
||||
#: E227
|
||||
a = b|c
|
||||
#: E227
|
||||
b = c&a
|
||||
#: E227
|
||||
c = b^a
|
||||
#: E228
|
||||
a = b%c
|
||||
#: E228
|
||||
msg = fmt%(errno, errmsg)
|
||||
#: E228
|
||||
msg = "Error %d occurred"%errno
|
||||
#:
|
||||
|
||||
#: Okay
|
||||
i = i + 1
|
||||
submitted += 1
|
||||
x = x * 2 - 1
|
||||
hypot2 = x * x + y * y
|
||||
c = (a + b) * (a - b)
|
||||
_1MiB = 2 ** 20
|
||||
_1TiB = 2**30
|
||||
foo(bar, key='word', *args, **kwargs)
|
||||
baz(**kwargs)
|
||||
negative = -1
|
||||
spam(-1)
|
||||
-negative
|
||||
func1(lambda *args, **kw: (args, kw))
|
||||
func2(lambda a, b=h[:], c=0: (a, b, c))
|
||||
if not -5 < x < +5:
|
||||
print >>sys.stderr, "x is out of range."
|
||||
print >> sys.stdout, "x is an integer."
|
||||
x = x / 2 - 1
|
||||
x = 1 @ 2
|
||||
|
||||
if alpha[:-i]:
|
||||
*a, b = (1, 2, 3)
|
||||
|
||||
|
||||
def squares(n):
|
||||
return (i**2 for i in range(n))
|
||||
|
||||
|
||||
ENG_PREFIXES = {
|
||||
-6: "\u03bc", # Greek letter mu
|
||||
-3: "m",
|
||||
}
|
||||
#:
|
||||
|
|
@ -11,5 +11,5 @@ doctest = false
|
|||
once_cell = { version = "1.17.0" }
|
||||
proc-macro2 = { version = "1.0.47" }
|
||||
quote = { version = "1.0.21" }
|
||||
syn = { version = "1.0.103", features = ["derive", "parsing"] }
|
||||
syn = { version = "1.0.103", features = ["derive", "parsing", "extra-traits"] }
|
||||
textwrap = { version = "0.16.0" }
|
||||
|
|
|
|||
|
|
@ -0,0 +1,206 @@
|
|||
use bisection::bisect_left;
|
||||
use itertools::Itertools;
|
||||
use rustpython_ast::Location;
|
||||
use rustpython_parser::lexer::LexResult;
|
||||
|
||||
use crate::ast::types::Range;
|
||||
use crate::registry::Diagnostic;
|
||||
use crate::rules::pycodestyle::logical_lines::iter_logical_lines;
|
||||
use crate::rules::pycodestyle::rules::{extraneous_whitespace, indentation, space_around_operator};
|
||||
use crate::settings::Settings;
|
||||
use crate::source_code::{Locator, Stylist};
|
||||
|
||||
/// Return the amount of indentation, expanding tabs to the next multiple of 8.
|
||||
fn expand_indent(mut line: &str) -> usize {
|
||||
while line.ends_with("\n\r") {
|
||||
line = &line[..line.len() - 2];
|
||||
}
|
||||
if !line.contains('\t') {
|
||||
return line.len() - line.trim_start().len();
|
||||
}
|
||||
let mut indent = 0;
|
||||
for c in line.chars() {
|
||||
if c == '\t' {
|
||||
indent = (indent / 8) * 8 + 8;
|
||||
} else if c == ' ' {
|
||||
indent += 1;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
indent
|
||||
}
|
||||
|
||||
pub fn check_logical_lines(
|
||||
tokens: &[LexResult],
|
||||
locator: &Locator,
|
||||
stylist: &Stylist,
|
||||
settings: &Settings,
|
||||
) -> Vec<Diagnostic> {
|
||||
let mut diagnostics = vec![];
|
||||
|
||||
let indent_char = stylist.indentation().as_char();
|
||||
let mut prev_line = None;
|
||||
let mut prev_indent_level = None;
|
||||
for line in iter_logical_lines(tokens, locator) {
|
||||
if line.mapping.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Extract the indentation level.
|
||||
let start_loc = line.mapping[0].1;
|
||||
let start_line = locator
|
||||
.slice_source_code_range(&Range::new(Location::new(start_loc.row(), 0), start_loc));
|
||||
let indent_level = expand_indent(start_line);
|
||||
let indent_size = 4;
|
||||
|
||||
// Generate mapping from logical to physical offsets.
|
||||
let mapping_offsets = line.mapping.iter().map(|(offset, _)| *offset).collect_vec();
|
||||
|
||||
if line.operator {
|
||||
for (index, kind) in space_around_operator(&line.text) {
|
||||
let (token_offset, pos) = line.mapping[bisect_left(&mapping_offsets, &index)];
|
||||
let location = Location::new(pos.row(), pos.column() + index - token_offset);
|
||||
if settings.rules.enabled(kind.rule()) {
|
||||
diagnostics.push(Diagnostic {
|
||||
kind,
|
||||
location,
|
||||
end_location: location,
|
||||
fix: None,
|
||||
parent: None,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
if line.bracket || line.punctuation {
|
||||
for (index, kind) in extraneous_whitespace(&line.text) {
|
||||
let (token_offset, pos) = line.mapping[bisect_left(&mapping_offsets, &index)];
|
||||
let location = Location::new(pos.row(), pos.column() + index - token_offset);
|
||||
if settings.rules.enabled(kind.rule()) {
|
||||
diagnostics.push(Diagnostic {
|
||||
kind,
|
||||
location,
|
||||
end_location: location,
|
||||
fix: None,
|
||||
parent: None,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (index, kind) in indentation(
|
||||
&line,
|
||||
prev_line.as_ref(),
|
||||
indent_char,
|
||||
indent_level,
|
||||
prev_indent_level,
|
||||
indent_size,
|
||||
) {
|
||||
let (token_offset, pos) = line.mapping[bisect_left(&mapping_offsets, &index)];
|
||||
let location = Location::new(pos.row(), pos.column() + index - token_offset);
|
||||
if settings.rules.enabled(kind.rule()) {
|
||||
diagnostics.push(Diagnostic {
|
||||
kind,
|
||||
location,
|
||||
end_location: location,
|
||||
fix: None,
|
||||
parent: None,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if !line.is_comment() {
|
||||
prev_line = Some(line);
|
||||
prev_indent_level = Some(indent_level);
|
||||
}
|
||||
}
|
||||
diagnostics
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use rustpython_parser::lexer;
|
||||
use rustpython_parser::lexer::LexResult;
|
||||
|
||||
use crate::checkers::logical_lines::iter_logical_lines;
|
||||
use crate::source_code::Locator;
|
||||
|
||||
#[test]
|
||||
fn split_logical_lines() {
|
||||
let contents = r#"
|
||||
x = 1
|
||||
y = 2
|
||||
z = x + 1"#;
|
||||
let lxr: Vec<LexResult> = lexer::make_tokenizer(contents).collect();
|
||||
let locator = Locator::new(contents);
|
||||
let actual: Vec<String> = iter_logical_lines(&lxr, &locator)
|
||||
.into_iter()
|
||||
.map(|line| line.text)
|
||||
.collect();
|
||||
let expected = vec![
|
||||
"x = 1".to_string(),
|
||||
"y = 2".to_string(),
|
||||
"z = x + 1".to_string(),
|
||||
];
|
||||
assert_eq!(actual, expected);
|
||||
|
||||
let contents = r#"
|
||||
x = [
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
]
|
||||
y = 2
|
||||
z = x + 1"#;
|
||||
let lxr: Vec<LexResult> = lexer::make_tokenizer(contents).collect();
|
||||
let locator = Locator::new(contents);
|
||||
let actual: Vec<String> = iter_logical_lines(&lxr, &locator)
|
||||
.into_iter()
|
||||
.map(|line| line.text)
|
||||
.collect();
|
||||
let expected = vec![
|
||||
"x = [1, 2, 3, ]".to_string(),
|
||||
"y = 2".to_string(),
|
||||
"z = x + 1".to_string(),
|
||||
];
|
||||
assert_eq!(actual, expected);
|
||||
|
||||
let contents = "x = 'abc'";
|
||||
let lxr: Vec<LexResult> = lexer::make_tokenizer(contents).collect();
|
||||
let locator = Locator::new(contents);
|
||||
let actual: Vec<String> = iter_logical_lines(&lxr, &locator)
|
||||
.into_iter()
|
||||
.map(|line| line.text)
|
||||
.collect();
|
||||
let expected = vec!["x = \"xxx\"".to_string()];
|
||||
assert_eq!(actual, expected);
|
||||
|
||||
let contents = r#"
|
||||
def f():
|
||||
x = 1
|
||||
f()"#;
|
||||
let lxr: Vec<LexResult> = lexer::make_tokenizer(contents).collect();
|
||||
let locator = Locator::new(contents);
|
||||
let actual: Vec<String> = iter_logical_lines(&lxr, &locator)
|
||||
.into_iter()
|
||||
.map(|line| line.text)
|
||||
.collect();
|
||||
let expected = vec!["def f():", "x = 1", "f()"];
|
||||
assert_eq!(actual, expected);
|
||||
|
||||
let contents = r#"
|
||||
def f():
|
||||
"""Docstring goes here."""
|
||||
# Comment goes here.
|
||||
x = 1
|
||||
f()"#;
|
||||
let lxr: Vec<LexResult> = lexer::make_tokenizer(contents).collect();
|
||||
let locator = Locator::new(contents);
|
||||
let actual: Vec<String> = iter_logical_lines(&lxr, &locator)
|
||||
.into_iter()
|
||||
.map(|line| line.text)
|
||||
.collect();
|
||||
let expected = vec!["def f():", "\"xxx\"", "", "x = 1", "f()"];
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
pub mod ast;
|
||||
pub mod filesystem;
|
||||
pub mod imports;
|
||||
pub mod lines;
|
||||
pub mod logical_lines;
|
||||
pub mod noqa;
|
||||
pub mod physical_lines;
|
||||
pub mod tokens;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
//! Lint rules based on checking raw physical lines.
|
||||
//! Lint rules based on checking physical lines.
|
||||
|
||||
use std::path::Path;
|
||||
|
||||
|
|
@ -15,7 +15,7 @@ use crate::rules::pyupgrade::rules::unnecessary_coding_comment;
|
|||
use crate::settings::{flags, Settings};
|
||||
use crate::source_code::Stylist;
|
||||
|
||||
pub fn check_lines(
|
||||
pub fn check_physical_lines(
|
||||
path: &Path,
|
||||
stylist: &Stylist,
|
||||
contents: &str,
|
||||
|
|
@ -164,7 +164,7 @@ mod tests {
|
|||
|
||||
use std::path::Path;
|
||||
|
||||
use super::check_lines;
|
||||
use super::check_physical_lines;
|
||||
use crate::registry::Rule;
|
||||
use crate::settings::{flags, Settings};
|
||||
use crate::source_code::{Locator, Stylist};
|
||||
|
|
@ -176,7 +176,7 @@ mod tests {
|
|||
let stylist = Stylist::from_contents(line, &locator);
|
||||
|
||||
let check_with_max_line_length = |line_length: usize| {
|
||||
check_lines(
|
||||
check_physical_lines(
|
||||
Path::new("foo.py"),
|
||||
&stylist,
|
||||
line,
|
||||
|
|
@ -10,8 +10,9 @@ use crate::autofix::fix_file;
|
|||
use crate::checkers::ast::check_ast;
|
||||
use crate::checkers::filesystem::check_file_path;
|
||||
use crate::checkers::imports::check_imports;
|
||||
use crate::checkers::lines::check_lines;
|
||||
use crate::checkers::logical_lines::check_logical_lines;
|
||||
use crate::checkers::noqa::check_noqa;
|
||||
use crate::checkers::physical_lines::check_physical_lines;
|
||||
use crate::checkers::tokens::check_tokens;
|
||||
use crate::directives::Directives;
|
||||
use crate::doc_lines::{doc_lines_from_ast, doc_lines_from_tokens};
|
||||
|
|
@ -89,6 +90,15 @@ pub fn check_path(
|
|||
diagnostics.extend(check_file_path(path, package, settings));
|
||||
}
|
||||
|
||||
// Run the logical line-based rules.
|
||||
if settings
|
||||
.rules
|
||||
.iter_enabled()
|
||||
.any(|rule_code| matches!(rule_code.lint_source(), LintSource::LogicalLines))
|
||||
{
|
||||
diagnostics.extend(check_logical_lines(&tokens, locator, stylist, settings));
|
||||
}
|
||||
|
||||
// Run the AST-based rules.
|
||||
let use_ast = settings
|
||||
.rules
|
||||
|
|
@ -152,9 +162,9 @@ pub fn check_path(
|
|||
if settings
|
||||
.rules
|
||||
.iter_enabled()
|
||||
.any(|rule_code| matches!(rule_code.lint_source(), LintSource::Lines))
|
||||
.any(|rule_code| matches!(rule_code.lint_source(), LintSource::PhysicalLines))
|
||||
{
|
||||
diagnostics.extend(check_lines(
|
||||
diagnostics.extend(check_physical_lines(
|
||||
path,
|
||||
stylist,
|
||||
contents,
|
||||
|
|
|
|||
|
|
@ -13,6 +13,34 @@ use crate::violation::Violation;
|
|||
ruff_macros::define_rule_mapping!(
|
||||
// pycodestyle errors
|
||||
E101 => rules::pycodestyle::rules::MixedSpacesAndTabs,
|
||||
#[cfg(feature = "logical_lines")]
|
||||
E111 => rules::pycodestyle::rules::IndentationWithInvalidMultiple,
|
||||
#[cfg(feature = "logical_lines")]
|
||||
E112 => rules::pycodestyle::rules::NoIndentedBlock,
|
||||
#[cfg(feature = "logical_lines")]
|
||||
E113 => rules::pycodestyle::rules::UnexpectedIndentation,
|
||||
#[cfg(feature = "logical_lines")]
|
||||
E114 => rules::pycodestyle::rules::IndentationWithInvalidMultipleComment,
|
||||
#[cfg(feature = "logical_lines")]
|
||||
E115 => rules::pycodestyle::rules::NoIndentedBlockComment,
|
||||
#[cfg(feature = "logical_lines")]
|
||||
E116 => rules::pycodestyle::rules::UnexpectedIndentationComment,
|
||||
#[cfg(feature = "logical_lines")]
|
||||
E117 => rules::pycodestyle::rules::OverIndented,
|
||||
#[cfg(feature = "logical_lines")]
|
||||
E201 => rules::pycodestyle::rules::WhitespaceAfterOpenBracket,
|
||||
#[cfg(feature = "logical_lines")]
|
||||
E202 => rules::pycodestyle::rules::WhitespaceBeforeCloseBracket,
|
||||
#[cfg(feature = "logical_lines")]
|
||||
E203 => rules::pycodestyle::rules::WhitespaceBeforePunctuation,
|
||||
#[cfg(feature = "logical_lines")]
|
||||
E221 => rules::pycodestyle::rules::MultipleSpacesBeforeOperator,
|
||||
#[cfg(feature = "logical_lines")]
|
||||
E222 => rules::pycodestyle::rules::MultipleSpacesAfterOperator,
|
||||
#[cfg(feature = "logical_lines")]
|
||||
E223 => rules::pycodestyle::rules::TabBeforeOperator,
|
||||
#[cfg(feature = "logical_lines")]
|
||||
E224 => rules::pycodestyle::rules::TabAfterOperator,
|
||||
E401 => rules::pycodestyle::rules::MultipleImportsOnOneLine,
|
||||
E402 => rules::pycodestyle::rules::ModuleImportNotAtTopOfFile,
|
||||
E501 => rules::pycodestyle::rules::LineTooLong,
|
||||
|
|
@ -671,7 +699,8 @@ impl Linter {
|
|||
pub enum LintSource {
|
||||
Ast,
|
||||
Io,
|
||||
Lines,
|
||||
PhysicalLines,
|
||||
LogicalLines,
|
||||
Tokens,
|
||||
Imports,
|
||||
NoQa,
|
||||
|
|
@ -695,7 +724,7 @@ impl Rule {
|
|||
| Rule::ShebangNotExecutable
|
||||
| Rule::ShebangNewline
|
||||
| Rule::ShebangPython
|
||||
| Rule::ShebangWhitespace => &LintSource::Lines,
|
||||
| Rule::ShebangWhitespace => &LintSource::PhysicalLines,
|
||||
Rule::AmbiguousUnicodeCharacterComment
|
||||
| Rule::AmbiguousUnicodeCharacterDocstring
|
||||
| Rule::AmbiguousUnicodeCharacterString
|
||||
|
|
@ -714,6 +743,21 @@ impl Rule {
|
|||
Rule::IOError => &LintSource::Io,
|
||||
Rule::UnsortedImports | Rule::MissingRequiredImport => &LintSource::Imports,
|
||||
Rule::ImplicitNamespacePackage => &LintSource::Filesystem,
|
||||
#[cfg(feature = "logical_lines")]
|
||||
Rule::IndentationWithInvalidMultiple
|
||||
| Rule::IndentationWithInvalidMultipleComment
|
||||
| Rule::MultipleSpacesAfterOperator
|
||||
| Rule::MultipleSpacesBeforeOperator
|
||||
| Rule::NoIndentedBlock
|
||||
| Rule::NoIndentedBlockComment
|
||||
| Rule::OverIndented
|
||||
| Rule::TabAfterOperator
|
||||
| Rule::TabBeforeOperator
|
||||
| Rule::UnexpectedIndentation
|
||||
| Rule::UnexpectedIndentationComment
|
||||
| Rule::WhitespaceAfterOpenBracket
|
||||
| Rule::WhitespaceBeforeCloseBracket
|
||||
| Rule::WhitespaceBeforePunctuation => &LintSource::LogicalLines,
|
||||
_ => &LintSource::Ast,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,171 @@
|
|||
use rustpython_ast::Location;
|
||||
use rustpython_parser::lexer::{LexResult, Tok};
|
||||
|
||||
use crate::ast::types::Range;
|
||||
use crate::source_code::Locator;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct LogicalLine {
|
||||
pub text: String,
|
||||
pub mapping: Vec<(usize, Location)>,
|
||||
/// Whether the logical line contains an operator.
|
||||
pub operator: bool,
|
||||
/// Whether the logical line contains a comment.
|
||||
pub bracket: bool,
|
||||
/// Whether the logical line contains a punctuation mark.
|
||||
pub punctuation: bool,
|
||||
}
|
||||
|
||||
impl LogicalLine {
|
||||
pub fn is_comment(&self) -> bool {
|
||||
self.text.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
fn build_line(tokens: &[(Location, &Tok, Location)], locator: &Locator) -> LogicalLine {
|
||||
let mut logical = String::with_capacity(88);
|
||||
let mut operator = false;
|
||||
let mut bracket = false;
|
||||
let mut punctuation = false;
|
||||
let mut mapping = Vec::new();
|
||||
let mut prev: Option<&Location> = None;
|
||||
let mut length = 0;
|
||||
for (start, tok, end) in tokens {
|
||||
if matches!(
|
||||
tok,
|
||||
Tok::Newline | Tok::NonLogicalNewline | Tok::Indent | Tok::Dedent
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if mapping.is_empty() {
|
||||
mapping.push((0, *start));
|
||||
}
|
||||
|
||||
if matches!(tok, Tok::Comment { .. }) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if !operator {
|
||||
operator |= matches!(
|
||||
tok,
|
||||
Tok::Amper
|
||||
| Tok::AmperEqual
|
||||
| Tok::CircumFlex
|
||||
| Tok::CircumflexEqual
|
||||
| Tok::Colon
|
||||
| Tok::ColonEqual
|
||||
| Tok::DoubleSlash
|
||||
| Tok::DoubleSlashEqual
|
||||
| Tok::DoubleStar
|
||||
| Tok::Equal
|
||||
| Tok::Greater
|
||||
| Tok::GreaterEqual
|
||||
| Tok::Less
|
||||
| Tok::LessEqual
|
||||
| Tok::Minus
|
||||
| Tok::MinusEqual
|
||||
| Tok::NotEqual
|
||||
| Tok::Percent
|
||||
| Tok::PercentEqual
|
||||
| Tok::Plus
|
||||
| Tok::PlusEqual
|
||||
| Tok::Slash
|
||||
| Tok::SlashEqual
|
||||
| Tok::Star
|
||||
| Tok::StarEqual
|
||||
| Tok::Vbar
|
||||
| Tok::VbarEqual
|
||||
);
|
||||
}
|
||||
|
||||
if !bracket {
|
||||
bracket |= matches!(
|
||||
tok,
|
||||
Tok::Lpar | Tok::Lsqb | Tok::Lbrace | Tok::Rpar | Tok::Rsqb | Tok::Rbrace
|
||||
);
|
||||
}
|
||||
|
||||
if !punctuation {
|
||||
punctuation |= matches!(tok, Tok::Comma | Tok::Semi | Tok::Colon);
|
||||
}
|
||||
|
||||
// TODO(charlie): "Mute" strings.
|
||||
let text = if let Tok::String { .. } = tok {
|
||||
"\"xxx\""
|
||||
} else {
|
||||
locator.slice_source_code_range(&Range {
|
||||
location: *start,
|
||||
end_location: *end,
|
||||
})
|
||||
};
|
||||
|
||||
if let Some(prev) = prev {
|
||||
if prev.row() != start.row() {
|
||||
let prev_text = locator.slice_source_code_range(&Range {
|
||||
location: Location::new(prev.row(), prev.column() - 1),
|
||||
end_location: Location::new(prev.row(), prev.column()),
|
||||
});
|
||||
if prev_text == ","
|
||||
|| ((prev_text != "{" && prev_text != "[" && prev_text != "(")
|
||||
&& (text != "}" && text != "]" && text != ")"))
|
||||
{
|
||||
logical.push(' ');
|
||||
length += 1;
|
||||
}
|
||||
} else if prev.column() != start.column() {
|
||||
let prev_text = locator.slice_source_code_range(&Range {
|
||||
location: *prev,
|
||||
end_location: *start,
|
||||
});
|
||||
logical.push_str(prev_text);
|
||||
length += prev_text.len();
|
||||
}
|
||||
}
|
||||
logical.push_str(text);
|
||||
length += text.len();
|
||||
mapping.push((length, *end));
|
||||
prev = Some(end);
|
||||
}
|
||||
|
||||
LogicalLine {
|
||||
text: logical,
|
||||
operator,
|
||||
bracket,
|
||||
punctuation,
|
||||
mapping,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn iter_logical_lines(tokens: &[LexResult], locator: &Locator) -> Vec<LogicalLine> {
|
||||
let mut parens = 0;
|
||||
let mut accumulator = Vec::with_capacity(32);
|
||||
let mut lines = Vec::with_capacity(128);
|
||||
for &(start, ref tok, end) in tokens.iter().flatten() {
|
||||
accumulator.push((start, tok, end));
|
||||
if matches!(tok, Tok::Lbrace | Tok::Lpar | Tok::Lsqb) {
|
||||
parens += 1;
|
||||
} else if matches!(tok, Tok::Rbrace | Tok::Rpar | Tok::Rsqb) {
|
||||
parens -= 1;
|
||||
} else if parens == 0 {
|
||||
if matches!(
|
||||
tok,
|
||||
Tok::Newline | Tok::NonLogicalNewline | Tok::Comment(..)
|
||||
) {
|
||||
if matches!(tok, Tok::Newline) {
|
||||
lines.push(build_line(&accumulator, locator));
|
||||
accumulator.drain(..);
|
||||
} else if tokens.len() == 1 {
|
||||
accumulator.remove(0);
|
||||
} else {
|
||||
lines.push(build_line(&accumulator, locator));
|
||||
accumulator.drain(..);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if !accumulator.is_empty() {
|
||||
lines.push(build_line(&accumulator, locator));
|
||||
}
|
||||
lines
|
||||
}
|
||||
|
|
@ -2,7 +2,8 @@
|
|||
pub(crate) mod rules;
|
||||
pub mod settings;
|
||||
|
||||
pub mod helpers;
|
||||
pub(crate) mod helpers;
|
||||
pub(crate) mod logical_lines;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
|
@ -49,6 +50,31 @@ mod tests {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(feature = "logical_lines")]
|
||||
#[test_case(Rule::IndentationWithInvalidMultiple, Path::new("E11.py"))]
|
||||
#[test_case(Rule::IndentationWithInvalidMultipleComment, Path::new("E11.py"))]
|
||||
#[test_case(Rule::MultipleSpacesAfterOperator, Path::new("E22.py"))]
|
||||
#[test_case(Rule::MultipleSpacesBeforeOperator, Path::new("E22.py"))]
|
||||
#[test_case(Rule::NoIndentedBlock, Path::new("E11.py"))]
|
||||
#[test_case(Rule::NoIndentedBlockComment, Path::new("E11.py"))]
|
||||
#[test_case(Rule::OverIndented, Path::new("E11.py"))]
|
||||
#[test_case(Rule::TabAfterOperator, Path::new("E22.py"))]
|
||||
#[test_case(Rule::TabBeforeOperator, Path::new("E22.py"))]
|
||||
#[test_case(Rule::UnexpectedIndentation, Path::new("E11.py"))]
|
||||
#[test_case(Rule::UnexpectedIndentationComment, Path::new("E11.py"))]
|
||||
#[test_case(Rule::WhitespaceAfterOpenBracket, Path::new("E20.py"))]
|
||||
#[test_case(Rule::WhitespaceBeforeCloseBracket, Path::new("E20.py"))]
|
||||
#[test_case(Rule::WhitespaceBeforePunctuation, Path::new("E20.py"))]
|
||||
fn logical(rule_code: Rule, path: &Path) -> Result<()> {
|
||||
let snapshot = format!("{}_{}", rule_code.code(), path.to_string_lossy());
|
||||
let diagnostics = test_path(
|
||||
Path::new("pycodestyle").join(path).as_path(),
|
||||
&settings::Settings::for_rule(rule_code),
|
||||
)?;
|
||||
assert_yaml_snapshot!(snapshot, diagnostics);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn constant_literals() -> Result<()> {
|
||||
let diagnostics = test_path(
|
||||
|
|
|
|||
|
|
@ -0,0 +1,71 @@
|
|||
#![allow(dead_code)]
|
||||
|
||||
use once_cell::sync::Lazy;
|
||||
use regex::Regex;
|
||||
|
||||
use ruff_macros::derive_message_formats;
|
||||
|
||||
use crate::define_violation;
|
||||
use crate::registry::DiagnosticKind;
|
||||
use crate::violation::Violation;
|
||||
|
||||
define_violation!(
|
||||
pub struct WhitespaceAfterOpenBracket;
|
||||
);
|
||||
impl Violation for WhitespaceAfterOpenBracket {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
format!("Whitespace after '('")
|
||||
}
|
||||
}
|
||||
|
||||
define_violation!(
|
||||
pub struct WhitespaceBeforeCloseBracket;
|
||||
);
|
||||
impl Violation for WhitespaceBeforeCloseBracket {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
format!("Whitespace before ')'")
|
||||
}
|
||||
}
|
||||
|
||||
define_violation!(
|
||||
pub struct WhitespaceBeforePunctuation;
|
||||
);
|
||||
impl Violation for WhitespaceBeforePunctuation {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
format!("Whitespace before ',', ';', or ':'")
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(charlie): Pycodestyle has a negative lookahead on the end.
|
||||
static EXTRANEOUS_WHITESPACE_REGEX: Lazy<Regex> =
|
||||
Lazy::new(|| Regex::new(r"([\[({][ \t]|[ \t][]}),;:])").unwrap());
|
||||
|
||||
/// E201, E202, E203
|
||||
#[cfg(feature = "logical_lines")]
|
||||
pub fn extraneous_whitespace(line: &str) -> Vec<(usize, DiagnosticKind)> {
|
||||
let mut diagnostics = vec![];
|
||||
for line_match in EXTRANEOUS_WHITESPACE_REGEX.captures_iter(line) {
|
||||
let match_ = line_match.get(1).unwrap();
|
||||
let text = match_.as_str();
|
||||
let char = text.trim();
|
||||
let found = match_.start();
|
||||
if text.chars().last().unwrap().is_ascii_whitespace() {
|
||||
diagnostics.push((found + 1, WhitespaceAfterOpenBracket.into()));
|
||||
} else if line.chars().nth(found - 1).map_or(false, |c| c != ',') {
|
||||
if char == "}" || char == "]" || char == ")" {
|
||||
diagnostics.push((found, WhitespaceBeforeCloseBracket.into()));
|
||||
} else {
|
||||
diagnostics.push((found, WhitespaceBeforePunctuation.into()));
|
||||
}
|
||||
}
|
||||
}
|
||||
diagnostics
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "logical_lines"))]
|
||||
pub fn extraneous_whitespace(_line: &str) -> Vec<(usize, DiagnosticKind)> {
|
||||
vec![]
|
||||
}
|
||||
|
|
@ -0,0 +1,150 @@
|
|||
#![allow(dead_code)]
|
||||
|
||||
use crate::define_violation;
|
||||
use crate::registry::DiagnosticKind;
|
||||
use crate::rules::pycodestyle::logical_lines::LogicalLine;
|
||||
use crate::violation::Violation;
|
||||
use ruff_macros::derive_message_formats;
|
||||
|
||||
define_violation!(
|
||||
pub struct IndentationWithInvalidMultiple {
|
||||
pub indent_size: usize,
|
||||
}
|
||||
);
|
||||
impl Violation for IndentationWithInvalidMultiple {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
let Self { indent_size } = self;
|
||||
format!("Indentation is not a multiple of {indent_size}")
|
||||
}
|
||||
}
|
||||
|
||||
define_violation!(
|
||||
pub struct IndentationWithInvalidMultipleComment {
|
||||
pub indent_size: usize,
|
||||
}
|
||||
);
|
||||
impl Violation for IndentationWithInvalidMultipleComment {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
let Self { indent_size } = self;
|
||||
format!("Indentation is not a multiple of {indent_size} (comment)")
|
||||
}
|
||||
}
|
||||
|
||||
define_violation!(
|
||||
pub struct NoIndentedBlock;
|
||||
);
|
||||
impl Violation for NoIndentedBlock {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
format!("Expected an indented block")
|
||||
}
|
||||
}
|
||||
|
||||
define_violation!(
|
||||
pub struct NoIndentedBlockComment;
|
||||
);
|
||||
impl Violation for NoIndentedBlockComment {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
format!("Expected an indented block (comment)")
|
||||
}
|
||||
}
|
||||
|
||||
define_violation!(
|
||||
pub struct UnexpectedIndentation;
|
||||
);
|
||||
impl Violation for UnexpectedIndentation {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
format!("Unexpected indentation")
|
||||
}
|
||||
}
|
||||
|
||||
define_violation!(
|
||||
pub struct UnexpectedIndentationComment;
|
||||
);
|
||||
impl Violation for UnexpectedIndentationComment {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
format!("Unexpected indentation (comment)")
|
||||
}
|
||||
}
|
||||
|
||||
define_violation!(
|
||||
pub struct OverIndented;
|
||||
);
|
||||
impl Violation for OverIndented {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
format!("Over-indented")
|
||||
}
|
||||
}
|
||||
|
||||
/// E111
|
||||
#[cfg(feature = "logical_lines")]
|
||||
pub fn indentation(
|
||||
logical_line: &LogicalLine,
|
||||
prev_logical_line: Option<&LogicalLine>,
|
||||
indent_char: char,
|
||||
indent_level: usize,
|
||||
prev_indent_level: Option<usize>,
|
||||
indent_size: usize,
|
||||
) -> Vec<(usize, DiagnosticKind)> {
|
||||
let mut diagnostics = vec![];
|
||||
if indent_level % indent_size != 0 {
|
||||
diagnostics.push((
|
||||
0,
|
||||
if logical_line.is_comment() {
|
||||
IndentationWithInvalidMultipleComment { indent_size }.into()
|
||||
} else {
|
||||
IndentationWithInvalidMultiple { indent_size }.into()
|
||||
},
|
||||
));
|
||||
}
|
||||
let indent_expect = prev_logical_line.map_or(false, |prev_logical_line| {
|
||||
prev_logical_line.text.ends_with(':')
|
||||
});
|
||||
if indent_expect && indent_level <= prev_indent_level.unwrap_or(0) {
|
||||
diagnostics.push((
|
||||
0,
|
||||
if logical_line.is_comment() {
|
||||
NoIndentedBlockComment.into()
|
||||
} else {
|
||||
NoIndentedBlock.into()
|
||||
},
|
||||
));
|
||||
} else if !indent_expect
|
||||
&& prev_indent_level.map_or(false, |prev_indent_level| indent_level > prev_indent_level)
|
||||
{
|
||||
diagnostics.push((
|
||||
0,
|
||||
if logical_line.is_comment() {
|
||||
UnexpectedIndentationComment.into()
|
||||
} else {
|
||||
UnexpectedIndentation.into()
|
||||
},
|
||||
));
|
||||
}
|
||||
if indent_expect {
|
||||
let expected_indent_amount = if indent_char == '\t' { 8 } else { 4 };
|
||||
let expected_indent_level = prev_indent_level.unwrap_or(0) + expected_indent_amount;
|
||||
if indent_level > expected_indent_level {
|
||||
diagnostics.push((0, OverIndented.into()));
|
||||
}
|
||||
}
|
||||
diagnostics
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "logical_lines"))]
|
||||
pub fn indentation(
|
||||
_logical_line: &LogicalLine,
|
||||
_prev_logical_line: Option<&LogicalLine>,
|
||||
_indent_char: char,
|
||||
_indent_level: usize,
|
||||
_prev_indent_level: Option<usize>,
|
||||
_indent_size: usize,
|
||||
) -> Vec<(usize, DiagnosticKind)> {
|
||||
vec![]
|
||||
}
|
||||
|
|
@ -5,16 +5,29 @@ pub use do_not_assign_lambda::{do_not_assign_lambda, DoNotAssignLambda};
|
|||
pub use do_not_use_bare_except::{do_not_use_bare_except, DoNotUseBareExcept};
|
||||
pub use doc_line_too_long::{doc_line_too_long, DocLineTooLong};
|
||||
pub use errors::{syntax_error, IOError, SyntaxError};
|
||||
pub use extraneous_whitespace::{
|
||||
extraneous_whitespace, WhitespaceAfterOpenBracket, WhitespaceBeforeCloseBracket,
|
||||
WhitespaceBeforePunctuation,
|
||||
};
|
||||
pub use imports::{
|
||||
module_import_not_at_top_of_file, multiple_imports_on_one_line, ModuleImportNotAtTopOfFile,
|
||||
MultipleImportsOnOneLine,
|
||||
};
|
||||
pub use indentation::{
|
||||
indentation, IndentationWithInvalidMultiple, IndentationWithInvalidMultipleComment,
|
||||
NoIndentedBlock, NoIndentedBlockComment, OverIndented, UnexpectedIndentation,
|
||||
UnexpectedIndentationComment,
|
||||
};
|
||||
pub use invalid_escape_sequence::{invalid_escape_sequence, InvalidEscapeSequence};
|
||||
pub use line_too_long::{line_too_long, LineTooLong};
|
||||
pub use literal_comparisons::{literal_comparisons, NoneComparison, TrueFalseComparison};
|
||||
pub use mixed_spaces_and_tabs::{mixed_spaces_and_tabs, MixedSpacesAndTabs};
|
||||
pub use no_newline_at_end_of_file::{no_newline_at_end_of_file, NoNewLineAtEndOfFile};
|
||||
pub use not_tests::{not_tests, NotInTest, NotIsTest};
|
||||
pub use space_around_operator::{
|
||||
space_around_operator, MultipleSpacesAfterOperator, MultipleSpacesBeforeOperator,
|
||||
TabAfterOperator, TabBeforeOperator,
|
||||
};
|
||||
pub use type_comparison::{type_comparison, TypeComparison};
|
||||
|
||||
mod ambiguous_class_name;
|
||||
|
|
@ -24,11 +37,14 @@ mod do_not_assign_lambda;
|
|||
mod do_not_use_bare_except;
|
||||
mod doc_line_too_long;
|
||||
mod errors;
|
||||
mod extraneous_whitespace;
|
||||
mod imports;
|
||||
mod indentation;
|
||||
mod invalid_escape_sequence;
|
||||
mod line_too_long;
|
||||
mod literal_comparisons;
|
||||
mod mixed_spaces_and_tabs;
|
||||
mod no_newline_at_end_of_file;
|
||||
mod not_tests;
|
||||
mod space_around_operator;
|
||||
mod type_comparison;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,81 @@
|
|||
#![allow(dead_code)]
|
||||
|
||||
use once_cell::sync::Lazy;
|
||||
use regex::Regex;
|
||||
|
||||
use ruff_macros::derive_message_formats;
|
||||
|
||||
use crate::define_violation;
|
||||
use crate::registry::DiagnosticKind;
|
||||
use crate::violation::Violation;
|
||||
|
||||
define_violation!(
|
||||
pub struct TabBeforeOperator;
|
||||
);
|
||||
impl Violation for TabBeforeOperator {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
format!("Tab before operator")
|
||||
}
|
||||
}
|
||||
|
||||
define_violation!(
|
||||
pub struct MultipleSpacesBeforeOperator;
|
||||
);
|
||||
impl Violation for MultipleSpacesBeforeOperator {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
format!("Multiple spaces before operator")
|
||||
}
|
||||
}
|
||||
|
||||
define_violation!(
|
||||
pub struct TabAfterOperator;
|
||||
);
|
||||
impl Violation for TabAfterOperator {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
format!("Tab after operator")
|
||||
}
|
||||
}
|
||||
|
||||
define_violation!(
|
||||
pub struct MultipleSpacesAfterOperator;
|
||||
);
|
||||
impl Violation for MultipleSpacesAfterOperator {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
format!("Multiple spaces after operator")
|
||||
}
|
||||
}
|
||||
|
||||
static OPERATOR_REGEX: Lazy<Regex> =
|
||||
Lazy::new(|| Regex::new(r"[^,\s](\s*)(?:[-+*/|!<=>%&^]+|:=)(\s*)").unwrap());
|
||||
|
||||
/// E221, E222, E223, E224
|
||||
#[cfg(feature = "logical_lines")]
|
||||
pub fn space_around_operator(line: &str) -> Vec<(usize, DiagnosticKind)> {
|
||||
let mut diagnostics = vec![];
|
||||
for line_match in OPERATOR_REGEX.captures_iter(line) {
|
||||
let before = line_match.get(1).unwrap();
|
||||
let after = line_match.get(2).unwrap();
|
||||
|
||||
if before.as_str().contains('\t') {
|
||||
diagnostics.push((before.start(), TabBeforeOperator.into()));
|
||||
} else if before.as_str().len() > 1 {
|
||||
diagnostics.push((before.start(), MultipleSpacesBeforeOperator.into()));
|
||||
}
|
||||
|
||||
if after.as_str().contains('\t') {
|
||||
diagnostics.push((after.start(), TabAfterOperator.into()));
|
||||
} else if after.as_str().len() > 1 {
|
||||
diagnostics.push((after.start(), MultipleSpacesAfterOperator.into()));
|
||||
}
|
||||
}
|
||||
diagnostics
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "logical_lines"))]
|
||||
pub fn space_around_operator(_line: &str) -> Vec<(usize, DiagnosticKind)> {
|
||||
vec![]
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
---
|
||||
source: src/rules/pycodestyle/mod.rs
|
||||
expression: diagnostics
|
||||
---
|
||||
- kind:
|
||||
IndentationWithInvalidMultiple:
|
||||
indent_size: 4
|
||||
location:
|
||||
row: 3
|
||||
column: 2
|
||||
end_location:
|
||||
row: 3
|
||||
column: 2
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
IndentationWithInvalidMultiple:
|
||||
indent_size: 4
|
||||
location:
|
||||
row: 6
|
||||
column: 5
|
||||
end_location:
|
||||
row: 6
|
||||
column: 5
|
||||
fix: ~
|
||||
parent: ~
|
||||
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
---
|
||||
source: src/rules/pycodestyle/mod.rs
|
||||
expression: diagnostics
|
||||
---
|
||||
- kind:
|
||||
NoIndentedBlock: ~
|
||||
location:
|
||||
row: 9
|
||||
column: 0
|
||||
end_location:
|
||||
row: 9
|
||||
column: 0
|
||||
fix: ~
|
||||
parent: ~
|
||||
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
---
|
||||
source: src/rules/pycodestyle/mod.rs
|
||||
expression: diagnostics
|
||||
---
|
||||
- kind:
|
||||
UnexpectedIndentation: ~
|
||||
location:
|
||||
row: 12
|
||||
column: 4
|
||||
end_location:
|
||||
row: 12
|
||||
column: 4
|
||||
fix: ~
|
||||
parent: ~
|
||||
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
---
|
||||
source: src/rules/pycodestyle/mod.rs
|
||||
expression: diagnostics
|
||||
---
|
||||
- kind:
|
||||
IndentationWithInvalidMultipleComment:
|
||||
indent_size: 4
|
||||
location:
|
||||
row: 15
|
||||
column: 5
|
||||
end_location:
|
||||
row: 15
|
||||
column: 5
|
||||
fix: ~
|
||||
parent: ~
|
||||
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
---
|
||||
source: src/rules/pycodestyle/mod.rs
|
||||
expression: diagnostics
|
||||
---
|
||||
- kind:
|
||||
NoIndentedBlockComment: ~
|
||||
location:
|
||||
row: 30
|
||||
column: 0
|
||||
end_location:
|
||||
row: 30
|
||||
column: 0
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
NoIndentedBlockComment: ~
|
||||
location:
|
||||
row: 31
|
||||
column: 0
|
||||
end_location:
|
||||
row: 31
|
||||
column: 0
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
NoIndentedBlockComment: ~
|
||||
location:
|
||||
row: 32
|
||||
column: 0
|
||||
end_location:
|
||||
row: 32
|
||||
column: 0
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
NoIndentedBlockComment: ~
|
||||
location:
|
||||
row: 33
|
||||
column: 0
|
||||
end_location:
|
||||
row: 33
|
||||
column: 0
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
NoIndentedBlockComment: ~
|
||||
location:
|
||||
row: 34
|
||||
column: 0
|
||||
end_location:
|
||||
row: 34
|
||||
column: 0
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
NoIndentedBlockComment: ~
|
||||
location:
|
||||
row: 35
|
||||
column: 0
|
||||
end_location:
|
||||
row: 35
|
||||
column: 0
|
||||
fix: ~
|
||||
parent: ~
|
||||
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
---
|
||||
source: src/rules/pycodestyle/mod.rs
|
||||
expression: diagnostics
|
||||
---
|
||||
- kind:
|
||||
UnexpectedIndentationComment: ~
|
||||
location:
|
||||
row: 15
|
||||
column: 5
|
||||
end_location:
|
||||
row: 15
|
||||
column: 5
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
UnexpectedIndentationComment: ~
|
||||
location:
|
||||
row: 22
|
||||
column: 12
|
||||
end_location:
|
||||
row: 22
|
||||
column: 12
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
UnexpectedIndentationComment: ~
|
||||
location:
|
||||
row: 24
|
||||
column: 12
|
||||
end_location:
|
||||
row: 24
|
||||
column: 12
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
UnexpectedIndentationComment: ~
|
||||
location:
|
||||
row: 26
|
||||
column: 12
|
||||
end_location:
|
||||
row: 26
|
||||
column: 12
|
||||
fix: ~
|
||||
parent: ~
|
||||
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
---
|
||||
source: src/rules/pycodestyle/mod.rs
|
||||
expression: diagnostics
|
||||
---
|
||||
- kind:
|
||||
OverIndented: ~
|
||||
location:
|
||||
row: 6
|
||||
column: 5
|
||||
end_location:
|
||||
row: 6
|
||||
column: 5
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
OverIndented: ~
|
||||
location:
|
||||
row: 39
|
||||
column: 8
|
||||
end_location:
|
||||
row: 39
|
||||
column: 8
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
OverIndented: ~
|
||||
location:
|
||||
row: 42
|
||||
column: 2
|
||||
end_location:
|
||||
row: 42
|
||||
column: 2
|
||||
fix: ~
|
||||
parent: ~
|
||||
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
---
|
||||
source: src/rules/pycodestyle/mod.rs
|
||||
expression: diagnostics
|
||||
---
|
||||
- kind:
|
||||
WhitespaceAfterOpenBracket: ~
|
||||
location:
|
||||
row: 2
|
||||
column: 5
|
||||
end_location:
|
||||
row: 2
|
||||
column: 5
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
WhitespaceAfterOpenBracket: ~
|
||||
location:
|
||||
row: 4
|
||||
column: 9
|
||||
end_location:
|
||||
row: 4
|
||||
column: 9
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
WhitespaceAfterOpenBracket: ~
|
||||
location:
|
||||
row: 6
|
||||
column: 14
|
||||
end_location:
|
||||
row: 6
|
||||
column: 14
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
WhitespaceAfterOpenBracket: ~
|
||||
location:
|
||||
row: 8
|
||||
column: 5
|
||||
end_location:
|
||||
row: 8
|
||||
column: 5
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
WhitespaceAfterOpenBracket: ~
|
||||
location:
|
||||
row: 10
|
||||
column: 9
|
||||
end_location:
|
||||
row: 10
|
||||
column: 9
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
WhitespaceAfterOpenBracket: ~
|
||||
location:
|
||||
row: 12
|
||||
column: 14
|
||||
end_location:
|
||||
row: 12
|
||||
column: 14
|
||||
fix: ~
|
||||
parent: ~
|
||||
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
---
|
||||
source: src/rules/pycodestyle/mod.rs
|
||||
expression: diagnostics
|
||||
---
|
||||
- kind:
|
||||
WhitespaceBeforeCloseBracket: ~
|
||||
location:
|
||||
row: 19
|
||||
column: 22
|
||||
end_location:
|
||||
row: 19
|
||||
column: 22
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
WhitespaceBeforeCloseBracket: ~
|
||||
location:
|
||||
row: 21
|
||||
column: 21
|
||||
end_location:
|
||||
row: 21
|
||||
column: 21
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
WhitespaceBeforeCloseBracket: ~
|
||||
location:
|
||||
row: 23
|
||||
column: 10
|
||||
end_location:
|
||||
row: 23
|
||||
column: 10
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
WhitespaceBeforeCloseBracket: ~
|
||||
location:
|
||||
row: 25
|
||||
column: 22
|
||||
end_location:
|
||||
row: 25
|
||||
column: 22
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
WhitespaceBeforeCloseBracket: ~
|
||||
location:
|
||||
row: 27
|
||||
column: 21
|
||||
end_location:
|
||||
row: 27
|
||||
column: 21
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
WhitespaceBeforeCloseBracket: ~
|
||||
location:
|
||||
row: 29
|
||||
column: 10
|
||||
end_location:
|
||||
row: 29
|
||||
column: 10
|
||||
fix: ~
|
||||
parent: ~
|
||||
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
---
|
||||
source: src/rules/pycodestyle/mod.rs
|
||||
expression: diagnostics
|
||||
---
|
||||
- kind:
|
||||
WhitespaceBeforePunctuation: ~
|
||||
location:
|
||||
row: 51
|
||||
column: 9
|
||||
end_location:
|
||||
row: 51
|
||||
column: 9
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
WhitespaceBeforePunctuation: ~
|
||||
location:
|
||||
row: 55
|
||||
column: 9
|
||||
end_location:
|
||||
row: 55
|
||||
column: 9
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
WhitespaceBeforePunctuation: ~
|
||||
location:
|
||||
row: 60
|
||||
column: 14
|
||||
end_location:
|
||||
row: 60
|
||||
column: 14
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
WhitespaceBeforePunctuation: ~
|
||||
location:
|
||||
row: 63
|
||||
column: 14
|
||||
end_location:
|
||||
row: 63
|
||||
column: 14
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
WhitespaceBeforePunctuation: ~
|
||||
location:
|
||||
row: 67
|
||||
column: 12
|
||||
end_location:
|
||||
row: 67
|
||||
column: 12
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
WhitespaceBeforePunctuation: ~
|
||||
location:
|
||||
row: 71
|
||||
column: 12
|
||||
end_location:
|
||||
row: 71
|
||||
column: 12
|
||||
fix: ~
|
||||
parent: ~
|
||||
|
||||
|
|
@ -0,0 +1,85 @@
|
|||
---
|
||||
source: src/rules/pycodestyle/mod.rs
|
||||
expression: diagnostics
|
||||
---
|
||||
- kind:
|
||||
MultipleSpacesBeforeOperator: ~
|
||||
location:
|
||||
row: 3
|
||||
column: 5
|
||||
end_location:
|
||||
row: 3
|
||||
column: 5
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
MultipleSpacesBeforeOperator: ~
|
||||
location:
|
||||
row: 5
|
||||
column: 1
|
||||
end_location:
|
||||
row: 5
|
||||
column: 1
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
MultipleSpacesBeforeOperator: ~
|
||||
location:
|
||||
row: 6
|
||||
column: 1
|
||||
end_location:
|
||||
row: 6
|
||||
column: 1
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
MultipleSpacesBeforeOperator: ~
|
||||
location:
|
||||
row: 9
|
||||
column: 4
|
||||
end_location:
|
||||
row: 9
|
||||
column: 4
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
MultipleSpacesBeforeOperator: ~
|
||||
location:
|
||||
row: 10
|
||||
column: 4
|
||||
end_location:
|
||||
row: 10
|
||||
column: 4
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
MultipleSpacesBeforeOperator: ~
|
||||
location:
|
||||
row: 13
|
||||
column: 8
|
||||
end_location:
|
||||
row: 13
|
||||
column: 8
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
MultipleSpacesBeforeOperator: ~
|
||||
location:
|
||||
row: 15
|
||||
column: 8
|
||||
end_location:
|
||||
row: 15
|
||||
column: 8
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
MultipleSpacesBeforeOperator: ~
|
||||
location:
|
||||
row: 19
|
||||
column: 13
|
||||
end_location:
|
||||
row: 19
|
||||
column: 13
|
||||
fix: ~
|
||||
parent: ~
|
||||
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
---
|
||||
source: src/rules/pycodestyle/mod.rs
|
||||
expression: diagnostics
|
||||
---
|
||||
- kind:
|
||||
MultipleSpacesAfterOperator: ~
|
||||
location:
|
||||
row: 28
|
||||
column: 7
|
||||
end_location:
|
||||
row: 28
|
||||
column: 7
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
MultipleSpacesAfterOperator: ~
|
||||
location:
|
||||
row: 31
|
||||
column: 3
|
||||
end_location:
|
||||
row: 31
|
||||
column: 3
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
MultipleSpacesAfterOperator: ~
|
||||
location:
|
||||
row: 32
|
||||
column: 3
|
||||
end_location:
|
||||
row: 32
|
||||
column: 3
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
MultipleSpacesAfterOperator: ~
|
||||
location:
|
||||
row: 35
|
||||
column: 6
|
||||
end_location:
|
||||
row: 35
|
||||
column: 6
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
MultipleSpacesAfterOperator: ~
|
||||
location:
|
||||
row: 36
|
||||
column: 6
|
||||
end_location:
|
||||
row: 36
|
||||
column: 6
|
||||
fix: ~
|
||||
parent: ~
|
||||
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
---
|
||||
source: src/rules/pycodestyle/mod.rs
|
||||
expression: diagnostics
|
||||
---
|
||||
- kind:
|
||||
TabBeforeOperator: ~
|
||||
location:
|
||||
row: 43
|
||||
column: 1
|
||||
end_location:
|
||||
row: 43
|
||||
column: 1
|
||||
fix: ~
|
||||
parent: ~
|
||||
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
---
|
||||
source: src/rules/pycodestyle/mod.rs
|
||||
expression: diagnostics
|
||||
---
|
||||
- kind:
|
||||
TabAfterOperator: ~
|
||||
location:
|
||||
row: 48
|
||||
column: 4
|
||||
end_location:
|
||||
row: 48
|
||||
column: 4
|
||||
fix: ~
|
||||
parent: ~
|
||||
|
||||
|
|
@ -112,6 +112,10 @@ impl Indentation {
|
|||
pub fn as_str(&self) -> &str {
|
||||
self.0.as_str()
|
||||
}
|
||||
|
||||
pub fn as_char(&self) -> char {
|
||||
self.0.chars().next().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for Indentation {
|
||||
|
|
|
|||
Loading…
Reference in New Issue