Compare commits

..

8 Commits

Author SHA1 Message Date
Charlie Marsh
7460ca28dd Fixup 2023-03-09 16:25:38 -05:00
AreamanM
56e8a4fd14 cleanup plc1901 impl 2023-03-09 15:49:09 -05:00
AreamanM
67444143b5 merge with main 2023-03-09 15:49:09 -05:00
AreamanM
daeb3ff37e fix cargo fmt warning 2023-03-09 15:49:09 -05:00
AreamanM
2f3734dd22 update test for plc1901 2023-03-09 15:49:09 -05:00
AreamanM
fc50d28fcf cleanup impl for plc1901 2023-03-09 15:49:09 -05:00
AreamanM
158dc5e7d4 update crate::ast:: to ruff_python_ast:: 2023-03-09 15:49:09 -05:00
AreamanM
9a42be8a90 rough implementation of c1901 2023-03-09 15:49:09 -05:00
142 changed files with 947 additions and 3700 deletions

View File

@@ -79,10 +79,6 @@ jobs:
env:
# Setting RUSTDOCFLAGS because `cargo doc --check` isn't yet implemented (https://github.com/rust-lang/cargo/issues/10025).
RUSTDOCFLAGS: "-D warnings"
- uses: actions/upload-artifact@v3
with:
name: ruff
path: target/debug/ruff
cargo-test-wasm:
@@ -127,39 +123,3 @@ jobs:
- uses: crate-ci/typos@master
with:
files: .
ecosystem:
name: "ecosystem"
runs-on: ubuntu-latest
needs: cargo-test
# Only runs on pull requests, since that is the only we way we can find the base version for comparison.
if: github.event_name == 'pull_request'
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: "3.11"
- uses: actions/download-artifact@v3
id: ruff-target
with:
name: ruff
path: target/debug
- uses: dawidd6/action-download-artifact@v2
with:
name: ruff
branch: ${{ github.event.pull_request.base_ref }}
check_artifacts: true
- name: Run ecosystem check
run: |
# Make executable, since artifact download doesn't preserve this
chmod +x ruff ${{ steps.ruff-target.outputs.download-path }}/ruff
scripts/check_ecosystem.py ruff ${{ steps.ruff-target.outputs.download-path }}/ruff | tee ecosystem-result
echo ${{ github.event.number }} > pr-number
- uses: actions/upload-artifact@v3
with:
name: ecosystem-result
path: |
ecosystem-result
pr-number

View File

@@ -1,31 +0,0 @@
on:
workflow_run:
workflows: [CI]
types: [completed]
permissions:
pull-requests: write
jobs:
comment:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: dawidd6/action-download-artifact@v2
id: download-result
with:
name: ecosystem-result
workflow: ci.yaml
run_id: ${{ github.event.workflow_run.id }}
if_no_artifact_found: ignore
- if: steps.download-result.outputs.found_artifact
id: result
run: |
echo "pr-number=$(<pr-number)" >> $GITHUB_OUTPUT
- name: Create comment
if: steps.download-result.outputs.found_artifact
uses: thollander/actions-comment-pull-request@v2
with:
pr_number: ${{ steps.result.outputs.pr-number }}
filePath: ecosystem-result
comment_tag: ecosystem-results

47
Cargo.lock generated
View File

