Compare commits

...

12 Commits

Author SHA1 Message Date
Charlie Marsh
2315db7d13 Bump version to 0.0.206 2023-01-01 16:39:29 -05:00
Charlie Marsh
f1a183c171 Rewrite mock.mock attribute accesses (#1533) 2023-01-01 13:14:09 -05:00
Harutaka Kawamura
509c6d5ec7 Add visit_format_spec to avoid false positives for F541 in f-string format specifier (#1528) 2023-01-01 13:03:32 -05:00
Maksudul Haque
6695988b59 Do not Change Quotation Style for SIM118 Autofix (#1529) 2023-01-01 12:53:46 -05:00
Harutaka Kawamura
e3867b172d Simplify unused snapshot check (#1525) 2023-01-01 02:43:07 -05:00
Harutaka Kawamura
4b8e30f350 Fix Name node range in NamedExpr node (#1526) 2023-01-01 02:41:49 -05:00
Charlie Marsh
8fd0d8e9d8 Bump pyupgrade implementation count 2022-12-31 21:25:34 -05:00
Colin Delahunty
70895a8f1e Pyupgrade: import mock to from unittest import mock (#1488) 2022-12-31 21:25:06 -05:00
Charlie Marsh
f2c9f94f73 Avoid some false positives for ends-in-period checks (#1521) 2022-12-31 18:38:22 -05:00
Charlie Marsh
605c6069e2 Ignore property assignments in RET504 (#1520) 2022-12-31 18:04:13 -05:00
Charlie Marsh
92c2981b6d Add dark mode variant for benchmark image (#1519) 2022-12-31 17:47:32 -05:00
Colin Delahunty
4ad8db3d61 Pyupgrade: Turn errors into OSError (#1434) 2022-12-31 16:36:05 -05:00
71 changed files with 2225 additions and 374 deletions

View File

@@ -116,18 +116,12 @@ jobs:
${{ runner.os }}-build-${{ env.cache-name }}-
${{ runner.os }}-build-
${{ runner.os }}-
- run: cargo install cargo-insta
- run: pip install black[d]==22.12.0
- name: Run tests
run: |
export INSTA_SNAPSHOT_REFERENCES_FILE="$(mktemp)"
cargo test --all
USED_SNAPSHOTS=$(cat $INSTA_SNAPSHOT_REFERENCES_FILE | xargs realpath --relative-to $PWD)
UNUSED_SNAPSHOTS=$(cat <(git ls-files '*.snap') <(echo "$USED_SNAPSHOTS") | sort | uniq -u)
if [ ! -z "$UNUSED_SNAPSHOTS" ]; then
echo "Found unused snapshots:"
echo "$UNUSED_SNAPSHOTS"
exit 1
fi
cargo insta test --all --delete-unreferenced-snapshots
git diff --exit-code
- run: cargo test --package ruff --test black_compatibility_test -- --ignored
# TODO(charlie): Re-enable the `wasm-pack` tests.

1
.gitignore vendored
View File

@@ -181,3 +181,4 @@ cython_debug/
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
.idea/
.vimspector.json

View File

@@ -1,6 +1,6 @@
repos:
- repo: https://github.com/charliermarsh/ruff-pre-commit
rev: v0.0.205
rev: v0.0.206
hooks:
- id: ruff

16
Cargo.lock generated
View File

@@ -750,7 +750,7 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
[[package]]
name = "flake8-to-ruff"
version = "0.0.205-dev.0"
version = "0.0.206-dev.0"
dependencies = [
"anyhow",
"clap 4.0.32",
@@ -1878,7 +1878,7 @@ dependencies = [
[[package]]
name = "ruff"
version = "0.0.205"
version = "0.0.206"
dependencies = [
"annotate-snippets 0.9.1",
"anyhow",
@@ -1946,7 +1946,7 @@ dependencies = [
[[package]]
name = "ruff_dev"
version = "0.0.205"
version = "0.0.206"
dependencies = [
"anyhow",
"clap 4.0.32",
@@ -1967,7 +1967,7 @@ dependencies = [
[[package]]
name = "ruff_macros"
version = "0.0.205"
version = "0.0.206"
dependencies = [
"proc-macro2",
"quote",
@@ -2010,7 +2010,7 @@ dependencies = [
[[package]]
name = "rustpython-ast"
version = "0.1.0"
source = "git+https://github.com/RustPython/RustPython.git?rev=8cb2b8292062adf13bde1b863a9b02c9f0bda3dd#8cb2b8292062adf13bde1b863a9b02c9f0bda3dd"
source = "git+https://github.com/RustPython/RustPython.git?rev=71becd4059fdce4bce7010f1208ed3b1c883abba#71becd4059fdce4bce7010f1208ed3b1c883abba"
dependencies = [
"num-bigint",
"rustpython-common",
@@ -2020,7 +2020,7 @@ dependencies = [
[[package]]
name = "rustpython-common"
version = "0.0.0"
source = "git+https://github.com/RustPython/RustPython.git?rev=8cb2b8292062adf13bde1b863a9b02c9f0bda3dd#8cb2b8292062adf13bde1b863a9b02c9f0bda3dd"
source = "git+https://github.com/RustPython/RustPython.git?rev=71becd4059fdce4bce7010f1208ed3b1c883abba#71becd4059fdce4bce7010f1208ed3b1c883abba"
dependencies = [
"ascii",
"cfg-if 1.0.0",
@@ -2043,7 +2043,7 @@ dependencies = [
[[package]]
name = "rustpython-compiler-core"
version = "0.1.2"
source = "git+https://github.com/RustPython/RustPython.git?rev=8cb2b8292062adf13bde1b863a9b02c9f0bda3dd#8cb2b8292062adf13bde1b863a9b02c9f0bda3dd"
source = "git+https://github.com/RustPython/RustPython.git?rev=71becd4059fdce4bce7010f1208ed3b1c883abba#71becd4059fdce4bce7010f1208ed3b1c883abba"
dependencies = [
"bincode",
"bitflags",
@@ -2060,7 +2060,7 @@ dependencies = [
[[package]]
name = "rustpython-parser"
version = "0.1.2"
source = "git+https://github.com/RustPython/RustPython.git?rev=8cb2b8292062adf13bde1b863a9b02c9f0bda3dd#8cb2b8292062adf13bde1b863a9b02c9f0bda3dd"
source = "git+https://github.com/RustPython/RustPython.git?rev=71becd4059fdce4bce7010f1208ed3b1c883abba#71becd4059fdce4bce7010f1208ed3b1c883abba"
dependencies = [
"ahash",
"anyhow",

View File

@@ -6,7 +6,7 @@ members = [
[package]
name = "ruff"
version = "0.0.205"
version = "0.0.206"
authors = ["Charlie Marsh <charlie.r.marsh@gmail.com>"]
edition = "2021"
rust-version = "1.65.0"
@@ -51,11 +51,11 @@ path-absolutize = { version = "3.0.14", features = ["once_cell_cache", "use_unix
quick-junit = { version = "0.3.2" }
regex = { version = "1.6.0" }
ropey = { version = "1.5.0", features = ["cr_lines", "simd"], default-features = false }
ruff_macros = { version = "0.0.205", path = "ruff_macros" }
ruff_macros = { version = "0.0.206", path = "ruff_macros" }
rustc-hash = { version = "1.1.0" }
rustpython-ast = { features = ["unparse"], git = "https://github.com/RustPython/RustPython.git", rev = "8cb2b8292062adf13bde1b863a9b02c9f0bda3dd" }
rustpython-common = { git = "https://github.com/RustPython/RustPython.git", rev = "8cb2b8292062adf13bde1b863a9b02c9f0bda3dd" }
rustpython-parser = { features = ["lalrpop"], git = "https://github.com/RustPython/RustPython.git", rev = "8cb2b8292062adf13bde1b863a9b02c9f0bda3dd" }
rustpython-ast = { features = ["unparse"], git = "https://github.com/RustPython/RustPython.git", rev = "71becd4059fdce4bce7010f1208ed3b1c883abba" }
rustpython-common = { git = "https://github.com/RustPython/RustPython.git", rev = "71becd4059fdce4bce7010f1208ed3b1c883abba" }
rustpython-parser = { features = ["lalrpop"], git = "https://github.com/RustPython/RustPython.git", rev = "71becd4059fdce4bce7010f1208ed3b1c883abba" }
schemars = { version = "0.8.11" }
semver = { version = "1.0.16" }
serde = { version = "1.0.147", features = ["derive"] }

View File

@@ -8,7 +8,11 @@
An extremely fast Python linter, written in Rust.
<p align="center">
<img alt="Bar chart with benchmark results" src="https://user-images.githubusercontent.com/1309177/187504482-6d9df992-a81d-4e86-9f6a-d958741c8182.svg">
<picture align="center">
<source media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/1309177/210156880-a97c2a0d-2c03-4393-8695-36547935a94e.svg">
<source media="(prefers-color-scheme: light)" srcset="https://user-images.githubusercontent.com/1309177/210156881-a88fd142-5008-4695-9407-d028cec3eff7.svg">
<img alt="Shows a bar chart with benchmark results." src="https://user-images.githubusercontent.com/1309177/210156881-a88fd142-5008-4695-9407-d028cec3eff7.svg">
</picture>
</p>
<p align="center">
@@ -169,7 +173,7 @@ Ruff also works with [pre-commit](https://pre-commit.com):
```yaml
- repo: https://github.com/charliermarsh/ruff-pre-commit
# Ruff version.
rev: 'v0.0.205'
rev: 'v0.0.206'
hooks:
- id: ruff
# Respect `exclude` and `extend-exclude` settings.
@@ -682,7 +686,9 @@ For more, see [pyupgrade](https://pypi.org/project/pyupgrade/3.2.0/) on PyPI.
| UP021 | ReplaceUniversalNewlines | `universal_newlines` is deprecated, use `text` | 🛠 |
| UP022 | ReplaceStdoutStderr | Sending stdout and stderr to pipe is deprecated, use `capture_output` | 🛠 |
| UP023 | RewriteCElementTree | `cElementTree` is deprecated, use `ElementTree` | 🛠 |
| UP024 | OSErrorAlias | Replace aliased errors with `OSError` | 🛠 |
| UP025 | RewriteUnicodeLiteral | Remove unicode literals from strings | 🛠 |
| UP026 | RewriteMockImport | `mock` is deprecated, use `unittest.mock` | 🛠 |
### pep8-naming (N)
@@ -1301,7 +1307,7 @@ natively, including:
- [`pep8-naming`](https://pypi.org/project/pep8-naming/)
- [`pydocstyle`](https://pypi.org/project/pydocstyle/)
- [`pygrep-hooks`](https://github.com/pre-commit/pygrep-hooks) (3/10)
- [`pyupgrade`](https://pypi.org/project/pyupgrade/) (20/33)
- [`pyupgrade`](https://pypi.org/project/pyupgrade/) (21/33)
- [`yesqa`](https://github.com/asottile/yesqa)
Note that, in some cases, Ruff uses different error code prefixes than would be found in the
@@ -1360,7 +1366,7 @@ Today, Ruff can be used to replace Flake8 when used with any of the following pl
Ruff can also replace [`isort`](https://pypi.org/project/isort/),
[`yesqa`](https://github.com/asottile/yesqa), [`eradicate`](https://pypi.org/project/eradicate/),
[`pygrep-hooks`](https://github.com/pre-commit/pygrep-hooks) (3/10), and a subset of the rules
implemented in [`pyupgrade`](https://pypi.org/project/pyupgrade/) (20/33).
implemented in [`pyupgrade`](https://pypi.org/project/pyupgrade/) (21/33).
If you're looking to use Ruff, but rely on an unsupported Flake8 plugin, free to file an Issue.

View File

@@ -771,7 +771,7 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
[[package]]
name = "flake8_to_ruff"
version = "0.0.205"
version = "0.0.206"
dependencies = [
"anyhow",
"clap",
@@ -1975,7 +1975,7 @@ dependencies = [
[[package]]
name = "ruff"
version = "0.0.205"
version = "0.0.206"
dependencies = [
"anyhow",
"bincode",

View File

@@ -1,6 +1,6 @@
[package]
name = "flake8-to-ruff"
version = "0.0.205-dev.0"
version = "0.0.206-dev.0"
edition = "2021"
[lib]

3
foo.py Normal file
View File

@@ -0,0 +1,3 @@
import mock.mock
x = mock.mock.Mock()

View File

@@ -4,7 +4,7 @@ build-backend = "maturin"
[project]
name = "ruff"
version = "0.0.205"
version = "0.0.206"
description = "An extremely fast Python linter, written in Rust."
authors = [
{ name = "Charlie Marsh", email = "charlie.r.marsh@gmail.com" },

View File

@@ -137,6 +137,14 @@ def x():
return a
# Considered OK, since attribute assignments can have side effects.
class X:
def x(self):
a = self.property
self.property = None
return a
# Test cases for using value for assignment then returning it
# See:https://github.com/afonasev/flake8-return/issues/47
def resolve_from_url(self, url: str) -> dict:
@@ -238,13 +246,16 @@ def close(self):
report(traceback.format_exc())
return any_failed
def global_assignment():
global X
X = 1
return X
def nonlocal_assignment():
X = 1
def inner():
nonlocal X
X = 1

View File

@@ -35,7 +35,6 @@ def f():
...
def f():
r"Here's a line without a period"
...
@@ -71,3 +70,24 @@ def f():
Here's a line without a period,
but here's the next line with trailing space """
...
def f(rounds: list[int], number: int) -> bool:
"""
:param rounds: list - rounds played.
:param number: int - round number.
:return: bool - was the round played?
"""
return number in rounds
def f(rounds: list[int], number: int) -> bool:
"""
Args:
rounds (list): rounds played.
number (int): round number.
Returns:
bool: was the round played?
"""
return number in rounds

View File

@@ -20,7 +20,8 @@ v = 23.234234
# OK
f"{v:0.2f}"
f"{f'{v:0.2f}'}"
# OK (false negatives)
# Errors
f"{v:{f'0.2f'}}"
f"{f''}"

View File

@@ -89,7 +89,8 @@ A = (
f'B'
f'{B}'
)
C = f'{A:{B}}'
C = f'{A:{f"{B}"}}'
from typing import Annotated, Literal

View File

@@ -0,0 +1,98 @@
import mmap, select, socket
from mmap import error
# These should be fixed
try:
pass
except EnvironmentError:
pass
try:
pass
except IOError:
pass
try:
pass
except WindowsError:
pass
try:
pass
except mmap.error:
pass
try:
pass
except select.error:
pass
try:
pass
except socket.error:
pass
try:
pass
except error:
pass
# Should NOT be in parentheses when replaced
try:
pass
except (IOError,):
pass
try:
pass
except (mmap.error,):
pass
try:
pass
except (EnvironmentError, IOError, OSError, select.error):
pass
# Should be kept in parentheses (because multiple)
try:
pass
except (IOError, KeyError, OSError):
pass
# First should change, second should not
from .mmap import error
try:
pass
except (IOError, error):
pass
# These should not change
from foo import error
try:
pass
except (OSError, error):
pass
try:
pass
except:
pass
try:
pass
except AssertionError:
pass
try:
pass
except (mmap).error:
pass
try:
pass
except OSError:
pass
try:
pass
except (OSError, KeyError):
pass

View File

@@ -0,0 +1,17 @@
import mmap, socket, select
try:
pass
except (OSError, mmap.error, IOError):
pass
except (OSError, socket.error, KeyError):
pass
try:
pass
except (
OSError,
select.error,
IOError,
):
pass

View File

@@ -0,0 +1,50 @@
# These should not change
raise ValueError
raise ValueError(1)
from .mmap import error
raise error
# Testing the modules
import socket, mmap, select
raise socket.error
raise mmap.error
raise select.error
raise socket.error()
raise mmap.error(1)
raise select.error(1, 2)
raise socket.error(
1,
2,
3,
)
from mmap import error
raise error
from socket import error
raise error(1)
from select import error
raise error(1, 2)
# Testing the names
raise EnvironmentError
raise IOError
raise WindowsError
raise EnvironmentError()
raise IOError(1)
raise WindowsError(1, 2)
raise EnvironmentError(
1,
2,
3,
)
raise WindowsError
raise EnvironmentError(1)
raise IOError(1, 2)

View File

@@ -0,0 +1,74 @@
# These should be changed
if True:
import mock
if True:
import mock, sys
# This goes to from unitest import mock
import mock.mock
# Mock should go on a new line as `from unittest import mock`
import contextlib, mock, sys
# Mock should go on a new line as `from unittest import mock`
import mock, sys
x = "This code should be preserved one line below the mock"
# Mock should go on a new line as `from unittest import mock`
from mock import mock
# Should keep trailing comma
from mock import (
mock,
a,
b,
c,
)
# Should not get a trailing comma
from mock import (
mock,
a,
b,
c
)
if True:
if False:
from mock import (
mock,
a,
b,
c
)
# These should not change:
import os, io
# Mock should go on a new line as `from unittest import mock`
import mock, mock
# Mock should go on a new line as `from unittest import mock as foo`
import mock as foo
# Mock should go on a new line as `from unittest import mock as foo`
from mock import mock as foo
if True:
# This should yield multiple, aliased imports.
import mock as foo, mock as bar, mock
# This should yield multiple, aliased imports, and preserve `os`.
import mock as foo, mock as bar, mock, os
if True:
# This should yield multiple, aliased imports.
from mock import mock as foo, mock as bar, mock
# This should be unchanged.
x = mock.Mock()
# This should change to `mock.Mock`().
x = mock.mock.Mock()

View File

@@ -909,7 +909,9 @@
"UP021",
"UP022",
"UP023",
"UP024",
"UP025",
"UP026",
"W",
"W2",
"W29",

View File

@@ -1,6 +1,6 @@
[package]
name = "ruff_dev"
version = "0.0.205"
version = "0.0.206"
edition = "2021"
[dependencies]
@@ -11,9 +11,9 @@ itertools = { version = "0.10.5" }
libcst = { git = "https://github.com/charliermarsh/LibCST", rev = "f2f0b7a487a8725d161fe8b3ed73a6758b21e177" }
once_cell = { version = "1.16.0" }
ruff = { path = ".." }
rustpython-ast = { features = ["unparse"], git = "https://github.com/RustPython/RustPython.git", rev = "8cb2b8292062adf13bde1b863a9b02c9f0bda3dd" }
rustpython-common = { git = "https://github.com/RustPython/RustPython.git", rev = "8cb2b8292062adf13bde1b863a9b02c9f0bda3dd" }
rustpython-parser = { features = ["lalrpop"], git = "https://github.com/RustPython/RustPython.git", rev = "8cb2b8292062adf13bde1b863a9b02c9f0bda3dd" }
rustpython-ast = { features = ["unparse"], git = "https://github.com/RustPython/RustPython.git", rev = "71becd4059fdce4bce7010f1208ed3b1c883abba" }
rustpython-common = { git = "https://github.com/RustPython/RustPython.git", rev = "71becd4059fdce4bce7010f1208ed3b1c883abba" }
rustpython-parser = { features = ["lalrpop"], git = "https://github.com/RustPython/RustPython.git", rev = "71becd4059fdce4bce7010f1208ed3b1c883abba" }
schemars = { version = "0.8.11" }
serde_json = {version="1.0.91"}
strum = { version = "0.24.1", features = ["strum_macros"] }

View File

@@ -1,6 +1,6 @@
[package]
name = "ruff_macros"
version = "0.0.205"
version = "0.0.206"
edition = "2021"
[lib]

View File

@@ -335,20 +335,17 @@ pub fn to_absolute(relative: Location, base: Location) -> Location {
/// Return `true` if a `Stmt` has leading content.
pub fn match_leading_content(stmt: &Stmt, locator: &SourceCodeLocator) -> bool {
let range = Range {
location: Location::new(stmt.location.row(), 0),
end_location: stmt.location,
};
let range = Range::new(Location::new(stmt.location.row(), 0), stmt.location);
let prefix = locator.slice_source_code_range(&range);
prefix.chars().any(|char| !char.is_whitespace())
}
/// Return `true` if a `Stmt` has trailing content.
pub fn match_trailing_content(stmt: &Stmt, locator: &SourceCodeLocator) -> bool {
let range = Range {
location: stmt.end_location.unwrap(),
end_location: Location::new(stmt.end_location.unwrap().row() + 1, 0),
};
let range = Range::new(
stmt.end_location.unwrap(),
Location::new(stmt.end_location.unwrap().row() + 1, 0),
);
let suffix = locator.slice_source_code_range(&range);
for char in suffix.chars() {
if char == '#' {
@@ -384,10 +381,7 @@ pub fn identifier_range(stmt: &Stmt, locator: &SourceCodeLocator) -> Range {
let contents = locator.slice_source_code_range(&Range::from_located(stmt));
for (start, tok, end) in lexer::make_tokenizer_located(&contents, stmt.location).flatten() {
if matches!(tok, Tok::Name { .. }) {
return Range {
location: start,
end_location: end,
};
return Range::new(start, end);
}
}
error!("Failed to find identifier for {:?}", stmt);
@@ -419,20 +413,15 @@ pub fn excepthandler_name_range(
match (name, type_) {
(Some(_), Some(type_)) => {
let type_end_location = type_.end_location.unwrap();
let contents = locator.slice_source_code_range(&Range {
location: type_end_location,
end_location: body[0].location,
});
let contents =
locator.slice_source_code_range(&Range::new(type_end_location, body[0].location));
let range = lexer::make_tokenizer_located(&contents, type_end_location)
.flatten()
.tuple_windows()
.find(|(tok, next_tok)| {
matches!(tok.1, Tok::As) && matches!(next_tok.1, Tok::Name { .. })
})
.map(|((..), (location, _, end_location))| Range {
location,
end_location,
});
.map(|((..), (location, _, end_location))| Range::new(location, end_location));
range
}
_ => None,
@@ -506,10 +495,10 @@ pub fn preceded_by_continuation(stmt: &Stmt, locator: &SourceCodeLocator) -> boo
// make conservative choices.
// TODO(charlie): Come up with a more robust strategy.
if stmt.location.row() > 1 {
let range = Range {
location: Location::new(stmt.location.row() - 1, 0),
end_location: Location::new(stmt.location.row(), 0),
};
let range = Range::new(
Location::new(stmt.location.row() - 1, 0),
Location::new(stmt.location.row(), 0),
);
let line = locator.slice_source_code_range(&range);
if line.trim().ends_with('\\') {
return true;
@@ -733,10 +722,7 @@ y = 2
let locator = SourceCodeLocator::new(contents);
assert_eq!(
identifier_range(stmt, &locator),
Range {
location: Location::new(1, 4),
end_location: Location::new(1, 5),
}
Range::new(Location::new(1, 4), Location::new(1, 5),)
);
let contents = r#"
@@ -750,10 +736,7 @@ def \
let locator = SourceCodeLocator::new(contents);
assert_eq!(
identifier_range(stmt, &locator),
Range {
location: Location::new(2, 2),
end_location: Location::new(2, 3),
}
Range::new(Location::new(2, 2), Location::new(2, 3),)
);
let contents = "class Class(): pass".trim();
@@ -762,10 +745,7 @@ def \
let locator = SourceCodeLocator::new(contents);
assert_eq!(
identifier_range(stmt, &locator),
Range {
location: Location::new(1, 6),
end_location: Location::new(1, 11),
}
Range::new(Location::new(1, 6), Location::new(1, 11),)
);
let contents = "class Class: pass".trim();
@@ -774,10 +754,7 @@ def \
let locator = SourceCodeLocator::new(contents);
assert_eq!(
identifier_range(stmt, &locator),
Range {
location: Location::new(1, 6),
end_location: Location::new(1, 11),
}
Range::new(Location::new(1, 6), Location::new(1, 11),)
);
let contents = r#"
@@ -791,10 +768,7 @@ class Class():
let locator = SourceCodeLocator::new(contents);
assert_eq!(
identifier_range(stmt, &locator),
Range {
location: Location::new(2, 6),
end_location: Location::new(2, 11),
}
Range::new(Location::new(2, 6), Location::new(2, 11),)
);
let contents = r#"x = y + 1"#.trim();
@@ -803,10 +777,7 @@ class Class():
let locator = SourceCodeLocator::new(contents);
assert_eq!(
identifier_range(stmt, &locator),
Range {
location: Location::new(1, 0),
end_location: Location::new(1, 9),
}
Range::new(Location::new(1, 0), Location::new(1, 9),)
);
Ok(())

View File

@@ -22,12 +22,16 @@ pub struct Range {
}
impl Range {
pub fn from_located<T>(located: &Located<T>) -> Self {
Range {
location: located.location,
end_location: located.end_location.unwrap(),
pub fn new(location: Location, end_location: Location) -> Self {
Self {
location,
end_location,
}
}
pub fn from_located<T>(located: &Located<T>) -> Self {
Range::new(located.location, located.end_location.unwrap())
}
}
#[derive(Debug)]

View File

@@ -38,6 +38,9 @@ pub trait Visitor<'a> {
fn visit_excepthandler(&mut self, excepthandler: &'a Excepthandler) {
walk_excepthandler(self, excepthandler);
}
fn visit_format_spec(&mut self, format_spec: &'a Expr) {
walk_expr(self, format_spec);
}
fn visit_arguments(&mut self, arguments: &'a Arguments) {
walk_arguments(self, arguments);
}
@@ -387,7 +390,7 @@ pub fn walk_expr<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, expr: &'a Expr) {
} => {
visitor.visit_expr(value);
if let Some(expr) = format_spec {
visitor.visit_expr(expr);
visitor.visit_format_spec(expr);
}
}
ExprKind::JoinedStr { values } => {

View File

@@ -9,10 +9,10 @@ use crate::checkers::ast::Checker;
/// Extract the leading indentation from a line.
pub fn indentation<'a, T>(checker: &'a Checker, located: &'a Located<T>) -> Cow<'a, str> {
let range = Range::from_located(located);
checker.locator.slice_source_code_range(&Range {
location: Location::new(range.location.row(), 0),
end_location: Location::new(range.location.row(), range.location.column()),
})
checker.locator.slice_source_code_range(&Range::new(
Location::new(range.location.row(), 0),
Location::new(range.location.row(), range.location.column()),
))
}
/// Extract the leading words from a line of text.

View File

@@ -68,10 +68,7 @@ fn apply_fixes<'a>(
}
// Add all contents from `last_pos` to `fix.location`.
let slice = locator.slice_source_code_range(&Range {
location: last_pos,
end_location: fix.location,
});
let slice = locator.slice_source_code_range(&Range::new(last_pos, fix.location));
output.append(&slice);
// Add the patch itself.

View File

@@ -91,7 +91,6 @@ pub struct Checker<'a> {
in_type_definition: bool,
in_deferred_string_type_definition: bool,
in_deferred_type_definition: bool,
in_f_string: bool,
in_literal: bool,
in_subscript: bool,
seen_import_boundary: bool,
@@ -148,7 +147,6 @@ impl<'a> Checker<'a> {
in_type_definition: false,
in_deferred_string_type_definition: false,
in_deferred_type_definition: false,
in_f_string: false,
in_literal: false,
in_subscript: false,
seen_import_boundary: false,
@@ -215,10 +213,10 @@ impl<'a> Checker<'a> {
return false;
}
let noqa_lineno = self.noqa_line_for.get(&lineno).unwrap_or(&lineno);
let line = self.locator.slice_source_code_range(&Range {
location: Location::new(*noqa_lineno, 0),
end_location: Location::new(noqa_lineno + 1, 0),
});
let line = self.locator.slice_source_code_range(&Range::new(
Location::new(*noqa_lineno, 0),
Location::new(noqa_lineno + 1, 0),
));
match noqa::extract_noqa_directive(&line) {
Directive::None => false,
Directive::All(..) => true,
@@ -643,6 +641,9 @@ where
if self.settings.enabled.contains(&CheckCode::UP023) {
pyupgrade::plugins::replace_c_element_tree(self, stmt);
}
if self.settings.enabled.contains(&CheckCode::UP026) {
pyupgrade::plugins::rewrite_mock_import(self, stmt);
}
for alias in names {
if alias.node.name.contains('.') && alias.node.asname.is_none() {
@@ -854,6 +855,9 @@ where
pyupgrade::plugins::unnecessary_future_import(self, stmt, names);
}
}
if self.settings.enabled.contains(&CheckCode::UP026) {
pyupgrade::plugins::rewrite_mock_import(self, stmt);
}
if self.settings.enabled.contains(&CheckCode::TID251) {
if let Some(module) = module {
@@ -1107,6 +1111,11 @@ where
flake8_errmsg::plugins::string_in_exception(self, exc);
}
}
if self.settings.enabled.contains(&CheckCode::UP024) {
if let Some(item) = exc {
pyupgrade::plugins::os_error_alias(self, item);
}
}
}
StmtKind::AugAssign { target, .. } => {
self.handle_node_load(target);
@@ -1191,6 +1200,9 @@ where
if self.settings.enabled.contains(&CheckCode::B013) {
flake8_bugbear::plugins::redundant_tuple_in_exception_handler(self, handlers);
}
if self.settings.enabled.contains(&CheckCode::UP024) {
pyupgrade::plugins::os_error_alias(self, handlers);
}
}
StmtKind::Assign { targets, value, .. } => {
if self.settings.enabled.contains(&CheckCode::E731) {
@@ -1472,7 +1484,6 @@ where
self.push_expr(expr);
let prev_in_f_string = self.in_f_string;
let prev_in_literal = self.in_literal;
let prev_in_type_definition = self.in_type_definition;
@@ -1595,6 +1606,10 @@ where
if self.settings.enabled.contains(&CheckCode::UP019) {
pyupgrade::plugins::typing_text_str_alias(self, expr);
}
if self.settings.enabled.contains(&CheckCode::UP026) {
pyupgrade::plugins::rewrite_mock_attribute(self, expr);
}
if self.settings.enabled.contains(&CheckCode::YTT202) {
flake8_2020::plugins::name_or_attribute(self, expr);
}
@@ -1716,6 +1731,9 @@ where
if self.settings.enabled.contains(&CheckCode::UP022) {
pyupgrade::plugins::replace_stdout_stderr(self, expr, keywords);
}
if self.settings.enabled.contains(&CheckCode::UP024) {
pyupgrade::plugins::os_error_alias(self, expr);
}
// flake8-print
if self.settings.enabled.contains(&CheckCode::T201)
@@ -2159,9 +2177,7 @@ where
}
}
ExprKind::JoinedStr { values } => {
// Conversion flags are parsed as f-strings without placeholders, so skip
// nested f-strings, which would lead to false positives.
if !self.in_f_string && self.settings.enabled.contains(&CheckCode::F541) {
if self.settings.enabled.contains(&CheckCode::F541) {
if !values
.iter()
.any(|value| matches!(value.node, ExprKind::FormattedValue { .. }))
@@ -2665,9 +2681,7 @@ where
self.in_subscript = prev_in_subscript;
}
ExprKind::JoinedStr { .. } => {
self.in_f_string = true;
visitor::walk_expr(self, expr);
self.in_f_string = prev_in_f_string;
}
_ => visitor::walk_expr(self, expr),
}
@@ -2686,7 +2700,6 @@ where
self.in_type_definition = prev_in_type_definition;
self.in_literal = prev_in_literal;
self.in_f_string = prev_in_f_string;
self.pop_expr();
}
@@ -2790,6 +2803,17 @@ where
}
}
fn visit_format_spec(&mut self, format_spec: &'b Expr) {
match &format_spec.node {
ExprKind::JoinedStr { values } => {
for value in values {
self.visit_expr(value);
}
}
_ => unreachable!("Unexpected expression for format_spec"),
}
}
fn visit_arguments(&mut self, arguments: &'b Arguments) {
if self.settings.enabled.contains(&CheckCode::B006) {
flake8_bugbear::plugins::mutable_argument_default(self, arguments);
@@ -3869,10 +3893,10 @@ impl<'a> Checker<'a> {
let content = self
.locator
.slice_source_code_range(&Range::from_located(expr));
let indentation = self.locator.slice_source_code_range(&Range {
location: Location::new(expr.location.row(), 0),
end_location: Location::new(expr.location.row(), expr.location.column()),
});
let indentation = self.locator.slice_source_code_range(&Range::new(
Location::new(expr.location.row(), 0),
Location::new(expr.location.row(), expr.location.column()),
));
let body = pydocstyle::helpers::raw_contents(&content);
let docstring = Docstring {
kind: definition.kind,

View File

@@ -102,10 +102,7 @@ pub fn check_noqa(
if matches.is_empty() {
let mut check = Check::new(
CheckKind::UnusedNOQA(None),
Range {
location: Location::new(row + 1, start),
end_location: Location::new(row + 1, end),
},
Range::new(Location::new(row + 1, start), Location::new(row + 1, end)),
);
if matches!(autofix, flags::Autofix::Enabled)
&& settings.fixable.contains(check.kind.code())
@@ -169,10 +166,7 @@ pub fn check_noqa(
.map(|code| (*code).to_string())
.collect(),
})),
Range {
location: Location::new(row + 1, start),
end_location: Location::new(row + 1, end),
},
Range::new(Location::new(row + 1, start), Location::new(row + 1, end)),
);
if matches!(autofix, flags::Autofix::Enabled)
&& settings.fixable.contains(check.kind.code())

View File

@@ -236,7 +236,9 @@ pub enum CheckCode {
UP021,
UP022,
UP023,
UP024,
UP025,
UP026,
// pydocstyle
D100,
D101,
@@ -703,6 +705,12 @@ pub struct UnusedCodes {
pub unmatched: Vec<String>,
}
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum MockReference {
Import,
Attribute,
}
#[derive(AsRefStr, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum CheckKind {
// pycodestyle errors
@@ -907,7 +915,9 @@ pub enum CheckKind {
ReplaceUniversalNewlines,
ReplaceStdoutStderr,
RewriteCElementTree,
OSErrorAlias(Option<String>),
RewriteUnicodeLiteral,
RewriteMockImport(MockReference),
// pydocstyle
BlankLineAfterLastSection(String),
BlankLineAfterSection(String),
@@ -1301,7 +1311,9 @@ impl CheckCode {
CheckCode::UP021 => CheckKind::ReplaceUniversalNewlines,
CheckCode::UP022 => CheckKind::ReplaceStdoutStderr,
CheckCode::UP023 => CheckKind::RewriteCElementTree,
CheckCode::UP024 => CheckKind::OSErrorAlias(None),
CheckCode::UP025 => CheckKind::RewriteUnicodeLiteral,
CheckCode::UP026 => CheckKind::RewriteMockImport(MockReference::Import),
// pydocstyle
CheckCode::D100 => CheckKind::PublicModule,
CheckCode::D101 => CheckKind::PublicClass,
@@ -1733,7 +1745,9 @@ impl CheckCode {
CheckCode::UP021 => CheckCategory::Pyupgrade,
CheckCode::UP022 => CheckCategory::Pyupgrade,
CheckCode::UP023 => CheckCategory::Pyupgrade,
CheckCode::UP024 => CheckCategory::Pyupgrade,
CheckCode::UP025 => CheckCategory::Pyupgrade,
CheckCode::UP026 => CheckCategory::Pyupgrade,
CheckCode::W292 => CheckCategory::Pycodestyle,
CheckCode::W605 => CheckCategory::Pycodestyle,
CheckCode::YTT101 => CheckCategory::Flake82020,
@@ -1955,7 +1969,9 @@ impl CheckKind {
CheckKind::ReplaceUniversalNewlines => &CheckCode::UP021,
CheckKind::ReplaceStdoutStderr => &CheckCode::UP022,
CheckKind::RewriteCElementTree => &CheckCode::UP023,
CheckKind::OSErrorAlias(..) => &CheckCode::UP024,
CheckKind::RewriteUnicodeLiteral => &CheckCode::UP025,
CheckKind::RewriteMockImport(..) => &CheckCode::UP026,
// pydocstyle
CheckKind::BlankLineAfterLastSection(..) => &CheckCode::D413,
CheckKind::BlankLineAfterSection(..) => &CheckCode::D410,
@@ -2715,7 +2731,11 @@ impl CheckKind {
CheckKind::RewriteCElementTree => {
"`cElementTree` is deprecated, use `ElementTree`".to_string()
}
CheckKind::OSErrorAlias(..) => "Replace aliased errors with `OSError`".to_string(),
CheckKind::RewriteUnicodeLiteral => "Remove unicode literals from strings".to_string(),
CheckKind::RewriteMockImport(..) => {
"`mock` is deprecated, use `unittest.mock`".to_string()
}
// pydocstyle
CheckKind::FitsOnOneLine => "One-line docstring should fit on one line".to_string(),
CheckKind::BlankLineAfterSummary => {
@@ -3129,8 +3149,8 @@ impl CheckKind {
matches!(
self,
CheckKind::AmbiguousUnicodeCharacterString(..)
| CheckKind::AmbiguousUnicodeCharacterDocstring(..)
| CheckKind::AmbiguousUnicodeCharacterComment(..)
| CheckKind::AmbiguousUnicodeCharacterDocstring(..)
| CheckKind::BlankLineAfterLastSection(..)
| CheckKind::BlankLineAfterSection(..)
| CheckKind::BlankLineAfterSummary
@@ -3156,12 +3176,7 @@ impl CheckKind {
| CheckKind::MisplacedComparisonConstant(..)
| CheckKind::MissingReturnTypeSpecialMethod(..)
| CheckKind::NativeLiterals(..)
| CheckKind::OpenAlias
| CheckKind::NewLineAfterLastParagraph
| CheckKind::ReplaceUniversalNewlines
| CheckKind::ReplaceStdoutStderr
| CheckKind::RewriteCElementTree
| CheckKind::RewriteUnicodeLiteral
| CheckKind::NewLineAfterSectionName(..)
| CheckKind::NoBlankLineAfterFunction(..)
| CheckKind::NoBlankLineBeforeClass(..)
@@ -3174,16 +3189,23 @@ impl CheckKind {
| CheckKind::NoneComparison(..)
| CheckKind::NotInTest
| CheckKind::NotIsTest
| CheckKind::OSErrorAlias(..)
| CheckKind::OneBlankLineAfterClass(..)
| CheckKind::OneBlankLineBeforeClass(..)
| CheckKind::OpenAlias
| CheckKind::PEP3120UnnecessaryCodingComment
| CheckKind::PPrintFound
| CheckKind::PrintFound
| CheckKind::PercentFormatExtraNamedArguments(..)
| CheckKind::PrintFound
| CheckKind::RaiseNotImplemented
| CheckKind::RedundantOpenModes(..)
| CheckKind::RedundantTupleInExceptionHandler(..)
| CheckKind::RemoveSixCompat
| CheckKind::ReplaceStdoutStderr
| CheckKind::ReplaceUniversalNewlines
| CheckKind::RewriteCElementTree
| CheckKind::RewriteMockImport(..)
| CheckKind::RewriteUnicodeLiteral
| CheckKind::SectionNameEndsInColon(..)
| CheckKind::SectionNotOverIndented(..)
| CheckKind::SectionUnderlineAfterName(..)
@@ -3295,6 +3317,10 @@ impl CheckKind {
}
CheckKind::RewriteCElementTree => Some("Replace with `ElementTree`".to_string()),
CheckKind::RewriteUnicodeLiteral => Some("Remove unicode prefix".to_string()),
CheckKind::RewriteMockImport(reference_type) => Some(match reference_type {
MockReference::Import => "Import from `unittest.mock` instead".to_string(),
MockReference::Attribute => "Replace `mock.mock` with `mock`".to_string(),
}),
CheckKind::NewLineAfterSectionName(name) => {
Some(format!("Add newline after \"{name}\""))
}
@@ -3313,6 +3339,10 @@ impl CheckKind {
CheckKind::OneBlankLineAfterClass(..) => {
Some("Insert 1 blank line after class docstring".to_string())
}
CheckKind::OSErrorAlias(name) => Some(match name {
None => "Replace with builtin `OSError`".to_string(),
Some(name) => format!("Replace `{name}` with builtin `OSError`"),
}),
CheckKind::NoBlankLinesBetweenHeaderAndContent(..) => {
Some("Remove blank line(s)".to_string())
}

View File

@@ -540,7 +540,9 @@ pub enum CheckCodePrefix {
UP021,
UP022,
UP023,
UP024,
UP025,
UP026,
W,
W2,
W29,
@@ -773,7 +775,9 @@ impl CheckCodePrefix {
CheckCode::UP021,
CheckCode::UP022,
CheckCode::UP023,
CheckCode::UP024,
CheckCode::UP025,
CheckCode::UP026,
CheckCode::D100,
CheckCode::D101,
CheckCode::D102,
@@ -2452,7 +2456,9 @@ impl CheckCodePrefix {
CheckCode::UP021,
CheckCode::UP022,
CheckCode::UP023,
CheckCode::UP024,
CheckCode::UP025,
CheckCode::UP026,
]
}
CheckCodePrefix::U0 => {
@@ -2485,7 +2491,9 @@ impl CheckCodePrefix {
CheckCode::UP021,
CheckCode::UP022,
CheckCode::UP023,
CheckCode::UP024,
CheckCode::UP025,
CheckCode::UP026,
]
}
CheckCodePrefix::U00 => {
@@ -2702,7 +2710,9 @@ impl CheckCodePrefix {
CheckCode::UP021,
CheckCode::UP022,
CheckCode::UP023,
CheckCode::UP024,
CheckCode::UP025,
CheckCode::UP026,
],
CheckCodePrefix::UP0 => vec![
CheckCode::UP001,
@@ -2727,7 +2737,9 @@ impl CheckCodePrefix {
CheckCode::UP021,
CheckCode::UP022,
CheckCode::UP023,
CheckCode::UP024,
CheckCode::UP025,
CheckCode::UP026,
],
CheckCodePrefix::UP00 => vec![
CheckCode::UP001,
@@ -2774,13 +2786,17 @@ impl CheckCodePrefix {
CheckCode::UP021,
CheckCode::UP022,
CheckCode::UP023,
CheckCode::UP024,
CheckCode::UP025,
CheckCode::UP026,
],
CheckCodePrefix::UP020 => vec![CheckCode::UP020],
CheckCodePrefix::UP021 => vec![CheckCode::UP021],
CheckCodePrefix::UP022 => vec![CheckCode::UP022],
CheckCodePrefix::UP023 => vec![CheckCode::UP023],
CheckCodePrefix::UP024 => vec![CheckCode::UP024],
CheckCodePrefix::UP025 => vec![CheckCode::UP025],
CheckCodePrefix::UP026 => vec![CheckCode::UP026],
CheckCodePrefix::W => vec![CheckCode::W292, CheckCode::W605],
CheckCodePrefix::W2 => vec![CheckCode::W292],
CheckCodePrefix::W29 => vec![CheckCode::W292],
@@ -3352,7 +3368,9 @@ impl CheckCodePrefix {
CheckCodePrefix::UP021 => SuffixLength::Three,
CheckCodePrefix::UP022 => SuffixLength::Three,
CheckCodePrefix::UP023 => SuffixLength::Three,
CheckCodePrefix::UP024 => SuffixLength::Three,
CheckCodePrefix::UP025 => SuffixLength::Three,
CheckCodePrefix::UP026 => SuffixLength::Three,
CheckCodePrefix::W => SuffixLength::Zero,
CheckCodePrefix::W2 => SuffixLength::One,
CheckCodePrefix::W29 => SuffixLength::Two,

View File

@@ -1,5 +1,5 @@
use anyhow::{bail, Result};
use libcst_native::{Expr, Module, SmallStatement, Statement};
use libcst_native::{Expr, Import, ImportFrom, Module, SmallStatement, Statement};
pub fn match_module(module_text: &str) -> Result<Module> {
match libcst_native::parse_module(module_text, None) {
@@ -19,3 +19,27 @@ pub fn match_expr<'a, 'b>(module: &'a mut Module<'b>) -> Result<&'a mut Expr<'b>
bail!("Expected Statement::Simple")
}
}
pub fn match_import<'a, 'b>(module: &'a mut Module<'b>) -> Result<&'a mut Import<'b>> {
if let Some(Statement::Simple(expr)) = module.body.first_mut() {
if let Some(SmallStatement::Import(expr)) = expr.body.first_mut() {
Ok(expr)
} else {
bail!("Expected SmallStatement::Expr")
}
} else {
bail!("Expected Statement::Simple")
}
}
pub fn match_import_from<'a, 'b>(module: &'a mut Module<'b>) -> Result<&'a mut ImportFrom<'b>> {
if let Some(Statement::Simple(expr)) = module.body.first_mut() {
if let Some(SmallStatement::ImportFrom(expr)) = expr.body.first_mut() {
Ok(expr)
} else {
bail!("Expected SmallStatement::Expr")
}
} else {
bail!("Expected Statement::Simple")
}
}

View File

@@ -110,10 +110,7 @@ pub fn extract_isort_directives(lxr: &[LexResult], locator: &SourceCodeLocator)
}
// TODO(charlie): Modify RustPython to include the comment text in the token.
let comment_text = locator.slice_source_code_range(&Range {
location: start,
end_location: end,
});
let comment_text = locator.slice_source_code_range(&Range::new(start, end));
if comment_text == "# isort: split" {
splits.push(start.row());

View File

@@ -1,4 +1,4 @@
// See: https://docs.python.org/3/reference/lexical_analysis.html#string-and-bytes-literals.
/// See: <https://docs.python.org/3/reference/lexical_analysis.html#string-and-bytes-literals>
pub const TRIPLE_QUOTE_PREFIXES: &[&str] = &[
"u\"\"\"", "u'''", "r\"\"\"", "r'''", "U\"\"\"", "U'''", "R\"\"\"", "R'''", "\"\"\"", "'''",

View File

@@ -5,21 +5,21 @@ use crate::docstrings::google::{GOOGLE_SECTION_NAMES, LOWERCASE_GOOGLE_SECTION_N
use crate::docstrings::numpy::{LOWERCASE_NUMPY_SECTION_NAMES, NUMPY_SECTION_NAMES};
pub(crate) enum SectionStyle {
NumPy,
Numpy,
Google,
}
impl SectionStyle {
pub(crate) fn section_names(&self) -> &Lazy<FxHashSet<&'static str>> {
match self {
SectionStyle::NumPy => &NUMPY_SECTION_NAMES,
SectionStyle::Numpy => &NUMPY_SECTION_NAMES,
SectionStyle::Google => &GOOGLE_SECTION_NAMES,
}
}
pub(crate) fn lowercase_section_names(&self) -> &Lazy<FxHashSet<&'static str>> {
match self {
SectionStyle::NumPy => &LOWERCASE_NUMPY_SECTION_NAMES,
SectionStyle::Numpy => &LOWERCASE_NUMPY_SECTION_NAMES,
SectionStyle::Google => &LOWERCASE_GOOGLE_SECTION_NAMES,
}
}

View File

@@ -28,20 +28,11 @@ pub fn commented_out_code(
) -> Option<Check> {
let location = Location::new(start.row(), 0);
let end_location = Location::new(end.row() + 1, 0);
let line = locator.slice_source_code_range(&Range {
location,
end_location,
});
let line = locator.slice_source_code_range(&Range::new(location, end_location));
// Verify that the comment is on its own line, and that it contains code.
if is_standalone_comment(&line) && comment_contains_code(&line) {
let mut check = Check::new(
CheckKind::CommentedOutCode,
Range {
location: start,
end_location: end,
},
);
let mut check = Check::new(CheckKind::CommentedOutCode, Range::new(start, end));
if matches!(autofix, flags::Autofix::Enabled)
&& settings.fixable.contains(&CheckCode::ERA001)
{

View File

@@ -49,7 +49,7 @@ expression: checks
column: 1
end_location:
row: 6
column: 13
column: 8
fix: ~
parent: ~
- kind:

View File

@@ -47,10 +47,7 @@ pub fn quotes(
is_docstring: bool,
settings: &Settings,
) -> Option<Check> {
let text = locator.slice_source_code_range(&Range {
location: start,
end_location: end,
});
let text = locator.slice_source_code_range(&Range::new(start, end));
// Remove any prefixes (e.g., remove `u` from `u"foo"`).
let last_quote_char = text.chars().last().unwrap();
@@ -76,10 +73,7 @@ pub fn quotes(
Some(Check::new(
CheckKind::BadQuotesDocstring(settings.docstring_quotes.clone()),
Range {
location: start,
end_location: end,
},
Range::new(start, end),
))
} else if is_multiline {
// If our string is or contains a known good string, ignore it.
@@ -94,10 +88,7 @@ pub fn quotes(
Some(Check::new(
CheckKind::BadQuotesMultilineString(settings.multiline_quotes.clone()),
Range {
location: start,
end_location: end,
},
Range::new(start, end),
))
} else {
let string_contents = &raw_text[1..raw_text.len() - 1];
@@ -112,10 +103,7 @@ pub fn quotes(
{
return Some(Check::new(
CheckKind::AvoidQuoteEscape,
Range {
location: start,
end_location: end,
},
Range::new(start, end),
));
}
return None;
@@ -125,10 +113,7 @@ pub fn quotes(
if !string_contents.contains(good_single(&settings.inline_quotes)) {
return Some(Check::new(
CheckKind::BadQuotesInlineString(settings.inline_quotes.clone()),
Range {
location: start,
end_location: end,
},
Range::new(start, end),
));
}

View File

@@ -38,6 +38,18 @@ impl<'a> ReturnVisitor<'a> {
.push(expr.location);
return;
}
ExprKind::Attribute { .. } => {
// Attribute assignments are often side-effects (e.g., `self.property = value`),
// so we conservatively treat them as references to every known
// variable.
for name in self.stack.assigns.keys() {
self.stack
.refs
.entry(name)
.or_insert_with(Vec::new)
.push(expr.location);
}
}
_ => {}
}
visitor::walk_expr(self, expr);

View File

@@ -1,9 +1,11 @@
use rustpython_ast::{Cmpop, Expr, ExprKind};
use crate::ast::helpers::create_expr;
use crate::ast::types::Range;
use crate::autofix::Fix;
use crate::checkers::ast::Checker;
use crate::checks::{Check, CheckCode, CheckKind};
use crate::checks::{Check, CheckKind};
use crate::source_code_generator::SourceCodeGenerator;
/// SIM118
fn key_in_dict(checker: &mut Checker, left: &Expr, right: &Expr, range: Range) {
@@ -29,13 +31,21 @@ fn key_in_dict(checker: &mut Checker, left: &Expr, right: &Expr, range: Range) {
CheckKind::KeyInDict(left.to_string(), value.to_string()),
range,
);
if checker.patch(&CheckCode::SIM118) {
let content = right.to_string().replace(".keys()", "");
check.amend(Fix::replacement(
content,
right.location,
right.end_location.unwrap(),
));
if checker.patch(check.kind.code()) {
let mut generator = SourceCodeGenerator::new(
checker.style.indentation(),
checker.style.quote(),
checker.style.line_ending(),
);
generator.unparse_expr(&create_expr(value.node.clone()), 0);
if let Ok(content) = generator.generate() {
check.amend(Fix::replacement(
content,
right.location,
right.end_location.unwrap(),
));
}
}
checker.add_check(check);
}
@@ -46,10 +56,7 @@ pub fn key_in_dict_for(checker: &mut Checker, target: &Expr, iter: &Expr) {
checker,
target,
iter,
Range {
location: target.location,
end_location: iter.end_location.unwrap(),
},
Range::new(target.location, iter.end_location.unwrap()),
);
}

View File

@@ -22,10 +22,7 @@ pub fn collect_comments<'a>(range: &Range, locator: &'a SourceCodeLocator) -> Ve
.filter_map(|(start, tok, end)| {
if matches!(tok, Tok::Comment) {
Some(Comment {
value: locator.slice_source_code_range(&Range {
location: start,
end_location: end,
}),
value: locator.slice_source_code_range(&Range::new(start, end)),
location: start,
end_location: end,
})

View File

@@ -23,12 +23,12 @@ use crate::SourceCodeLocator;
mod categorize;
mod comments;
pub mod format;
mod helpers;
pub mod helpers;
pub mod plugins;
pub mod settings;
mod sorting;
pub mod track;
mod types;
pub mod types;
#[derive(Debug)]
pub struct AnnotatedAliasData<'a> {

View File

@@ -19,18 +19,12 @@ use crate::{Check, Settings, SourceCodeLocator};
fn extract_range(body: &[&Stmt]) -> Range {
let location = body.first().unwrap().location;
let end_location = body.last().unwrap().end_location.unwrap();
Range {
location,
end_location,
}
Range::new(location, end_location)
}
fn extract_indentation_range(body: &[&Stmt]) -> Range {
let location = body.first().unwrap().location;
Range {
location: Location::new(location.row(), 0),
end_location: location,
}
Range::new(Location::new(location.row(), 0), location)
}
/// I001
@@ -57,10 +51,10 @@ pub fn check_imports(
// Extract comments. Take care to grab any inline comments from the last line.
let comments = comments::collect_comments(
&Range {
location: range.location,
end_location: Location::new(range.end_location.row() + 1, 0),
},
&Range::new(
range.location,
Location::new(range.end_location.row() + 1, 0),
),
locator,
);
@@ -90,10 +84,10 @@ pub fn check_imports(
);
// Expand the span the entire range, including leading and trailing space.
let range = Range {
location: Location::new(range.location.row(), 0),
end_location: Location::new(range.end_location.row() + 1 + num_trailing_lines, 0),
};
let range = Range::new(
Location::new(range.location.row(), 0),
Location::new(range.end_location.row() + 1 + num_trailing_lines, 0),
);
let actual = dedent(&locator.slice_source_code_range(&range));
if actual == dedent(&expected) {
None

View File

@@ -120,10 +120,7 @@ pub(crate) fn check_path(
if settings.enabled.contains(&CheckCode::E999) {
checks.push(Check::new(
CheckKind::SyntaxError(parse_error.error.to_string()),
Range {
location: parse_error.location,
end_location: parse_error.location,
},
Range::new(parse_error.location, parse_error.location),
));
}
}

View File

@@ -55,22 +55,18 @@ pub struct Source {
impl Source {
pub fn from_check(check: &Check, locator: &SourceCodeLocator) -> Self {
let source = locator.slice_source_code_range(&Range {
location: Location::new(check.location.row(), 0),
// Checks can already extend one-past-the-end per Ropey's semantics. If they do, though,
// then they'll end at the start of a line. We need to avoid extending by yet another
// line past-the-end.
end_location: if check.end_location.column() == 0 {
check.end_location
} else {
Location::new(check.end_location.row() + 1, 0)
},
});
let location = Location::new(check.location.row(), 0);
// Checks can already extend one-past-the-end per Ropey's semantics. If they do,
// though, then they'll end at the start of a line. We need to avoid
// extending by yet another line past-the-end.
let end_location = if check.end_location.column() == 0 {
check.end_location
} else {
Location::new(check.end_location.row() + 1, 0)
};
let source = locator.slice_source_code_range(&Range::new(location, end_location));
let num_chars_in_range = locator
.slice_source_code_range(&Range {
location: check.location,
end_location: check.end_location,
})
.slice_source_code_range(&Range::new(check.location, check.end_location))
.chars()
.count();
Source {

View File

@@ -248,10 +248,7 @@ mod tests {
let checks = vec![Check::new(
CheckKind::UnusedVariable("x".to_string()),
Range {
location: Location::new(1, 0),
end_location: Location::new(1, 0),
},
Range::new(Location::new(1, 0), Location::new(1, 0)),
)];
let contents = "x = 1";
let noqa_line_for = IntMap::default();
@@ -269,17 +266,11 @@ mod tests {
let checks = vec![
Check::new(
CheckKind::AmbiguousVariableName("x".to_string()),
Range {
location: Location::new(1, 0),
end_location: Location::new(1, 0),
},
Range::new(Location::new(1, 0), Location::new(1, 0)),
),
Check::new(
CheckKind::UnusedVariable("x".to_string()),
Range {
location: Location::new(1, 0),
end_location: Location::new(1, 0),
},
Range::new(Location::new(1, 0), Location::new(1, 0)),
),
];
let contents = "x = 1 # noqa: E741\n";
@@ -298,17 +289,11 @@ mod tests {
let checks = vec![
Check::new(
CheckKind::AmbiguousVariableName("x".to_string()),
Range {
location: Location::new(1, 0),
end_location: Location::new(1, 0),
},
Range::new(Location::new(1, 0), Location::new(1, 0)),
),
Check::new(
CheckKind::UnusedVariable("x".to_string()),
Range {
location: Location::new(1, 0),
end_location: Location::new(1, 0),
},
Range::new(Location::new(1, 0), Location::new(1, 0)),
),
];
let contents = "x = 1 # noqa";

View File

@@ -34,10 +34,10 @@ pub fn line_too_long(lineno: usize, line: &str, max_line_length: usize) -> Optio
Some(Check::new(
CheckKind::LineTooLong(line_length, max_line_length),
Range {
location: Location::new(lineno + 1, max_line_length),
end_location: Location::new(lineno + 1, line_length),
},
Range::new(
Location::new(lineno + 1, max_line_length),
Location::new(lineno + 1, line_length),
),
))
}
@@ -162,10 +162,7 @@ pub fn no_newline_at_end_of_file(contents: &str, autofix: bool) -> Option<Check>
let location = Location::new(contents.lines().count(), line.len());
let mut check = Check::new(
CheckKind::NoNewLineAtEndOfFile,
Range {
location,
end_location: location,
},
Range::new(location, location),
);
if autofix {
check.amend(Fix::insertion("\n".to_string(), location));
@@ -203,10 +200,7 @@ pub fn invalid_escape_sequence(
) -> Vec<Check> {
let mut checks = vec![];
let text = locator.slice_source_code_range(&Range {
location: start,
end_location: end,
});
let text = locator.slice_source_code_range(&Range::new(start, end));
// Determine whether the string is single- or triple-quoted.
let quote = extract_quote(&text);
@@ -249,10 +243,7 @@ pub fn invalid_escape_sequence(
let end_location = Location::new(location.row(), location.column() + 2);
let mut check = Check::new(
CheckKind::InvalidEscapeSequence(next_char),
Range {
location,
end_location,
},
Range::new(location, end_location),
);
if autofix {
check.amend(Fix::insertion(r"\".to_string(), location));

View File

@@ -329,10 +329,10 @@ pub fn do_not_assign_lambda(checker: &mut Checker, target: &Expr, value: &Expr,
{
match function(id, args, body, checker.style) {
Ok(content) => {
let first_line = checker.locator.slice_source_code_range(&Range {
location: Location::new(stmt.location.row(), 0),
end_location: Location::new(stmt.location.row() + 1, 0),
});
let first_line = checker.locator.slice_source_code_range(&Range::new(
Location::new(stmt.location.row(), 0),
Location::new(stmt.location.row() + 1, 0),
));
let indentation = &leading_space(&first_line);
let mut indented = String::new();
for (idx, line) in content.lines().enumerate() {

View File

@@ -249,7 +249,7 @@ expression: checks
column: 4
end_location:
row: 74
column: 10
column: 5
fix: ~
parent: ~

View File

@@ -34,10 +34,7 @@ pub fn not_missing(
if checker.settings.enabled.contains(&CheckCode::D100) {
checker.add_check(Check::new(
CheckKind::PublicModule,
Range {
location: Location::new(1, 0),
end_location: Location::new(1, 0),
},
Range::new(Location::new(1, 0), Location::new(1, 0)),
));
}
false
@@ -46,10 +43,7 @@ pub fn not_missing(
if checker.settings.enabled.contains(&CheckCode::D104) {
checker.add_check(Check::new(
CheckKind::PublicPackage,
Range {
location: Location::new(1, 0),
end_location: Location::new(1, 0),
},
Range::new(Location::new(1, 0), Location::new(1, 0)),
));
}
false
@@ -412,10 +406,10 @@ pub fn indent(checker: &mut Checker, docstring: &Docstring) {
{
let mut check = Check::new(
CheckKind::NoUnderIndentation,
Range {
location: Location::new(docstring.expr.location.row() + i, 0),
end_location: Location::new(docstring.expr.location.row() + i, 0),
},
Range::new(
Location::new(docstring.expr.location.row() + i, 0),
Location::new(docstring.expr.location.row() + i, 0),
),
);
if checker.patch(check.kind.code()) {
check.amend(Fix::replacement(
@@ -462,10 +456,10 @@ pub fn indent(checker: &mut Checker, docstring: &Docstring) {
// enables autofix.
let mut check = Check::new(
CheckKind::NoOverIndentation,
Range {
location: Location::new(docstring.expr.location.row() + i, 0),
end_location: Location::new(docstring.expr.location.row() + i, 0),
},
Range::new(
Location::new(docstring.expr.location.row() + i, 0),
Location::new(docstring.expr.location.row() + i, 0),
),
);
if checker.patch(check.kind.code()) {
check.amend(Fix::replacement(
@@ -486,10 +480,10 @@ pub fn indent(checker: &mut Checker, docstring: &Docstring) {
if line_indent.len() > docstring.indentation.len() {
let mut check = Check::new(
CheckKind::NoOverIndentation,
Range {
location: Location::new(docstring.expr.location.row() + i, 0),
end_location: Location::new(docstring.expr.location.row() + i, 0),
},
Range::new(
Location::new(docstring.expr.location.row() + i, 0),
Location::new(docstring.expr.location.row() + i, 0),
),
);
if checker.patch(check.kind.code()) {
check.amend(Fix::replacement(
@@ -671,9 +665,35 @@ 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().lines().next() {
let trimmed = first_line.trim();
// Avoid false-positives: `:param`, etc.
for prefix in [":param", ":type", ":raises", ":return", ":rtype"] {
if trimmed.starts_with(prefix) {
return;
}
}
// Avoid false-positives: `Args:`, etc.
for style in [SectionStyle::Google, SectionStyle::Numpy] {
for section_name in style.section_names().iter() {
if let Some(suffix) = trimmed.strip_suffix(section_name) {
if suffix.is_empty() {
return;
}
if suffix == ":" {
return;
}
}
}
}
}
if let Some(index) = logical_line(body) {
let line = body.lines().nth(index).unwrap();
let trimmed = line.trim_end();
if !trimmed.ends_with('.') {
let mut check =
Check::new(CheckKind::EndsInPeriod, Range::from_located(docstring.expr));
@@ -718,7 +738,7 @@ pub fn no_signature(checker: &mut Checker, docstring: &Docstring) {
let body = docstring.body;
let Some(first_line) = body.lines().next() else {
let Some(first_line) = body.trim().lines().next() else {
return;
};
if !first_line.contains(&format!("{name}(")) {
@@ -791,6 +811,31 @@ 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().lines().next() {
let trimmed = first_line.trim();
// Avoid false-positives: `:param`, etc.
for prefix in [":param", ":type", ":raises", ":return", ":rtype"] {
if trimmed.starts_with(prefix) {
return;
}
}
// Avoid false-positives: `Args:`, etc.
for style in [SectionStyle::Google, SectionStyle::Numpy] {
for section_name in style.section_names().iter() {
if let Some(suffix) = trimmed.strip_suffix(section_name) {
if suffix.is_empty() {
return;
}
if suffix == ":" {
return;
}
}
}
}
}
if let Some(index) = logical_line(body) {
let line = body.lines().nth(index).unwrap();
let trimmed = line.trim_end();
@@ -875,14 +920,14 @@ pub fn sections(checker: &mut Checker, docstring: &Docstring, convention: Option
}
}
Some(Convention::Numpy) => {
for context in &section_contexts(&lines, &SectionStyle::NumPy) {
for context in &section_contexts(&lines, &SectionStyle::Numpy) {
numpy_section(checker, docstring, context);
}
}
None => {
// First, interpret as NumPy-style sections.
let mut found_numpy_section = false;
for context in &section_contexts(&lines, &SectionStyle::NumPy) {
for context in &section_contexts(&lines, &SectionStyle::Numpy) {
found_numpy_section = true;
numpy_section(checker, docstring, context);
}
@@ -1424,7 +1469,7 @@ fn parameters_section(checker: &mut Checker, docstring: &Docstring, context: &Se
}
fn numpy_section(checker: &mut Checker, docstring: &Docstring, context: &SectionContext) {
common_section(checker, docstring, context, &SectionStyle::NumPy);
common_section(checker, docstring, context, &SectionStyle::Numpy);
if checker.settings.enabled.contains(&CheckCode::D406) {
let suffix = context

View File

@@ -100,98 +100,98 @@ expression: checks
parent: ~
- kind: EndsInPeriod
location:
row: 40
row: 39
column: 4
end_location:
row: 40
row: 39
column: 37
fix:
content: "."
location:
row: 40
row: 39
column: 36
end_location:
row: 40
row: 39
column: 36
parent: ~
- kind: EndsInPeriod
location:
row: 45
row: 44
column: 4
end_location:
row: 45
row: 44
column: 41
fix:
content: "."
location:
row: 45
row: 44
column: 38
end_location:
row: 45
row: 44
column: 38
parent: ~
- kind: EndsInPeriod
location:
row: 50
row: 49
column: 4
end_location:
row: 53
row: 52
column: 7
fix:
content: "."
location:
row: 52
row: 51
column: 28
end_location:
row: 52
row: 51
column: 28
parent: ~
- kind: EndsInPeriod
location:
row: 58
row: 57
column: 4
end_location:
row: 58
row: 57
column: 41
fix:
content: "."
location:
row: 58
row: 57
column: 38
end_location:
row: 58
row: 57
column: 38
parent: ~
- kind: EndsInPeriod
location:
row: 63
row: 62
column: 4
end_location:
row: 65
row: 64
column: 31
fix:
content: "."
location:
row: 65
row: 64
column: 28
end_location:
row: 65
row: 64
column: 28
parent: ~
- kind: EndsInPeriod
location:
row: 70
row: 69
column: 4
end_location:
row: 72
row: 71
column: 52
fix:
content: "."
location:
row: 72
row: 71
column: 48
end_location:
row: 72
row: 71
column: 48
parent: ~

View File

@@ -38,4 +38,22 @@ expression: checks
column: 16
fix: ~
parent: ~
- kind: FStringMissingPlaceholders
location:
row: 26
column: 6
end_location:
row: 26
column: 13
fix: ~
parent: ~
- kind: FStringMissingPlaceholders
location:
row: 27
column: 3
end_location:
row: 27
column: 6
fix: ~
parent: ~

View File

@@ -82,33 +82,53 @@ expression: checks
column: 8
fix: ~
parent: ~
- kind:
UndefinedName: B
location:
row: 92
column: 10
end_location:
row: 92
column: 11
fix: ~
parent: ~
- kind:
UndefinedName: B
location:
row: 93
column: 13
end_location:
row: 93
column: 14
fix: ~
parent: ~
- kind:
UndefinedName: PEP593Test123
location:
row: 114
row: 115
column: 8
end_location:
row: 114
row: 115
column: 23
fix: ~
parent: ~
- kind:
UndefinedName: foo
location:
row: 122
row: 123
column: 13
end_location:
row: 122
row: 123
column: 18
fix: ~
parent: ~
- kind:
UndefinedName: bar
location:
row: 122
row: 123
column: 20
end_location:
row: 122
row: 123
column: 25
fix: ~
parent: ~

View File

@@ -13,10 +13,10 @@ pub fn blanket_noqa(lineno: usize, line: &str) -> Option<Check> {
BLANKET_NOQA_REGEX.find(line).map(|m| {
Check::new(
CheckKind::BlanketNOQA,
Range {
location: Location::new(lineno + 1, m.start()),
end_location: Location::new(lineno + 1, m.end()),
},
Range::new(
Location::new(lineno + 1, m.start()),
Location::new(lineno + 1, m.end()),
),
)
})
}

View File

@@ -13,10 +13,10 @@ pub fn blanket_type_ignore(lineno: usize, line: &str) -> Option<Check> {
BLANKET_TYPE_IGNORE_REGEX.find(line).map(|m| {
Check::new(
CheckKind::BlanketTypeIgnore,
Range {
location: Location::new(lineno + 1, m.start()),
end_location: Location::new(lineno + 1, m.end()),
},
Range::new(
Location::new(lineno + 1, m.start()),
Location::new(lineno + 1, m.end()),
),
)
})
}

View File

@@ -170,10 +170,7 @@ pub fn unnecessary_coding_comment(lineno: usize, line: &str, autofix: bool) -> O
if CODING_COMMENT_REGEX.is_match(line) {
let mut check = Check::new(
CheckKind::PEP3120UnnecessaryCodingComment,
Range {
location: Location::new(lineno + 1, 0),
end_location: Location::new(lineno + 2, 0),
},
Range::new(Location::new(lineno + 1, 0), Location::new(lineno + 2, 0)),
);
if autofix {
check.amend(Fix::deletion(
@@ -216,10 +213,7 @@ pub fn unnecessary_lru_cache_params(
continue;
}
let range = Range {
location: func.end_location.unwrap(),
end_location: expr.end_location.unwrap(),
};
let range = Range::new(func.end_location.unwrap(), expr.end_location.unwrap());
// Ex) `functools.lru_cache()`
if keywords.is_empty() {
return Some(Check::new(CheckKind::UnnecessaryLRUCacheParams, range));

View File

@@ -42,7 +42,11 @@ mod tests {
#[test_case(CheckCode::UP021, Path::new("UP021.py"); "UP021")]
#[test_case(CheckCode::UP022, Path::new("UP022.py"); "UP022")]
#[test_case(CheckCode::UP023, Path::new("UP023.py"); "UP023")]
#[test_case(CheckCode::UP024, Path::new("UP024_0.py"); "UP024_0")]
#[test_case(CheckCode::UP024, Path::new("UP024_1.py"); "UP024_1")]
#[test_case(CheckCode::UP024, Path::new("UP024_2.py"); "UP024_2")]
#[test_case(CheckCode::UP025, Path::new("UP025.py"); "UP025")]
#[test_case(CheckCode::UP026, Path::new("UP026.py"); "UP026")]
fn checks(check_code: CheckCode, path: &Path) -> Result<()> {
let snapshot = format!("{}_{}", check_code.as_ref(), path.to_string_lossy());
let checks = test_path(

View File

@@ -4,11 +4,13 @@ pub use datetime_utc_alias::datetime_utc_alias;
pub use deprecated_unittest_alias::deprecated_unittest_alias;
pub use native_literals::native_literals;
pub use open_alias::open_alias;
pub use os_error_alias::os_error_alias;
pub use redundant_open_modes::redundant_open_modes;
pub use remove_six_compat::remove_six_compat;
pub use replace_stdout_stderr::replace_stdout_stderr;
pub use replace_universal_newlines::replace_universal_newlines;
pub use rewrite_c_element_tree::replace_c_element_tree;
pub use rewrite_mock_import::{rewrite_mock_attribute, rewrite_mock_import};
pub use rewrite_unicode_literal::rewrite_unicode_literal;
pub use super_call_with_parameters::super_call_with_parameters;
pub use type_of_primitive::type_of_primitive;
@@ -27,11 +29,13 @@ mod datetime_utc_alias;
mod deprecated_unittest_alias;
mod native_literals;
mod open_alias;
mod os_error_alias;
mod redundant_open_modes;
mod remove_six_compat;
mod replace_stdout_stderr;
mod replace_universal_newlines;
mod rewrite_c_element_tree;
mod rewrite_mock_import;
mod rewrite_unicode_literal;
mod super_call_with_parameters;
mod type_of_primitive;

View File

@@ -0,0 +1,236 @@
#![allow(clippy::len_zero, clippy::needless_pass_by_value)]
use itertools::Itertools;
use rustpython_ast::{Excepthandler, ExcepthandlerKind, Expr, ExprKind, Located};
use crate::ast::helpers::{compose_call_path, match_module_member};
use crate::ast::types::Range;
use crate::autofix::Fix;
use crate::checkers::ast::Checker;
use crate::checks::{Check, CheckKind};
const ERROR_NAMES: &[&str] = &["EnvironmentError", "IOError", "WindowsError"];
const ERROR_MODULES: &[&str] = &["mmap", "select", "socket"];
fn get_correct_name(original: &str) -> String {
if ERROR_NAMES.contains(&original) {
"OSError".to_string()
} else {
original.to_string()
}
}
fn get_before_replace(elts: &[Expr]) -> Vec<String> {
elts.iter()
.map(|elt| {
if let ExprKind::Name { id, .. } = &elt.node {
id.to_string()
} else {
String::new()
}
})
.collect()
}
fn check_module(checker: &Checker, expr: &Expr) -> (Vec<String>, Vec<String>) {
let mut replacements: Vec<String> = vec![];
let mut before_replace: Vec<String> = vec![];
for module in ERROR_MODULES.iter() {
if match_module_member(
expr,
module,
"error",
&checker.from_imports,
&checker.import_aliases,
) {
replacements.push("OSError".to_string());
before_replace.push(format!("{module}.error"));
break;
}
}
(replacements, before_replace)
}
fn handle_name_or_attribute(
checker: &Checker,
item: &Expr,
replacements: &mut Vec<String>,
before_replace: &mut Vec<String>,
) {
match &item.node {
ExprKind::Name { id, .. } => {
let (temp_replacements, temp_before_replace) = check_module(checker, item);
replacements.extend(temp_replacements);
before_replace.extend(temp_before_replace);
if replacements.is_empty() {
let new_name = get_correct_name(id);
replacements.push(new_name);
before_replace.push(id.to_string());
}
}
ExprKind::Attribute { .. } => {
let (temp_replacements, temp_before_replace) = check_module(checker, item);
replacements.extend(temp_replacements);
before_replace.extend(temp_before_replace);
}
_ => (),
}
}
/// Handles one block of an except (use a loop if there are multile blocks)
fn handle_except_block(checker: &mut Checker, handler: &Located<ExcepthandlerKind>) {
let ExcepthandlerKind::ExceptHandler { type_, .. } = &handler.node;
let Some(error_handlers) = type_.as_ref() else {
return;
};
// The first part creates list of all the exceptions being caught, and
// what they should be changed to
let mut replacements: Vec<String> = vec![];
let mut before_replace: Vec<String> = vec![];
match &error_handlers.node {
ExprKind::Name { .. } | ExprKind::Attribute { .. } => {
handle_name_or_attribute(
checker,
error_handlers,
&mut replacements,
&mut before_replace,
);
}
ExprKind::Tuple { elts, .. } => {
before_replace = get_before_replace(elts);
for elt in elts {
match &elt.node {
ExprKind::Name { id, .. } => {
let new_name = get_correct_name(id);
replacements.push(new_name);
}
ExprKind::Attribute { .. } => {
let (new_replacements, new_before_replace) = check_module(checker, elt);
replacements.extend(new_replacements);
before_replace.extend(new_before_replace);
}
_ => (),
}
}
}
_ => return,
}
replacements = replacements
.iter()
.unique()
.map(std::string::ToString::to_string)
.collect();
before_replace = before_replace
.iter()
.filter(|x| !x.is_empty())
.map(std::string::ToString::to_string)
.collect();
// This part checks if there are differences between what there is and
// what there should be. Where differences, the changes are applied
handle_making_changes(checker, error_handlers, &before_replace, &replacements);
}
fn handle_making_changes(
checker: &mut Checker,
target: &Expr,
before_replace: &[String],
replacements: &[String],
) {
if before_replace != replacements && replacements.len() > 0 {
let range = Range::new(target.location, target.end_location.unwrap());
let contents = checker.locator.slice_source_code_range(&range);
// Pyyupgrade does not want imports changed if a module only is
// surrounded by parentheses. For example: `except mmap.error:`
// would be changed, but: `(mmap).error:` would not. One issue with
// this implementation is that any valid changes will also be
// ignored. Let me know if you want me to go with a more
// complicated solution that avoids this.
if contents.contains(").") {
return;
}
let mut final_str: String;
if replacements.len() == 1 {
final_str = replacements.get(0).unwrap().to_string();
} else {
final_str = replacements.join(", ");
final_str.insert(0, '(');
final_str.push(')');
}
let mut check = Check::new(CheckKind::OSErrorAlias(compose_call_path(target)), range);
if checker.patch(check.kind.code()) {
check.amend(Fix::replacement(
final_str,
range.location,
range.end_location,
));
}
checker.add_check(check);
}
}
// This is a hacky way to handle the different variable types we get since
// raise and try are very different. Would love input on a cleaner way
pub trait OSErrorAliasChecker {
fn check_error(&self, checker: &mut Checker)
where
Self: Sized;
}
impl OSErrorAliasChecker for &Vec<Excepthandler> {
fn check_error(&self, checker: &mut Checker) {
// Each separate except block is a separate error and fix
for handler in self.iter() {
handle_except_block(checker, handler);
}
}
}
impl OSErrorAliasChecker for &Box<Expr> {
fn check_error(&self, checker: &mut Checker) {
let mut replacements: Vec<String> = vec![];
let mut before_replace: Vec<String> = vec![];
match &self.node {
ExprKind::Name { .. } | ExprKind::Attribute { .. } => {
handle_name_or_attribute(checker, self, &mut replacements, &mut before_replace);
}
_ => return,
}
handle_making_changes(checker, self, &before_replace, &replacements);
}
}
impl OSErrorAliasChecker for &Expr {
fn check_error(&self, checker: &mut Checker) {
let mut replacements: Vec<String> = vec![];
let mut before_replace: Vec<String> = vec![];
let change_target: &Expr;
match &self.node {
ExprKind::Name { .. } | ExprKind::Attribute { .. } => {
change_target = self;
handle_name_or_attribute(checker, self, &mut replacements, &mut before_replace);
}
ExprKind::Call { func, .. } => {
change_target = func;
match &func.node {
ExprKind::Name { .. } | ExprKind::Attribute { .. } => {
handle_name_or_attribute(
checker,
func,
&mut replacements,
&mut before_replace,
);
}
_ => return,
}
}
_ => return,
}
handle_making_changes(checker, change_target, &before_replace, &replacements);
}
}
/// UP024
pub fn os_error_alias<U: OSErrorAliasChecker>(checker: &mut Checker, handlers: U) {
handlers.check_error(checker);
}

View File

@@ -107,10 +107,8 @@ fn create_remove_param_fix(
expr: &Expr,
mode_param: &Expr,
) -> Result<Fix> {
let content = locator.slice_source_code_range(&Range {
location: expr.location,
end_location: expr.end_location.unwrap(),
});
let content =
locator.slice_source_code_range(&Range::new(expr.location, expr.end_location.unwrap()));
// Find the last comma before mode_param and create a deletion fix
// starting from the comma and ending after mode_param.
let mut fix_start: Option<Location> = None;

View File

@@ -65,10 +65,10 @@ fn replace_by_str_literal(
let content = format!(
"{}{}",
if binary { "b" } else { "" },
locator.slice_source_code_range(&Range {
location: arg.location,
end_location: arg.end_location.unwrap(),
})
locator.slice_source_code_range(&Range::new(
arg.location,
arg.end_location.unwrap(),
))
);
check.amend(Fix::replacement(
content,

View File

@@ -87,10 +87,12 @@ pub fn replace_stdout_stderr(checker: &mut Checker, expr: &Expr, kwargs: &[Keywo
stderr
};
let mut contents = String::from("capture_output=True");
if let Some(middle) = extract_middle(&checker.locator.slice_source_code_range(&Range {
location: first.end_location.unwrap(),
end_location: last.location,
})) {
if let Some(middle) =
extract_middle(&checker.locator.slice_source_code_range(&Range::new(
first.end_location.unwrap(),
last.location,
)))
{
if middle.multi_line {
contents.push(',');
contents.push('\n');

View File

@@ -16,13 +16,13 @@ pub fn replace_universal_newlines(checker: &mut Checker, expr: &Expr, kwargs: &[
&checker.import_aliases,
) {
let Some(kwarg) = find_keyword(kwargs, "universal_newlines") else { return; };
let range = Range {
location: kwarg.location,
end_location: Location::new(
let range = Range::new(
kwarg.location,
Location::new(
kwarg.location.row(),
kwarg.location.column() + "universal_newlines".len(),
),
};
);
let mut check = Check::new(CheckKind::ReplaceUniversalNewlines, range);
if checker.patch(check.kind.code()) {
check.amend(Fix::replacement(

View File

@@ -0,0 +1,275 @@
use anyhow::Result;
use libcst_native::{
AsName, AssignTargetExpression, Attribute, Codegen, CodegenState, Dot, Expression, Import,
ImportAlias, ImportFrom, ImportNames, Name, NameOrAttribute, ParenthesizableWhitespace,
};
use log::error;
use rustpython_ast::{Expr, ExprKind, Stmt, StmtKind};
use crate::ast::helpers::collect_call_paths;
use crate::ast::types::Range;
use crate::ast::whitespace::indentation;
use crate::autofix::Fix;
use crate::checkers::ast::Checker;
use crate::checks::{Check, CheckCode, CheckKind, MockReference};
use crate::cst::matchers::{match_import, match_import_from, match_module};
use crate::source_code_locator::SourceCodeLocator;
use crate::source_code_style::SourceCodeStyleDetector;
/// Return a vector of all non-`mock` imports.
fn clean_import_aliases(aliases: Vec<ImportAlias>) -> (Vec<ImportAlias>, Vec<Option<AsName>>) {
let mut clean_aliases: Vec<ImportAlias> = vec![];
let mut mock_aliases: Vec<Option<AsName>> = vec![];
for alias in aliases {
match &alias.name {
// Ex) `import mock`
NameOrAttribute::N(name_struct) => {
if name_struct.value == "mock" {
mock_aliases.push(alias.asname.clone());
continue;
}
clean_aliases.push(alias);
}
// Ex) `import mock.mock`
NameOrAttribute::A(attribute_struct) => {
if let Expression::Name(name_struct) = &*attribute_struct.value {
if name_struct.value == "mock" && attribute_struct.attr.value == "mock" {
mock_aliases.push(alias.asname.clone());
continue;
}
}
clean_aliases.push(alias);
}
}
}
(clean_aliases, mock_aliases)
}
/// Return `true` if the aliases contain `mock`.
fn includes_mock_member(aliases: &[ImportAlias]) -> bool {
for alias in aliases {
let ImportAlias { name, .. } = &alias;
// Ex) `import mock.mock`
if let NameOrAttribute::A(attribute_struct) = name {
if let Expression::Name(name_struct) = &*attribute_struct.value {
if name_struct.value == "mock" && attribute_struct.attr.value == "mock" {
return true;
}
}
}
}
false
}
fn format_mocks(
aliases: Vec<Option<AsName>>,
indent: &str,
stylist: &SourceCodeStyleDetector,
) -> String {
let mut content = String::new();
for alias in aliases {
match alias {
None => {
if !content.is_empty() {
content.push_str(stylist.line_ending());
content.push_str(indent);
}
content.push_str("from unittest import mock");
}
Some(as_name) => {
if let AssignTargetExpression::Name(name) = as_name.name {
if !content.is_empty() {
content.push_str(stylist.line_ending());
content.push_str(indent);
}
content.push_str("from unittest import mock as ");
content.push_str(name.value);
}
}
}
}
content
}
/// Format the `import mock` rewrite.
fn format_import(
stmt: &Stmt,
indent: &str,
locator: &SourceCodeLocator,
stylist: &SourceCodeStyleDetector,
) -> Result<String> {
let module_text = locator.slice_source_code_range(&Range::from_located(stmt));
let mut tree = match_module(&module_text)?;
let mut import = match_import(&mut tree)?;
let Import { names, .. } = import.clone();
let (clean_aliases, mock_aliases) = clean_import_aliases(names);
Ok(if clean_aliases.is_empty() {
format_mocks(mock_aliases, indent, stylist)
} else {
import.names = clean_aliases;
let mut state = CodegenState::default();
tree.codegen(&mut state);
let mut content = state.to_string();
content.push_str(stylist.line_ending());
content.push_str(indent);
content.push_str(&format_mocks(mock_aliases, indent, stylist));
content
})
}
/// Format the `from mock import ...` rewrite.
fn format_import_from(
stmt: &Stmt,
indent: &str,
locator: &SourceCodeLocator,
stylist: &SourceCodeStyleDetector,
) -> Result<String> {
let module_text = locator.slice_source_code_range(&Range::from_located(stmt));
let mut tree = match_module(&module_text).unwrap();
let mut import = match_import_from(&mut tree)?;
let ImportFrom {
names: ImportNames::Aliases(names),
..
} = import.clone() else {
unreachable!("Expected ImportNames::Aliases");
};
let has_mock_member = includes_mock_member(&names);
let (clean_aliases, mock_aliases) = clean_import_aliases(names);
Ok(if clean_aliases.is_empty() {
format_mocks(mock_aliases, indent, stylist)
} else {
import.names = ImportNames::Aliases(clean_aliases);
import.module = Some(NameOrAttribute::A(Box::new(Attribute {
value: Box::new(Expression::Name(Box::new(Name {
value: "unittest",
lpar: vec![],
rpar: vec![],
}))),
attr: Name {
value: "mock",
lpar: vec![],
rpar: vec![],
},
dot: Dot {
whitespace_before: ParenthesizableWhitespace::default(),
whitespace_after: ParenthesizableWhitespace::default(),
},
lpar: vec![],
rpar: vec![],
})));
let mut state = CodegenState::default();
tree.codegen(&mut state);
let mut content = state.to_string();
if has_mock_member {
content.push_str(stylist.line_ending());
content.push_str(indent);
content.push_str(&format_mocks(mock_aliases, indent, stylist));
}
content
})
}
/// UP026
pub fn rewrite_mock_attribute(checker: &mut Checker, expr: &Expr) {
if let ExprKind::Attribute { value, .. } = &expr.node {
if collect_call_paths(value) == ["mock", "mock"] {
let mut check = Check::new(
CheckKind::RewriteMockImport(MockReference::Attribute),
Range::from_located(value),
);
if checker.patch(&CheckCode::UP026) {
check.amend(Fix::replacement(
"mock".to_string(),
value.location,
value.end_location.unwrap(),
));
}
checker.add_check(check);
}
}
}
/// UP026
pub fn rewrite_mock_import(checker: &mut Checker, stmt: &Stmt) {
match &stmt.node {
StmtKind::Import { names } => {
// Find all `mock` imports.
if names
.iter()
.any(|name| name.node.name == "mock" || name.node.name == "mock.mock")
{
// Generate the fix, if needed, which is shared between all `mock` imports.
let content = if checker.patch(&CheckCode::UP026) {
let indent = indentation(checker, stmt);
match format_import(stmt, &indent, checker.locator, checker.style) {
Ok(content) => Some(content),
Err(e) => {
error!("Failed to rewrite `mock` import: {e}");
None
}
}
} else {
None
};
// Add a `Check` for each `mock` import.
for name in names {
if name.node.name == "mock" || name.node.name == "mock.mock" {
let mut check = Check::new(
CheckKind::RewriteMockImport(MockReference::Import),
Range::from_located(name),
);
if let Some(content) = content.as_ref() {
check.amend(Fix::replacement(
content.clone(),
stmt.location,
stmt.end_location.unwrap(),
));
}
checker.add_check(check);
}
}
}
}
StmtKind::ImportFrom {
module: Some(module),
level,
..
} => {
if level.map_or(false, |level| level > 0) {
return;
}
if module == "mock" {
let mut check = Check::new(
CheckKind::RewriteMockImport(MockReference::Import),
Range::from_located(stmt),
);
if checker.patch(&CheckCode::UP026) {
let indent = indentation(checker, stmt);
match format_import_from(stmt, &indent, checker.locator, checker.style) {
Ok(content) => {
check.amend(Fix::replacement(
content,
stmt.location,
stmt.end_location.unwrap(),
));
}
Err(e) => error!("Failed to rewrite `mock` import: {e}"),
}
}
checker.add_check(check);
}
}
_ => (),
}
}

View File

@@ -84,10 +84,10 @@ fn replace_with_bytes_literal(
) -> Check {
let mut check = Check::new(CheckKind::UnnecessaryEncodeUTF8, Range::from_located(expr));
if patch {
let content = locator.slice_source_code_range(&Range {
location: constant.location,
end_location: constant.end_location.unwrap(),
});
let content = locator.slice_source_code_range(&Range::new(
constant.location,
constant.end_location.unwrap(),
));
let content = format!(
"b{}",
content.trim_start_matches('u').trim_start_matches('U')

View File

@@ -80,7 +80,7 @@ pub fn unnecessary_future_import(checker: &mut Checker, stmt: &Stmt, names: &[Lo
}
check.amend(fix);
}
Err(e) => error!("Failed to remove __future__ import: {e}"),
Err(e) => error!("Failed to remove `__future__` import: {e}"),
}
}
checker.add_check(check);

View File

@@ -0,0 +1,209 @@
---
source: src/pyupgrade/mod.rs
expression: checks
---
- kind:
OSErrorAlias: EnvironmentError
location:
row: 6
column: 7
end_location:
row: 6
column: 23
fix:
content: OSError
location:
row: 6
column: 7
end_location:
row: 6
column: 23
parent: ~
- kind:
OSErrorAlias: IOError
location:
row: 11
column: 7
end_location:
row: 11
column: 14
fix:
content: OSError
location:
row: 11
column: 7
end_location:
row: 11
column: 14
parent: ~
- kind:
OSErrorAlias: WindowsError
location:
row: 16
column: 7
end_location:
row: 16
column: 19
fix:
content: OSError
location:
row: 16
column: 7
end_location:
row: 16
column: 19
parent: ~
- kind:
OSErrorAlias: mmap.error
location:
row: 21
column: 7
end_location:
row: 21
column: 17
fix:
content: OSError
location:
row: 21
column: 7
end_location:
row: 21
column: 17
parent: ~
- kind:
OSErrorAlias: select.error
location:
row: 26
column: 7
end_location:
row: 26
column: 19
fix:
content: OSError
location:
row: 26
column: 7
end_location:
row: 26
column: 19
parent: ~
- kind:
OSErrorAlias: socket.error
location:
row: 31
column: 7
end_location:
row: 31
column: 19
fix:
content: OSError
location:
row: 31
column: 7
end_location:
row: 31
column: 19
parent: ~
- kind:
OSErrorAlias: error
location:
row: 36
column: 7
end_location:
row: 36
column: 12
fix:
content: OSError
location:
row: 36
column: 7
end_location:
row: 36
column: 12
parent: ~
- kind:
OSErrorAlias: ~
location:
row: 43
column: 7
end_location:
row: 43
column: 17
fix:
content: OSError
location:
row: 43
column: 7
end_location:
row: 43
column: 17
parent: ~
- kind:
OSErrorAlias: ~
location:
row: 47
column: 7
end_location:
row: 47
column: 20
fix:
content: OSError
location:
row: 47
column: 7
end_location:
row: 47
column: 20
parent: ~
- kind:
OSErrorAlias: ~
location:
row: 51
column: 7
end_location:
row: 51
column: 57
fix:
content: OSError
location:
row: 51
column: 7
end_location:
row: 51
column: 57
parent: ~
- kind:
OSErrorAlias: ~
location:
row: 58
column: 7
end_location:
row: 58
column: 35
fix:
content: "(OSError, KeyError)"
location:
row: 58
column: 7
end_location:
row: 58
column: 35
parent: ~
- kind:
OSErrorAlias: ~
location:
row: 65
column: 7
end_location:
row: 65
column: 23
fix:
content: "(OSError, error)"
location:
row: 65
column: 7
end_location:
row: 65
column: 23
parent: ~

View File

@@ -0,0 +1,56 @@
---
source: src/pyupgrade/mod.rs
expression: checks
---
- kind:
OSErrorAlias: ~
location:
row: 5
column: 7
end_location:
row: 5
column: 37
fix:
content: OSError
location:
row: 5
column: 7
end_location:
row: 5
column: 37
parent: ~
- kind:
OSErrorAlias: ~
location:
row: 7
column: 7
end_location:
row: 7
column: 40
fix:
content: "(OSError, KeyError)"
location:
row: 7
column: 7
end_location:
row: 7
column: 40
parent: ~
- kind:
OSErrorAlias: ~
location:
row: 12
column: 7
end_location:
row: 16
column: 1
fix:
content: OSError
location:
row: 12
column: 7
end_location:
row: 16
column: 1
parent: ~

View File

@@ -0,0 +1,345 @@
---
source: src/pyupgrade/mod.rs
expression: checks
---
- kind:
OSErrorAlias: socket.error
location:
row: 10
column: 6
end_location:
row: 10
column: 18
fix:
content: OSError
location:
row: 10
column: 6
end_location:
row: 10
column: 18
parent: ~
- kind:
OSErrorAlias: mmap.error
location:
row: 11
column: 6
end_location:
row: 11
column: 16
fix:
content: OSError
location:
row: 11
column: 6
end_location:
row: 11
column: 16
parent: ~
- kind:
OSErrorAlias: select.error
location:
row: 12
column: 6
end_location:
row: 12
column: 18
fix:
content: OSError
location:
row: 12
column: 6
end_location:
row: 12
column: 18
parent: ~
- kind:
OSErrorAlias: socket.error
location:
row: 14
column: 6
end_location:
row: 14
column: 18
fix:
content: OSError
location:
row: 14
column: 6
end_location:
row: 14
column: 18
parent: ~
- kind:
OSErrorAlias: mmap.error
location:
row: 15
column: 6
end_location:
row: 15
column: 16
fix:
content: OSError
location:
row: 15
column: 6
end_location:
row: 15
column: 16
parent: ~
- kind:
OSErrorAlias: select.error
location:
row: 16
column: 6
end_location:
row: 16
column: 18
fix:
content: OSError
location:
row: 16
column: 6
end_location:
row: 16
column: 18
parent: ~
- kind:
OSErrorAlias: socket.error
location:
row: 18
column: 6
end_location:
row: 18
column: 18
fix:
content: OSError
location:
row: 18
column: 6
end_location:
row: 18
column: 18
parent: ~
- kind:
OSErrorAlias: error
location:
row: 25
column: 6
end_location:
row: 25
column: 11
fix:
content: OSError
location:
row: 25
column: 6
end_location:
row: 25
column: 11
parent: ~
- kind:
OSErrorAlias: error
location:
row: 28
column: 6
end_location:
row: 28
column: 11
fix:
content: OSError
location:
row: 28
column: 6
end_location:
row: 28
column: 11
parent: ~
- kind:
OSErrorAlias: error
location:
row: 31
column: 6
end_location:
row: 31
column: 11
fix:
content: OSError
location:
row: 31
column: 6
end_location:
row: 31
column: 11
parent: ~
- kind:
OSErrorAlias: EnvironmentError
location:
row: 34
column: 6
end_location:
row: 34
column: 22
fix:
content: OSError
location:
row: 34
column: 6
end_location:
row: 34
column: 22
parent: ~
- kind:
OSErrorAlias: IOError
location:
row: 35
column: 6
end_location:
row: 35
column: 13
fix:
content: OSError
location:
row: 35
column: 6
end_location:
row: 35
column: 13
parent: ~
- kind:
OSErrorAlias: WindowsError
location:
row: 36
column: 6
end_location:
row: 36
column: 18
fix:
content: OSError
location:
row: 36
column: 6
end_location:
row: 36
column: 18
parent: ~
- kind:
OSErrorAlias: EnvironmentError
location:
row: 38
column: 6
end_location:
row: 38
column: 22
fix:
content: OSError
location:
row: 38
column: 6
end_location:
row: 38
column: 22
parent: ~
- kind:
OSErrorAlias: IOError
location:
row: 39
column: 6
end_location:
row: 39
column: 13
fix:
content: OSError
location:
row: 39
column: 6
end_location:
row: 39
column: 13
parent: ~
- kind:
OSErrorAlias: WindowsError
location:
row: 40
column: 6
end_location:
row: 40
column: 18
fix:
content: OSError
location:
row: 40
column: 6
end_location:
row: 40
column: 18
parent: ~
- kind:
OSErrorAlias: EnvironmentError
location:
row: 42
column: 6
end_location:
row: 42
column: 22
fix:
content: OSError
location:
row: 42
column: 6
end_location:
row: 42
column: 22
parent: ~
- kind:
OSErrorAlias: WindowsError
location:
row: 48
column: 6
end_location:
row: 48
column: 18
fix:
content: OSError
location:
row: 48
column: 6
end_location:
row: 48
column: 18
parent: ~
- kind:
OSErrorAlias: EnvironmentError
location:
row: 49
column: 6
end_location:
row: 49
column: 22
fix:
content: OSError
location:
row: 49
column: 6
end_location:
row: 49
column: 22
parent: ~
- kind:
OSErrorAlias: IOError
location:
row: 50
column: 6
end_location:
row: 50
column: 13
fix:
content: OSError
location:
row: 50
column: 6
end_location:
row: 50
column: 13
parent: ~

View File

@@ -0,0 +1,362 @@
---
source: src/pyupgrade/mod.rs
expression: checks
---
- kind:
RewriteMockImport: Import
location:
row: 3
column: 11
end_location:
row: 3
column: 15
fix:
content: from unittest import mock
location:
row: 3
column: 4
end_location:
row: 3
column: 15
parent: ~
- kind:
RewriteMockImport: Import
location:
row: 6
column: 11
end_location:
row: 6
column: 15
fix:
content: "import sys\n from unittest import mock"
location:
row: 6
column: 4
end_location:
row: 6
column: 20
parent: ~
- kind:
RewriteMockImport: Import
location:
row: 9
column: 7
end_location:
row: 9
column: 16
fix:
content: from unittest import mock
location:
row: 9
column: 0
end_location:
row: 9
column: 16
parent: ~
- kind:
RewriteMockImport: Import
location:
row: 12
column: 19
end_location:
row: 12
column: 23
fix:
content: "import contextlib, sys\nfrom unittest import mock"
location:
row: 12
column: 0
end_location:
row: 12
column: 28
parent: ~
- kind:
RewriteMockImport: Import
location:
row: 15
column: 7
end_location:
row: 15
column: 11
fix:
content: "import sys\nfrom unittest import mock"
location:
row: 15
column: 0
end_location:
row: 15
column: 16
parent: ~
- kind:
RewriteMockImport: Import
location:
row: 19
column: 0
end_location:
row: 19
column: 21
fix:
content: from unittest import mock
location:
row: 19
column: 0
end_location:
row: 19
column: 21
parent: ~
- kind:
RewriteMockImport: Import
location:
row: 22
column: 0
end_location:
row: 27
column: 1
fix:
content: "from unittest.mock import (\n a,\n b,\n c,\n)"
location:
row: 22
column: 0
end_location:
row: 27
column: 1
parent: ~
- kind:
RewriteMockImport: Import
location:
row: 30
column: 0
end_location:
row: 35
column: 1
fix:
content: "from unittest.mock import (\n a,\n b,\n c\n)"
location:
row: 30
column: 0
end_location:
row: 35
column: 1
parent: ~
- kind:
RewriteMockImport: Import
location:
row: 39
column: 8
end_location:
row: 44
column: 9
fix:
content: "from unittest.mock import (\n a,\n b,\n c\n )"
location:
row: 39
column: 8
end_location:
row: 44
column: 9
parent: ~
- kind:
RewriteMockImport: Import
location:
row: 50
column: 7
end_location:
row: 50
column: 11
fix:
content: "from unittest import mock\nfrom unittest import mock"
location:
row: 50
column: 0
end_location:
row: 50
column: 17
parent: ~
- kind:
RewriteMockImport: Import
location:
row: 50
column: 13
end_location:
row: 50
column: 17
fix:
content: "from unittest import mock\nfrom unittest import mock"
location:
row: 50
column: 0
end_location:
row: 50
column: 17
parent: ~
- kind:
RewriteMockImport: Import
location:
row: 53
column: 7
end_location:
row: 53
column: 18
fix:
content: from unittest import mock as foo
location:
row: 53
column: 0
end_location:
row: 53
column: 18
parent: ~
- kind:
RewriteMockImport: Import
location:
row: 56
column: 0
end_location:
row: 56
column: 28
fix:
content: from unittest import mock as foo
location:
row: 56
column: 0
end_location:
row: 56
column: 28
parent: ~
- kind:
RewriteMockImport: Import
location:
row: 60
column: 11
end_location:
row: 60
column: 22
fix:
content: "from unittest import mock as foo\n from unittest import mock as bar\n from unittest import mock"
location:
row: 60
column: 4
end_location:
row: 60
column: 41
parent: ~
- kind:
RewriteMockImport: Import
location:
row: 60
column: 24
end_location:
row: 60
column: 35
fix:
content: "from unittest import mock as foo\n from unittest import mock as bar\n from unittest import mock"
location:
row: 60
column: 4
end_location:
row: 60
column: 41
parent: ~
- kind:
RewriteMockImport: Import
location:
row: 60
column: 37
end_location:
row: 60
column: 41
fix:
content: "from unittest import mock as foo\n from unittest import mock as bar\n from unittest import mock"
location:
row: 60
column: 4
end_location:
row: 60
column: 41
parent: ~
- kind:
RewriteMockImport: Import
location:
row: 63
column: 11
end_location:
row: 63
column: 22
fix:
content: "import os\n from unittest import mock as foo\n from unittest import mock as bar\n from unittest import mock"
location:
row: 63
column: 4
end_location:
row: 63
column: 45
parent: ~
- kind:
RewriteMockImport: Import
location:
row: 63
column: 24
end_location:
row: 63
column: 35
fix:
content: "import os\n from unittest import mock as foo\n from unittest import mock as bar\n from unittest import mock"
location:
row: 63
column: 4
end_location:
row: 63
column: 45
parent: ~
- kind:
RewriteMockImport: Import
location:
row: 63
column: 37
end_location:
row: 63
column: 41
fix:
content: "import os\n from unittest import mock as foo\n from unittest import mock as bar\n from unittest import mock"
location:
row: 63
column: 4
end_location:
row: 63
column: 45
parent: ~
- kind:
RewriteMockImport: Import
location:
row: 67
column: 4
end_location:
row: 67
column: 51
fix:
content: "from unittest import mock as foo\n from unittest import mock as bar\n from unittest import mock"
location:
row: 67
column: 4
end_location:
row: 67
column: 51
parent: ~
- kind:
RewriteMockImport: Attribute
location:
row: 74
column: 4
end_location:
row: 74
column: 13
fix:
content: mock
location:
row: 74
column: 4
end_location:
row: 74
column: 13
parent: ~

View File

@@ -1614,10 +1614,7 @@ pub fn ambiguous_unicode_character(
) -> Vec<Check> {
let mut checks = vec![];
let text = locator.slice_source_code_range(&Range {
location: start,
end_location: end,
});
let text = locator.slice_source_code_range(&Range::new(start, end));
let mut col_offset = 0;
let mut row_offset = 0;
@@ -1648,10 +1645,7 @@ pub fn ambiguous_unicode_character(
representant,
),
},
Range {
location,
end_location,
},
Range::new(location, end_location),
);
if settings.enabled.contains(check.kind.code()) {
if matches!(autofix, flags::Autofix::Enabled)

View File

@@ -125,10 +125,7 @@ fn detect_indentation(contents: &str, locator: &SourceCodeLocator) -> Option<Ind
for (_start, tok, end) in lexer::make_tokenizer(contents).flatten() {
if let Tok::Indent { .. } = tok {
let start = Location::new(end.row(), 0);
let whitespace = locator.slice_source_code_range(&Range {
location: start,
end_location: end,
});
let whitespace = locator.slice_source_code_range(&Range::new(start, end));
return Some(Indentation(whitespace.to_string()));
}
}
@@ -139,10 +136,7 @@ fn detect_indentation(contents: &str, locator: &SourceCodeLocator) -> Option<Ind
fn detect_quote(contents: &str, locator: &SourceCodeLocator) -> Option<Quote> {
for (start, tok, end) in lexer::make_tokenizer(contents).flatten() {
if let Tok::String { .. } = tok {
let content = locator.slice_source_code_range(&Range {
location: start,
end_location: end,
});
let content = locator.slice_source_code_range(&Range::new(start, end));
if let Some(pattern) = leading_quote(&content) {
if pattern.contains('\'') {
return Some(Quote::Single);