@@ -769,7 +769,7 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
[[package]]
name = "flake8-to-ruff"
version = "0.0.255"
version = "0.0.254"
dependencies = [
"anyhow",
"clap 4.1.8",
@@ -1503,12 +1503,6 @@ dependencies = [
"once_cell",
]
[[package]]
name = "pathdiff"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd"
[[package]]
name = "peg"
version = "0.8.1"
@@ -1536,18 +1530,6 @@ version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9fa00462b37ead6d11a82c9d568b26682d78e0477dc02d1966c013af80969739"
[[package]]
name = "pep440_rs"
version = "0.2.0"
source = "git+https://github.com/konstin/pep440-rs.git?rev=a8fef4ec47f4c25b070b39cdbe6a0b9847e49941#a8fef4ec47f4c25b070b39cdbe6a0b9847e49941"
dependencies = [
"lazy_static",
"regex",
"serde",
"tracing",
"unicode-width",
]
[[package]]
name = "percent-encoding"
version = "2.2.0"
@@ -1971,7 +1953,7 @@ dependencies = [
[[package]]
name = "ruff"
version = "0.0.255"
version = "0.0.254"
dependencies = [
"anyhow",
"bisection",
@@ -1997,9 +1979,6 @@ dependencies = [
"num-traits",
"once_cell",
"path-absolutize",
"pathdiff",
"pep440_rs",
"pretty_assertions",
"regex",
"result-like",
"ruff_cache",
@@ -2037,7 +2016,7 @@ dependencies = [
[[package]]
name = "ruff_cli"
version = "0.0.255"
version = "0.0.254"
dependencies = [
"annotate-snippets 0.9.1",
"anyhow",
@@ -2282,7 +2261,7 @@ dependencies = [
[[package]]
name = "rustpython-ast"
version = "0.2.0"
source = "git+https://github.com/RustPython/RustPython.git?rev=c15f670f2c30cfae6b41a1874893590148c74bc4#c15f670f2c30cfae6b41a1874893590148c74bc4"
source = "git+https://github.com/RustPython/RustPython.git?rev=1871a1632e310985414211222f5bf8069678892f#1871a1632e310985414211222f5bf8069678892f"
dependencies = [
"num-bigint",
"rustpython-compiler-core",
@@ -2291,7 +2270,7 @@ dependencies = [
[[package]]
name = "rustpython-common"
version = "0.2.0"
source = "git+https://github.com/RustPython/RustPython.git?rev=c15f670f2c30cfae6b41a1874893590148c74bc4#c15f670f2c30cfae6b41a1874893590148c74bc4"
source = "git+https://github.com/RustPython/RustPython.git?rev=1871a1632e310985414211222f5bf8069678892f#1871a1632e310985414211222f5bf8069678892f"
dependencies = [
"ascii",
"bitflags",
@@ -2316,7 +2295,7 @@ dependencies = [
[[package]]
name = "rustpython-compiler-core"
version = "0.2.0"
source = "git+https://github.com/RustPython/RustPython.git?rev=c15f670f2c30cfae6b41a1874893590148c74bc4#c15f670f2c30cfae6b41a1874893590148c74bc4"
source = "git+https://github.com/RustPython/RustPython.git?rev=1871a1632e310985414211222f5bf8069678892f#1871a1632e310985414211222f5bf8069678892f"
dependencies = [
"bitflags",
"bstr 0.2.17",
@@ -2330,7 +2309,7 @@ dependencies = [
[[package]]
name = "rustpython-parser"
version = "0.2.0"
source = "git+https://github.com/RustPython/RustPython.git?rev=c15f670f2c30cfae6b41a1874893590148c74bc4#c15f670f2c30cfae6b41a1874893590148c74bc4"
source = "git+https://github.com/RustPython/RustPython.git?rev=1871a1632e310985414211222f5bf8069678892f#1871a1632e310985414211222f5bf8069678892f"
dependencies = [
"ahash",
"anyhow",
@@ -2855,21 +2834,9 @@ checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8"
dependencies = [
"cfg-if",
"pin-project-lite",
"tracing-attributes",
"tracing-core",
]
[[package]]
name = "tracing-attributes"
version = "0.1.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "tracing-core"
version = "0.1.30"

View File

@@ -26,11 +26,11 @@ proc-macro2 = { version = "1.0.51" }
quote = { version = "1.0.23" }
regex = { version = "1.7.1" }
rustc-hash = { version = "1.1.0" }
rustpython-common = { git = "https://github.com/RustPython/RustPython.git", rev = "c15f670f2c30cfae6b41a1874893590148c74bc4" }
rustpython-common = { git = "https://github.com/RustPython/RustPython.git", rev = "1871a1632e310985414211222f5bf8069678892f" }
rustpython-parser = { features = [
"lalrpop",
"serde",
], git = "https://github.com/RustPython/RustPython.git", rev = "c15f670f2c30cfae6b41a1874893590148c74bc4" }
], git = "https://github.com/RustPython/RustPython.git", rev = "1871a1632e310985414211222f5bf8069678892f" }
schemars = { version = "0.8.12" }
serde = { version = "1.0.152", features = ["derive"] }
serde_json = { version = "1.0.93" }

View File

@@ -137,7 +137,7 @@ Ruff can also be used as a [pre-commit](https://pre-commit.com) hook:
```yaml
- repo: https://github.com/charliermarsh/ruff-pre-commit
# Ruff version.
rev: 'v0.0.255'
rev: 'v0.0.254'
hooks:
- id: ruff
```
@@ -306,9 +306,6 @@ Ruff is used in a number of major open-source projects, including:
- [meson-python](https://github.com/mesonbuild/meson-python)
- [ZenML](https://github.com/zenml-io/zenml)
- [delta-rs](https://github.com/delta-io/delta-rs)
- [Starlite](https://github.com/starlite-api/starlite)
- [telemetry-airflow (Mozilla)](https://github.com/mozilla/telemetry-airflow)
- [Stable Baselines3](https://github.com/DLR-RM/stable-baselines3)
## License

View File

@@ -1,6 +1,6 @@
[package]
name = "flake8-to-ruff"
version = "0.0.255"
version = "0.0.254"
edition = { workspace = true }
rust-version = { workspace = true }

View File

@@ -46,15 +46,8 @@ fn main() -> Result<()> {
.map(|tool| ExternalConfig {
black: tool.black.as_ref(),
isort: tool.isort.as_ref(),
..Default::default()
})
.unwrap_or_default();
let external_config = ExternalConfig {
project: pyproject
.as_ref()
.and_then(|pyproject| pyproject.project.as_ref()),
..external_config
};
// Create Ruff's pyproject.toml section.
let pyproject = flake8_to_ruff::convert(&config, &external_config, args.plugin)?;

View File

@@ -1,6 +1,6 @@
[package]
name = "ruff"
version = "0.0.255"
version = "0.0.254"
authors = ["Charlie Marsh <charlie.r.marsh@gmail.com>"]
edition = { workspace = true }
rust-version = { workspace = true }
@@ -12,6 +12,8 @@ license = "MIT"
[lib]
name = "ruff"
crate-type = ["cdylib", "rlib"]
doctest = false
[dependencies]
ruff_cache = { path = "../ruff_cache" }
@@ -46,10 +48,6 @@ path-absolutize = { workspace = true, features = [
"once_cell_cache",
"use_unix_paths_on_wasm",
] }
pathdiff = { version = "0.2.1" }
pep440_rs = { git = "https://github.com/konstin/pep440-rs.git", features = [
"serde",
], rev = "a8fef4ec47f4c25b070b39cdbe6a0b9847e49941" }
regex = { workspace = true }
result-like = { version = "0.4.6" }
rustc-hash = { workspace = true }
@@ -67,10 +65,9 @@ thiserror = { version = "1.0.38" }
toml = { workspace = true }
[dev-dependencies]
criterion = { version = "0.4.0" }
insta = { workspace = true, features = ["yaml", "redactions"] }
pretty_assertions = "1.3.0"
test-case = { workspace = true }
criterion = { version = "0.4.0" }
[features]

View File

@@ -6,8 +6,6 @@ obj.endswith("foo") or obj.endswith("bar")
obj.startswith(foo) or obj.startswith(bar)
# error
obj.startswith(foo) or obj.startswith("foo")
# error
obj.endswith(foo) or obj.startswith(foo) or obj.startswith("foo")
# ok
obj.startswith(("foo", "bar"))

View File

@@ -61,43 +61,3 @@ def f22(
x: complex = -42.5j # Error PYI011 Only simple default values allowed for typed arguments
+ 4.3j,
) -> None: ...
def f23(
x: bool = True, # OK
) -> None: ...
def f24(
x: float = 3.14, # OK
) -> None: ...
def f25(
x: float = -3.14, # OK
) -> None: ...
def f26(
x: complex = -3.14j, # OK
) -> None: ...
def f27(
x: complex = -3 - 3.14j, # OK
) -> None: ...
def f28(
x: float = math.tau, # OK
) -> None: ...
def f29(
x: float = math.inf, # OK
) -> None: ...
def f30(
x: float = -math.inf, # OK
) -> None: ...
def f31(
x: float = inf, # Error PYI011 Only simple default values allowed for typed arguments
) -> None: ...
def f32(
x: float = np.inf, # Error PYI011 Only simple default values allowed for typed arguments
) -> None: ...
def f33(
x: float = math.nan, # OK
) -> None: ...
def f34(
x: float = -math.nan, # Error PYI011 Only simple default values allowed for typed arguments
) -> None: ...
def f35(
x: complex = math.inf # Error PYI011 Only simple default values allowed for typed arguments
+ 1j,
) -> None: ...

View File

@@ -43,6 +43,3 @@ def f21(
def f22(
x=-42.5j + 4.3j, # Error PYI014
) -> None: ...
def f23(
x=True, # OK
) -> None: ...

View File

@@ -1,8 +0,0 @@
import os
tempVar = os.getenv("TEST", 12) # [invalid-envvar-default]
goodVar = os.getenv("TESTING", None)
dictVarBad = os.getenv("AAA", {"a", 7}) # [invalid-envvar-default]
print(os.getenv("TEST", False)) # [invalid-envvar-default]
os.getenv("AA", "GOOD")
os.getenv("B", Z)

View File

@@ -1,12 +0,0 @@
import os
os.getenv(1) # [invalid-envvar-value]
os.getenv("a")
os.getenv("test")
os.getenv(key="testingAgain")
os.getenv(key=11) # [invalid-envvar-value]
os.getenv(["hello"]) # [invalid-envvar-value]
os.getenv(key="foo", default="bar")
AA = "aa"
os.getenv(AA)

View File

@@ -1,10 +0,0 @@
import socket
from kombu import Connection, exceptions
try:
conn = Connection(settings.CELERY_BROKER_URL)
conn.ensure_connection(max_retries=2)
conn._close()
except (socket.error, exceptions.OperationalError):
return HttpResponseServerError("cache: cannot connect to broker.")

View File

@@ -41,7 +41,7 @@ if True:
Good,
)
from typing import Callable, Match, Pattern, List, OrderedDict, AbstractSet
from typing import Callable, Match, Pattern, List, OrderedDict
if True: from collections import (
Mapping, Counter)

View File

@@ -1,13 +0,0 @@
# noqa
# noqa # comment
print() # noqa
print() # noqa # comment
print(a) # noqa
print(a) # noqa # comment
# noqa: E501, F821
# noqa: E501, F821 # comment
print() # noqa: E501, F821
print() # noqa: E501, F821 # comment
print(a) # noqa: E501, F821
print(a) # noqa: E501, F821 # comment

View File

@@ -9,8 +9,8 @@ use rustpython_parser::{lexer, Mode, Tok};
use ruff_diagnostics::Fix;
use ruff_python_ast::helpers;
use ruff_python_ast::helpers::to_absolute;
use ruff_python_ast::newlines::NewlineWithTrailingNewline;
use ruff_python_ast::source_code::{Indexer, Locator, Stylist};
use ruff_python_ast::whitespace::LinesWithTrailingNewline;
use crate::cst::helpers::compose_module_path;
use crate::cst::matchers::match_module;
@@ -100,7 +100,7 @@ fn is_lone_child(child: &Stmt, parent: &Stmt, deleted: &[&Stmt]) -> Result<bool>
/// of a multi-statement line.
fn trailing_semicolon(stmt: &Stmt, locator: &Locator) -> Option<Location> {
let contents = locator.skip(stmt.end_location.unwrap());
for (row, line) in NewlineWithTrailingNewline::from(contents).enumerate() {
for (row, line) in LinesWithTrailingNewline::from(contents).enumerate() {
let trimmed = line.trim();
if trimmed.starts_with(';') {
let column = line
@@ -123,7 +123,7 @@ fn trailing_semicolon(stmt: &Stmt, locator: &Locator) -> Option<Location> {
fn next_stmt_break(semicolon: Location, locator: &Locator) -> Location {
let start_location = Location::new(semicolon.row(), semicolon.column() + 1);
let contents = locator.skip(start_location);
for (row, line) in NewlineWithTrailingNewline::from(contents).enumerate() {
for (row, line) in LinesWithTrailingNewline::from(contents).enumerate() {
let trimmed = line.trim();
// Skip past any continuations.
if trimmed.starts_with('\\') {

View File

@@ -1138,8 +1138,8 @@ where
if self.settings.rules.enabled(&Rule::RewriteCElementTree) {
pyupgrade::rules::replace_c_element_tree(self, stmt);
}
if self.settings.rules.enabled(&Rule::DeprecatedImport) {
pyupgrade::rules::deprecated_import(
if self.settings.rules.enabled(&Rule::ImportReplacements) {
pyupgrade::rules::import_replacements(
self,
stmt,
names,
@@ -1505,7 +1505,7 @@ where
}
if self.settings.rules.enabled(&Rule::OSErrorAlias) {
if let Some(item) = exc {
pyupgrade::rules::os_error_alias_raise(self, item);
pyupgrade::rules::os_error_alias(self, &item);
}
}
if self.settings.rules.enabled(&Rule::RaiseVanillaClass) {
@@ -1757,7 +1757,7 @@ where
flake8_bugbear::rules::redundant_tuple_in_exception_handler(self, handlers);
}
if self.settings.rules.enabled(&Rule::OSErrorAlias) {
pyupgrade::rules::os_error_alias_handlers(self, handlers);
pyupgrade::rules::os_error_alias(self, &handlers);
}
if self.settings.rules.enabled(&Rule::AssertInExcept) {
self.diagnostics.extend(
@@ -2484,7 +2484,7 @@ where
pyupgrade::rules::replace_stdout_stderr(self, expr, func, keywords);
}
if self.settings.rules.enabled(&Rule::OSErrorAlias) {
pyupgrade::rules::os_error_alias_call(self, func);
pyupgrade::rules::os_error_alias(self, &expr);
}
if self.settings.rules.enabled(&Rule::IsinstanceWithTuple)
&& self.settings.target_version >= PythonVersion::Py310
@@ -2866,12 +2866,6 @@ where
if self.settings.rules.enabled(&Rule::BadStrStripCall) {
pylint::rules::bad_str_strip_call(self, func, args);
}
if self.settings.rules.enabled(&Rule::InvalidEnvvarDefault) {
pylint::rules::invalid_envvar_default(self, func, args, keywords);
}
if self.settings.rules.enabled(&Rule::InvalidEnvvarValue) {
pylint::rules::invalid_envvar_value(self, func, args, keywords);
}
// flake8-pytest-style
if self.settings.rules.enabled(&Rule::PatchWithLambda) {
@@ -3441,7 +3435,7 @@ where
pylint::rules::merge_isinstance(self, expr, op, values);
}
if self.settings.rules.enabled(&Rule::SingleStartsEndsWith) {
flake8_pie::rules::single_starts_ends_with(self, expr);
flake8_pie::rules::single_starts_ends_with(self, values, op);
}
if self.settings.rules.enabled(&Rule::DuplicateIsinstanceCall) {
flake8_simplify::rules::duplicate_isinstance_call(self, expr);
@@ -4971,9 +4965,7 @@ impl<'a> Checker<'a> {
if let Some(diagnostic) =
flake8_type_checking::rules::runtime_import_in_type_checking_block(binding)
{
if self.settings.rules.enabled(diagnostic.kind.rule()) {
diagnostics.push(diagnostic);
}
diagnostics.push(diagnostic);
}
if let Some(diagnostic) =
flake8_type_checking::rules::typing_only_runtime_import(

View File

@@ -5,7 +5,6 @@ use nohash_hasher::IntMap;
use rustpython_parser::ast::Location;
use ruff_diagnostics::{Diagnostic, Fix};
use ruff_python_ast::newlines::StrExt;
use ruff_python_ast::types::Range;
use crate::codes::NoqaCode;
@@ -23,7 +22,7 @@ pub fn check_noqa(
noqa_line_for: &IntMap<usize, usize>,
settings: &Settings,
autofix: flags::Autofix,
) -> Vec<usize> {
) {
let enforce_noqa = settings.rules.enabled(&Rule::UnusedNOQA);
// Whether the file is exempted from all checks.
@@ -39,7 +38,7 @@ pub fn check_noqa(
// Indices of diagnostics that were ignored by a `noqa` directive.
let mut ignored_diagnostics = vec![];
let lines: Vec<&str> = contents.universal_newlines().collect();
let lines: Vec<&str> = contents.lines().collect();
for lineno in commented_lines {
match extract_file_exemption(lines[lineno - 1]) {
Exemption::All => {
@@ -98,7 +97,7 @@ pub fn check_noqa(
ignored_diagnostics.push(index);
continue;
}
(Directive::Codes(.., codes, _), matches) => {
(Directive::Codes(.., codes), matches) => {
if noqa::includes(diagnostic.kind.rule(), codes) {
matches.push(diagnostic.kind.rule().noqa_code());
ignored_diagnostics.push(index);
@@ -125,7 +124,7 @@ pub fn check_noqa(
ignored_diagnostics.push(index);
continue;
}
(Directive::Codes(.., codes, _), matches) => {
(Directive::Codes(.., codes), matches) => {
if noqa::includes(diagnostic.kind.rule(), codes) {
matches.push(diagnostic.kind.rule().noqa_code());
ignored_diagnostics.push(index);
@@ -141,7 +140,7 @@ pub fn check_noqa(
if enforce_noqa {
for (row, (directive, matches)) in noqa_directives {
match directive {
Directive::All(leading_spaces, start_byte, end_byte, trailing_spaces) => {
Directive::All(spaces, start_byte, end_byte) => {
if matches.is_empty() {
let start = lines[row][..start_byte].chars().count();
let end = start + lines[row][start_byte..end_byte].chars().count();
@@ -151,27 +150,15 @@ pub fn check_noqa(
Range::new(Location::new(row + 1, start), Location::new(row + 1, end)),
);
if autofix.into() && settings.rules.should_fix(diagnostic.kind.rule()) {
if start - leading_spaces == 0 && end == lines[row].chars().count() {
diagnostic.amend(Fix::deletion(
Location::new(row + 1, 0),
Location::new(row + 2, 0),
));
} else if end == lines[row].chars().count() {
diagnostic.amend(Fix::deletion(
Location::new(row + 1, start - leading_spaces),
Location::new(row + 1, end + trailing_spaces),
));
} else {
diagnostic.amend(Fix::deletion(
Location::new(row + 1, start),
Location::new(row + 1, end + trailing_spaces),
));
}
diagnostic.amend(Fix::deletion(
Location::new(row + 1, start - spaces),
Location::new(row + 1, lines[row].chars().count()),
));
}
diagnostics.push(diagnostic);
}
}
Directive::Codes(leading_spaces, start_byte, end_byte, codes, trailing_spaces) => {
Directive::Codes(spaces, start_byte, end_byte, codes) => {
let mut disabled_codes = vec![];
let mut unknown_codes = vec![];
let mut unmatched_codes = vec![];
@@ -231,28 +218,15 @@ pub fn check_noqa(
);
if autofix.into() && settings.rules.should_fix(diagnostic.kind.rule()) {
if valid_codes.is_empty() {
if start - leading_spaces == 0 && end == lines[row].chars().count()
{
diagnostic.amend(Fix::deletion(
Location::new(row + 1, 0),
Location::new(row + 2, 0),
));
} else if end == lines[row].chars().count() {
diagnostic.amend(Fix::deletion(
Location::new(row + 1, start - leading_spaces),
Location::new(row + 1, end + trailing_spaces),
));
} else {
diagnostic.amend(Fix::deletion(
Location::new(row + 1, start),
Location::new(row + 1, end + trailing_spaces),
));
}
diagnostic.amend(Fix::deletion(
Location::new(row + 1, start - spaces),
Location::new(row + 1, lines[row].chars().count()),
));
} else {
diagnostic.amend(Fix::replacement(
format!("# noqa: {}", valid_codes.join(", ")),
Location::new(row + 1, start),
Location::new(row + 1, end),
Location::new(row + 1, lines[row].chars().count()),
));
}
}
@@ -265,5 +239,7 @@ pub fn check_noqa(
}
ignored_diagnostics.sort_unstable();
ignored_diagnostics
for index in ignored_diagnostics.iter().rev() {
diagnostics.swap_remove(*index);
}
}

View File

@@ -3,8 +3,7 @@
use std::path::Path;
use ruff_diagnostics::Diagnostic;
use ruff_python_ast::newlines::StrExt;
use ruff_python_ast::source_code::{Locator, Stylist};
use ruff_python_ast::source_code::Stylist;
use crate::registry::Rule;
use crate::rules::flake8_executable::helpers::{extract_shebang, ShebangDirective};
@@ -22,8 +21,8 @@ use crate::settings::{flags, Settings};
pub fn check_physical_lines(
path: &Path,
locator: &Locator,
stylist: &Stylist,
contents: &str,
commented_lines: &[usize],
doc_lines: &[usize],
settings: &Settings,
@@ -57,7 +56,7 @@ pub fn check_physical_lines(
let mut commented_lines_iter = commented_lines.iter().peekable();
let mut doc_lines_iter = doc_lines.iter().peekable();
for (index, line) in locator.contents().universal_newlines().enumerate() {
for (index, line) in contents.lines().enumerate() {
while commented_lines_iter
.next_if(|lineno| &(index + 1) == *lineno)
.is_some()
@@ -163,8 +162,8 @@ pub fn check_physical_lines(
if enforce_no_newline_at_end_of_file {
if let Some(diagnostic) = no_newline_at_end_of_file(
locator,
stylist,
contents,
autofix.into() && settings.rules.should_fix(&Rule::NoNewLineAtEndOfFile),
) {
diagnostics.push(diagnostic);
@@ -200,8 +199,8 @@ mod tests {
let check_with_max_line_length = |line_length: usize| {
check_physical_lines(
Path::new("foo.py"),
&locator,
&stylist,
line,
&[],
&[],
&Settings {

View File

@@ -161,37 +161,35 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<Rule> {
(Pyflakes, "901") => Rule::RaiseNotImplemented,
// pylint
(Pylint, "C0414") => Rule::UselessImportAlias,
(Pylint, "C1901") => Rule::CompareToEmptyString,
(Pylint, "C3002") => Rule::UnnecessaryDirectLambdaCall,
(Pylint, "E0100") => Rule::YieldInInit,
(Pylint, "E0101") => Rule::ReturnInInit,
(Pylint, "E0117") => Rule::NonlocalWithoutBinding,
(Pylint, "E0118") => Rule::UsedPriorGlobalDeclaration,
(Pylint, "E0604") => Rule::InvalidAllObject,
(Pylint, "E0605") => Rule::InvalidAllFormat,
(Pylint, "W1508") => Rule::InvalidEnvvarDefault,
(Pylint, "E1142") => Rule::AwaitOutsideAsync,
(Pylint, "E1205") => Rule::LoggingTooManyArgs,
(Pylint, "E1206") => Rule::LoggingTooFewArgs,
(Pylint, "E1307") => Rule::BadStringFormatType,
(Pylint, "E1310") => Rule::BadStrStripCall,
(Pylint, "E1507") => Rule::InvalidEnvvarValue,
(Pylint, "E2502") => Rule::BidirectionalUnicode,
(Pylint, "R0133") => Rule::ComparisonOfConstant,
(Pylint, "E1310") => Rule::BadStrStripCall,
(Pylint, "C0414") => Rule::UselessImportAlias,
(Pylint, "C3002") => Rule::UnnecessaryDirectLambdaCall,
(Pylint, "E0117") => Rule::NonlocalWithoutBinding,
(Pylint, "E0118") => Rule::UsedPriorGlobalDeclaration,
(Pylint, "E1142") => Rule::AwaitOutsideAsync,
(Pylint, "R0206") => Rule::PropertyWithParameters,
(Pylint, "R0402") => Rule::ConsiderUsingFromImport,
(Pylint, "R0911") => Rule::TooManyReturnStatements,
(Pylint, "R0912") => Rule::TooManyBranches,
(Pylint, "R0913") => Rule::TooManyArguments,
(Pylint, "R0915") => Rule::TooManyStatements,
(Pylint, "C1901") => Rule::CompareToEmptyString,
(Pylint, "R0133") => Rule::ComparisonOfConstant,
(Pylint, "R1701") => Rule::ConsiderMergingIsinstance,
(Pylint, "R1722") => Rule::ConsiderUsingSysExit,
(Pylint, "R2004") => Rule::MagicValueComparison,
(Pylint, "R5501") => Rule::CollapsibleElseIf,
(Pylint, "R2004") => Rule::MagicValueComparison,
(Pylint, "W0120") => Rule::UselessElseOnLoop,
(Pylint, "W0602") => Rule::GlobalVariableNotAssigned,
(Pylint, "W0603") => Rule::GlobalStatement,
(Pylint, "R0911") => Rule::TooManyReturnStatements,
(Pylint, "R0913") => Rule::TooManyArguments,
(Pylint, "R0912") => Rule::TooManyBranches,
(Pylint, "R0915") => Rule::TooManyStatements,
(Pylint, "W2901") => Rule::RedefinedLoopName,
// flake8-builtins
@@ -375,7 +373,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<Rule> {
(Pyupgrade, "032") => Rule::FString,
(Pyupgrade, "033") => Rule::FunctoolsCache,
(Pyupgrade, "034") => Rule::ExtraneousParentheses,
(Pyupgrade, "035") => Rule::DeprecatedImport,
(Pyupgrade, "035") => Rule::ImportReplacements,
(Pyupgrade, "036") => Rule::OutdatedVersionBlock,
(Pyupgrade, "037") => Rule::QuotedAnnotation,
(Pyupgrade, "038") => Rule::IsinstanceWithTuple,

View File

@@ -20,7 +20,6 @@ use crate::rules::{
};
use crate::settings::options::Options;
use crate::settings::pyproject::Pyproject;
use crate::settings::types::PythonVersion;
use crate::warn_user;
const DEFAULT_SELECTORS: &[RuleSelector] = &[
@@ -425,15 +424,6 @@ pub fn convert(
}
}
if let Some(project) = &external_config.project {
if let Some(requires_python) = &project.requires_python {
if options.target_version.is_none() {
options.target_version =
PythonVersion::get_minimum_supported_version(requires_python);
}
}
}
// Create the pyproject.toml.
Ok(Pyproject::new(options))
}
@@ -449,17 +439,13 @@ fn resolve_select(plugins: &[Plugin]) -> HashSet<RuleSelector> {
#[cfg(test)]
mod tests {
use std::collections::HashMap;
use std::str::FromStr;
use anyhow::Result;
use itertools::Itertools;
use pep440_rs::VersionSpecifiers;
use pretty_assertions::assert_eq;
use super::super::plugin::Plugin;
use super::convert;
use crate::flake8_to_ruff::converter::DEFAULT_SELECTORS;
use crate::flake8_to_ruff::pep621::Project;
use crate::flake8_to_ruff::ExternalConfig;
use crate::registry::Linter;
use crate::rule_selector::RuleSelector;
@@ -467,7 +453,6 @@ mod tests {
use crate::rules::{flake8_quotes, pydocstyle};
use crate::settings::options::Options;
use crate::settings::pyproject::Pyproject;
use crate::settings::types::PythonVersion;
fn default_options(plugins: impl IntoIterator<Item = RuleSelector>) -> Options {
Options {
@@ -624,25 +609,4 @@ mod tests {
Ok(())
}
#[test]
fn it_converts_project_requires_python() -> Result<()> {
let actual = convert(
&HashMap::from([("flake8".to_string(), HashMap::default())]),
&ExternalConfig {
project: Some(&Project {
requires_python: Some(VersionSpecifiers::from_str(">=3.8.16, <3.11")?),
}),
..ExternalConfig::default()
},
Some(vec![]),
)?;
let expected = Pyproject::new(Options {
target_version: Some(PythonVersion::Py38),
..default_options([])
});
assert_eq!(actual, expected);
Ok(())
}
}

View File

@@ -1,10 +1,8 @@
use super::black::Black;
use super::isort::Isort;
use super::pep621::Project;
#[derive(Default)]
pub struct ExternalConfig<'a> {
pub black: Option<&'a Black>,
pub isort: Option<&'a Isort>,
pub project: Option<&'a Project>,
}

View File

@@ -3,7 +3,6 @@ mod converter;
mod external_config;
mod isort;
mod parser;
pub mod pep621;
mod plugin;
mod pyproject;

View File

@@ -1,10 +0,0 @@
//! Extract PEP 621 configuration settings from a pyproject.toml.
use pep440_rs::VersionSpecifiers;
use serde::{Deserialize, Serialize};
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Default)]
pub struct Project {
#[serde(alias = "requires-python", alias = "requires_python")]
pub requires_python: Option<VersionSpecifiers>,
}

View File

@@ -5,7 +5,6 @@ use serde::{Deserialize, Serialize};
use super::black::Black;
use super::isort::Isort;
use super::pep621::Project;
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct Tools {
@@ -16,7 +15,6 @@ pub struct Tools {
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct Pyproject {
pub tool: Option<Tools>,
pub project: Option<Project>,
}
pub fn parse<P: AsRef<Path>>(path: P) -> Result<Pyproject> {

View File

@@ -74,20 +74,10 @@ pub fn normalize_path_to<P: AsRef<Path>, R: AsRef<Path>>(path: P, project_root:
}
/// Convert an absolute path to be relative to the current working directory.
pub fn relativize_path<P: AsRef<Path>>(path: P) -> String {
pub fn relativize_path(path: impl AsRef<Path>) -> String {
let path = path.as_ref();
if let Ok(path) = path.strip_prefix(&*path_dedot::CWD) {
return format!("{}", path.display());
}
format!("{}", path.display())
}
/// Convert an absolute path to be relative to the specified project root.
pub fn relativize_path_to<P: AsRef<Path>, R: AsRef<Path>>(path: P, project_root: R) -> String {
format!(
"{}",
pathdiff::diff_paths(&path, project_root)
.expect("Could not diff paths")
.display()
)
}

View File

@@ -191,8 +191,8 @@ pub fn check_path(
{
diagnostics.extend(check_physical_lines(
path,
locator,
stylist,
contents,
indexer.commented_lines(),
&doc_lines,
settings,
@@ -215,7 +215,7 @@ pub fn check_path(
.iter_enabled()
.any(|rule_code| rule_code.lint_source().is_noqa())
{
let ignored = check_noqa(
check_noqa(
&mut diagnostics,
contents,
indexer.commented_lines(),
@@ -223,11 +223,6 @@ pub fn check_path(
settings,
error.as_ref().map_or(autofix, |_| flags::Autofix::Disabled),
);
if noqa.into() {
for index in ignored.iter().rev() {
diagnostics.swap_remove(*index);
}
}
}
LinterResult::new(diagnostics, error)

View File

@@ -12,7 +12,6 @@ use rustc_hash::{FxHashMap, FxHashSet};
use rustpython_parser::ast::Location;
use ruff_diagnostics::Diagnostic;
use ruff_python_ast::newlines::StrExt;
use ruff_python_ast::source_code::{LineEnding, Locator};
use ruff_python_ast::types::Range;
@@ -22,7 +21,7 @@ use crate::rule_redirects::get_redirect_target;
static NOQA_LINE_REGEX: Lazy<Regex> = Lazy::new(|| {
Regex::new(
r"(?P<leading_spaces>\s*)(?P<noqa>(?i:# noqa)(?::\s?(?P<codes>(?:[A-Z]+[0-9]+)(?:[,\s]+[A-Z]+[0-9]+)*))?)(?P<trailing_spaces>\s*)",
r"(?P<spaces>\s*)(?P<noqa>(?i:# noqa)(?::\s?(?P<codes>([A-Z]+[0-9]+(?:[,\s]+)?)+))?)",
)
.unwrap()
});
@@ -74,42 +73,35 @@ pub fn extract_file_exemption(line: &str) -> Exemption {
#[derive(Debug)]
pub enum Directive<'a> {
None,
All(usize, usize, usize, usize),
Codes(usize, usize, usize, Vec<&'a str>, usize),
All(usize, usize, usize),
Codes(usize, usize, usize, Vec<&'a str>),
}
/// Extract the noqa `Directive` from a line of Python source code.
pub fn extract_noqa_directive(line: &str) -> Directive {
match NOQA_LINE_REGEX.captures(line) {
Some(caps) => match caps.name("leading_spaces") {
Some(leading_spaces) => match caps.name("trailing_spaces") {
Some(trailing_spaces) => match caps.name("noqa") {
Some(noqa) => match caps.name("codes") {
Some(codes) => {
let codes: Vec<&str> = SPLIT_COMMA_REGEX
.split(codes.as_str().trim())
.map(str::trim)
.filter(|code| !code.is_empty())
.collect();
if codes.is_empty() {
warn!("Expected rule codes on `noqa` directive: \"{line}\"");
}
Directive::Codes(
leading_spaces.as_str().chars().count(),
noqa.start(),
noqa.end(),
codes,
trailing_spaces.as_str().chars().count(),
)
Some(caps) => match caps.name("spaces") {
Some(spaces) => match caps.name("noqa") {
Some(noqa) => match caps.name("codes") {
Some(codes) => {
let codes: Vec<&str> = SPLIT_COMMA_REGEX
.split(codes.as_str().trim())
.map(str::trim)
.filter(|code| !code.is_empty())
.collect();
if codes.is_empty() {
warn!("Expected rule codes on `noqa` directive: \"{line}\"");
}
None => Directive::All(
leading_spaces.as_str().chars().count(),
Directive::Codes(
spaces.as_str().chars().count(),
noqa.start(),
noqa.end(),
trailing_spaces.as_str().chars().count(),
),
},
None => Directive::None,
codes,
)
}
None => {
Directive::All(spaces.as_str().chars().count(), noqa.start(), noqa.end())
}
},
None => Directive::None,
},
@@ -143,7 +135,7 @@ pub fn rule_is_ignored(
match extract_noqa_directive(line) {
Directive::None => false,
Directive::All(..) => true,
Directive::Codes(.., codes, _) => includes(code, &codes),
Directive::Codes(.., codes) => includes(code, &codes),
}
}
@@ -182,7 +174,7 @@ fn add_noqa_inner(
// Codes that are globally exempted (within the current file).
let mut file_exemptions: Vec<NoqaCode> = vec![];
let lines: Vec<&str> = contents.universal_newlines().collect();
let lines: Vec<&str> = contents.lines().collect();
for lineno in commented_lines {
match extract_file_exemption(lines[lineno - 1]) {
Exemption::All => {
@@ -224,7 +216,7 @@ fn add_noqa_inner(
Directive::All(..) => {
continue;
}
Directive::Codes(.., codes, _) => {
Directive::Codes(.., codes) => {
if includes(diagnostic.kind.rule(), &codes) {
continue;
}
@@ -244,7 +236,7 @@ fn add_noqa_inner(
Directive::All(..) => {
continue;
}
Directive::Codes(.., codes, _) => {
Directive::Codes(.., codes) => {
if includes(diagnostic.kind.rule(), &codes) {
continue;
}
@@ -264,7 +256,7 @@ fn add_noqa_inner(
let mut count: usize = 0;
let mut output = String::new();
for (lineno, line) in lines.into_iter().enumerate() {
for (lineno, line) in contents.lines().enumerate() {
match matches_by_line.get(&lineno) {
None => {
output.push_str(line);
@@ -289,7 +281,7 @@ fn add_noqa_inner(
output.push_str(line);
output.push_str(line_ending);
}
Directive::Codes(_, start_byte, _, existing, _) => {
Directive::Codes(_, start_byte, _, existing) => {
// Reconstruct the line based on the preserved rule codes.
// This enables us to tally the number of edits.
let mut formatted = String::with_capacity(line.len());

View File

@@ -146,8 +146,6 @@ ruff_macros::register_rules!(
rules::pylint::rules::YieldInInit,
rules::pylint::rules::InvalidAllObject,
rules::pylint::rules::InvalidAllFormat,
rules::pylint::rules::InvalidEnvvarDefault,
rules::pylint::rules::InvalidEnvvarValue,
rules::pylint::rules::BadStringFormatType,
rules::pylint::rules::BidirectionalUnicode,
rules::pylint::rules::BadStrStripCall,
@@ -341,7 +339,7 @@ ruff_macros::register_rules!(
rules::pyupgrade::rules::FString,
rules::pyupgrade::rules::FunctoolsCache,
rules::pyupgrade::rules::ExtraneousParentheses,
rules::pyupgrade::rules::DeprecatedImport,
rules::pyupgrade::rules::ImportReplacements,
rules::pyupgrade::rules::OutdatedVersionBlock,
rules::pyupgrade::rules::QuotedAnnotation,
rules::pyupgrade::rules::IsinstanceWithTuple,
@@ -805,7 +803,7 @@ impl Linter {
}
}
#[derive(is_macro::Is, Copy, Clone)]
#[derive(is_macro::Is)]
pub enum LintSource {
Ast,
Io,
@@ -820,9 +818,9 @@ pub enum LintSource {
impl Rule {
/// The source for the diagnostic (either the AST, the filesystem, or the
/// physical lines).
pub const fn lint_source(&self) -> LintSource {
pub const fn lint_source(&self) -> &'static LintSource {
match self {
Rule::UnusedNOQA => LintSource::Noqa,
Rule::UnusedNOQA => &LintSource::Noqa,
Rule::BlanketNOQA
| Rule::BlanketTypeIgnore
| Rule::DocLineTooLong
@@ -838,7 +836,7 @@ impl Rule {
| Rule::ShebangWhitespace
| Rule::TrailingWhitespace
| Rule::IndentationContainsTabs
| Rule::BlankLineContainsWhitespace => LintSource::PhysicalLines,
| Rule::BlankLineContainsWhitespace => &LintSource::PhysicalLines,
Rule::AmbiguousUnicodeCharacterComment
| Rule::AmbiguousUnicodeCharacterDocstring
| Rule::AmbiguousUnicodeCharacterString
@@ -857,10 +855,10 @@ impl Rule {
| Rule::UselessSemicolon
| Rule::MultipleStatementsOnOneLineSemicolon
| Rule::TrailingCommaProhibited
| Rule::TypeCommentInStub => LintSource::Tokens,
Rule::IOError => LintSource::Io,
Rule::UnsortedImports | Rule::MissingRequiredImport => LintSource::Imports,
Rule::ImplicitNamespacePackage | Rule::InvalidModuleName => LintSource::Filesystem,
| Rule::TypeCommentInStub => &LintSource::Tokens,
Rule::IOError => &LintSource::Io,
Rule::UnsortedImports | Rule::MissingRequiredImport => &LintSource::Imports,
Rule::ImplicitNamespacePackage | Rule::InvalidModuleName => &LintSource::Filesystem,
#[cfg(feature = "logical_lines")]
Rule::IndentationWithInvalidMultiple
| Rule::IndentationWithInvalidMultipleComment
@@ -892,8 +890,8 @@ impl Rule {
| Rule::WhitespaceAfterOpenBracket
| Rule::WhitespaceBeforeCloseBracket
| Rule::WhitespaceBeforeParameters
| Rule::WhitespaceBeforePunctuation => LintSource::LogicalLines,
_ => LintSource::Ast,
| Rule::WhitespaceBeforePunctuation => &LintSource::LogicalLines,
_ => &LintSource::Ast,
}
}
}

View File

@@ -18,7 +18,9 @@ static REDIRECTS: Lazy<HashMap<&'static str, &'static str>> = Lazy::new(|| {
// The following are here because we don't yet have the many-to-one mapping enabled.
("SIM111", "SIM110"),
// The following are deprecated.
("C", "C4"),
("C9", "C90"),
("T", "T10"),
("T1", "T10"),
("T2", "T20"),
// TODO(charlie): Remove by 2023-02-01.

View File

@@ -15,17 +15,9 @@ use crate::rule_redirects::get_redirect;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum RuleSelector {
/// Select all rules.
/// All rules
All,
/// Legacy category to select both the `mccabe` and `flake8-comprehensions` linters
/// via a single selector.
C,
/// Legacy category to select both the `flake8-debugger` and `flake8-print` linters
/// via a single selector.
T,
/// Select all rules for a given linter.
Linter(Linter),
/// Select all rules for a given linter with a given prefix.
Prefix {
prefix: RuleCodePrefix,
redirected_from: Option<&'static str>,
@@ -44,10 +36,6 @@ impl FromStr for RuleSelector {
fn from_str(s: &str) -> Result<Self, Self::Err> {
if s == "ALL" {
Ok(Self::All)
} else if s == "C" {
Ok(Self::C)
} else if s == "T" {
Ok(Self::T)
} else {
let (s, redirected_from) = match get_redirect(s) {
Some((from, target)) => (target, Some(from)),
@@ -82,8 +70,6 @@ impl RuleSelector {
pub fn prefix_and_code(&self) -> (&'static str, &'static str) {
match self {
RuleSelector::All => ("", "ALL"),
RuleSelector::C => ("", "C"),
RuleSelector::T => ("", "T"),
RuleSelector::Prefix { prefix, .. } => {
(prefix.linter().common_prefix(), prefix.short_code())
}
@@ -152,16 +138,6 @@ impl IntoIterator for &RuleSelector {
fn into_iter(self) -> Self::IntoIter {
match self {
RuleSelector::All => RuleSelectorIter::All(Rule::iter()),
RuleSelector::C => RuleSelectorIter::Chain(
Linter::Flake8Comprehensions
.into_iter()
.chain(Linter::McCabe.into_iter()),
),
RuleSelector::T => RuleSelectorIter::Chain(
Linter::Flake8Debugger
.into_iter()
.chain(Linter::Flake8Print.into_iter()),
),
RuleSelector::Linter(linter) => RuleSelectorIter::Vec(linter.into_iter()),
RuleSelector::Prefix { prefix, .. } => RuleSelectorIter::Vec(prefix.into_iter()),
}
@@ -170,7 +146,6 @@ impl IntoIterator for &RuleSelector {
pub enum RuleSelectorIter {
All(RuleIter),
Chain(std::iter::Chain<std::vec::IntoIter<Rule>, std::vec::IntoIter<Rule>>),
Vec(std::vec::IntoIter<Rule>),
}
@@ -180,14 +155,13 @@ impl Iterator for RuleSelectorIter {
fn next(&mut self) -> Option<Self::Item> {
match self {
RuleSelectorIter::All(iter) => iter.next(),
RuleSelectorIter::Chain(iter) => iter.next(),
RuleSelectorIter::Vec(iter) => iter.next(),
}
}
}
/// A const alternative to the `impl From<RuleCodePrefix> for RuleSelector`
/// to let us keep the fields of [`RuleSelector`] private.
// to let us keep the fields of RuleSelector private.
// Note that Rust doesn't yet support `impl const From<RuleCodePrefix> for
// RuleSelector` (see https://github.com/rust-lang/rust/issues/67792).
// TODO(martin): Remove once RuleSelector is an enum with Linter & Rule variants
@@ -203,7 +177,7 @@ impl JsonSchema for RuleSelector {
"RuleSelector".to_string()
}
fn json_schema(_gen: &mut schemars::gen::SchemaGenerator) -> Schema {
fn json_schema(_gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
Schema::Object(SchemaObject {
instance_type: Some(InstanceType::String.into()),
enum_values: Some(
@@ -247,8 +221,6 @@ impl RuleSelector {
pub(crate) fn specificity(&self) -> Specificity {
match self {
RuleSelector::All => Specificity::All,
RuleSelector::T => Specificity::LinterGroup,
RuleSelector::C => Specificity::LinterGroup,
RuleSelector::Linter(..) => Specificity::Linter,
RuleSelector::Prefix { prefix, .. } => {
let prefix: &'static str = prefix.short_code();
@@ -268,7 +240,6 @@ impl RuleSelector {
#[derive(EnumIter, PartialEq, Eq, PartialOrd, Ord)]
pub(crate) enum Specificity {
All,
LinterGroup,
Linter,
Code1Char,
Code2Chars,

View File

@@ -108,7 +108,7 @@ pub fn bad_file_permissions(
.map_or(false, |call_path| call_path.as_slice() == ["os", "chmod"])
{
let call_args = SimpleCallArgs::new(args, keywords);
if let Some(mode_arg) = call_args.argument("mode", 1) {
if let Some(mode_arg) = call_args.get_argument("mode", Some(1)) {
if let Some(int_value) = get_int_value(mode_arg) {
if (int_value & WRITE_WORLD > 0) || (int_value & EXECUTE_GROUP > 0) {
checker.diagnostics.push(Diagnostic::new(

View File

@@ -25,7 +25,7 @@ impl Violation for HashlibInsecureHashFunction {
const WEAK_HASHES: [&str; 4] = ["md4", "md5", "sha", "sha1"];
fn is_used_for_security(call_args: &SimpleCallArgs) -> bool {
match call_args.keyword_argument("usedforsecurity") {
match call_args.get_argument("usedforsecurity", None) {
Some(expr) => !matches!(
&expr.node,
ExprKind::Constant {
@@ -67,7 +67,7 @@ pub fn hashlib_insecure_hash_functions(
return;
}
if let Some(name_arg) = call_args.argument("name", 0) {
if let Some(name_arg) = call_args.get_argument("name", Some(0)) {
if let Some(hash_func_name) = string_literal(name_arg) {
if WEAK_HASHES.contains(&hash_func_name.to_lowercase().as_str()) {
checker.diagnostics.push(Diagnostic::new(

View File

@@ -46,7 +46,7 @@ pub fn jinja2_autoescape_false(
{
let call_args = SimpleCallArgs::new(args, keywords);
if let Some(autoescape_arg) = call_args.keyword_argument("autoescape") {
if let Some(autoescape_arg) = call_args.get_argument("autoescape", None) {
match &autoescape_arg.node {
ExprKind::Constant {
value: Constant::Bool(true),

View File

@@ -33,7 +33,7 @@ pub fn logging_config_insecure_listen(
{
let call_args = SimpleCallArgs::new(args, keywords);
if call_args.keyword_argument("verify").is_none() {
if call_args.get_argument("verify", None).is_none() {
checker.diagnostics.push(Diagnostic::new(
LoggingConfigInsecureListen,
Range::from(func),

View File

@@ -56,7 +56,7 @@ pub fn request_with_no_cert_validation(
None
}) {
let call_args = SimpleCallArgs::new(args, keywords);
if let Some(verify_arg) = call_args.keyword_argument("verify") {
if let Some(verify_arg) = call_args.get_argument("verify", None) {
if let ExprKind::Constant {
value: Constant::Bool(false),
..

View File

@@ -44,7 +44,7 @@ pub fn request_without_timeout(
})
{
let call_args = SimpleCallArgs::new(args, keywords);
if let Some(timeout_arg) = call_args.keyword_argument("timeout") {
if let Some(timeout_arg) = call_args.get_argument("timeout", None) {
if let Some(timeout) = match &timeout_arg.node {
ExprKind::Constant {
value: value @ Constant::None,

View File

@@ -33,7 +33,7 @@ pub fn snmp_insecure_version(
})
{
let call_args = SimpleCallArgs::new(args, keywords);
if let Some(mp_model_arg) = call_args.keyword_argument("mpModel") {
if let Some(mp_model_arg) = call_args.get_argument("mpModel", None) {
if let ExprKind::Constant {
value: Constant::Int(value),
..

View File

@@ -39,7 +39,7 @@ pub fn unsafe_yaml_load(checker: &mut Checker, func: &Expr, args: &[Expr], keywo
.map_or(false, |call_path| call_path.as_slice() == ["yaml", "load"])
{
let call_args = SimpleCallArgs::new(args, keywords);
if let Some(loader_arg) = call_args.argument("Loader", 1) {
if let Some(loader_arg) = call_args.get_argument("Loader", Some(1)) {
if !checker
.ctx
.resolve_call_path(loader_arg)

View File

@@ -1,4 +1,6 @@
#[derive(Debug, PartialEq, Eq)]
use serde::{Deserialize, Serialize};
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum DebuggerUsingType {
Call(String),
Import(String),

View File

@@ -146,7 +146,7 @@ pub fn logging_call(checker: &mut Checker, func: &Expr, args: &[Expr], keywords:
);
// G001 - G004
if let Some(format_arg) = call_args.argument("msg", 0) {
if let Some(format_arg) = call_args.get_argument("msg", Some(0)) {
check_msg(checker, format_arg);
}

View File

@@ -1,18 +1,12 @@
use itertools::Either::{Left, Right};
use std::collections::BTreeMap;
use std::iter;
use log::error;
use rustc_hash::FxHashSet;
use rustpython_parser::ast::{
Boolop, Constant, Expr, ExprContext, ExprKind, Keyword, Stmt, StmtKind,
};
use rustpython_parser::ast::{Boolop, Constant, Expr, ExprKind, Keyword, Stmt, StmtKind};
use ruff_diagnostics::{AlwaysAutofixableViolation, Violation};
use ruff_diagnostics::{Diagnostic, Fix};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::comparable::ComparableExpr;
use ruff_python_ast::helpers::{create_expr, match_trailing_comment, unparse_expr};
use ruff_python_ast::helpers::{match_trailing_comment, unparse_expr};
use ruff_python_ast::types::{Range, RefEquality};
use ruff_python_stdlib::identifiers::is_identifier;
use ruff_python_stdlib::keyword::KWLIST;
@@ -126,17 +120,12 @@ pub struct SingleStartsEndsWith {
pub attr: String,
}
impl AlwaysAutofixableViolation for SingleStartsEndsWith {
impl Violation for SingleStartsEndsWith {
#[derive_message_formats]
fn message(&self) -> String {
let SingleStartsEndsWith { attr } = self;
format!("Call `{attr}` once with a `tuple`")
}
fn autofix_title(&self) -> String {
let SingleStartsEndsWith { attr } = self;
format!("Merge into a single `{attr}` call")
}
}
#[violation]
@@ -403,116 +392,39 @@ pub fn no_unnecessary_dict_kwargs(checker: &mut Checker, expr: &Expr, kwargs: &[
}
/// PIE810
pub fn single_starts_ends_with(checker: &mut Checker, expr: &Expr) {
let ExprKind::BoolOp { op: Boolop::Or, values } = &expr.node else {
pub fn single_starts_ends_with(checker: &mut Checker, values: &[Expr], node: &Boolop) {
if *node != Boolop::Or {
return;
};
}
let mut duplicates = BTreeMap::new();
for (index, call) in values.iter().enumerate() {
let ExprKind::Call {
// Given `foo.startswith`, insert ("foo", "startswith") into the set.
let mut seen = FxHashSet::default();
for expr in values {
if let ExprKind::Call {
func,
args,
keywords,
..
} = &call.node else {
continue
};
if !(args.len() == 1 && keywords.is_empty()) {
continue;
}
let ExprKind::Attribute { value, attr, .. } = &func.node else {
continue
};
if attr != "startswith" && attr != "endswith" {
continue;
}
let ExprKind::Name { id: arg_name, .. } = &value.node else {
continue
};
duplicates
.entry((attr.as_str(), arg_name.as_str()))
.or_insert_with(Vec::new)
.push(index);
}
// Generate a `Diagnostic` for each duplicate.
for ((attr_name, arg_name), indices) in duplicates {
if indices.len() > 1 {
let mut diagnostic = Diagnostic::new(
SingleStartsEndsWith {
attr: attr_name.to_string(),
},
Range::from(expr),
);
if checker.patch(diagnostic.kind.rule()) {
let words: Vec<&Expr> = indices
.iter()
.map(|index| &values[*index])
.map(|expr| {
let ExprKind::Call { func: _, args, keywords: _} = &expr.node else {
unreachable!("{}", format!("Indices should only contain `{attr_name}` calls"))
};
args.get(0)
.unwrap_or_else(|| panic!("`{attr_name}` should have one argument"))
})
.collect();
let call = create_expr(ExprKind::Call {
func: Box::new(create_expr(ExprKind::Attribute {
value: Box::new(create_expr(ExprKind::Name {
id: arg_name.to_string(),
ctx: ExprContext::Load,
})),
attr: attr_name.to_string(),
ctx: ExprContext::Load,
})),
args: vec![create_expr(ExprKind::Tuple {
elts: words
.iter()
.flat_map(|value| {
if let ExprKind::Tuple { elts, .. } = &value.node {
Left(elts.iter())
} else {
Right(iter::once(*value))
}
})
.map(Clone::clone)
.collect(),
ctx: ExprContext::Load,
})],
keywords: vec![],
});
// Generate the combined `BoolOp`.
let mut call = Some(call);
let bool_op = create_expr(ExprKind::BoolOp {
op: Boolop::Or,
values: values
.iter()
.enumerate()
.filter_map(|(index, elt)| {
if indices.contains(&index) {
std::mem::take(&mut call)
} else {
Some(elt.clone())
}
})
.collect(),
});
diagnostic.amend(Fix::replacement(
unparse_expr(&bool_op, checker.stylist),
expr.location,
expr.end_location.unwrap(),
));
} = &expr.node
{
if !(args.len() == 1 && keywords.is_empty()) {
continue;
}
if let ExprKind::Attribute { value, attr, .. } = &func.node {
if attr != "startswith" && attr != "endswith" {
continue;
}
if let ExprKind::Name { id, .. } = &value.node {
if !seen.insert((id, attr)) {
checker.diagnostics.push(Diagnostic::new(
SingleStartsEndsWith {
attr: attr.to_string(),
},
Range::from(value),
));
}
}
}
checker.diagnostics.push(diagnostic);
}
}
}

View File

@@ -5,101 +5,53 @@ expression: diagnostics
- kind:
name: SingleStartsEndsWith
body: "Call `startswith` once with a `tuple`"
suggestion: "Merge into a single `startswith` call"
fixable: true
suggestion: ~
fixable: false
location:
row: 2
column: 0
column: 25
end_location:
row: 2
column: 46
fix:
content: "obj.startswith((\"foo\", \"bar\"))"
location:
row: 2
column: 0
end_location:
row: 2
column: 46
column: 28
fix: ~
parent: ~
- kind:
name: SingleStartsEndsWith
body: "Call `endswith` once with a `tuple`"
suggestion: "Merge into a single `endswith` call"
fixable: true
suggestion: ~
fixable: false
location:
row: 4
column: 0
column: 23
end_location:
row: 4
column: 42
fix:
content: "obj.endswith((\"foo\", \"bar\"))"
location:
row: 4
column: 0
end_location:
row: 4
column: 42
column: 26
fix: ~
parent: ~
- kind:
name: SingleStartsEndsWith
body: "Call `startswith` once with a `tuple`"
suggestion: "Merge into a single `startswith` call"
fixable: true
suggestion: ~
fixable: false
location:
row: 6
column: 0
column: 23
end_location:
row: 6
column: 42
fix:
content: "obj.startswith((foo, bar))"
location:
row: 6
column: 0
end_location:
row: 6
column: 42
column: 26
fix: ~
parent: ~
- kind:
name: SingleStartsEndsWith
body: "Call `startswith` once with a `tuple`"
suggestion: "Merge into a single `startswith` call"
fixable: true
suggestion: ~
fixable: false
location:
row: 8
column: 0
column: 23
end_location:
row: 8
column: 44
fix:
content: "obj.startswith((foo, \"foo\"))"
location:
row: 8
column: 0
end_location:
row: 8
column: 44
parent: ~
- kind:
name: SingleStartsEndsWith
body: "Call `startswith` once with a `tuple`"
suggestion: "Merge into a single `startswith` call"
fixable: true
location:
row: 10
column: 0
end_location:
row: 10
column: 65
fix:
content: "obj.endswith(foo) or obj.startswith((foo, \"foo\"))"
location:
row: 10
column: 0
end_location:
row: 10
column: 65
column: 26
fix: ~
parent: ~

View File

@@ -1,6 +1,7 @@
use std::fmt;
use rustpython_parser::ast::{Expr, ExprKind};
use serde::{Deserialize, Serialize};
use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation};
@@ -8,7 +9,7 @@ use ruff_python_ast::types::Range;
use crate::checkers::ast::Checker;
#[derive(Debug, PartialEq, Eq)]
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum VarKind {
TypeVar,
ParamSpec,

View File

@@ -28,14 +28,6 @@ impl Violation for ArgumentSimpleDefaults {
}
}
const ALLOWED_MATH_ATTRIBUTES_IN_DEFAULTS: &[&[&str]] = &[
&["math", "inf"],
&["math", "nan"],
&["math", "e"],
&["math", "pi"],
&["math", "tau"],
];
const ALLOWED_ATTRIBUTES_IN_DEFAULTS: &[&[&str]] = &[
&["sys", "stdin"],
&["sys", "stdout"],
@@ -69,74 +61,42 @@ fn is_valid_default_value_with_annotation(default: &Expr, checker: &Checker) ->
value: Constant::Bytes(..),
..
} => return checker.locator.slice(default).len() <= 50,
// Ex) `123`, `True`, `False`, `3.14`
ExprKind::Constant {
value: Constant::Int(..) | Constant::Bool(..) | Constant::Float(..),
value: Constant::Int(..),
..
} => {
return checker.locator.slice(default).len() <= 10;
}
// Ex) `2j`
ExprKind::Constant {
value: Constant::Complex { real, .. },
..
} => {
if *real == 0.0 {
return checker.locator.slice(default).len() <= 10;
}
}
ExprKind::UnaryOp {
op: Unaryop::USub,
operand,
} => {
// Ex) `-1`, `-3.14`
if let ExprKind::Constant {
value: Constant::Int(..) | Constant::Float(..),
value: Constant::Int(..),
..
} = &operand.node
{
return checker.locator.slice(operand).len() <= 10;
}
// Ex) `-2j`
if let ExprKind::Constant {
value: Constant::Complex { real, .. },
..
} = &operand.node
{
if *real == 0.0 {
return checker.locator.slice(operand).len() <= 10;
}
}
// Ex) `-math.inf`, `-math.pi`, etc.
if let ExprKind::Attribute { .. } = &operand.node {
if checker
.ctx
.resolve_call_path(default)
.map_or(false, |call_path| {
ALLOWED_MATH_ATTRIBUTES_IN_DEFAULTS.iter().any(|target| {
// reject `-math.nan`
call_path.as_slice() == *target && *target != ["math", "nan"]
})
})
{
return true;
}
}
}
ExprKind::BinOp {
left,
op: Operator::Add | Operator::Sub,
right,
} => {
// Ex) `1 + 2j`, `1 - 2j`, `-1 - 2j`, `-1 + 2j`
// 1 + 2j
// 1 - 2j
// -1 - 2j
// -1 + 2j
if let ExprKind::Constant {
value: Constant::Complex { .. },
..
} = right.node
{
// Ex) `1 + 2j`, `1 - 2j`
// 1 + 2j
// 1 - 2j
if let ExprKind::Constant {
value: Constant::Int(..) | Constant::Float(..),
value: Constant::Int(..),
..
} = &left.node
{
@@ -146,9 +106,10 @@ fn is_valid_default_value_with_annotation(default: &Expr, checker: &Checker) ->
operand,
} = &left.node
{
// Ex) `-1 + 2j`, `-1 - 2j`
// -1 + 2j
// -1 - 2j
if let ExprKind::Constant {
value: Constant::Int(..) | Constant::Float(..),
value: Constant::Int(..),
..
} = &operand.node
{
@@ -157,15 +118,14 @@ fn is_valid_default_value_with_annotation(default: &Expr, checker: &Checker) ->
}
}
}
// Ex) `math.inf`, `sys.stdin`, etc.
// `sys.stdin`, etc.
ExprKind::Attribute { .. } => {
if checker
.ctx
.resolve_call_path(default)
.map_or(false, |call_path| {
ALLOWED_MATH_ATTRIBUTES_IN_DEFAULTS
ALLOWED_ATTRIBUTES_IN_DEFAULTS
.iter()
.chain(ALLOWED_ATTRIBUTES_IN_DEFAULTS.iter())
.any(|target| call_path.as_slice() == *target)
})
{

View File

@@ -145,108 +145,4 @@ expression: diagnostics
column: 10
fix: ~
parent: ~
- kind:
name: TypedArgumentSimpleDefaults
body: Only simple default values allowed for typed arguments
suggestion: ~
fixable: false
location:
row: 80
column: 15
end_location:
row: 80
column: 23
fix: ~
parent: ~
- kind:
name: TypedArgumentSimpleDefaults
body: Only simple default values allowed for typed arguments
suggestion: ~
fixable: false
location:
row: 83
column: 15
end_location:
row: 83
column: 23
fix: ~
parent: ~
- kind:
name: TypedArgumentSimpleDefaults
body: Only simple default values allowed for typed arguments
suggestion: ~
fixable: false
location:
row: 86
column: 15
end_location:
row: 86
column: 24
fix: ~
parent: ~
- kind:
name: TypedArgumentSimpleDefaults
body: Only simple default values allowed for typed arguments
suggestion: ~
fixable: false
location:
row: 89
column: 15
end_location:
row: 89
column: 18
fix: ~
parent: ~
- kind:
name: TypedArgumentSimpleDefaults
body: Only simple default values allowed for typed arguments
suggestion: ~
fixable: false
location:
row: 92
column: 15
end_location:
row: 92
column: 21
fix: ~
parent: ~
- kind:
name: TypedArgumentSimpleDefaults
body: Only simple default values allowed for typed arguments
suggestion: ~
fixable: false
location:
row: 95
column: 15
end_location:
row: 95
column: 23
fix: ~
parent: ~
- kind:
name: TypedArgumentSimpleDefaults
body: Only simple default values allowed for typed arguments
suggestion: ~
fixable: false
location:
row: 98
column: 15
end_location:
row: 98
column: 24
fix: ~
parent: ~
- kind:
name: TypedArgumentSimpleDefaults
body: Only simple default values allowed for typed arguments
suggestion: ~
fixable: false
location:
row: 101
column: 17
end_location:
row: 102
column: 8
fix: ~
parent: ~

View File

@@ -22,7 +22,7 @@ impl Violation for FailWithoutMessage {
pub fn fail_call(checker: &mut Checker, func: &Expr, args: &[Expr], keywords: &[Keyword]) {
if is_pytest_fail(func, checker) {
let call_args = SimpleCallArgs::new(args, keywords);
let msg = call_args.argument("msg", 0);
let msg = call_args.get_argument("msg", Some(0));
if let Some(msg) = msg {
if is_empty_or_null_string(msg) {

View File

@@ -68,11 +68,11 @@ fn check_patch_call(
new_arg_number: usize,
) -> Option<Diagnostic> {
let simple_args = SimpleCallArgs::new(args, keywords);
if simple_args.keyword_argument("return_value").is_some() {
if simple_args.get_argument("return_value", None).is_some() {
return None;
}
if let Some(new_arg) = simple_args.argument("new", new_arg_number) {
if let Some(new_arg) = simple_args.get_argument("new", Some(new_arg_number)) {
if let ExprKind::Lambda { args, body } = &new_arg.node {
// Walk the lambda body.
let mut visitor = LambdaBodyVisitor {

View File

@@ -1,6 +1,8 @@
use std::fmt;
#[derive(Debug, PartialEq, Eq)]
use serde::{Deserialize, Serialize};
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum Branch {
Elif,
Else,

View File

@@ -9,7 +9,6 @@ use ruff_python_ast::helpers::{
contains_call_path, contains_effect, create_expr, create_stmt, first_colon_range, has_comments,
has_comments_in, unparse_expr, unparse_stmt,
};
use ruff_python_ast::newlines::StrExt;
use ruff_python_ast::types::Range;
use crate::checkers::ast::Checker;
@@ -284,7 +283,7 @@ pub fn nested_if_statements(
Ok(fix) => {
if fix
.content
.universal_newlines()
.lines()
.all(|line| line.len() <= checker.settings.line_length)
{
diagnostic.amend(fix);

View File

@@ -5,7 +5,6 @@ use ruff_diagnostics::Diagnostic;
use ruff_diagnostics::{AutofixKind, Availability, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::helpers::{first_colon_range, has_comments_in};
use ruff_python_ast::newlines::StrExt;
use ruff_python_ast::types::Range;
use crate::checkers::ast::Checker;
@@ -116,7 +115,7 @@ pub fn multiple_with_statements(
Ok(fix) => {
if fix
.content
.universal_newlines()
.lines()
.all(|line| line.len() <= checker.settings.line_length)
{
diagnostic.amend(fix);

View File

@@ -2,7 +2,6 @@ use rustpython_parser::ast::{Location, Stmt};
use rustpython_parser::{lexer, Mode, Tok};
use ruff_python_ast::helpers::is_docstring_stmt;
use ruff_python_ast::newlines::StrExt;
use ruff_python_ast::source_code::Locator;
use super::types::TrailingComma;
@@ -63,7 +62,7 @@ pub fn has_comment_break(stmt: &Stmt, locator: &Locator) -> bool {
// # Direct comment.
// def f(): pass
let mut seen_blank = false;
for line in locator.take(stmt.location).universal_newlines().rev() {
for line in locator.take(stmt.location).lines().rev() {
let line = line.trim();
if seen_blank {
if line.starts_with('#') {

View File

@@ -4,24 +4,6 @@ use ruff_python_ast::types::Range;
use crate::rules::pycodestyle::helpers::is_ambiguous_name;
/// ## What it does
/// Checks for the use of the characters 'l', 'O', or 'I' as class names.
///
/// ## Why is this bad?
/// In some fonts, these characters are indistinguishable from the
/// numerals one and zero. When tempted to use 'l', use 'L' instead.
///
/// ## Example
/// ```python
/// class I(object):
/// ...
/// ```
///
/// Use instead:
/// ```python
/// class Integer(object):
/// ...
/// ```
#[violation]
pub struct AmbiguousClassName(pub String);

View File

@@ -4,24 +4,6 @@ use ruff_python_ast::types::Range;
use crate::rules::pycodestyle::helpers::is_ambiguous_name;
/// ## What it does
/// Checks for the use of the characters 'l', 'O', or 'I' as function names.
///
/// ## Why is this bad?
/// In some fonts, these characters are indistinguishable from the
/// numerals one and zero. When tempted to use 'l', use 'L' instead.
///
/// ## Example
/// ```python
/// def l(x):
/// ...
/// ```
///
/// Use instead:
/// ```python
/// def long_name(x):
/// ...
/// ```
#[violation]
pub struct AmbiguousFunctionName(pub String);

View File

@@ -4,27 +4,6 @@ use ruff_python_ast::types::Range;
use crate::rules::pycodestyle::helpers::is_ambiguous_name;
/// ## What it does
/// Checks for the use of the characters 'l', 'O', or 'I' as variable names.
///
/// ## Why is this bad?
/// In some fonts, these characters are indistinguishable from the
/// numerals one and zero. When tempted to use 'l', use 'L' instead.
///
/// ## Example
/// ```python
/// l = 0
/// O = 123
/// I = 42
/// ```
///
/// Use instead:
/// ```python
/// L = 0
/// o = 123
/// i = 42
/// ```
#[violation]
pub struct AmbiguousVariableName(pub String);

View File

@@ -9,25 +9,6 @@ use ruff_python_ast::types::Range;
use crate::registry::Rule;
use crate::settings::{flags, Settings};
/// ## What it does
/// Checks for compound statements (multiple statements on the same line).
///
/// ## Why is this bad?
/// Per PEP 8, "compound statements are generally discouraged".
///
/// ## Example
/// ```python
/// if foo == 'blah': do_blah_thing()
/// ```
///
/// Use instead:
/// ```python
/// if foo == 'blah':
/// do_blah_thing()
/// ```
///
/// ## References
/// - [PEP 8](https://peps.python.org/pep-0008/#other-recommendations)
#[violation]
pub struct MultipleStatementsOnOneLineColon;
@@ -38,27 +19,6 @@ impl Violation for MultipleStatementsOnOneLineColon {
}
}
/// ## What it does
/// Checks for multiline statements on one line.
///
/// ## Why is this bad?
/// Per PEP 8, including multi-clause statements on the same line is
/// discouraged.
///
/// ## Example
/// ```python
/// do_one(); do_two(); do_three()
/// ```
///
/// Use instead:
/// ```python
/// do_one()
/// do_two()
/// do_three()
/// ```
///
/// ## References
/// - [PEP 8](https://peps.python.org/pep-0008/#other-recommendations)
#[violation]
pub struct MultipleStatementsOnOneLineSemicolon;
@@ -69,21 +29,6 @@ impl Violation for MultipleStatementsOnOneLineSemicolon {
}
}
/// ## What it does
/// Checks for statements that end with an unnecessary semicolon.
///
/// ## Why is this bad?
/// A trailing semicolon is unnecessary and should be removed.
///
/// ## Example
/// ```python
/// do_four(); # useless semicolon
/// ```
///
/// Use instead:
/// ```python
/// do_four()
/// ```
#[violation]
pub struct UselessSemicolon;
@@ -98,7 +43,6 @@ impl AlwaysAutofixableViolation for UselessSemicolon {
}
}
/// E701, E702, E703
pub fn compound_statements(
lxr: &[LexResult],
settings: &Settings,

View File

@@ -7,27 +7,6 @@ use ruff_python_ast::types::Range;
use crate::rules::pycodestyle::helpers::is_overlong;
use crate::settings::Settings;
/// ## What it does
/// Checks for doc lines that exceed the specified maximum character length.
///
/// ## Why is this bad?
/// For flowing long blocks of text (docstrings or comments), overlong lines
/// can hurt readability.
///
/// ## Example
/// ```python
/// def function(x):
/// """Lorem ipsum dolor sit amet, consectetur adipiscing elit. Duis auctor purus ut ex fermentum, at maximus est hendrerit."""
/// ```
///
/// Use instead:
/// ```python
/// def function(x):
/// """
/// Lorem ipsum dolor sit amet, consectetur adipiscing elit.
/// Duis auctor purus ut ex fermentum, at maximus est hendrerit.
/// """
/// ```
#[violation]
pub struct DocLineTooLong(pub usize, pub usize);

View File

@@ -9,7 +9,6 @@ pub struct IOError {
pub message: String,
}
/// E902
impl Violation for IOError {
#[derive_message_formats]
fn message(&self) -> String {
@@ -31,7 +30,6 @@ impl Violation for SyntaxError {
}
}
/// E901
pub fn syntax_error(diagnostics: &mut Vec<Diagnostic>, parse_error: &ParseError) {
diagnostics.push(Diagnostic::new(
SyntaxError {

View File

@@ -7,28 +7,6 @@ use ruff_diagnostics::DiagnosticKind;
use ruff_diagnostics::Violation;
use ruff_macros::{derive_message_formats, violation};
/// ## What it does
/// Checks for the use of extraneous whitespace after "(".
///
/// ## Why is this bad?
/// PEP 8 recommends the omission of whitespace in the following cases:
/// - "Immediately inside parentheses, brackets or braces."
/// - "Immediately before a comma, semicolon, or colon."
///
/// ## Example
/// ```python
/// spam( ham[1], {eggs: 2})
/// spam(ham[ 1], {eggs: 2})
/// spam(ham[1], { eggs: 2})
/// ```
///
/// Use instead:
/// ```python
/// spam(ham[1], {eggs: 2})
/// ```
///
/// ## References
/// - [PEP 8](https://peps.python.org/pep-0008/#pet-peeves)
#[violation]
pub struct WhitespaceAfterOpenBracket;
@@ -39,28 +17,6 @@ impl Violation for WhitespaceAfterOpenBracket {
}
}
/// ## What it does
/// Checks for the use of extraneous whitespace before ")".
///
/// ## Why is this bad?
/// PEP 8 recommends the omission of whitespace in the following cases:
/// - "Immediately inside parentheses, brackets or braces."
/// - "Immediately before a comma, semicolon, or colon."
///
/// ## Example
/// ```python
/// spam(ham[1], {eggs: 2} )
/// spam(ham[1 ], {eggs: 2})
/// spam(ham[1], {eggs: 2 })
/// ```
///
/// Use instead:
/// ```python
/// spam(ham[1], {eggs: 2})
/// ```
///
/// ## References
/// - [PEP 8](https://peps.python.org/pep-0008/#pet-peeves)
#[violation]
pub struct WhitespaceBeforeCloseBracket;
@@ -71,26 +27,6 @@ impl Violation for WhitespaceBeforeCloseBracket {
}
}
/// ## What it does
/// Checks for the use of extraneous whitespace before ",", ";" or ":".
///
/// ## Why is this bad?
/// PEP 8 recommends the omission of whitespace in the following cases:
/// - "Immediately inside parentheses, brackets or braces."
/// - "Immediately before a comma, semicolon, or colon."
///
/// ## Example
/// ```python
/// if x == 4: print(x, y); x, y = y , x
/// ```
///
/// Use instead:
/// ```python
/// if x == 4: print(x, y); x, y = y, x
/// ```
///
/// ## References
/// - [PEP 8](https://peps.python.org/pep-0008/#pet-peeves)
#[violation]
pub struct WhitespaceBeforePunctuation;

View File

@@ -6,25 +6,6 @@ use ruff_python_ast::types::Range;
use crate::checkers::ast::Checker;
/// ## What it does
/// Check for multiple imports on one line.
///
/// ## Why is this bad?
/// Per PEP 8, "imports should usually be on separate lines."
///
/// ## Example
/// ```python
/// import sys, os
/// ```
///
/// Use instead:
/// ```python
/// import os
/// import sys
/// ```
///
/// ## References
/// - [PEP 8](https://peps.python.org/pep-0008/#imports)
#[violation]
pub struct MultipleImportsOnOneLine;
@@ -35,33 +16,6 @@ impl Violation for MultipleImportsOnOneLine {
}
}
/// ## What it does
/// Checks for imports that are not at the top of the file.
///
/// ## Why is this bad?
/// Per PEP 8, "imports are always put at the top of the file, just after any
/// module comments and docstrings, and before module globals and constants."
///
/// ## Example
/// ```python
/// 'One string'
/// "Two string"
/// a = 1
/// import os
/// from sys import x
/// ```
///
/// Use instead:
/// ```python
/// import os
/// from sys import x
/// 'One string'
/// "Two string"
/// a = 1
/// ```
///
/// ## References
/// - [PEP 8](https://peps.python.org/pep-0008/#imports)
#[violation]
pub struct ModuleImportNotAtTopOfFile;
@@ -72,7 +26,6 @@ impl Violation for ModuleImportNotAtTopOfFile {
}
}
/// E401
pub fn multiple_imports_on_one_line(checker: &mut Checker, stmt: &Stmt, names: &[Alias]) {
if names.len() > 1 {
checker
@@ -81,7 +34,6 @@ pub fn multiple_imports_on_one_line(checker: &mut Checker, stmt: &Stmt, names: &
}
}
/// E402
pub fn module_import_not_at_top_of_file(checker: &mut Checker, stmt: &Stmt) {
if checker.ctx.seen_import_boundary && stmt.location.column() == 0 {
checker.diagnostics.push(Diagnostic::new(

View File

@@ -6,26 +6,6 @@ use ruff_macros::{derive_message_formats, violation};
use crate::rules::pycodestyle::logical_lines::LogicalLine;
/// ## What it does
/// Checks for indentation with a non-multiple of 4 spaces.
///
/// ## Why is this bad?
/// Per PEP 8, 4 spaces per indentation level should be preferred.
///
/// ## Example
/// ```python
/// if True:
/// a = 1
/// ```
///
/// Use instead:
/// ```python
/// if True:
/// a = 1
/// ```
///
/// ## References
/// - [PEP 8](https://peps.python.org/pep-0008/#indentation)
#[violation]
pub struct IndentationWithInvalidMultiple {
pub indent_size: usize,
@@ -39,26 +19,6 @@ impl Violation for IndentationWithInvalidMultiple {
}
}
/// ## What it does
/// Checks for indentation of comments with a non-multiple of 4 spaces.
///
/// ## Why is this bad?
/// Per PEP 8, 4 spaces per indentation level should be preferred.
///
/// ## Example
/// ```python
/// if True:
/// # a = 1
/// ```
///
/// Use instead:
/// ```python
/// if True:
/// # a = 1
/// ```
///
/// ## References
/// - [PEP 8](https://peps.python.org/pep-0008/#indentation)
#[violation]
pub struct IndentationWithInvalidMultipleComment {
pub indent_size: usize,
@@ -72,28 +32,6 @@ impl Violation for IndentationWithInvalidMultipleComment {
}
}
/// ## What it does
/// Checks for indented blocks that are lacking indentation.
///
/// ## Why is this bad?
/// All indented blocks should be indented; otherwise, they are not valid
/// Python syntax.
///
/// ## Example
/// ```python
/// for item in items:
/// pass
///
/// ```
///
/// Use instead:
/// ```python
/// for item in items:
/// pass
/// ```
///
/// ## References
/// - [PEP 8](https://peps.python.org/pep-0008/#indentation)
#[violation]
pub struct NoIndentedBlock;
@@ -104,29 +42,6 @@ impl Violation for NoIndentedBlock {
}
}
/// ## What it does
/// Checks for comments in a code blocks that are lacking indentation.
///
/// ## Why is this bad?
/// Comments within an indented block should themselves be indented, to
/// indicate that they are part of the block.
///
/// ## Example
/// ```python
/// for item in items:
/// # Hi
/// pass
/// ```
///
/// Use instead:
/// ```python
/// for item in items:
/// # Hi
/// pass
/// ```
///
/// ## References
/// - [PEP 8](https://peps.python.org/pep-0008/#indentation)
#[violation]
pub struct NoIndentedBlockComment;
@@ -137,26 +52,6 @@ impl Violation for NoIndentedBlockComment {
}
}
/// ## What it does
/// Checks for unexpected indentation.
///
/// ## Why is this bad?
/// Indentation outside of a code block is not valid Python syntax.
///
/// ## Example
/// ```python
/// a = 1
/// b = 2
/// ```
///
/// Use instead:
/// ```python
/// a = 1
/// b = 2
/// ```
///
/// ## References
/// - [PEP 8](https://peps.python.org/pep-0008/#indentation)
#[violation]
pub struct UnexpectedIndentation;
@@ -167,26 +62,6 @@ impl Violation for UnexpectedIndentation {
}
}
/// ## What it does
/// Checks for unexpected indentation of comment.
///
/// ## Why is this bad?
/// Comments should match the indentation of the containing code block.
///
/// ## Example
/// ```python
/// a = 1
/// # b = 2
/// ```
///
/// Use instead:
/// ```python
/// a = 1
/// # b = 2
/// ```
///
/// ## References
/// - [PEP 8](https://peps.python.org/pep-0008/#indentation)
#[violation]
pub struct UnexpectedIndentationComment;
@@ -197,28 +72,6 @@ impl Violation for UnexpectedIndentationComment {
}
}
/// ## What it does
/// Checks for over-indented code.
///
/// ## Why is this bad?
/// Per PEP 8, 4 spaces per indentation level should be preferred. Increased
/// indentation can lead to inconsistent formatting, which can hurt
/// readability.
///
/// ## Example
/// ```python
/// for item in items:
/// pass
/// ```
///
/// Use instead:
/// ```python
/// for item in items:
/// pass
/// ```
///
/// ## References
/// - [PEP 8](https://peps.python.org/pep-0008/#indentation)
#[violation]
pub struct OverIndented;
@@ -229,7 +82,7 @@ impl Violation for OverIndented {
}
}
/// E111, E114, E112, E113, E115, E116, E117
/// E111
#[cfg(feature = "logical_lines")]
pub fn indentation(
logical_line: &LogicalLine,

View File

@@ -4,25 +4,9 @@ use rustpython_parser::ast::Location;
use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Fix};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::newlines::StrExt;
use ruff_python_ast::source_code::Locator;
use ruff_python_ast::types::Range;
/// ## What it does
/// Checks for invalid escape sequences.
///
/// ## Why is this bad?
/// Invalid escape sequences are deprecated in Python 3.6.
///
/// ## Example
/// ```python
/// regex = '\.png$'
/// ```
///
/// Use instead:
/// ```python
/// regex = r'\.png$'
/// ```
#[violation]
pub struct InvalidEscapeSequence(pub char);
@@ -77,7 +61,7 @@ pub fn invalid_escape_sequence(
let body = &text[(quote_pos + quote.len())..(text.len() - quote.len())];
if !prefix.contains('r') {
for (row_offset, line) in body.universal_newlines().enumerate() {
for (row_offset, line) in body.lines().enumerate() {
let chars: Vec<char> = line.chars().collect();
for col_offset in 0..chars.len() {
if chars[col_offset] != '\\' {

View File

@@ -3,7 +3,6 @@ use rustpython_parser::ast::{Arguments, Expr, ExprKind, Location, Stmt, StmtKind
use ruff_diagnostics::{AutofixKind, Availability, Diagnostic, Fix, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::helpers::{match_leading_content, match_trailing_content, unparse_stmt};
use ruff_python_ast::newlines::StrExt;
use ruff_python_ast::source_code::Stylist;
use ruff_python_ast::types::{Range, ScopeKind};
use ruff_python_ast::whitespace::leading_space;
@@ -11,30 +10,6 @@ use ruff_python_ast::whitespace::leading_space;
use crate::checkers::ast::Checker;
use crate::registry::AsRule;
/// ## What it does
/// Checks for lambda expressions which are assigned to a variable.
///
/// ## Why is this bad?
/// Per PEP 8, you should "Always use a def statement instead of an assignment
/// statement that binds a lambda expression directly to an identifier."
///
/// Using a `def` statement leads to better tracebacks, and the assignment
/// itself negates the primary benefit of using a `lambda` expression (i.e.,
/// that it can be embedded inside another expression).
///
/// ## Example
/// ```python
/// f = lambda x: 2*x
/// ```
///
/// Use instead:
/// ```python
/// def f(x):
/// return 2 * x
/// ```
///
/// ## References
/// - [PEP 8](https://peps.python.org/pep-0008/#programming-recommendations)
#[violation]
pub struct LambdaAssignment {
pub name: String,
@@ -87,7 +62,7 @@ pub fn lambda_assignment(checker: &mut Checker, target: &Expr, value: &Expr, stm
let indentation = &leading_space(first_line);
let mut indented = String::new();
for (idx, line) in function(id, args, body, checker.stylist)
.universal_newlines()
.lines()
.enumerate()
{
if idx == 0 {

View File

@@ -7,24 +7,6 @@ use ruff_python_ast::types::Range;
use crate::rules::pycodestyle::helpers::is_overlong;
use crate::settings::Settings;
/// ## What it does
/// Checks for lines that exceed the specified maximum character length.
///
/// ## Why is this bad?
/// Overlong lines can hurt readability.
///
/// ## Example
/// ```python
/// my_function(param1, param2, param3, param4, param5, param6, param7, param8, param9, param10)
/// ```
///
/// Use instead:
/// ```python
/// my_function(
/// param1, param2, param3, param4, param5,
/// param6, param7, param8, param9, param10
/// )
/// ```
#[violation]
pub struct LineTooLong(pub usize, pub usize);

View File

@@ -1,6 +1,7 @@
use itertools::izip;
use rustc_hash::FxHashMap;
use rustpython_parser::ast::{Cmpop, Constant, Expr, ExprKind};
use serde::{Deserialize, Serialize};
use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Fix};
use ruff_macros::{derive_message_formats, violation};
@@ -11,7 +12,7 @@ use crate::checkers::ast::Checker;
use crate::registry::AsRule;
use crate::rules::pycodestyle::helpers::compare;
#[derive(Debug, PartialEq, Eq)]
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum EqCmpop {
Eq,
NotEq,
@@ -27,26 +28,6 @@ impl From<&Cmpop> for EqCmpop {
}
}
/// ## What it does
/// Checks for comparisons to `None` which are not using the `is` operator.
///
/// ## Why is this bad?
/// Per PEP 8, "Comparisons to singletons like None should always be done with
/// is or is not, never the equality operators."
///
/// ## Example
/// ```python
/// if arg != None:
/// if None == arg:
/// ```
///
/// Use instead:
/// ```python
/// if arg is not None:
/// ```
///
/// ## References
/// - [PEP 8](https://peps.python.org/pep-0008/#programming-recommendations)
#[violation]
pub struct NoneComparison(pub EqCmpop);
@@ -69,27 +50,6 @@ impl AlwaysAutofixableViolation for NoneComparison {
}
}
/// ## What it does
/// Checks for comparisons to booleans which are not using the `is` operator.
///
/// ## Why is this bad?
/// Per PEP 8, "Comparisons to singletons like None should always be done with
/// is or is not, never the equality operators."
///
/// ## Example
/// ```python
/// if arg == True:
/// if False == arg:
/// ```
///
/// Use instead:
/// ```python
/// if arg is True:
/// if arg is False:
/// ```
///
/// ## References
/// - [PEP 8](https://peps.python.org/pep-0008/#programming-recommendations)
#[violation]
pub struct TrueFalseComparison(pub bool, pub EqCmpop);

View File

@@ -5,26 +5,6 @@ use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::types::Range;
use ruff_python_ast::whitespace::leading_space;
/// ## What it does
/// Checks for mixed tabs and spaces in indentation.
///
/// ## Why is this bad?
/// Never mix tabs and spaces.
///
/// The most popular way of indenting Python is with spaces only. The
/// second-most popular way is with tabs only. Code indented with a
/// mixture of tabs and spaces should be converted to using spaces
/// exclusively.
///
/// ## Example
/// ```python
/// if a == 0:\n a = 1\n\tb = 1
/// ```
///
/// Use instead:
/// ```python
/// if a == 0:\n a = 1\n b = 1
/// ```
#[violation]
pub struct MixedSpacesAndTabs;

View File

@@ -2,26 +2,9 @@ use rustpython_parser::ast::Location;
use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Fix};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::newlines::StrExt;
use ruff_python_ast::source_code::{Locator, Stylist};
use ruff_python_ast::source_code::Stylist;
use ruff_python_ast::types::Range;
/// ## What it does
/// Checks for files missing a new line at the end of the file.
///
/// ## Why is this bad?
/// Trailing blank lines are superfluous.
/// However the last line should end with a new line.
///
/// ## Example
/// ```python
/// spam(1)
/// ```
///
/// Use instead:
/// ```python
/// spam(1)\n
/// ```
#[violation]
pub struct NoNewLineAtEndOfFile;
@@ -38,16 +21,16 @@ impl AlwaysAutofixableViolation for NoNewLineAtEndOfFile {
/// W292
pub fn no_newline_at_end_of_file(
locator: &Locator,
stylist: &Stylist,
contents: &str,
autofix: bool,
) -> Option<Diagnostic> {
if !locator.contents().ends_with(['\n', '\r']) {
if !contents.ends_with('\n') {
// Note: if `lines.last()` is `None`, then `contents` is empty (and so we don't
// want to raise W292 anyway).
if let Some(line) = locator.contents().universal_newlines().last() {
if let Some(line) = contents.lines().last() {
// Both locations are at the end of the file (and thus the same).
let location = Location::new(locator.count_lines(), line.len());
let location = Location::new(contents.lines().count(), line.len());
let mut diagnostic =
Diagnostic::new(NoNewLineAtEndOfFile, Range::new(location, location));
if autofix {

View File

@@ -8,25 +8,6 @@ use crate::checkers::ast::Checker;
use crate::registry::AsRule;
use crate::rules::pycodestyle::helpers::compare;
/// ## What it does
/// Checks for negative comparison using `not {foo} in {bar}`.
///
/// ## Why is this bad?
/// Negative comparison should be done using `not in`.
///
/// ## Example
/// ```python
/// Z = not X in Y
/// if not X.B in Y:\n pass
///
/// ```
///
/// Use instead:
/// ```python
/// if x not in y:\n pass
/// assert (X in Y or X is Z)
///
/// ```
#[violation]
pub struct NotInTest;
@@ -41,25 +22,6 @@ impl AlwaysAutofixableViolation for NotInTest {
}
}
/// ## What it does
/// Checks for negative comparison using `not {foo} is {bar}`.
///
/// ## Why is this bad?
/// Negative comparison should be done using `is not`.
///
/// ## Example
/// ```python
/// if not X is Y:
/// pass
/// Z = not X.B is Y
/// ```
///
/// Use instead:
/// ```python
/// if not (X in Y):
/// pass
/// zz = x is not y
/// ```
#[violation]
pub struct NotIsTest;

View File

@@ -7,25 +7,6 @@ use ruff_diagnostics::DiagnosticKind;
use ruff_diagnostics::Violation;
use ruff_macros::{derive_message_formats, violation};
/// ## What it does
/// Checks for extraneous tabs before an operator.
///
/// ## Why is this bad?
/// Per PEP 8, operators should be surrounded by at most a single space on either
/// side.
///
/// ## Example
/// ```python
/// a = 4\t+ 5
/// ```
///
/// Use instead:
/// ```python
/// a = 12 + 3
/// ```
///
/// ## References
/// - [PEP 8](https://peps.python.org/pep-0008/#whitespace-in-expressions-and-statements)
#[violation]
pub struct TabBeforeOperator;
@@ -36,25 +17,6 @@ impl Violation for TabBeforeOperator {
}
}
/// ## What it does
/// Checks for extraneous whitespace before an operator.
///
/// ## Why is this bad?
/// Per PEP 8, operators should be surrounded by at most a single space on either
/// side.
///
/// ## Example
/// ```python
/// a = 4 + 5
/// ```
///
/// Use instead:
/// ```python
/// a = 12 + 3
/// ```
///
/// ## References
/// - [PEP 8](https://peps.python.org/pep-0008/#whitespace-in-expressions-and-statements)
#[violation]
pub struct MultipleSpacesBeforeOperator;
@@ -65,25 +27,6 @@ impl Violation for MultipleSpacesBeforeOperator {
}
}
/// ## What it does
/// Checks for extraneous tabs after an operator.
///
/// ## Why is this bad?
/// Per PEP 8, operators should be surrounded by at most a single space on either
/// side.
///
/// ## Example
/// ```python
/// a = 4 +\t5
/// ```
///
/// Use instead:
/// ```python
/// a = 12 + 3
/// ```
///
/// ## References
/// - [PEP 8](https://peps.python.org/pep-0008/#whitespace-in-expressions-and-statements)
#[violation]
pub struct TabAfterOperator;
@@ -94,25 +37,6 @@ impl Violation for TabAfterOperator {
}
}
/// ## What it does
/// Checks for extraneous whitespace after an operator.
///
/// ## Why is this bad?
/// Per PEP 8, operators should be surrounded by at most a single space on either
/// side.
///
/// ## Example
/// ```python
/// a = 4 + 5
/// ```
///
/// Use instead:
/// ```python
/// a = 12 + 3
/// ```
///
/// ## References
/// - [PEP 8](https://peps.python.org/pep-0008/#whitespace-in-expressions-and-statements)
#[violation]
pub struct MultipleSpacesAfterOperator;

View File

@@ -7,25 +7,6 @@ use ruff_python_ast::types::Range;
use crate::registry::Rule;
use crate::settings::{flags, Settings};
/// ## What it does
/// Checks for superfluous trailing whitespace.
///
/// ## Why is this bad?
/// Per PEP 8, "avoid trailing whitespace anywhere. Because its usually
/// invisible, it can be confusing"
///
/// ## Example
/// ```python
/// spam(1) \n#
/// ```
///
/// Use instead:
/// ```python
/// spam(1)\n#
/// ```
///
/// ## References
/// - [PEP 8](https://peps.python.org/pep-0008/#other-recommendations)
#[violation]
pub struct TrailingWhitespace;
@@ -40,26 +21,6 @@ impl AlwaysAutofixableViolation for TrailingWhitespace {
}
}
/// ## What it does
/// Checks for superfluous whitespace in blank lines.
///
/// ## Why is this bad?
/// Per PEP 8, "avoid trailing whitespace anywhere. Because its usually
/// invisible, it can be confusing"
///
/// ## Example
/// ```python
/// class Foo(object):\n \n bang = 12
///
/// ```
///
/// Use instead:
/// ```python
/// class Foo(object):\n\n bang = 12
/// ```
///
/// ## References
/// - [PEP 8](https://peps.python.org/pep-0008/#other-recommendations)
#[violation]
pub struct BlankLineContainsWhitespace;

View File

@@ -5,24 +5,6 @@ use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::types::Range;
/// ## What it does
/// Checks for object type comparisons without using isinstance().
///
/// ## Why is this bad?
/// Do not compare types directly.
/// When checking if an object is a instance of a certain type, keep in mind that it might
/// be subclassed. E.g. `bool` inherits from `int` or `Exception` inherits from `BaseException`.
///
/// ## Example
/// ```python
/// if type(obj) is type(1):
/// ```
///
/// Use instead:
/// ```python
/// if isinstance(obj, int):
/// if type(a1) is type(b1):
/// ```
#[violation]
pub struct TypeComparison;

View File

@@ -7,21 +7,6 @@ use ruff_diagnostics::DiagnosticKind;
use ruff_diagnostics::Violation;
use ruff_macros::{derive_message_formats, violation};
/// ## What it does
/// Checks for extraneous whitespace after keywords.
///
/// ## Why is this bad?
///
///
/// ## Example
/// ```python
/// True and False
/// ```
///
/// Use instead:
/// ```python
/// True and False
/// ```
#[violation]
pub struct MultipleSpacesAfterKeyword;
@@ -32,22 +17,6 @@ impl Violation for MultipleSpacesAfterKeyword {
}
}
/// ## What it does
/// Checks for extraneous whitespace before keywords.
///
/// ## Why is this bad?
///
///
/// ## Example
/// ```python
/// True and False
///
/// ```
///
/// Use instead:
/// ```python
/// True and False
/// ```
#[violation]
pub struct MultipleSpacesBeforeKeyword;
@@ -58,22 +27,6 @@ impl Violation for MultipleSpacesBeforeKeyword {
}
}
/// ## What it does
/// Checks for extraneous tabs after keywords.
///
/// ## Why is this bad?
///
///
/// ## Example
/// ```python
/// True and\tFalse
///
/// ```
///
/// Use instead:
/// ```python
/// True and False
/// ```
#[violation]
pub struct TabAfterKeyword;
@@ -84,22 +37,6 @@ impl Violation for TabAfterKeyword {
}
}
/// ## What it does
/// Checks for extraneous tabs before keywords.
///
/// ## Why is this bad?
///
///
/// ## Example
/// ```python
/// True\tand False
///
/// ```
///
/// Use instead:
/// ```python
/// True and False
/// ```
#[violation]
pub struct TabBeforeKeyword;

View File

@@ -9,25 +9,6 @@ use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::source_code::Locator;
use ruff_python_ast::types::Range;
/// ## What it does
/// Checks if inline comments are separated by at least two spaces.
///
/// ## Why is this bad?
/// An inline comment is a comment on the same line as a statement.
///
/// Per PEP8, inline comments should be separated by at least two spaces from
/// the preceding statement.
///
/// ## Example
/// ```python
/// x = x + 1 # Increment x
/// ```
///
/// Use instead:
/// ```python
/// x = x + 1 # Increment x
/// x = x + 1 # Increment x
/// ```
#[violation]
pub struct TooFewSpacesBeforeInlineComment;
@@ -38,29 +19,6 @@ impl Violation for TooFewSpacesBeforeInlineComment {
}
}
/// ## What it does
/// Checks if one space is used after inline comments.
///
/// ## Why is this bad?
/// An inline comment is a comment on the same line as a statement.
///
/// Per PEP8, inline comments should start with a # and a single space.
///
/// ## Example
/// ```python
/// x = x + 1 #Increment x
/// x = x + 1 # Increment x
/// x = x + 1 # \xa0Increment x
/// ```
///
/// Use instead:
/// ```python
/// x = x + 1 # Increment x
/// x = x + 1 # Increment x
/// ```
///
/// ## References
/// - [PEP 8](https://peps.python.org/pep-0008/#comments)
#[violation]
pub struct NoSpaceAfterInlineComment;
@@ -71,29 +29,6 @@ impl Violation for NoSpaceAfterInlineComment {
}
}
/// ## What it does
/// Checks if one space is used after block comments.
///
/// ## Why is this bad?
/// Per PEP8, "Block comments generally consist of one or more paragraphs built
/// out of complete sentences, with each sentence ending in a period."
///
/// Block comments should start with a # and a single space.
///
/// ## Example
/// ```python
/// #Block comment
/// ```
///
/// Use instead:
/// ```python
/// # Block comments:
/// # - Block comment list
/// # \xa0- Block comment list
/// ```
///
/// ## References
/// - [PEP 8](https://peps.python.org/pep-0008/#comments)
#[violation]
pub struct NoSpaceAfterBlockComment;
@@ -104,30 +39,6 @@ impl Violation for NoSpaceAfterBlockComment {
}
}
/// ## What it does
/// Checks if block comments start with a single "#".
///
/// ## Why is this bad?
/// Per PEP8, "Block comments generally consist of one or more paragraphs built
/// out of complete sentences, with each sentence ending in a period."
///
/// Each line of a block comment should start with a # and a single space.
///
/// ## Example
/// ```python
/// ### Block comment
///
/// ```
///
/// Use instead:
/// ```python
/// # Block comments:
/// # - Block comment list
/// # \xa0- Block comment list
/// ```
///
/// ## References
/// - [PEP 8](https://peps.python.org/pep-0008/#comments)
#[violation]
pub struct MultipleLeadingHashesForBlockComment;

View File

@@ -2,7 +2,6 @@ use std::collections::BTreeSet;
use ruff_python_ast::cast;
use ruff_python_ast::helpers::{map_callable, to_call_path};
use ruff_python_ast::newlines::StrExt;
use crate::checkers::ast::Checker;
use crate::docstrings::definition::{Definition, DefinitionKind};
@@ -11,7 +10,7 @@ use crate::docstrings::definition::{Definition, DefinitionKind};
pub fn logical_line(content: &str) -> Option<usize> {
// Find the first logical line.
let mut logical_line = None;
for (i, line) in content.universal_newlines().enumerate() {
for (i, line) in content.lines().enumerate() {
if line.trim().is_empty() {
// Empty line. If this is the line _after_ the first logical line, stop.
if logical_line.is_some() {

View File

@@ -1,6 +1,5 @@
use ruff_diagnostics::{AutofixKind, Availability, Diagnostic, Fix, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::newlines::StrExt;
use ruff_python_ast::types::Range;
use crate::checkers::ast::Checker;
@@ -46,7 +45,7 @@ pub fn blank_after_summary(checker: &mut Checker, docstring: &Docstring) {
let mut lines_count = 1;
let mut blanks_count = 0;
for line in body.trim().universal_newlines().skip(1) {
for line in body.trim().lines().skip(1) {
lines_count += 1;
if line.trim().is_empty() {
blanks_count += 1;
@@ -65,7 +64,7 @@ pub fn blank_after_summary(checker: &mut Checker, docstring: &Docstring) {
if blanks_count > 1 {
// Find the "summary" line (defined as the first non-blank line).
let mut summary_line = 0;
for line in body.universal_newlines() {
for line in body.lines() {
if line.trim().is_empty() {
summary_line += 1;
} else {

View File

@@ -1,6 +1,5 @@
use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Fix};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::newlines::StrExt;
use ruff_python_ast::types::Range;
use crate::checkers::ast::Checker;
@@ -76,7 +75,7 @@ pub fn blank_before_after_class(checker: &mut Checker, docstring: &Docstring) {
.slice(Range::new(parent.location, docstring.expr.location));
let blank_lines_before = before
.universal_newlines()
.lines()
.rev()
.skip(1)
.take_while(|line| line.trim().is_empty())
@@ -139,7 +138,7 @@ pub fn blank_before_after_class(checker: &mut Checker, docstring: &Docstring) {
));
let all_blank_after = after
.universal_newlines()
.lines()
.skip(1)
.all(|line| line.trim().is_empty() || line.trim_start().starts_with('#'));
if all_blank_after {
@@ -147,7 +146,7 @@ pub fn blank_before_after_class(checker: &mut Checker, docstring: &Docstring) {
}
let blank_lines_after = after
.universal_newlines()
.lines()
.skip(1)
.take_while(|line| line.trim().is_empty())
.count();

View File

@@ -3,7 +3,6 @@ use regex::Regex;
use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Fix};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::newlines::StrExt;
use ruff_python_ast::types::Range;
use crate::checkers::ast::Checker;
@@ -68,7 +67,7 @@ pub fn blank_before_after_function(checker: &mut Checker, docstring: &Docstring)
.slice(Range::new(parent.location, docstring.expr.location));
let blank_lines_before = before
.universal_newlines()
.lines()
.rev()
.skip(1)
.take_while(|line| line.trim().is_empty())
@@ -103,7 +102,7 @@ pub fn blank_before_after_function(checker: &mut Checker, docstring: &Docstring)
// If the docstring is only followed by blank and commented lines, abort.
let all_blank_after = after
.universal_newlines()
.lines()
.skip(1)
.all(|line| line.trim().is_empty() || line.trim_start().starts_with('#'));
if all_blank_after {
@@ -112,7 +111,7 @@ pub fn blank_before_after_function(checker: &mut Checker, docstring: &Docstring)
// Count the number of blank lines after the docstring.
let blank_lines_after = after
.universal_newlines()
.lines()
.skip(1)
.take_while(|line| line.trim().is_empty())
.count();
@@ -120,7 +119,7 @@ pub fn blank_before_after_function(checker: &mut Checker, docstring: &Docstring)
// Avoid violations for blank lines followed by inner functions or classes.
if blank_lines_after == 1
&& after
.universal_newlines()
.lines()
.skip(1 + blank_lines_after)
.find(|line| !line.trim_start().starts_with('#'))
.map_or(false, |line| INNER_FUNCTION_OR_CLASS_REGEX.is_match(line))

View File

@@ -2,7 +2,6 @@ use strum::IntoEnumIterator;
use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Fix};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::newlines::StrExt;
use ruff_python_ast::str::leading_quote;
use ruff_python_ast::types::Range;
@@ -32,7 +31,7 @@ pub fn ends_with_period(checker: &mut Checker, docstring: &Docstring) {
let contents = docstring.contents;
let body = docstring.body;
if let Some(first_line) = body.trim().universal_newlines().next() {
if let Some(first_line) = body.trim().lines().next() {
let trimmed = first_line.trim();
// Avoid false-positives: `:param`, etc.
@@ -56,7 +55,7 @@ pub fn ends_with_period(checker: &mut Checker, docstring: &Docstring) {
}
if let Some(index) = logical_line(body) {
let line = body.universal_newlines().nth(index).unwrap();
let line = body.lines().nth(index).unwrap();
let trimmed = line.trim_end();
if !trimmed.ends_with('.') {

View File

@@ -2,7 +2,6 @@ use strum::IntoEnumIterator;
use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Fix};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::newlines::StrExt;
use ruff_python_ast::str::leading_quote;
use ruff_python_ast::types::Range;
@@ -32,7 +31,7 @@ pub fn ends_with_punctuation(checker: &mut Checker, docstring: &Docstring) {
let contents = docstring.contents;
let body = docstring.body;
if let Some(first_line) = body.trim().universal_newlines().next() {
if let Some(first_line) = body.trim().lines().next() {
let trimmed = first_line.trim();
// Avoid false-positives: `:param`, etc.
@@ -56,7 +55,7 @@ pub fn ends_with_punctuation(checker: &mut Checker, docstring: &Docstring) {
}
if let Some(index) = logical_line(body) {
let line = body.universal_newlines().nth(index).unwrap();
let line = body.lines().nth(index).unwrap();
let trimmed = line.trim_end();
if !(trimmed.ends_with('.') || trimmed.ends_with('!') || trimmed.ends_with('?')) {
let mut diagnostic = Diagnostic::new(EndsInPunctuation, Range::from(docstring.expr));

View File

@@ -1,9 +1,9 @@
use ruff_diagnostics::{AlwaysAutofixableViolation, Violation};
use ruff_diagnostics::{Diagnostic, Fix};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::newlines::NewlineWithTrailingNewline;
use ruff_python_ast::types::Range;
use ruff_python_ast::whitespace;
use ruff_python_ast::whitespace::LinesWithTrailingNewline;
use crate::checkers::ast::Checker;
use crate::docstrings::definition::Docstring;
@@ -53,7 +53,7 @@ pub fn indent(checker: &mut Checker, docstring: &Docstring) {
let body = docstring.body;
// Split the docstring into lines.
let lines: Vec<&str> = NewlineWithTrailingNewline::from(body).collect();
let lines: Vec<&str> = LinesWithTrailingNewline::from(body).collect();
if lines.len() <= 1 {
return;
}

View File

@@ -1,8 +1,8 @@
use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Fix};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::newlines::{NewlineWithTrailingNewline, StrExt};
use ruff_python_ast::str::{is_triple_quote, leading_quote};
use ruff_python_ast::types::Range;
use ruff_python_ast::whitespace::LinesWithTrailingNewline;
use crate::checkers::ast::Checker;
use crate::docstrings::definition::{DefinitionKind, Docstring};
@@ -42,10 +42,10 @@ pub fn multi_line_summary_start(checker: &mut Checker, docstring: &Docstring) {
let contents = docstring.contents;
let body = docstring.body;
if NewlineWithTrailingNewline::from(body).nth(1).is_none() {
if LinesWithTrailingNewline::from(body).nth(1).is_none() {
return;
};
let mut content_lines = contents.universal_newlines();
let mut content_lines = contents.lines();
let Some(first_line) = content_lines
.next()
else

View File

@@ -1,8 +1,8 @@
use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Fix};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::newlines::{NewlineWithTrailingNewline, StrExt};
use ruff_python_ast::types::Range;
use ruff_python_ast::whitespace;
use ruff_python_ast::whitespace::LinesWithTrailingNewline;
use crate::checkers::ast::Checker;
use crate::docstrings::definition::Docstring;
@@ -29,12 +29,12 @@ pub fn newline_after_last_paragraph(checker: &mut Checker, docstring: &Docstring
let body = docstring.body;
let mut line_count = 0;
for line in NewlineWithTrailingNewline::from(body) {
for line in LinesWithTrailingNewline::from(body) {
if !line.trim().is_empty() {
line_count += 1;
}
if line_count > 1 {
if let Some(last_line) = contents.universal_newlines().last().map(str::trim) {
if let Some(last_line) = contents.lines().last().map(str::trim) {
if last_line != "\"\"\"" && last_line != "'''" {
let mut diagnostic =
Diagnostic::new(NewLineAfterLastParagraph, Range::from(docstring.expr));

View File

@@ -2,7 +2,6 @@ use rustpython_parser::ast::StmtKind;
use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::newlines::StrExt;
use ruff_python_ast::types::Range;
use crate::checkers::ast::Checker;
@@ -33,7 +32,7 @@ pub fn no_signature(checker: &mut Checker, docstring: &Docstring) {
let body = docstring.body;
let Some(first_line) = body.trim().universal_newlines().next() else {
let Some(first_line) = body.trim().lines().next() else {
return;
};
if !first_line.contains(&format!("{name}(")) {

View File

@@ -1,8 +1,8 @@
use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Fix};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::newlines::NewlineWithTrailingNewline;
use ruff_python_ast::str::leading_quote;
use ruff_python_ast::types::Range;
use ruff_python_ast::whitespace::LinesWithTrailingNewline;
use crate::checkers::ast::Checker;
use crate::docstrings::definition::Docstring;
@@ -28,7 +28,7 @@ pub fn no_surrounding_whitespace(checker: &mut Checker, docstring: &Docstring) {
let contents = docstring.contents;
let body = docstring.body;
let mut lines = NewlineWithTrailingNewline::from(body);
let mut lines = LinesWithTrailingNewline::from(body);
let Some(line) = lines.next() else {
return;
};

View File

@@ -7,7 +7,6 @@ use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::cast;
use ruff_python_ast::helpers::to_call_path;
use ruff_python_ast::newlines::StrExt;
use ruff_python_ast::types::{CallPath, Range};
use ruff_python_ast::visibility::{is_property, is_test};
@@ -49,7 +48,7 @@ pub fn non_imperative_mood(
let body = docstring.body;
// Find first line, disregarding whitespace.
let line = match body.trim().universal_newlines().next() {
let line = match body.trim().lines().next() {
Some(line) => line.trim(),
None => return,
};

View File

@@ -1,8 +1,8 @@
use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Fix};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::newlines::NewlineWithTrailingNewline;
use ruff_python_ast::str::{leading_quote, trailing_quote};
use ruff_python_ast::types::Range;
use ruff_python_ast::whitespace::LinesWithTrailingNewline;
use crate::checkers::ast::Checker;
use crate::docstrings::definition::Docstring;
@@ -26,7 +26,7 @@ impl AlwaysAutofixableViolation for FitsOnOneLine {
pub fn one_liner(checker: &mut Checker, docstring: &Docstring) {
let mut line_count = 0;
let mut non_empty_line_count = 0;
for line in NewlineWithTrailingNewline::from(docstring.body) {
for line in LinesWithTrailingNewline::from(docstring.body) {
line_count += 1;
if !line.trim().is_empty() {
non_empty_line_count += 1;

View File

@@ -8,9 +8,9 @@ use ruff_diagnostics::{AlwaysAutofixableViolation, Violation};
use ruff_diagnostics::{Diagnostic, Fix};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::helpers::identifier_range;
use ruff_python_ast::newlines::NewlineWithTrailingNewline;
use ruff_python_ast::types::Range;
use ruff_python_ast::visibility::is_staticmethod;
use ruff_python_ast::whitespace::LinesWithTrailingNewline;
use ruff_python_ast::{cast, whitespace};
use crate::checkers::ast::Checker;
@@ -273,7 +273,7 @@ impl AlwaysAutofixableViolation for NoBlankLinesBetweenHeaderAndContent {
pub fn sections(checker: &mut Checker, docstring: &Docstring, convention: Option<&Convention>) {
let body = docstring.body;
let lines: Vec<&str> = NewlineWithTrailingNewline::from(body).collect();
let lines: Vec<&str> = LinesWithTrailingNewline::from(body).collect();
if lines.len() < 2 {
return;
}
@@ -294,6 +294,18 @@ pub fn sections(checker: &mut Checker, docstring: &Docstring, convention: Option
// (e.g., "Returns", "Raises"). Break ties by checking for the presence of some of the
// section names that are unique to each convention.
// If the docstring contains `Args:` or `Arguments:`, use the Google convention.
let google_sections = section_contexts(&lines, &SectionStyle::Google);
if google_sections
.iter()
.any(|context| matches!(context.kind, SectionKind::Arguments | SectionKind::Args))
{
for context in &google_sections {
google_section(checker, docstring, context);
}
return;
}
// If the docstring contains `Parameters:` or `Other Parameters:`, use the NumPy
// convention.
let numpy_sections = section_contexts(&lines, &SectionStyle::Numpy);
@@ -309,18 +321,6 @@ pub fn sections(checker: &mut Checker, docstring: &Docstring, convention: Option
return;
}
// If the docstring contains `Args:` or `Arguments:`, use the Google convention.
let google_sections = section_contexts(&lines, &SectionStyle::Google);
if google_sections
.iter()
.any(|context| matches!(context.kind, SectionKind::Arguments | SectionKind::Args))
{
for context in &google_sections {
google_section(checker, docstring, context);
}
return;
}
// Otherwise, use whichever convention matched more sections.
if google_sections.len() > numpy_sections.len() {
for context in &google_sections {
@@ -923,32 +923,30 @@ fn parameters_section(checker: &mut Checker, docstring: &Docstring, context: &Se
// Join line continuations, then resplit by line.
let adjusted_following_lines = context.following_lines.join("\n").replace("\\\n", "");
let mut lines = NewlineWithTrailingNewline::from(&adjusted_following_lines);
if let Some(mut current_line) = lines.next() {
for next_line in lines {
let current_leading_space = whitespace::leading_space(current_line);
if current_leading_space == section_level_indent
&& (whitespace::leading_space(next_line).len() > current_leading_space.len())
&& !next_line.trim().is_empty()
{
let parameters = if let Some(semi_index) = current_line.find(':') {
// If the parameter has a type annotation, exclude it.
&current_line[..semi_index]
} else {
// Otherwise, it's just a list of parameters on the current line.
current_line.trim()
};
// Notably, NumPy lets you put multiple parameters of the same type on the same
// line.
for parameter in parameters.split(',') {
docstring_args.insert(parameter.trim());
}
}
let lines: Vec<&str> = LinesWithTrailingNewline::from(&adjusted_following_lines).collect();
current_line = next_line;
for i in 1..lines.len() {
let current_line = lines[i - 1];
let current_leading_space = whitespace::leading_space(current_line);
let next_line = lines[i];
if current_leading_space == section_level_indent
&& (whitespace::leading_space(next_line).len() > current_leading_space.len())
&& !next_line.trim().is_empty()
{
let parameters = if let Some(semi_index) = current_line.find(':') {
// If the parameter has a type annotation, exclude it.
&current_line[..semi_index]
} else {
// Otherwise, it's just a list of parameters on the current line.
current_line.trim()
};
// Notably, NumPy lets you put multiple parameters of the same type on the same
// line.
for parameter in parameters.split(',') {
docstring_args.insert(parameter.trim());
}
}
}
// Validate that all arguments were documented.
missing_args(checker, docstring, &docstring_args);
}

View File

@@ -1,6 +1,5 @@
use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::newlines::StrExt;
use ruff_python_ast::types::Range;
use crate::checkers::ast::Checker;
@@ -21,7 +20,8 @@ pub fn triple_quotes(checker: &mut Checker, docstring: &Docstring) {
let contents = docstring.contents;
let body = docstring.body;
let Some(first_line) = contents.universal_newlines()
let Some(first_line) = contents
.lines()
.next()
.map(str::to_lowercase) else
{

View File

@@ -2,6 +2,7 @@ use itertools::izip;
use log::error;
use once_cell::unsync::Lazy;
use rustpython_parser::ast::{Cmpop, Expr};
use serde::{Deserialize, Serialize};
use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Fix};
use ruff_macros::{derive_message_formats, violation};
@@ -12,7 +13,7 @@ use ruff_python_ast::types::Range;
use crate::checkers::ast::Checker;
use crate::registry::AsRule;
#[derive(Debug, PartialEq, Eq)]
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum IsCmpop {
Is,
IsNot,

View File

@@ -1,6 +1,7 @@
use std::fmt;
use rustpython_parser::ast::{Expr, ExprKind};
use serde::{Deserialize, Serialize};
use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation};
@@ -8,7 +9,7 @@ use ruff_python_ast::types::{Range, ScopeKind};
use crate::checkers::ast::Checker;
#[derive(Debug, PartialEq, Eq)]
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum DeferralKeyword {
Yield,
YieldFrom,

View File

@@ -43,8 +43,6 @@ mod tests {
#[test_case(Rule::GlobalStatement, Path::new("global_statement.py"); "PLW0603")]
#[test_case(Rule::InvalidAllFormat, Path::new("invalid_all_format.py"); "PLE0605")]
#[test_case(Rule::InvalidAllObject, Path::new("invalid_all_object.py"); "PLE0604")]
#[test_case(Rule::InvalidEnvvarDefault, Path::new("invalid_envvar_default.py"); "PLW1508")]
#[test_case(Rule::InvalidEnvvarValue, Path::new("invalid_envvar_value.py"); "PLE1507")]
#[test_case(Rule::TooManyReturnStatements, Path::new("too_many_return_statements.py"); "PLR0911")]
#[test_case(Rule::TooManyArguments, Path::new("too_many_arguments.py"); "PLR0913")]
#[test_case(Rule::TooManyBranches, Path::new("too_many_branches.py"); "PLR0912")]

View File

@@ -2,6 +2,7 @@ use std::fmt;
use rustc_hash::FxHashSet;
use rustpython_parser::ast::{Constant, Expr, ExprKind};
use serde::{Deserialize, Serialize};
use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation};
@@ -30,7 +31,7 @@ impl Violation for BadStrStripCall {
}
}
#[derive(Debug, PartialEq, Eq)]
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum StripKind {
Strip,
LStrip,
@@ -59,7 +60,7 @@ impl fmt::Display for StripKind {
}
}
#[derive(Debug, PartialEq, Eq)]
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum RemovalKind {
RemovePrefix,
RemoveSuffix,

View File

@@ -86,10 +86,17 @@ pub fn compare_to_empty_string(
if let ExprKind::Constant { value, .. } = &lhs.node {
if let Constant::Str(s) = value {
if s.is_empty() {
let constant = unparse_constant(value, checker.stylist);
let expr = unparse_expr(rhs, checker.stylist);
let existing = format!("{constant} {op} {expr}");
let replacement = format!("{}{expr}", op.into_unary());
let existing = format!(
"{} {} {}",
unparse_constant(value, checker.stylist),
op,
unparse_expr(rhs, checker.stylist)
);
let replacement = format!(
"{}{}",
op.into_unary(),
unparse_expr(rhs, checker.stylist)
);
checker.diagnostics.push(Diagnostic::new(
CompareToEmptyString {
existing,
@@ -106,10 +113,14 @@ pub fn compare_to_empty_string(
if let ExprKind::Constant { value, .. } = &rhs.node {
if let Constant::Str(s) = value {
if s.is_empty() {
let expr = unparse_expr(lhs, checker.stylist);
let constant = unparse_constant(value, checker.stylist);
let existing = format!("{expr} {op} {constant}");
let replacement = format!("{}{expr}", op.into_unary());
let existing = format!(
"{} {} {}",
unparse_expr(lhs, checker.stylist),
op,
unparse_constant(value, checker.stylist),
);
let replacement =
format!("{}{}", op.into_unary(), unparse_expr(lhs, checker.stylist));
checker.diagnostics.push(Diagnostic::new(
CompareToEmptyString {
existing,

View File

@@ -2,6 +2,7 @@ use std::fmt;
use itertools::Itertools;
use rustpython_parser::ast::{Cmpop, Expr, ExprKind, Located};
use serde::{Deserialize, Serialize};
use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation};
@@ -10,7 +11,7 @@ use ruff_python_ast::types::Range;
use crate::checkers::ast::Checker;
#[derive(Debug, PartialEq, Eq)]
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum ViolationsCmpop {
Eq,
NotEq,

View File

@@ -1,90 +0,0 @@
use rustpython_parser::ast::{Constant, Expr, ExprKind, Keyword};
use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::types::Range;
use crate::checkers::ast::Checker;
/// ## What it does
/// Checks for `env.getenv` calls with invalid default values.
///
/// ## Why is this bad?
/// If an environment variable is set, `env.getenv` will return its value as
/// a string. If the environment variable is _not_ set, `env.getenv` will
/// return `None`, or the default value if one is provided.
///
/// If the default value is not a string or `None`, then it will be
/// inconsistent with the return type of `env.getenv`, which can lead to
/// confusing behavior.
///
/// ## Example
/// ```python
/// int(env.getenv("FOO", 1))
/// ```
///
/// Use instead:
/// ```python
/// int(env.getenv("FOO", "1"))
/// ```
#[violation]
pub struct InvalidEnvvarDefault;
impl Violation for InvalidEnvvarDefault {
#[derive_message_formats]
fn message(&self) -> String {
format!("Invalid type for environment variable default; expected `str` or `None`")
}
}
fn is_valid_default(expr: &Expr) -> bool {
// We can't infer the types of these defaults, so assume they're valid.
if matches!(
expr.node,
ExprKind::Name { .. }
| ExprKind::Attribute { .. }
| ExprKind::Subscript { .. }
| ExprKind::Call { .. }
) {
return true;
}
// Otherwise, the default must be a string or `None`.
matches!(
expr.node,
ExprKind::Constant {
value: Constant::Str { .. } | Constant::None { .. },
..
}
)
}
/// PLW1508
pub fn invalid_envvar_default(
checker: &mut Checker,
func: &Expr,
args: &[Expr],
keywords: &[Keyword],
) {
if checker
.ctx
.resolve_call_path(func)
.map_or(false, |call_path| call_path.as_slice() == ["os", "getenv"])
{
// Find the `default` argument, if it exists.
let Some(expr) = args.get(1).or_else(|| {
keywords
.iter()
.find(|keyword| keyword.node.arg.as_ref().map_or(false, |arg| arg == "default"))
.map(|keyword| &keyword.node.value)
}) else {
return;
};
if !is_valid_default(expr) {
checker
.diagnostics
.push(Diagnostic::new(InvalidEnvvarDefault, Range::from(expr)));
}
}
}

View File

@@ -1,73 +0,0 @@
use rustpython_parser::ast::{Constant, Expr, ExprKind, Keyword};
use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::types::Range;
use crate::checkers::ast::Checker;
/// ## What it does
/// Checks for `os.getenv` calls with an invalid `key` argument.
///
/// ## Why is this bad?
/// `os.getenv` only supports strings as the first argument (`key`).
///
/// If the provided argument is not a string, `os.getenv` will throw a
/// `TypeError` at runtime.
///
/// ## Example
/// ```python
/// os.getenv(1)
/// ```
///
/// Use instead:
/// ```python
/// os.getenv("1")
/// ```
#[violation]
pub struct InvalidEnvvarValue;
impl Violation for InvalidEnvvarValue {
#[derive_message_formats]
fn message(&self) -> String {
format!("Invalid type for initial `os.getenv` argument; expected `str`")
}
}
/// PLE1507
pub fn invalid_envvar_value(
checker: &mut Checker,
func: &Expr,
args: &[Expr],
keywords: &[Keyword],
) {
if checker
.ctx
.resolve_call_path(func)
.map_or(false, |call_path| call_path.as_slice() == ["os", "getenv"])
{
// Get the first argument for `getenv`
if let Some(expr) = args.get(0).or_else(|| {
keywords
.iter()
.find(|keyword| keyword.node.arg.as_ref().map_or(false, |arg| arg == "key"))
.map(|keyword| &keyword.node.value)
}) {
// Ignoring types that are inferred, only do direct constants
if !matches!(
expr.node,
ExprKind::Constant {
value: Constant::Str { .. },
..
} | ExprKind::Name { .. }
| ExprKind::Attribute { .. }
| ExprKind::Subscript { .. }
| ExprKind::Call { .. }
) {
checker
.diagnostics
.push(Diagnostic::new(InvalidEnvvarValue, Range::from(expr)));
}
}
}
}

View File

@@ -107,7 +107,7 @@ pub fn logging_call(checker: &mut Checker, func: &Expr, args: &[Expr], keywords:
if let ExprKind::Attribute { attr, .. } = &func.node {
if LoggingLevel::from_attribute(attr.as_str()).is_some() {
let call_args = SimpleCallArgs::new(args, keywords);
if let Some(msg) = call_args.argument("msg", 0) {
if let Some(msg) = call_args.get_argument("msg", Some(0)) {
if let ExprKind::Constant {
value: Constant::Str(value),
..

View File

@@ -10,8 +10,6 @@ pub use global_statement::{global_statement, GlobalStatement};
pub use global_variable_not_assigned::GlobalVariableNotAssigned;
pub use invalid_all_format::{invalid_all_format, InvalidAllFormat};
pub use invalid_all_object::{invalid_all_object, InvalidAllObject};
pub use invalid_envvar_default::{invalid_envvar_default, InvalidEnvvarDefault};
pub use invalid_envvar_value::{invalid_envvar_value, InvalidEnvvarValue};
pub use logging::{logging_call, LoggingTooFewArgs, LoggingTooManyArgs};
pub use magic_value_comparison::{magic_value_comparison, MagicValueComparison};
pub use merge_isinstance::{merge_isinstance, ConsiderMergingIsinstance};
@@ -46,8 +44,6 @@ mod global_statement;
mod global_variable_not_assigned;
mod invalid_all_format;
mod invalid_all_object;
mod invalid_envvar_default;
mod invalid_envvar_value;
mod logging;
mod magic_value_comparison;
mod merge_isinstance;

View File

@@ -2,6 +2,7 @@ use std::{fmt, iter};
use regex::Regex;
use rustpython_parser::ast::{Expr, ExprContext, ExprKind, Stmt, StmtKind, Withitem};
use serde::{Deserialize, Serialize};
use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation};
@@ -13,7 +14,7 @@ use ruff_python_ast::visitor::Visitor;
use crate::checkers::ast::Checker;
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone, Copy)]
pub enum OuterBindingKind {
For,
With,
@@ -28,7 +29,7 @@ impl fmt::Display for OuterBindingKind {
}
}
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone, Copy)]
pub enum InnerBindingKind {
For,
With,

Some files were not shown because too many files have changed in this diff Show More