Compare commits
21 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
51bda28a7d | ||
|
|
cc26051b7a | ||
|
|
3ac5a9aa31 | ||
|
|
451047c30d | ||
|
|
6907df489b | ||
|
|
970f882b03 | ||
|
|
3eff9a2860 | ||
|
|
a4a24a0ef3 | ||
|
|
48e3c046b0 | ||
|
|
03e4f5be8a | ||
|
|
99657b7d92 | ||
|
|
40377aa1fc | ||
|
|
2a37017e8c | ||
|
|
ff66d08cef | ||
|
|
dad8035eef | ||
|
|
bf5fec342c | ||
|
|
66a6c81ebf | ||
|
|
5c70f5044b | ||
|
|
953d141ab2 | ||
|
|
07dba46039 | ||
|
|
3b02da9d7b |
@@ -1,6 +1,6 @@
|
||||
repos:
|
||||
- repo: https://github.com/charliermarsh/ruff-pre-commit
|
||||
rev: v0.0.190
|
||||
rev: v0.0.192
|
||||
hooks:
|
||||
- id: ruff
|
||||
|
||||
|
||||
20
Cargo.lock
generated
20
Cargo.lock
generated
@@ -724,7 +724,7 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
|
||||
|
||||
[[package]]
|
||||
name = "flake8-to-ruff"
|
||||
version = "0.0.190-dev.0"
|
||||
version = "0.0.192-dev.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap 4.0.29",
|
||||
@@ -735,6 +735,8 @@ dependencies = [
|
||||
"rustc-hash",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"strum",
|
||||
"strum_macros",
|
||||
"toml",
|
||||
]
|
||||
|
||||
@@ -1845,7 +1847,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.0.190"
|
||||
version = "0.0.192"
|
||||
dependencies = [
|
||||
"annotate-snippets 0.9.1",
|
||||
"anyhow",
|
||||
@@ -1888,6 +1890,7 @@ dependencies = [
|
||||
"rustpython-parser",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"shellexpand",
|
||||
"strum",
|
||||
"strum_macros",
|
||||
"test-case",
|
||||
@@ -1901,7 +1904,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_dev"
|
||||
version = "0.0.190"
|
||||
version = "0.0.192"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap 4.0.29",
|
||||
@@ -1919,7 +1922,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_macros"
|
||||
version = "0.0.190"
|
||||
version = "0.0.192"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -2114,6 +2117,15 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shellexpand"
|
||||
version = "3.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dd1c7ddea665294d484c39fd0c0d2b7e35bbfe10035c5fe1854741a57f6880e1"
|
||||
dependencies = [
|
||||
"dirs 4.0.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "similar"
|
||||
version = "2.2.1"
|
||||
|
||||
@@ -6,7 +6,7 @@ members = [
|
||||
|
||||
[package]
|
||||
name = "ruff"
|
||||
version = "0.0.190"
|
||||
version = "0.0.192"
|
||||
edition = "2021"
|
||||
rust-version = "1.65.0"
|
||||
|
||||
@@ -43,13 +43,14 @@ quick-junit = { version = "0.3.2" }
|
||||
rayon = { version = "1.5.3" }
|
||||
regex = { version = "1.6.0" }
|
||||
ropey = { version = "1.5.0", features = ["cr_lines", "simd"], default-features = false }
|
||||
ruff_macros = { version = "0.0.190", path = "ruff_macros" }
|
||||
ruff_macros = { version = "0.0.192", path = "ruff_macros" }
|
||||
rustc-hash = { version = "1.1.0" }
|
||||
rustpython-ast = { features = ["unparse"], git = "https://github.com/RustPython/RustPython.git", rev = "c01f014b1269eedcf4bdb45d5fbc62ae2beecf31" }
|
||||
rustpython-common = { git = "https://github.com/RustPython/RustPython.git", rev = "c01f014b1269eedcf4bdb45d5fbc62ae2beecf31" }
|
||||
rustpython-parser = { features = ["lalrpop"], git = "https://github.com/RustPython/RustPython.git", rev = "c01f014b1269eedcf4bdb45d5fbc62ae2beecf31" }
|
||||
serde = { version = "1.0.147", features = ["derive"] }
|
||||
serde_json = { version = "1.0.87" }
|
||||
shellexpand = { version = "3.0.0" }
|
||||
strum = { version = "0.24.1", features = ["strum_macros"] }
|
||||
strum_macros = { version = "0.24.3" }
|
||||
textwrap = { version = "0.16.0" }
|
||||
|
||||
30
README.md
30
README.md
@@ -39,9 +39,11 @@ Ruff is extremely actively developed and used in major open-source projects like
|
||||
- [Bokeh](https://github.com/bokeh/bokeh)
|
||||
- [Zulip](https://github.com/zulip/zulip)
|
||||
- [Pydantic](https://github.com/pydantic/pydantic)
|
||||
- [Saleor](https://github.com/saleor/saleor)
|
||||
- [Hatch](https://github.com/pypa/hatch)
|
||||
- [Jupyter Server](https://github.com/jupyter-server/jupyter_server)
|
||||
- [Jupyter](https://github.com/jupyter-server/jupyter_server)
|
||||
- [Synapse](https://github.com/matrix-org/synapse)
|
||||
- [Ibis](https://github.com/ibis-project/ibis)
|
||||
- [Saleor](https://github.com/saleor/saleor)
|
||||
|
||||
Read the [launch blog post](https://notes.crmarsh.com/python-tooling-could-be-much-much-faster).
|
||||
|
||||
@@ -158,11 +160,13 @@ ruff path/to/code/ --watch
|
||||
Ruff also works with [pre-commit](https://pre-commit.com):
|
||||
|
||||
```yaml
|
||||
repos:
|
||||
- repo: https://github.com/charliermarsh/ruff-pre-commit
|
||||
rev: v0.0.190
|
||||
hooks:
|
||||
- id: ruff
|
||||
- repo: https://github.com/charliermarsh/ruff-pre-commit
|
||||
# Ruff version.
|
||||
rev: 'v0.0.192'
|
||||
hooks:
|
||||
- id: ruff
|
||||
# Respect `exclude` and `extend-exclude` settings.
|
||||
args: ["--force-exclude"]
|
||||
```
|
||||
|
||||
## Configuration
|
||||
@@ -517,6 +521,7 @@ For more, see [pycodestyle](https://pypi.org/project/pycodestyle/2.9.1/) on PyPI
|
||||
|
||||
| Code | Name | Message | Fix |
|
||||
| ---- | ---- | ------- | --- |
|
||||
| E401 | MultipleImportsOnOneLine | Multiple imports on one line | |
|
||||
| E402 | ModuleImportNotAtTopOfFile | Module level import not at top of file | |
|
||||
| E501 | LineTooLong | Line too long (89 > 88 characters) | |
|
||||
| E711 | NoneComparison | Comparison to `None` should be `cond is None` | 🛠 |
|
||||
@@ -623,6 +628,7 @@ For more, see [pyupgrade](https://pypi.org/project/pyupgrade/3.2.0/) on PyPI.
|
||||
| UP014 | ConvertNamedTupleFunctionalToClass | Convert `...` from `NamedTuple` functional to class syntax | 🛠 |
|
||||
| UP015 | RedundantOpenModes | Unnecessary open mode parameters | 🛠 |
|
||||
| UP016 | RemoveSixCompat | Unnecessary `six` compatibility usage | 🛠 |
|
||||
| UP017 | DatetimeTimezoneUTC | Use `datetime.UTC` alias | 🛠 |
|
||||
|
||||
### pep8-naming (N)
|
||||
|
||||
@@ -1219,7 +1225,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/) (16/33)
|
||||
- [`pyupgrade`](https://pypi.org/project/pyupgrade/) (17/33)
|
||||
- [`yesqa`](https://github.com/asottile/yesqa)
|
||||
|
||||
Note that, in some cases, Ruff uses different error code prefixes than would be found in the
|
||||
@@ -1276,7 +1282,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/) (16/33).
|
||||
implemented in [`pyupgrade`](https://pypi.org/project/pyupgrade/) (17/33).
|
||||
|
||||
If you're looking to use Ruff, but rely on an unsupported Flake8 plugin, free to file an Issue.
|
||||
|
||||
@@ -1632,7 +1638,8 @@ exclude = [".venv"]
|
||||
|
||||
#### [`extend`](#extend)
|
||||
|
||||
A path to a local `pyproject.toml` file to merge into this configuration.
|
||||
A path to a local `pyproject.toml` file to merge into this configuration. User home
|
||||
directory and environment variables will be expanded.
|
||||
|
||||
To resolve the current `pyproject.toml` file, Ruff will first resolve this base
|
||||
configuration file, then merge in any properties defined in the current configuration
|
||||
@@ -1970,7 +1977,8 @@ when resolving imports, `my_package.foo` is considered a first-party import.
|
||||
|
||||
This field supports globs. For example, if you have a series of Python packages in
|
||||
a `python_modules` directory, `src = ["python_modules/*"]` would expand to incorporate
|
||||
all of the packages in that directory.
|
||||
all of the packages in that directory. User home directory and environment variables
|
||||
will also be expanded.
|
||||
|
||||
**Default value**: `["."]`
|
||||
|
||||
|
||||
4
flake8_to_ruff/Cargo.lock
generated
4
flake8_to_ruff/Cargo.lock
generated
@@ -771,7 +771,7 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
|
||||
|
||||
[[package]]
|
||||
name = "flake8_to_ruff"
|
||||
version = "0.0.190"
|
||||
version = "0.0.192"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap",
|
||||
@@ -1975,7 +1975,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.0.190"
|
||||
version = "0.0.192"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bincode",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "flake8-to-ruff"
|
||||
version = "0.0.190-dev.0"
|
||||
version = "0.0.192-dev.0"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
@@ -16,6 +16,8 @@ ruff = { path = "..", default-features = false }
|
||||
rustc-hash = { version = "1.1.0" }
|
||||
serde = { version = "1.0.147", features = ["derive"] }
|
||||
serde_json = { version = "1.0.87" }
|
||||
strum = { version = "0.24.1", features = ["strum_macros"] }
|
||||
strum_macros = { version = "0.24.3" }
|
||||
toml = { version = "0.5.9" }
|
||||
|
||||
[dev-dependencies]
|
||||
|
||||
65
flake8_to_ruff/examples/cryptography/pyproject.toml
Normal file
65
flake8_to_ruff/examples/cryptography/pyproject.toml
Normal file
@@ -0,0 +1,65 @@
|
||||
[build-system]
|
||||
requires = [
|
||||
# The minimum setuptools version is specific to the PEP 517 backend,
|
||||
# and may be stricter than the version required in `setup.cfg`
|
||||
"setuptools>=40.6.0,!=60.9.0",
|
||||
"wheel",
|
||||
# Must be kept in sync with the `install_requirements` in `setup.cfg`
|
||||
"cffi>=1.12; platform_python_implementation != 'PyPy'",
|
||||
"setuptools-rust>=0.11.4",
|
||||
]
|
||||
build-backend = "setuptools.build_meta"
|
||||
|
||||
[tool.black]
|
||||
line-length = 79
|
||||
target-version = ["py36"]
|
||||
|
||||
[tool.pytest.ini_options]
|
||||
addopts = "-r s --capture=no --strict-markers --benchmark-disable"
|
||||
markers = [
|
||||
"skip_fips: this test is not executed in FIPS mode",
|
||||
"supported: parametrized test requiring only_if and skip_message",
|
||||
]
|
||||
|
||||
[tool.mypy]
|
||||
show_error_codes = true
|
||||
check_untyped_defs = true
|
||||
no_implicit_reexport = true
|
||||
warn_redundant_casts = true
|
||||
warn_unused_ignores = true
|
||||
warn_unused_configs = true
|
||||
strict_equality = true
|
||||
|
||||
[[tool.mypy.overrides]]
|
||||
module = [
|
||||
"pretend"
|
||||
]
|
||||
ignore_missing_imports = true
|
||||
|
||||
[tool.coverage.run]
|
||||
branch = true
|
||||
relative_files = true
|
||||
source = [
|
||||
"cryptography",
|
||||
"tests/",
|
||||
]
|
||||
|
||||
[tool.coverage.paths]
|
||||
source = [
|
||||
"src/cryptography",
|
||||
"*.tox/*/lib*/python*/site-packages/cryptography",
|
||||
"*.tox\\*\\Lib\\site-packages\\cryptography",
|
||||
"*.tox/pypy/site-packages/cryptography",
|
||||
]
|
||||
tests =[
|
||||
"tests/",
|
||||
"*tests\\",
|
||||
]
|
||||
|
||||
[tool.coverage.report]
|
||||
exclude_lines = [
|
||||
"@abc.abstractmethod",
|
||||
"@abc.abstractproperty",
|
||||
"@typing.overload",
|
||||
"if typing.TYPE_CHECKING",
|
||||
]
|
||||
91
flake8_to_ruff/examples/cryptography/setup.cfg
Normal file
91
flake8_to_ruff/examples/cryptography/setup.cfg
Normal file
@@ -0,0 +1,91 @@
|
||||
[metadata]
|
||||
name = cryptography
|
||||
version = attr: cryptography.__version__
|
||||
description = cryptography is a package which provides cryptographic recipes and primitives to Python developers.
|
||||
long_description = file: README.rst
|
||||
long_description_content_type = text/x-rst
|
||||
license = BSD-3-Clause OR Apache-2.0
|
||||
url = https://github.com/pyca/cryptography
|
||||
author = The Python Cryptographic Authority and individual contributors
|
||||
author_email = cryptography-dev@python.org
|
||||
project_urls =
|
||||
Documentation=https://cryptography.io/
|
||||
Source=https://github.com/pyca/cryptography/
|
||||
Issues=https://github.com/pyca/cryptography/issues
|
||||
Changelog=https://cryptography.io/en/latest/changelog/
|
||||
classifiers =
|
||||
Development Status :: 5 - Production/Stable
|
||||
Intended Audience :: Developers
|
||||
License :: OSI Approved :: Apache Software License
|
||||
License :: OSI Approved :: BSD License
|
||||
Natural Language :: English
|
||||
Operating System :: MacOS :: MacOS X
|
||||
Operating System :: POSIX
|
||||
Operating System :: POSIX :: BSD
|
||||
Operating System :: POSIX :: Linux
|
||||
Operating System :: Microsoft :: Windows
|
||||
Programming Language :: Python
|
||||
Programming Language :: Python :: 3
|
||||
Programming Language :: Python :: 3 :: Only
|
||||
Programming Language :: Python :: 3.6
|
||||
Programming Language :: Python :: 3.7
|
||||
Programming Language :: Python :: 3.8
|
||||
Programming Language :: Python :: 3.9
|
||||
Programming Language :: Python :: 3.10
|
||||
Programming Language :: Python :: 3.11
|
||||
Programming Language :: Python :: Implementation :: CPython
|
||||
Programming Language :: Python :: Implementation :: PyPy
|
||||
Topic :: Security :: Cryptography
|
||||
|
||||
[options]
|
||||
python_requires = >=3.6
|
||||
include_package_data = True
|
||||
zip_safe = False
|
||||
package_dir =
|
||||
=src
|
||||
packages = find:
|
||||
# `install_requires` must be kept in sync with `pyproject.toml`
|
||||
install_requires =
|
||||
cffi >=1.12
|
||||
|
||||
[options.packages.find]
|
||||
where = src
|
||||
exclude =
|
||||
_cffi_src
|
||||
_cffi_src.*
|
||||
|
||||
[options.extras_require]
|
||||
test =
|
||||
pytest>=6.2.0
|
||||
pytest-benchmark
|
||||
pytest-cov
|
||||
pytest-subtests
|
||||
pytest-xdist
|
||||
pretend
|
||||
iso8601
|
||||
pytz
|
||||
hypothesis>=1.11.4,!=3.79.2
|
||||
docs =
|
||||
sphinx >= 1.6.5,!=1.8.0,!=3.1.0,!=3.1.1,!=5.2.0,!=5.2.0.post0
|
||||
sphinx_rtd_theme
|
||||
docstest =
|
||||
pyenchant >= 1.6.11
|
||||
twine >= 1.12.0
|
||||
sphinxcontrib-spelling >= 4.0.1
|
||||
sdist =
|
||||
setuptools_rust >= 0.11.4
|
||||
pep8test =
|
||||
black
|
||||
flake8
|
||||
flake8-import-order
|
||||
pep8-naming
|
||||
# This extra is for OpenSSH private keys that use bcrypt KDF
|
||||
# Versions: v3.1.3 - ignore_few_rounds, v3.1.5 - abi3
|
||||
ssh =
|
||||
bcrypt >= 3.1.5
|
||||
|
||||
[flake8]
|
||||
ignore = E203,E211,W503,W504,N818
|
||||
exclude = .tox,*.egg,.git,_build,.hypothesis
|
||||
select = E,W,F,N,I
|
||||
application-import-names = cryptography,cryptography_vectors,tests
|
||||
32
flake8_to_ruff/src/black.rs
Normal file
32
flake8_to_ruff/src/black.rs
Normal file
@@ -0,0 +1,32 @@
|
||||
//! Extract Black configuration settings from a pyproject.toml.
|
||||
|
||||
use std::path::Path;
|
||||
|
||||
use anyhow::Result;
|
||||
use ruff::settings::types::PythonVersion;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Default)]
|
||||
pub struct Black {
|
||||
#[serde(alias = "line-length", alias = "line_length")]
|
||||
pub line_length: Option<usize>,
|
||||
#[serde(alias = "target-version", alias = "target_version")]
|
||||
pub target_version: Option<Vec<PythonVersion>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
struct Tools {
|
||||
black: Option<Black>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
struct Pyproject {
|
||||
tool: Option<Tools>,
|
||||
}
|
||||
|
||||
pub fn parse_black_options<P: AsRef<Path>>(path: P) -> Result<Option<Black>> {
|
||||
let contents = std::fs::read_to_string(path)?;
|
||||
Ok(toml::from_str::<Pyproject>(&contents)?
|
||||
.tool
|
||||
.and_then(|tool| tool.black))
|
||||
}
|
||||
@@ -11,13 +11,20 @@ use ruff::{
|
||||
pep8_naming,
|
||||
};
|
||||
|
||||
use crate::black::Black;
|
||||
use crate::plugin::Plugin;
|
||||
use crate::{parser, plugin};
|
||||
|
||||
pub fn convert(
|
||||
flake8: &HashMap<String, Option<String>>,
|
||||
config: &HashMap<String, HashMap<String, Option<String>>>,
|
||||
black: Option<&Black>,
|
||||
plugins: Option<Vec<Plugin>>,
|
||||
) -> Result<Pyproject> {
|
||||
// Extract the Flake8 section.
|
||||
let flake8 = config
|
||||
.get("flake8")
|
||||
.expect("Unable to find flake8 section in INI file");
|
||||
|
||||
// Extract all referenced check code prefixes, to power plugin inference.
|
||||
let mut referenced_codes: BTreeSet<CheckCodePrefix> = BTreeSet::default();
|
||||
for (key, value) in flake8 {
|
||||
@@ -54,10 +61,18 @@ pub fn convert(
|
||||
plugin::resolve_select(
|
||||
flake8,
|
||||
&plugins.unwrap_or_else(|| {
|
||||
plugin::infer_plugins_from_options(flake8)
|
||||
.into_iter()
|
||||
.chain(plugin::infer_plugins_from_codes(&referenced_codes))
|
||||
.collect()
|
||||
let from_options = plugin::infer_plugins_from_options(flake8);
|
||||
if !from_options.is_empty() {
|
||||
eprintln!("Inferred plugins from settings: {from_options:#?}");
|
||||
}
|
||||
let from_codes = plugin::infer_plugins_from_codes(&referenced_codes);
|
||||
if !from_codes.is_empty() {
|
||||
eprintln!(
|
||||
"Inferred plugins from referenced check codes: {:#?}",
|
||||
from_codes
|
||||
);
|
||||
}
|
||||
from_options.into_iter().chain(from_codes).collect()
|
||||
}),
|
||||
)
|
||||
});
|
||||
@@ -236,6 +251,19 @@ pub fn convert(
|
||||
options.pep8_naming = Some(pep8_naming);
|
||||
}
|
||||
|
||||
// Extract any settings from the existing `pyproject.toml`.
|
||||
if let Some(black) = black {
|
||||
if let Some(line_length) = &black.line_length {
|
||||
options.line_length = Some(*line_length);
|
||||
}
|
||||
|
||||
if let Some(target_version) = &black.target_version {
|
||||
if let Some(target_version) = target_version.iter().min() {
|
||||
options.target_version = Some(*target_version);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create the pyproject.toml.
|
||||
Ok(Pyproject::new(options))
|
||||
}
|
||||
@@ -255,7 +283,11 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn it_converts_empty() -> Result<()> {
|
||||
let actual = convert(&HashMap::from([]), None)?;
|
||||
let actual = convert(
|
||||
&HashMap::from([("flake8".to_string(), HashMap::default())]),
|
||||
None,
|
||||
None,
|
||||
)?;
|
||||
let expected = Pyproject::new(Options {
|
||||
allowed_confusables: None,
|
||||
dummy_variable_rgx: None,
|
||||
@@ -303,7 +335,11 @@ mod tests {
|
||||
#[test]
|
||||
fn it_converts_dashes() -> Result<()> {
|
||||
let actual = convert(
|
||||
&HashMap::from([("max-line-length".to_string(), Some("100".to_string()))]),
|
||||
&HashMap::from([(
|
||||
"flake8".to_string(),
|
||||
HashMap::from([("max-line-length".to_string(), Some("100".to_string()))]),
|
||||
)]),
|
||||
None,
|
||||
Some(vec![]),
|
||||
)?;
|
||||
let expected = Pyproject::new(Options {
|
||||
@@ -353,7 +389,11 @@ mod tests {
|
||||
#[test]
|
||||
fn it_converts_underscores() -> Result<()> {
|
||||
let actual = convert(
|
||||
&HashMap::from([("max_line_length".to_string(), Some("100".to_string()))]),
|
||||
&HashMap::from([(
|
||||
"flake8".to_string(),
|
||||
HashMap::from([("max_line_length".to_string(), Some("100".to_string()))]),
|
||||
)]),
|
||||
None,
|
||||
Some(vec![]),
|
||||
)?;
|
||||
let expected = Pyproject::new(Options {
|
||||
@@ -403,7 +443,11 @@ mod tests {
|
||||
#[test]
|
||||
fn it_ignores_parse_errors() -> Result<()> {
|
||||
let actual = convert(
|
||||
&HashMap::from([("max_line_length".to_string(), Some("abc".to_string()))]),
|
||||
&HashMap::from([(
|
||||
"flake8".to_string(),
|
||||
HashMap::from([("max_line_length".to_string(), Some("abc".to_string()))]),
|
||||
)]),
|
||||
None,
|
||||
Some(vec![]),
|
||||
)?;
|
||||
let expected = Pyproject::new(Options {
|
||||
@@ -453,7 +497,11 @@ mod tests {
|
||||
#[test]
|
||||
fn it_converts_plugin_options() -> Result<()> {
|
||||
let actual = convert(
|
||||
&HashMap::from([("inline-quotes".to_string(), Some("single".to_string()))]),
|
||||
&HashMap::from([(
|
||||
"flake8".to_string(),
|
||||
HashMap::from([("inline-quotes".to_string(), Some("single".to_string()))]),
|
||||
)]),
|
||||
None,
|
||||
Some(vec![]),
|
||||
)?;
|
||||
let expected = Pyproject::new(Options {
|
||||
@@ -509,9 +557,13 @@ mod tests {
|
||||
fn it_converts_docstring_conventions() -> Result<()> {
|
||||
let actual = convert(
|
||||
&HashMap::from([(
|
||||
"docstring-convention".to_string(),
|
||||
Some("numpy".to_string()),
|
||||
"flake8".to_string(),
|
||||
HashMap::from([(
|
||||
"docstring-convention".to_string(),
|
||||
Some("numpy".to_string()),
|
||||
)]),
|
||||
)]),
|
||||
None,
|
||||
Some(vec![Plugin::Flake8Docstrings]),
|
||||
)?;
|
||||
let expected = Pyproject::new(Options {
|
||||
@@ -597,7 +649,11 @@ mod tests {
|
||||
#[test]
|
||||
fn it_infers_plugins_if_omitted() -> Result<()> {
|
||||
let actual = convert(
|
||||
&HashMap::from([("inline-quotes".to_string(), Some("single".to_string()))]),
|
||||
&HashMap::from([(
|
||||
"flake8".to_string(),
|
||||
HashMap::from([("inline-quotes".to_string(), Some("single".to_string()))]),
|
||||
)]),
|
||||
None,
|
||||
None,
|
||||
)?;
|
||||
let expected = Pyproject::new(Options {
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
clippy::too_many_lines
|
||||
)]
|
||||
|
||||
pub mod black;
|
||||
pub mod converter;
|
||||
mod parser;
|
||||
pub mod plugin;
|
||||
|
||||
@@ -17,6 +17,7 @@ use std::path::PathBuf;
|
||||
use anyhow::Result;
|
||||
use clap::Parser;
|
||||
use configparser::ini::Ini;
|
||||
use flake8_to_ruff::black::parse_black_options;
|
||||
use flake8_to_ruff::converter;
|
||||
use flake8_to_ruff::plugin::Plugin;
|
||||
|
||||
@@ -26,10 +27,14 @@ use flake8_to_ruff::plugin::Plugin;
|
||||
long_about = None
|
||||
)]
|
||||
struct Cli {
|
||||
/// Path to the Flake8 configuration file (e.g., 'setup.cfg', 'tox.ini', or
|
||||
/// '.flake8').
|
||||
/// Path to the Flake8 configuration file (e.g., `setup.cfg`, `tox.ini`, or
|
||||
/// `.flake8`).
|
||||
#[arg(required = true)]
|
||||
file: PathBuf,
|
||||
/// Optional path to a `pyproject.toml` file, used to ensure compatibility
|
||||
/// with Black.
|
||||
#[arg(long)]
|
||||
pyproject: Option<PathBuf>,
|
||||
/// List of plugins to enable.
|
||||
#[arg(long, value_delimiter = ',')]
|
||||
plugin: Option<Vec<Plugin>>,
|
||||
@@ -43,13 +48,15 @@ fn main() -> Result<()> {
|
||||
ini.set_multiline(true);
|
||||
let config = ini.load(cli.file).map_err(|msg| anyhow::anyhow!(msg))?;
|
||||
|
||||
// Extract the Flake8 section.
|
||||
let flake8 = config
|
||||
.get("flake8")
|
||||
.expect("Unable to find flake8 section in INI file");
|
||||
// Read the pyproject.toml file.
|
||||
let black = cli
|
||||
.pyproject
|
||||
.map(parse_black_options)
|
||||
.transpose()?
|
||||
.flatten();
|
||||
|
||||
// Create the pyproject.toml.
|
||||
let pyproject = converter::convert(flake8, cli.plugin)?;
|
||||
// Create Ruff's pyproject.toml section.
|
||||
let pyproject = converter::convert(&config, black.as_ref(), cli.plugin)?;
|
||||
println!("{}", toml::to_string_pretty(&pyproject)?);
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -3,6 +3,7 @@ use std::str::FromStr;
|
||||
use anyhow::{bail, Result};
|
||||
use once_cell::sync::Lazy;
|
||||
use regex::Regex;
|
||||
use ruff::checks::PREFIX_REDIRECTS;
|
||||
use ruff::checks_gen::CheckCodePrefix;
|
||||
use ruff::settings::types::PatternPrefixPair;
|
||||
use rustc_hash::FxHashMap;
|
||||
@@ -18,7 +19,9 @@ pub fn parse_prefix_codes(value: &str) -> Vec<CheckCodePrefix> {
|
||||
if code.is_empty() {
|
||||
continue;
|
||||
}
|
||||
if let Ok(code) = CheckCodePrefix::from_str(code) {
|
||||
if let Some(code) = PREFIX_REDIRECTS.get(code) {
|
||||
codes.push(code.clone());
|
||||
} else if let Ok(code) = CheckCodePrefix::from_str(code) {
|
||||
codes.push(code);
|
||||
} else {
|
||||
eprintln!("Unsupported prefix code: {code}");
|
||||
@@ -83,16 +86,22 @@ impl State {
|
||||
fn parse(&self) -> Vec<PatternPrefixPair> {
|
||||
let mut codes: Vec<PatternPrefixPair> = vec![];
|
||||
for code in &self.codes {
|
||||
match CheckCodePrefix::from_str(code) {
|
||||
Ok(code) => {
|
||||
for filename in &self.filenames {
|
||||
codes.push(PatternPrefixPair {
|
||||
pattern: filename.clone(),
|
||||
prefix: code.clone(),
|
||||
});
|
||||
}
|
||||
if let Some(code) = PREFIX_REDIRECTS.get(code.as_str()) {
|
||||
for filename in &self.filenames {
|
||||
codes.push(PatternPrefixPair {
|
||||
pattern: filename.clone(),
|
||||
prefix: code.clone(),
|
||||
});
|
||||
}
|
||||
Err(_) => eprintln!("Skipping unrecognized prefix: {code}"),
|
||||
} else if let Ok(code) = CheckCodePrefix::from_str(code) {
|
||||
for filename in &self.filenames {
|
||||
codes.push(PatternPrefixPair {
|
||||
pattern: filename.clone(),
|
||||
prefix: code.clone(),
|
||||
});
|
||||
}
|
||||
} else {
|
||||
eprintln!("Unsupported prefix code: {code}");
|
||||
}
|
||||
}
|
||||
codes
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
use std::collections::{BTreeSet, HashMap};
|
||||
use std::fmt;
|
||||
use std::str::FromStr;
|
||||
|
||||
use anyhow::anyhow;
|
||||
use ruff::checks_gen::CheckCodePrefix;
|
||||
|
||||
#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq)]
|
||||
#[derive(Clone, Ord, PartialOrd, Eq, PartialEq)]
|
||||
pub enum Plugin {
|
||||
Flake8Annotations,
|
||||
Flake8Bandit,
|
||||
@@ -42,7 +43,7 @@ impl FromStr for Plugin {
|
||||
"flake8-datetimez" => Ok(Plugin::Flake8Datetimez),
|
||||
"flake8-debugger" => Ok(Plugin::Flake8Debugger),
|
||||
"flake8-docstrings" => Ok(Plugin::Flake8Docstrings),
|
||||
"flake8-eradicate" => Ok(Plugin::Flake8BlindExcept),
|
||||
"flake8-eradicate" => Ok(Plugin::Flake8Eradicate),
|
||||
"flake8-errmsg" => Ok(Plugin::Flake8ErrMsg),
|
||||
"flake8-print" => Ok(Plugin::Flake8Print),
|
||||
"flake8-quotes" => Ok(Plugin::Flake8Quotes),
|
||||
@@ -58,6 +59,37 @@ impl FromStr for Plugin {
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for Plugin {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{}",
|
||||
match self {
|
||||
Plugin::Flake8Annotations => "flake8-annotations",
|
||||
Plugin::Flake8Bandit => "flake8-bandit",
|
||||
Plugin::Flake8BlindExcept => "flake8-blind-except",
|
||||
Plugin::Flake8Bugbear => "flake8-bugbear",
|
||||
Plugin::Flake8Builtins => "flake8-builtins",
|
||||
Plugin::Flake8Comprehensions => "flake8-comprehensions",
|
||||
Plugin::Flake8Datetimez => "flake8-datetimez",
|
||||
Plugin::Flake8Debugger => "flake8-debugger",
|
||||
Plugin::Flake8Docstrings => "flake8-docstrings",
|
||||
Plugin::Flake8Eradicate => "flake8-eradicate",
|
||||
Plugin::Flake8ErrMsg => "flake8-errmsg",
|
||||
Plugin::Flake8Print => "flake8-print",
|
||||
Plugin::Flake8Quotes => "flake8-quotes",
|
||||
Plugin::Flake8Return => "flake8-return",
|
||||
Plugin::Flake8Simplify => "flake8-simplify",
|
||||
Plugin::Flake8TidyImports => "flake8-tidy-imports",
|
||||
Plugin::McCabe => "mccabe",
|
||||
Plugin::PandasVet => "pandas-vet",
|
||||
Plugin::PEP8Naming => "pep8-naming",
|
||||
Plugin::Pyupgrade => "pyupgrade",
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Plugin {
|
||||
pub fn default(&self) -> CheckCodePrefix {
|
||||
match self {
|
||||
@@ -78,11 +110,11 @@ impl Plugin {
|
||||
Plugin::Flake8Quotes => CheckCodePrefix::Q,
|
||||
Plugin::Flake8Return => CheckCodePrefix::RET,
|
||||
Plugin::Flake8Simplify => CheckCodePrefix::SIM,
|
||||
Plugin::Flake8TidyImports => CheckCodePrefix::I25,
|
||||
Plugin::Flake8TidyImports => CheckCodePrefix::TID25,
|
||||
Plugin::McCabe => CheckCodePrefix::C9,
|
||||
Plugin::PandasVet => CheckCodePrefix::PD,
|
||||
Plugin::PEP8Naming => CheckCodePrefix::N,
|
||||
Plugin::Pyupgrade => CheckCodePrefix::U,
|
||||
Plugin::Pyupgrade => CheckCodePrefix::UP,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -424,7 +456,6 @@ pub fn infer_plugins_from_codes(codes: &BTreeSet<CheckCodePrefix>) -> Vec<Plugin
|
||||
Plugin::Flake8TidyImports,
|
||||
Plugin::PandasVet,
|
||||
Plugin::PEP8Naming,
|
||||
Plugin::Pyupgrade,
|
||||
]
|
||||
.into_iter()
|
||||
.filter(|plugin| {
|
||||
|
||||
@@ -55,3 +55,5 @@ a.get("hello", False)
|
||||
{}.pop(True, False)
|
||||
dict.fromkeys(("world",), True)
|
||||
{}.deploy(True, False)
|
||||
getattr(someobj, attrname, False)
|
||||
mylist.index(True)
|
||||
|
||||
@@ -6,6 +6,9 @@ datetime.datetime(2000, 1, 1, 0, 0, 0)
|
||||
# none args
|
||||
datetime.datetime(2000, 1, 1, 0, 0, 0, 0, None)
|
||||
|
||||
# not none arg
|
||||
datetime.datetime(2000, 1, 1, 0, 0, 0, 0, datetime.timezone.utc)
|
||||
|
||||
# no kwargs
|
||||
datetime.datetime(2000, 1, 1, fold=1)
|
||||
|
||||
|
||||
61
resources/test/fixtures/pycodestyle/E40.py
vendored
Normal file
61
resources/test/fixtures/pycodestyle/E40.py
vendored
Normal file
@@ -0,0 +1,61 @@
|
||||
#: E401
|
||||
import os, sys
|
||||
#: Okay
|
||||
import os
|
||||
import sys
|
||||
|
||||
from subprocess import Popen, PIPE
|
||||
|
||||
from myclass import MyClass
|
||||
from foo.bar.yourclass import YourClass
|
||||
|
||||
import myclass
|
||||
import foo.bar.yourclass
|
||||
#: Okay
|
||||
__all__ = ['abc']
|
||||
|
||||
import foo
|
||||
#: Okay
|
||||
__version__ = "42"
|
||||
|
||||
import foo
|
||||
#: Okay
|
||||
__author__ = "Simon Gomizelj"
|
||||
|
||||
import foo
|
||||
#: Okay
|
||||
try:
|
||||
import foo
|
||||
except ImportError:
|
||||
pass
|
||||
else:
|
||||
print('imported foo')
|
||||
finally:
|
||||
print('made attempt to import foo')
|
||||
|
||||
import bar
|
||||
#: Okay
|
||||
with warnings.catch_warnings():
|
||||
warnings.filterwarnings("ignore", DeprecationWarning)
|
||||
import foo
|
||||
|
||||
import bar
|
||||
#: Okay
|
||||
if False:
|
||||
import foo
|
||||
elif not True:
|
||||
import bar
|
||||
else:
|
||||
import mwahaha
|
||||
|
||||
import bar
|
||||
#: E402
|
||||
VERSION = '1.2.3'
|
||||
|
||||
import foo
|
||||
#: E402
|
||||
import foo
|
||||
|
||||
a = 1
|
||||
|
||||
import bar
|
||||
11
resources/test/fixtures/pyupgrade/UP017.py
vendored
Normal file
11
resources/test/fixtures/pyupgrade/UP017.py
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
import datetime
|
||||
import datetime as dt
|
||||
from datetime import timezone
|
||||
from datetime import timezone as tz
|
||||
|
||||
print(datetime.timezone(-1))
|
||||
print(timezone.utc)
|
||||
print(tz.utc)
|
||||
|
||||
print(datetime.timezone.utc)
|
||||
print(dt.timezone.utc)
|
||||
@@ -9,14 +9,14 @@ Running from the repo root should pick up and enforce the appropriate settings f
|
||||
|
||||
```
|
||||
∴ cargo run resources/test/project/
|
||||
Found 7 error(s).
|
||||
resources/test/project/examples/.dotfiles/script.py:1:1: I001 Import block is un-sorted or un-formatted
|
||||
resources/test/project/examples/.dotfiles/script.py:1:8: F401 `numpy` imported but unused
|
||||
resources/test/project/examples/.dotfiles/script.py:2:17: F401 `app.app_file` imported but unused
|
||||
resources/test/project/examples/docs/docs/file.py:1:1: I001 Import block is un-sorted or un-formatted
|
||||
resources/test/project/examples/docs/docs/file.py:8:5: F841 Local variable `x` is assigned to but never used
|
||||
resources/test/project/src/file.py:1:8: F401 `os` imported but unused
|
||||
resources/test/project/src/import_file.py:1:1: I001 Import block is un-sorted or un-formatted
|
||||
resources/test/project/project/file.py:1:8: F401 `os` imported but unused
|
||||
resources/test/project/project/import_file.py:1:1: I001 Import block is un-sorted or un-formatted
|
||||
Found 7 error(s).
|
||||
6 potentially fixable with the --fix option.
|
||||
```
|
||||
|
||||
@@ -24,14 +24,14 @@ Running from the project directory itself should exhibit the same behavior:
|
||||
|
||||
```
|
||||
∴ (cd resources/test/project/ && cargo run .)
|
||||
Found 7 error(s).
|
||||
examples/.dotfiles/script.py:1:1: I001 Import block is un-sorted or un-formatted
|
||||
examples/.dotfiles/script.py:1:8: F401 `numpy` imported but unused
|
||||
examples/.dotfiles/script.py:2:17: F401 `app.app_file` imported but unused
|
||||
examples/docs/docs/file.py:1:1: I001 Import block is un-sorted or un-formatted
|
||||
examples/docs/docs/file.py:8:5: F841 Local variable `x` is assigned to but never used
|
||||
src/file.py:1:8: F401 `os` imported but unused
|
||||
src/import_file.py:1:1: I001 Import block is un-sorted or un-formatted
|
||||
project/file.py:1:8: F401 `os` imported but unused
|
||||
project/import_file.py:1:1: I001 Import block is un-sorted or un-formatted
|
||||
Found 7 error(s).
|
||||
6 potentially fixable with the --fix option.
|
||||
```
|
||||
|
||||
@@ -40,9 +40,9 @@ files:
|
||||
|
||||
```
|
||||
∴ (cd resources/test/project/examples/docs && cargo run .)
|
||||
Found 2 error(s).
|
||||
docs/file.py:1:1: I001 Import block is un-sorted or un-formatted
|
||||
docs/file.py:8:5: F841 Local variable `x` is assigned to but never used
|
||||
Found 2 error(s).
|
||||
1 potentially fixable with the --fix option.
|
||||
```
|
||||
|
||||
@@ -51,8 +51,6 @@ file paths from the current working directory:
|
||||
|
||||
```
|
||||
∴ (cargo run -- --config=resources/test/project/pyproject.toml resources/test/project/)
|
||||
Found 11 error(s).
|
||||
resources/test/project/examples/.dotfiles/script.py:1:1: I001 Import block is un-sorted or un-formatted
|
||||
resources/test/project/examples/.dotfiles/script.py:1:8: F401 `numpy` imported but unused
|
||||
resources/test/project/examples/.dotfiles/script.py:2:17: F401 `app.app_file` imported but unused
|
||||
resources/test/project/examples/docs/docs/concepts/file.py:1:8: F401 `os` imported but unused
|
||||
@@ -61,9 +59,9 @@ resources/test/project/examples/docs/docs/file.py:1:8: F401 `os` imported but un
|
||||
resources/test/project/examples/docs/docs/file.py:3:8: F401 `numpy` imported but unused
|
||||
resources/test/project/examples/docs/docs/file.py:4:27: F401 `docs.concepts.file` imported but unused
|
||||
resources/test/project/examples/excluded/script.py:1:8: F401 `os` imported but unused
|
||||
resources/test/project/src/file.py:1:8: F401 `os` imported but unused
|
||||
resources/test/project/src/import_file.py:1:1: I001 Import block is un-sorted or un-formatted
|
||||
11 potentially fixable with the --fix option.
|
||||
resources/test/project/project/file.py:1:8: F401 `os` imported but unused
|
||||
Found 9 error(s).
|
||||
9 potentially fixable with the --fix option.
|
||||
```
|
||||
|
||||
Running from a parent directory should this "ignore" the `exclude` (hence, `concepts/file.py` gets
|
||||
@@ -71,11 +69,11 @@ included in the output):
|
||||
|
||||
```
|
||||
∴ (cd resources/test/project/examples && cargo run -- --config=docs/pyproject.toml .)
|
||||
Found 4 error(s).
|
||||
docs/docs/concepts/file.py:5:5: F841 Local variable `x` is assigned to but never used
|
||||
docs/docs/file.py:1:1: I001 Import block is un-sorted or un-formatted
|
||||
docs/docs/file.py:8:5: F841 Local variable `x` is assigned to but never used
|
||||
excluded/script.py:5:5: F841 Local variable `x` is assigned to but never used
|
||||
Found 4 error(s).
|
||||
1 potentially fixable with the --fix option.
|
||||
```
|
||||
|
||||
@@ -83,7 +81,14 @@ Passing an excluded directory directly should report errors in the contained fil
|
||||
|
||||
```
|
||||
∴ cargo run resources/test/project/examples/excluded/
|
||||
Found 1 error(s).
|
||||
resources/test/project/examples/excluded/script.py:1:8: F401 `os` imported but unused
|
||||
Found 1 error(s).
|
||||
1 potentially fixable with the --fix option.
|
||||
```
|
||||
|
||||
Unless we `--force-exclude`:
|
||||
|
||||
```
|
||||
∴ cargo run resources/test/project/examples/excluded/ --force-exclude
|
||||
∴ cargo run resources/test/project/examples/excluded/script.py --force-exclude
|
||||
```
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff_dev"
|
||||
version = "0.0.190"
|
||||
version = "0.0.192"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
|
||||
@@ -10,7 +10,7 @@ use anyhow::{ensure, Result};
|
||||
use clap::Parser;
|
||||
use codegen::{Scope, Type, Variant};
|
||||
use itertools::Itertools;
|
||||
use ruff::checks::{CheckCode, CODE_REDIRECTS, PREFIX_REDIRECTS};
|
||||
use ruff::checks::{CheckCode, PREFIX_REDIRECTS};
|
||||
use strum::IntoEnumIterator;
|
||||
|
||||
#[derive(Parser)]
|
||||
@@ -40,18 +40,7 @@ pub fn main(cli: &Cli) -> Result<()> {
|
||||
}
|
||||
|
||||
// Add any prefix aliases (e.g., "U" to "UP").
|
||||
for (alias, source) in PREFIX_REDIRECTS.iter() {
|
||||
prefix_to_codes.insert(
|
||||
(*alias).to_string(),
|
||||
prefix_to_codes
|
||||
.get(&(*source).to_string())
|
||||
.unwrap_or_else(|| panic!("Unknown CheckCode: {source:?}"))
|
||||
.clone(),
|
||||
);
|
||||
}
|
||||
|
||||
// Add any check code aliases (e.g., "U001" to "UP001").
|
||||
for (alias, check_code) in CODE_REDIRECTS.iter() {
|
||||
for (alias, check_code) in PREFIX_REDIRECTS.iter() {
|
||||
prefix_to_codes.insert(
|
||||
(*alias).to_string(),
|
||||
prefix_to_codes
|
||||
@@ -105,7 +94,7 @@ pub fn main(cli: &Cli) -> Result<()> {
|
||||
.line("#[allow(clippy::match_same_arms)]")
|
||||
.line("match self {");
|
||||
for (prefix, codes) in &prefix_to_codes {
|
||||
if let Some(target) = CODE_REDIRECTS.get(&prefix.as_str()) {
|
||||
if let Some(target) = PREFIX_REDIRECTS.get(&prefix.as_str()) {
|
||||
gen = gen.line(format!(
|
||||
"CheckCodePrefix::{prefix} => {{ one_time_warning!(\"{{}}{{}} {{}}\", \
|
||||
\"warning\".yellow().bold(), \":\".bold(), \"`{}` has been remapped to \
|
||||
@@ -117,18 +106,6 @@ pub fn main(cli: &Cli) -> Result<()> {
|
||||
.map(|code| format!("CheckCode::{}", code.as_ref()))
|
||||
.join(", ")
|
||||
));
|
||||
} else if let Some(target) = PREFIX_REDIRECTS.get(&prefix.as_str()) {
|
||||
gen = gen.line(format!(
|
||||
"CheckCodePrefix::{prefix} => {{ one_time_warning!(\"{{}}{{}} {{}}\", \
|
||||
\"warning\".yellow().bold(), \":\".bold(), \"`{}` has been remapped to \
|
||||
`{}`\".bold()); \n vec![{}] }}",
|
||||
prefix,
|
||||
target,
|
||||
codes
|
||||
.iter()
|
||||
.map(|code| format!("CheckCode::{}", code.as_ref()))
|
||||
.join(", ")
|
||||
));
|
||||
} else {
|
||||
gen = gen.line(format!(
|
||||
"CheckCodePrefix::{prefix} => vec![{}],",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff_macros"
|
||||
version = "0.0.190"
|
||||
version = "0.0.192"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
|
||||
@@ -628,6 +628,15 @@ where
|
||||
}
|
||||
}
|
||||
StmtKind::Import { names } => {
|
||||
if self.settings.enabled.contains(&CheckCode::E401) {
|
||||
if names.len() > 1 {
|
||||
self.add_check(Check::new(
|
||||
CheckKind::MultipleImportsOnOneLine,
|
||||
Range::from_located(stmt),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::E402) {
|
||||
if self.seen_import_boundary && stmt.location.column() == 0 {
|
||||
self.add_check(Check::new(
|
||||
@@ -1544,6 +1553,12 @@ where
|
||||
pyupgrade::plugins::remove_six_compat(self, expr);
|
||||
}
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::UP017)
|
||||
&& self.settings.target_version >= PythonVersion::Py311
|
||||
{
|
||||
pyupgrade::plugins::datetime_utc_alias(self, expr);
|
||||
}
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::YTT202) {
|
||||
flake8_2020::plugins::name_or_attribute(self, expr);
|
||||
}
|
||||
|
||||
121
src/checks.rs
121
src/checks.rs
@@ -33,6 +33,7 @@ use crate::pyupgrade::types::Primitive;
|
||||
)]
|
||||
pub enum CheckCode {
|
||||
// pycodestyle errors
|
||||
E401,
|
||||
E402,
|
||||
E501,
|
||||
E711,
|
||||
@@ -224,6 +225,7 @@ pub enum CheckCode {
|
||||
UP014,
|
||||
UP015,
|
||||
UP016,
|
||||
UP017,
|
||||
// pydocstyle
|
||||
D100,
|
||||
D101,
|
||||
@@ -644,6 +646,7 @@ pub enum CheckKind {
|
||||
IOError(String),
|
||||
LineTooLong(usize, usize),
|
||||
ModuleImportNotAtTopOfFile,
|
||||
MultipleImportsOnOneLine,
|
||||
NoneComparison(RejectedCmpop),
|
||||
NotInTest,
|
||||
NotIsTest,
|
||||
@@ -825,6 +828,7 @@ pub enum CheckKind {
|
||||
ConvertNamedTupleFunctionalToClass(String),
|
||||
RedundantOpenModes,
|
||||
RemoveSixCompat,
|
||||
DatetimeTimezoneUTC,
|
||||
// pydocstyle
|
||||
BlankLineAfterLastSection(String),
|
||||
BlankLineAfterSection(String),
|
||||
@@ -978,6 +982,7 @@ impl CheckCode {
|
||||
pub fn kind(&self) -> CheckKind {
|
||||
match self {
|
||||
// pycodestyle errors
|
||||
CheckCode::E401 => CheckKind::MultipleImportsOnOneLine,
|
||||
CheckCode::E402 => CheckKind::ModuleImportNotAtTopOfFile,
|
||||
CheckCode::E501 => CheckKind::LineTooLong(89, 88),
|
||||
CheckCode::E711 => CheckKind::NoneComparison(RejectedCmpop::Eq),
|
||||
@@ -1197,6 +1202,7 @@ impl CheckCode {
|
||||
CheckCode::UP014 => CheckKind::ConvertNamedTupleFunctionalToClass("...".to_string()),
|
||||
CheckCode::UP015 => CheckKind::RedundantOpenModes,
|
||||
CheckCode::UP016 => CheckKind::RemoveSixCompat,
|
||||
CheckCode::UP017 => CheckKind::DatetimeTimezoneUTC,
|
||||
// pydocstyle
|
||||
CheckCode::D100 => CheckKind::PublicModule,
|
||||
CheckCode::D101 => CheckKind::PublicClass,
|
||||
@@ -1462,6 +1468,7 @@ impl CheckCode {
|
||||
CheckCode::DTZ007 => CheckCategory::Flake8Datetimez,
|
||||
CheckCode::DTZ011 => CheckCategory::Flake8Datetimez,
|
||||
CheckCode::DTZ012 => CheckCategory::Flake8Datetimez,
|
||||
CheckCode::E401 => CheckCategory::Pycodestyle,
|
||||
CheckCode::E402 => CheckCategory::Pycodestyle,
|
||||
CheckCode::E501 => CheckCategory::Pycodestyle,
|
||||
CheckCode::E711 => CheckCategory::Pycodestyle,
|
||||
@@ -1613,6 +1620,7 @@ impl CheckCode {
|
||||
CheckCode::UP014 => CheckCategory::Pyupgrade,
|
||||
CheckCode::UP015 => CheckCategory::Pyupgrade,
|
||||
CheckCode::UP016 => CheckCategory::Pyupgrade,
|
||||
CheckCode::UP017 => CheckCategory::Pyupgrade,
|
||||
CheckCode::W292 => CheckCategory::Pycodestyle,
|
||||
CheckCode::W605 => CheckCategory::Pycodestyle,
|
||||
CheckCode::YTT101 => CheckCategory::Flake82020,
|
||||
@@ -1657,6 +1665,7 @@ impl CheckKind {
|
||||
CheckKind::IsLiteral => &CheckCode::F632,
|
||||
CheckKind::LateFutureImport => &CheckCode::F404,
|
||||
CheckKind::LineTooLong(..) => &CheckCode::E501,
|
||||
CheckKind::MultipleImportsOnOneLine => &CheckCode::E401,
|
||||
CheckKind::ModuleImportNotAtTopOfFile => &CheckCode::E402,
|
||||
CheckKind::MultiValueRepeatedKeyLiteral => &CheckCode::F601,
|
||||
CheckKind::MultiValueRepeatedKeyVariable(_) => &CheckCode::F602,
|
||||
@@ -1822,6 +1831,7 @@ impl CheckKind {
|
||||
CheckKind::ConvertNamedTupleFunctionalToClass(_) => &CheckCode::UP014,
|
||||
CheckKind::RedundantOpenModes => &CheckCode::UP015,
|
||||
CheckKind::RemoveSixCompat => &CheckCode::UP016,
|
||||
CheckKind::DatetimeTimezoneUTC => &CheckCode::UP017,
|
||||
// pydocstyle
|
||||
CheckKind::BlankLineAfterLastSection(_) => &CheckCode::D413,
|
||||
CheckKind::BlankLineAfterSection(_) => &CheckCode::D410,
|
||||
@@ -2016,6 +2026,7 @@ impl CheckKind {
|
||||
CheckKind::ModuleImportNotAtTopOfFile => {
|
||||
"Module level import not at top of file".to_string()
|
||||
}
|
||||
CheckKind::MultipleImportsOnOneLine => "Multiple imports on one line".to_string(),
|
||||
CheckKind::MultiValueRepeatedKeyLiteral => {
|
||||
"Dictionary key literal repeated".to_string()
|
||||
}
|
||||
@@ -2543,6 +2554,7 @@ impl CheckKind {
|
||||
CheckKind::UnnecessaryEncodeUTF8 => "Unnecessary call to `encode` as UTF-8".to_string(),
|
||||
CheckKind::RedundantOpenModes => "Unnecessary open mode parameters".to_string(),
|
||||
CheckKind::RemoveSixCompat => "Unnecessary `six` compatibility usage".to_string(),
|
||||
CheckKind::DatetimeTimezoneUTC => "Use `datetime.UTC` alias".to_string(),
|
||||
CheckKind::ConvertTypedDictFunctionalToClass(name) => {
|
||||
format!("Convert `{name}` from `TypedDict` functional to class syntax")
|
||||
}
|
||||
@@ -2979,6 +2991,7 @@ impl CheckKind {
|
||||
| CheckKind::RedundantOpenModes
|
||||
| CheckKind::RedundantTupleInExceptionHandler(..)
|
||||
| CheckKind::RemoveSixCompat
|
||||
| CheckKind::DatetimeTimezoneUTC
|
||||
| CheckKind::SectionNameEndsInColon(..)
|
||||
| CheckKind::SectionNotOverIndented(..)
|
||||
| CheckKind::SectionUnderlineAfterName(..)
|
||||
@@ -3043,6 +3056,81 @@ impl Check {
|
||||
}
|
||||
}
|
||||
|
||||
/// A hash map from deprecated `CheckCodePrefix` to latest `CheckCodePrefix`.
|
||||
pub static PREFIX_REDIRECTS: Lazy<FxHashMap<&'static str, CheckCodePrefix>> = Lazy::new(|| {
|
||||
FxHashMap::from_iter([
|
||||
// TODO(charlie): Remove by 2023-01-01.
|
||||
("U001", CheckCodePrefix::UP001),
|
||||
("U003", CheckCodePrefix::UP003),
|
||||
("U004", CheckCodePrefix::UP004),
|
||||
("U005", CheckCodePrefix::UP005),
|
||||
("U006", CheckCodePrefix::UP006),
|
||||
("U007", CheckCodePrefix::UP007),
|
||||
("U008", CheckCodePrefix::UP008),
|
||||
("U009", CheckCodePrefix::UP009),
|
||||
("U010", CheckCodePrefix::UP010),
|
||||
("U011", CheckCodePrefix::UP011),
|
||||
("U012", CheckCodePrefix::UP012),
|
||||
("U013", CheckCodePrefix::UP013),
|
||||
("U014", CheckCodePrefix::UP014),
|
||||
("U015", CheckCodePrefix::UP015),
|
||||
("U016", CheckCodePrefix::UP016),
|
||||
("U017", CheckCodePrefix::UP017),
|
||||
// TODO(charlie): Remove by 2023-02-01.
|
||||
("I252", CheckCodePrefix::TID252),
|
||||
("M001", CheckCodePrefix::RUF100),
|
||||
// TODO(charlie): Remove by 2023-02-01.
|
||||
("PDV002", CheckCodePrefix::PD002),
|
||||
("PDV003", CheckCodePrefix::PD003),
|
||||
("PDV004", CheckCodePrefix::PD004),
|
||||
("PDV007", CheckCodePrefix::PD007),
|
||||
("PDV008", CheckCodePrefix::PD008),
|
||||
("PDV009", CheckCodePrefix::PD009),
|
||||
("PDV010", CheckCodePrefix::PD010),
|
||||
("PDV011", CheckCodePrefix::PD011),
|
||||
("PDV012", CheckCodePrefix::PD012),
|
||||
("PDV013", CheckCodePrefix::PD013),
|
||||
("PDV015", CheckCodePrefix::PD015),
|
||||
("PDV901", CheckCodePrefix::PD901),
|
||||
// TODO(charlie): Remove by 2023-02-01.
|
||||
("R501", CheckCodePrefix::RET501),
|
||||
("R502", CheckCodePrefix::RET502),
|
||||
("R503", CheckCodePrefix::RET503),
|
||||
("R504", CheckCodePrefix::RET504),
|
||||
("R505", CheckCodePrefix::RET505),
|
||||
("R506", CheckCodePrefix::RET506),
|
||||
("R507", CheckCodePrefix::RET507),
|
||||
("R508", CheckCodePrefix::RET508),
|
||||
("IC001", CheckCodePrefix::ICN001),
|
||||
("IC002", CheckCodePrefix::ICN001),
|
||||
("IC003", CheckCodePrefix::ICN001),
|
||||
("IC004", CheckCodePrefix::ICN001),
|
||||
// TODO(charlie): Remove by 2023-01-01.
|
||||
("U", CheckCodePrefix::UP),
|
||||
("U0", CheckCodePrefix::UP0),
|
||||
("U00", CheckCodePrefix::UP00),
|
||||
("U01", CheckCodePrefix::UP01),
|
||||
// TODO(charlie): Remove by 2023-02-01.
|
||||
("I2", CheckCodePrefix::TID2),
|
||||
("I25", CheckCodePrefix::TID25),
|
||||
("M", CheckCodePrefix::RUF100),
|
||||
("M0", CheckCodePrefix::RUF100),
|
||||
// TODO(charlie): Remove by 2023-02-01.
|
||||
("PDV", CheckCodePrefix::PD),
|
||||
("PDV0", CheckCodePrefix::PD0),
|
||||
("PDV01", CheckCodePrefix::PD01),
|
||||
("PDV9", CheckCodePrefix::PD9),
|
||||
("PDV90", CheckCodePrefix::PD90),
|
||||
// TODO(charlie): Remove by 2023-02-01.
|
||||
("R", CheckCodePrefix::RET),
|
||||
("R5", CheckCodePrefix::RET5),
|
||||
("R50", CheckCodePrefix::RET50),
|
||||
// TODO(charlie): Remove by 2023-02-01.
|
||||
("IC", CheckCodePrefix::ICN),
|
||||
("IC0", CheckCodePrefix::ICN0),
|
||||
])
|
||||
});
|
||||
|
||||
/// A hash map from deprecated to latest `CheckCode`.
|
||||
pub static CODE_REDIRECTS: Lazy<FxHashMap<&'static str, CheckCode>> = Lazy::new(|| {
|
||||
FxHashMap::from_iter([
|
||||
@@ -3062,6 +3150,7 @@ pub static CODE_REDIRECTS: Lazy<FxHashMap<&'static str, CheckCode>> = Lazy::new(
|
||||
("U014", CheckCode::UP014),
|
||||
("U015", CheckCode::UP015),
|
||||
("U016", CheckCode::UP016),
|
||||
("U017", CheckCode::UP017),
|
||||
// TODO(charlie): Remove by 2023-02-01.
|
||||
("I252", CheckCode::TID252),
|
||||
("M001", CheckCode::RUF100),
|
||||
@@ -3078,28 +3167,20 @@ pub static CODE_REDIRECTS: Lazy<FxHashMap<&'static str, CheckCode>> = Lazy::new(
|
||||
("PDV013", CheckCode::PD013),
|
||||
("PDV015", CheckCode::PD015),
|
||||
("PDV901", CheckCode::PD901),
|
||||
])
|
||||
});
|
||||
|
||||
/// A hash map from deprecated `CheckCodePrefix` to latest `CheckCodePrefix`.
|
||||
pub static PREFIX_REDIRECTS: Lazy<FxHashMap<&'static str, &'static str>> = Lazy::new(|| {
|
||||
FxHashMap::from_iter([
|
||||
// TODO(charlie): Remove by 2023-01-01.
|
||||
("U", "UP"),
|
||||
("U0", "UP0"),
|
||||
("U00", "UP00"),
|
||||
("U01", "UP01"),
|
||||
// TODO(charlie): Remove by 2023-02-01.
|
||||
("I2", "TID2"),
|
||||
("I25", "TID25"),
|
||||
("M", "RUF100"),
|
||||
("M0", "RUF100"),
|
||||
("R501", CheckCode::RET501),
|
||||
("R502", CheckCode::RET502),
|
||||
("R503", CheckCode::RET503),
|
||||
("R504", CheckCode::RET504),
|
||||
("R505", CheckCode::RET505),
|
||||
("R506", CheckCode::RET506),
|
||||
("R507", CheckCode::RET507),
|
||||
("R508", CheckCode::RET508),
|
||||
// TODO(charlie): Remove by 2023-02-01.
|
||||
("PDV", "PD"),
|
||||
("PDV0", "PD0"),
|
||||
("PDV01", "PD01"),
|
||||
("PDV9", "PD9"),
|
||||
("PDV90", "PD90"),
|
||||
("IC001", CheckCode::ICN001),
|
||||
("IC002", CheckCode::ICN001),
|
||||
("IC003", CheckCode::ICN001),
|
||||
("IC004", CheckCode::ICN001),
|
||||
])
|
||||
});
|
||||
|
||||
|
||||
@@ -179,6 +179,7 @@ pub enum CheckCodePrefix {
|
||||
E,
|
||||
E4,
|
||||
E40,
|
||||
E401,
|
||||
E402,
|
||||
E5,
|
||||
E50,
|
||||
@@ -291,6 +292,12 @@ pub enum CheckCodePrefix {
|
||||
I2,
|
||||
I25,
|
||||
I252,
|
||||
IC,
|
||||
IC0,
|
||||
IC001,
|
||||
IC002,
|
||||
IC003,
|
||||
IC004,
|
||||
ICN,
|
||||
ICN0,
|
||||
ICN00,
|
||||
@@ -410,6 +417,17 @@ pub enum CheckCodePrefix {
|
||||
Q001,
|
||||
Q002,
|
||||
Q003,
|
||||
R,
|
||||
R5,
|
||||
R50,
|
||||
R501,
|
||||
R502,
|
||||
R503,
|
||||
R504,
|
||||
R505,
|
||||
R506,
|
||||
R507,
|
||||
R508,
|
||||
RET,
|
||||
RET5,
|
||||
RET50,
|
||||
@@ -474,6 +492,7 @@ pub enum CheckCodePrefix {
|
||||
U014,
|
||||
U015,
|
||||
U016,
|
||||
U017,
|
||||
UP,
|
||||
UP0,
|
||||
UP00,
|
||||
@@ -493,6 +512,7 @@ pub enum CheckCodePrefix {
|
||||
UP014,
|
||||
UP015,
|
||||
UP016,
|
||||
UP017,
|
||||
W,
|
||||
W2,
|
||||
W29,
|
||||
@@ -1046,6 +1066,7 @@ impl CheckCodePrefix {
|
||||
CheckCodePrefix::DTZ011 => vec![CheckCode::DTZ011],
|
||||
CheckCodePrefix::DTZ012 => vec![CheckCode::DTZ012],
|
||||
CheckCodePrefix::E => vec![
|
||||
CheckCode::E401,
|
||||
CheckCode::E402,
|
||||
CheckCode::E501,
|
||||
CheckCode::E711,
|
||||
@@ -1061,8 +1082,9 @@ impl CheckCodePrefix {
|
||||
CheckCode::E902,
|
||||
CheckCode::E999,
|
||||
],
|
||||
CheckCodePrefix::E4 => vec![CheckCode::E402],
|
||||
CheckCodePrefix::E40 => vec![CheckCode::E402],
|
||||
CheckCodePrefix::E4 => vec![CheckCode::E401, CheckCode::E402],
|
||||
CheckCodePrefix::E40 => vec![CheckCode::E401, CheckCode::E402],
|
||||
CheckCodePrefix::E401 => vec![CheckCode::E401],
|
||||
CheckCodePrefix::E402 => vec![CheckCode::E402],
|
||||
CheckCodePrefix::E5 => vec![CheckCode::E501],
|
||||
CheckCodePrefix::E50 => vec![CheckCode::E501],
|
||||
@@ -1343,6 +1365,60 @@ impl CheckCodePrefix {
|
||||
);
|
||||
vec![CheckCode::TID252]
|
||||
}
|
||||
CheckCodePrefix::IC => {
|
||||
one_time_warning!(
|
||||
"{}{} {}",
|
||||
"warning".yellow().bold(),
|
||||
":".bold(),
|
||||
"`IC` has been remapped to `ICN`".bold()
|
||||
);
|
||||
vec![CheckCode::ICN001]
|
||||
}
|
||||
CheckCodePrefix::IC0 => {
|
||||
one_time_warning!(
|
||||
"{}{} {}",
|
||||
"warning".yellow().bold(),
|
||||
":".bold(),
|
||||
"`IC0` has been remapped to `ICN0`".bold()
|
||||
);
|
||||
vec![CheckCode::ICN001]
|
||||
}
|
||||
CheckCodePrefix::IC001 => {
|
||||
one_time_warning!(
|
||||
"{}{} {}",
|
||||
"warning".yellow().bold(),
|
||||
":".bold(),
|
||||
"`IC001` has been remapped to `ICN001`".bold()
|
||||
);
|
||||
vec![CheckCode::ICN001]
|
||||
}
|
||||
CheckCodePrefix::IC002 => {
|
||||
one_time_warning!(
|
||||
"{}{} {}",
|
||||
"warning".yellow().bold(),
|
||||
":".bold(),
|
||||
"`IC002` has been remapped to `ICN001`".bold()
|
||||
);
|
||||
vec![CheckCode::ICN001]
|
||||
}
|
||||
CheckCodePrefix::IC003 => {
|
||||
one_time_warning!(
|
||||
"{}{} {}",
|
||||
"warning".yellow().bold(),
|
||||
":".bold(),
|
||||
"`IC003` has been remapped to `ICN001`".bold()
|
||||
);
|
||||
vec![CheckCode::ICN001]
|
||||
}
|
||||
CheckCodePrefix::IC004 => {
|
||||
one_time_warning!(
|
||||
"{}{} {}",
|
||||
"warning".yellow().bold(),
|
||||
":".bold(),
|
||||
"`IC004` has been remapped to `ICN001`".bold()
|
||||
);
|
||||
vec![CheckCode::ICN001]
|
||||
}
|
||||
CheckCodePrefix::ICN => vec![CheckCode::ICN001],
|
||||
CheckCodePrefix::ICN0 => vec![CheckCode::ICN001],
|
||||
CheckCodePrefix::ICN00 => vec![CheckCode::ICN001],
|
||||
@@ -1764,6 +1840,132 @@ impl CheckCodePrefix {
|
||||
CheckCodePrefix::Q001 => vec![CheckCode::Q001],
|
||||
CheckCodePrefix::Q002 => vec![CheckCode::Q002],
|
||||
CheckCodePrefix::Q003 => vec![CheckCode::Q003],
|
||||
CheckCodePrefix::R => {
|
||||
one_time_warning!(
|
||||
"{}{} {}",
|
||||
"warning".yellow().bold(),
|
||||
":".bold(),
|
||||
"`R` has been remapped to `RET`".bold()
|
||||
);
|
||||
vec![
|
||||
CheckCode::RET501,
|
||||
CheckCode::RET502,
|
||||
CheckCode::RET503,
|
||||
CheckCode::RET504,
|
||||
CheckCode::RET505,
|
||||
CheckCode::RET506,
|
||||
CheckCode::RET507,
|
||||
CheckCode::RET508,
|
||||
]
|
||||
}
|
||||
CheckCodePrefix::R5 => {
|
||||
one_time_warning!(
|
||||
"{}{} {}",
|
||||
"warning".yellow().bold(),
|
||||
":".bold(),
|
||||
"`R5` has been remapped to `RET5`".bold()
|
||||
);
|
||||
vec![
|
||||
CheckCode::RET501,
|
||||
CheckCode::RET502,
|
||||
CheckCode::RET503,
|
||||
CheckCode::RET504,
|
||||
CheckCode::RET505,
|
||||
CheckCode::RET506,
|
||||
CheckCode::RET507,
|
||||
CheckCode::RET508,
|
||||
]
|
||||
}
|
||||
CheckCodePrefix::R50 => {
|
||||
one_time_warning!(
|
||||
"{}{} {}",
|
||||
"warning".yellow().bold(),
|
||||
":".bold(),
|
||||
"`R50` has been remapped to `RET50`".bold()
|
||||
);
|
||||
vec![
|
||||
CheckCode::RET501,
|
||||
CheckCode::RET502,
|
||||
CheckCode::RET503,
|
||||
CheckCode::RET504,
|
||||
CheckCode::RET505,
|
||||
CheckCode::RET506,
|
||||
CheckCode::RET507,
|
||||
CheckCode::RET508,
|
||||
]
|
||||
}
|
||||
CheckCodePrefix::R501 => {
|
||||
one_time_warning!(
|
||||
"{}{} {}",
|
||||
"warning".yellow().bold(),
|
||||
":".bold(),
|
||||
"`R501` has been remapped to `RET501`".bold()
|
||||
);
|
||||
vec![CheckCode::RET501]
|
||||
}
|
||||
CheckCodePrefix::R502 => {
|
||||
one_time_warning!(
|
||||
"{}{} {}",
|
||||
"warning".yellow().bold(),
|
||||
":".bold(),
|
||||
"`R502` has been remapped to `RET502`".bold()
|
||||
);
|
||||
vec![CheckCode::RET502]
|
||||
}
|
||||
CheckCodePrefix::R503 => {
|
||||
one_time_warning!(
|
||||
"{}{} {}",
|
||||
"warning".yellow().bold(),
|
||||
":".bold(),
|
||||
"`R503` has been remapped to `RET503`".bold()
|
||||
);
|
||||
vec![CheckCode::RET503]
|
||||
}
|
||||
CheckCodePrefix::R504 => {
|
||||
one_time_warning!(
|
||||
"{}{} {}",
|
||||
"warning".yellow().bold(),
|
||||
":".bold(),
|
||||
"`R504` has been remapped to `RET504`".bold()
|
||||
);
|
||||
vec![CheckCode::RET504]
|
||||
}
|
||||
CheckCodePrefix::R505 => {
|
||||
one_time_warning!(
|
||||
"{}{} {}",
|
||||
"warning".yellow().bold(),
|
||||
":".bold(),
|
||||
"`R505` has been remapped to `RET505`".bold()
|
||||
);
|
||||
vec![CheckCode::RET505]
|
||||
}
|
||||
CheckCodePrefix::R506 => {
|
||||
one_time_warning!(
|
||||
"{}{} {}",
|
||||
"warning".yellow().bold(),
|
||||
":".bold(),
|
||||
"`R506` has been remapped to `RET506`".bold()
|
||||
);
|
||||
vec![CheckCode::RET506]
|
||||
}
|
||||
CheckCodePrefix::R507 => {
|
||||
one_time_warning!(
|
||||
"{}{} {}",
|
||||
"warning".yellow().bold(),
|
||||
":".bold(),
|
||||
"`R507` has been remapped to `RET507`".bold()
|
||||
);
|
||||
vec![CheckCode::RET507]
|
||||
}
|
||||
CheckCodePrefix::R508 => {
|
||||
one_time_warning!(
|
||||
"{}{} {}",
|
||||
"warning".yellow().bold(),
|
||||
":".bold(),
|
||||
"`R508` has been remapped to `RET508`".bold()
|
||||
);
|
||||
vec![CheckCode::RET508]
|
||||
}
|
||||
CheckCodePrefix::RET => vec![
|
||||
CheckCode::RET501,
|
||||
CheckCode::RET502,
|
||||
@@ -1885,6 +2087,7 @@ impl CheckCodePrefix {
|
||||
CheckCode::UP014,
|
||||
CheckCode::UP015,
|
||||
CheckCode::UP016,
|
||||
CheckCode::UP017,
|
||||
]
|
||||
}
|
||||
CheckCodePrefix::U0 => {
|
||||
@@ -1910,6 +2113,7 @@ impl CheckCodePrefix {
|
||||
CheckCode::UP014,
|
||||
CheckCode::UP015,
|
||||
CheckCode::UP016,
|
||||
CheckCode::UP017,
|
||||
]
|
||||
}
|
||||
CheckCodePrefix::U00 => {
|
||||
@@ -2017,6 +2221,7 @@ impl CheckCodePrefix {
|
||||
CheckCode::UP014,
|
||||
CheckCode::UP015,
|
||||
CheckCode::UP016,
|
||||
CheckCode::UP017,
|
||||
]
|
||||
}
|
||||
CheckCodePrefix::U010 => {
|
||||
@@ -2082,6 +2287,15 @@ impl CheckCodePrefix {
|
||||
);
|
||||
vec![CheckCode::UP016]
|
||||
}
|
||||
CheckCodePrefix::U017 => {
|
||||
one_time_warning!(
|
||||
"{}{} {}",
|
||||
"warning".yellow().bold(),
|
||||
":".bold(),
|
||||
"`U017` has been remapped to `UP017`".bold()
|
||||
);
|
||||
vec![CheckCode::UP017]
|
||||
}
|
||||
CheckCodePrefix::UP => vec![
|
||||
CheckCode::UP001,
|
||||
CheckCode::UP003,
|
||||
@@ -2098,6 +2312,7 @@ impl CheckCodePrefix {
|
||||
CheckCode::UP014,
|
||||
CheckCode::UP015,
|
||||
CheckCode::UP016,
|
||||
CheckCode::UP017,
|
||||
],
|
||||
CheckCodePrefix::UP0 => vec![
|
||||
CheckCode::UP001,
|
||||
@@ -2115,6 +2330,7 @@ impl CheckCodePrefix {
|
||||
CheckCode::UP014,
|
||||
CheckCode::UP015,
|
||||
CheckCode::UP016,
|
||||
CheckCode::UP017,
|
||||
],
|
||||
CheckCodePrefix::UP00 => vec![
|
||||
CheckCode::UP001,
|
||||
@@ -2142,6 +2358,7 @@ impl CheckCodePrefix {
|
||||
CheckCode::UP014,
|
||||
CheckCode::UP015,
|
||||
CheckCode::UP016,
|
||||
CheckCode::UP017,
|
||||
],
|
||||
CheckCodePrefix::UP010 => vec![CheckCode::UP010],
|
||||
CheckCodePrefix::UP011 => vec![CheckCode::UP011],
|
||||
@@ -2150,6 +2367,7 @@ impl CheckCodePrefix {
|
||||
CheckCodePrefix::UP014 => vec![CheckCode::UP014],
|
||||
CheckCodePrefix::UP015 => vec![CheckCode::UP015],
|
||||
CheckCodePrefix::UP016 => vec![CheckCode::UP016],
|
||||
CheckCodePrefix::UP017 => vec![CheckCode::UP017],
|
||||
CheckCodePrefix::W => vec![CheckCode::W292, CheckCode::W605],
|
||||
CheckCodePrefix::W2 => vec![CheckCode::W292],
|
||||
CheckCodePrefix::W29 => vec![CheckCode::W292],
|
||||
@@ -2371,6 +2589,7 @@ impl CheckCodePrefix {
|
||||
CheckCodePrefix::E => SuffixLength::Zero,
|
||||
CheckCodePrefix::E4 => SuffixLength::One,
|
||||
CheckCodePrefix::E40 => SuffixLength::Two,
|
||||
CheckCodePrefix::E401 => SuffixLength::Three,
|
||||
CheckCodePrefix::E402 => SuffixLength::Three,
|
||||
CheckCodePrefix::E5 => SuffixLength::One,
|
||||
CheckCodePrefix::E50 => SuffixLength::Two,
|
||||
@@ -2483,6 +2702,12 @@ impl CheckCodePrefix {
|
||||
CheckCodePrefix::I2 => SuffixLength::One,
|
||||
CheckCodePrefix::I25 => SuffixLength::Two,
|
||||
CheckCodePrefix::I252 => SuffixLength::Three,
|
||||
CheckCodePrefix::IC => SuffixLength::Zero,
|
||||
CheckCodePrefix::IC0 => SuffixLength::One,
|
||||
CheckCodePrefix::IC001 => SuffixLength::Three,
|
||||
CheckCodePrefix::IC002 => SuffixLength::Three,
|
||||
CheckCodePrefix::IC003 => SuffixLength::Three,
|
||||
CheckCodePrefix::IC004 => SuffixLength::Three,
|
||||
CheckCodePrefix::ICN => SuffixLength::Zero,
|
||||
CheckCodePrefix::ICN0 => SuffixLength::One,
|
||||
CheckCodePrefix::ICN00 => SuffixLength::Two,
|
||||
@@ -2602,6 +2827,17 @@ impl CheckCodePrefix {
|
||||
CheckCodePrefix::Q001 => SuffixLength::Three,
|
||||
CheckCodePrefix::Q002 => SuffixLength::Three,
|
||||
CheckCodePrefix::Q003 => SuffixLength::Three,
|
||||
CheckCodePrefix::R => SuffixLength::Zero,
|
||||
CheckCodePrefix::R5 => SuffixLength::One,
|
||||
CheckCodePrefix::R50 => SuffixLength::Two,
|
||||
CheckCodePrefix::R501 => SuffixLength::Three,
|
||||
CheckCodePrefix::R502 => SuffixLength::Three,
|
||||
CheckCodePrefix::R503 => SuffixLength::Three,
|
||||
CheckCodePrefix::R504 => SuffixLength::Three,
|
||||
CheckCodePrefix::R505 => SuffixLength::Three,
|
||||
CheckCodePrefix::R506 => SuffixLength::Three,
|
||||
CheckCodePrefix::R507 => SuffixLength::Three,
|
||||
CheckCodePrefix::R508 => SuffixLength::Three,
|
||||
CheckCodePrefix::RET => SuffixLength::Zero,
|
||||
CheckCodePrefix::RET5 => SuffixLength::One,
|
||||
CheckCodePrefix::RET50 => SuffixLength::Two,
|
||||
@@ -2666,6 +2902,7 @@ impl CheckCodePrefix {
|
||||
CheckCodePrefix::U014 => SuffixLength::Three,
|
||||
CheckCodePrefix::U015 => SuffixLength::Three,
|
||||
CheckCodePrefix::U016 => SuffixLength::Three,
|
||||
CheckCodePrefix::U017 => SuffixLength::Three,
|
||||
CheckCodePrefix::UP => SuffixLength::Zero,
|
||||
CheckCodePrefix::UP0 => SuffixLength::One,
|
||||
CheckCodePrefix::UP00 => SuffixLength::Two,
|
||||
@@ -2685,6 +2922,7 @@ impl CheckCodePrefix {
|
||||
CheckCodePrefix::UP014 => SuffixLength::Three,
|
||||
CheckCodePrefix::UP015 => SuffixLength::Three,
|
||||
CheckCodePrefix::UP016 => SuffixLength::Three,
|
||||
CheckCodePrefix::UP017 => SuffixLength::Three,
|
||||
CheckCodePrefix::W => SuffixLength::Zero,
|
||||
CheckCodePrefix::W2 => SuffixLength::One,
|
||||
CheckCodePrefix::W29 => SuffixLength::Two,
|
||||
|
||||
@@ -114,16 +114,26 @@ fn read_from_stdin() -> Result<String> {
|
||||
|
||||
/// Run the linter over a single file, read from `stdin`.
|
||||
pub fn run_stdin(
|
||||
strategy: &PyprojectDiscovery,
|
||||
filename: &Path,
|
||||
filename: Option<&Path>,
|
||||
pyproject_strategy: &PyprojectDiscovery,
|
||||
file_strategy: &FileDiscovery,
|
||||
overrides: &Overrides,
|
||||
autofix: fixer::Mode,
|
||||
) -> Result<Diagnostics> {
|
||||
let stdin = read_from_stdin()?;
|
||||
let settings = match strategy {
|
||||
if let Some(filename) = filename {
|
||||
if !resolver::python_file_at_path(filename, pyproject_strategy, file_strategy, overrides)? {
|
||||
return Ok(Diagnostics::default());
|
||||
}
|
||||
}
|
||||
let settings = match pyproject_strategy {
|
||||
PyprojectDiscovery::Fixed(settings) => settings,
|
||||
PyprojectDiscovery::Hierarchical(settings) => settings,
|
||||
};
|
||||
let mut diagnostics = lint_stdin(filename, &stdin, settings, autofix)?;
|
||||
let package_root = filename
|
||||
.and_then(Path::parent)
|
||||
.and_then(packages::detect_package_root);
|
||||
let stdin = read_from_stdin()?;
|
||||
let mut diagnostics = lint_stdin(filename, package_root, &stdin, settings, autofix)?;
|
||||
diagnostics.messages.sort_unstable();
|
||||
Ok(diagnostics)
|
||||
}
|
||||
|
||||
@@ -5,16 +5,35 @@ use crate::ast::types::Range;
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::checks::{Check, CheckKind};
|
||||
|
||||
const FUNC_NAME_ALLOWLIST: &[&str] = &["get", "setdefault", "pop", "fromkeys"];
|
||||
const FUNC_NAME_ALLOWLIST: &[&str] = &[
|
||||
"assertEqual",
|
||||
"assertEquals",
|
||||
"assertNotEqual",
|
||||
"assertNotEquals",
|
||||
"failIfEqual",
|
||||
"failUnlessEqual",
|
||||
"fromkeys",
|
||||
"get",
|
||||
"getattr",
|
||||
"index",
|
||||
"pop",
|
||||
"setattr",
|
||||
"setdefault",
|
||||
];
|
||||
|
||||
/// Returns `true` if an argument is allowed to use a boolean trap. To return
|
||||
/// `true`, the function name must be explicitly allowed, and the argument must
|
||||
/// be either the first or second argument in the call.
|
||||
fn allow_boolean_trap(func: &Expr) -> bool {
|
||||
let ExprKind::Attribute { attr, .. } = &func.node else {
|
||||
return false;
|
||||
};
|
||||
FUNC_NAME_ALLOWLIST.contains(&attr.as_ref())
|
||||
if let ExprKind::Attribute { attr, .. } = &func.node {
|
||||
return FUNC_NAME_ALLOWLIST.contains(&attr.as_ref());
|
||||
}
|
||||
|
||||
if let ExprKind::Name { id, .. } = &func.node {
|
||||
return FUNC_NAME_ALLOWLIST.contains(&id.as_ref());
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
fn is_boolean_arg(arg: &Expr) -> bool {
|
||||
@@ -79,8 +98,8 @@ pub fn check_boolean_positional_value_in_function_call(
|
||||
args: &[Expr],
|
||||
func: &Expr,
|
||||
) {
|
||||
for (index, arg) in args.iter().enumerate() {
|
||||
if index < 2 && allow_boolean_trap(func) {
|
||||
for arg in args {
|
||||
if allow_boolean_trap(func) {
|
||||
continue;
|
||||
}
|
||||
add_if_boolean(
|
||||
|
||||
@@ -19,20 +19,14 @@ pub fn call_datetime_without_tzinfo(
|
||||
return;
|
||||
}
|
||||
|
||||
// no args / no args unqualified
|
||||
if args.len() < 8 && keywords.is_empty() {
|
||||
// No positional arg: keyword is missing or constant None.
|
||||
if args.len() < 8 && !has_non_none_keyword(keywords, "tzinfo") {
|
||||
checker.add_check(Check::new(CheckKind::CallDatetimeWithoutTzinfo, location));
|
||||
return;
|
||||
}
|
||||
|
||||
// none args
|
||||
if args.len() == 8 && is_const_none(&args[7]) {
|
||||
checker.add_check(Check::new(CheckKind::CallDatetimeWithoutTzinfo, location));
|
||||
return;
|
||||
}
|
||||
|
||||
// no kwargs / none kwargs
|
||||
if !has_non_none_keyword(keywords, "tzinfo") {
|
||||
// Positional arg: is constant None.
|
||||
if args.len() >= 8 && is_const_none(&args[7]) {
|
||||
checker.add_check(Check::new(CheckKind::CallDatetimeWithoutTzinfo, location));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,26 +20,26 @@ expression: checks
|
||||
fix: ~
|
||||
- kind: CallDatetimeWithoutTzinfo
|
||||
location:
|
||||
row: 10
|
||||
row: 13
|
||||
column: 0
|
||||
end_location:
|
||||
row: 10
|
||||
row: 13
|
||||
column: 37
|
||||
fix: ~
|
||||
- kind: CallDatetimeWithoutTzinfo
|
||||
location:
|
||||
row: 13
|
||||
row: 16
|
||||
column: 0
|
||||
end_location:
|
||||
row: 13
|
||||
row: 16
|
||||
column: 42
|
||||
fix: ~
|
||||
- kind: CallDatetimeWithoutTzinfo
|
||||
location:
|
||||
row: 18
|
||||
row: 21
|
||||
column: 0
|
||||
end_location:
|
||||
row: 18
|
||||
row: 21
|
||||
column: 29
|
||||
fix: ~
|
||||
|
||||
|
||||
@@ -260,7 +260,8 @@ pub fn autoformat_path(path: &Path, _settings: &Settings) -> Result<()> {
|
||||
/// Generate a list of `Check` violations from source code content derived from
|
||||
/// stdin.
|
||||
pub fn lint_stdin(
|
||||
path: &Path,
|
||||
path: Option<&Path>,
|
||||
package: Option<&Path>,
|
||||
stdin: &str,
|
||||
settings: &Settings,
|
||||
autofix: fixer::Mode,
|
||||
@@ -269,7 +270,13 @@ pub fn lint_stdin(
|
||||
let contents = stdin.to_string();
|
||||
|
||||
// Lint the file.
|
||||
let (contents, fixed, messages) = lint(contents, path, None, settings, autofix)?;
|
||||
let (contents, fixed, messages) = lint(
|
||||
contents,
|
||||
path.unwrap_or_else(|| Path::new("-")),
|
||||
package,
|
||||
settings,
|
||||
autofix,
|
||||
)?;
|
||||
|
||||
// Write the fixed contents to stdout.
|
||||
if matches!(autofix, fixer::Mode::Apply) {
|
||||
|
||||
@@ -224,8 +224,13 @@ fn inner_main() -> Result<ExitCode> {
|
||||
|
||||
// Generate lint violations.
|
||||
let diagnostics = if is_stdin {
|
||||
let path = cli.stdin_filename.unwrap_or_else(|| PathBuf::from("-"));
|
||||
commands::run_stdin(&pyproject_strategy, &path, autofix)?
|
||||
commands::run_stdin(
|
||||
cli.stdin_filename.as_deref(),
|
||||
&pyproject_strategy,
|
||||
&file_strategy,
|
||||
&overrides,
|
||||
autofix,
|
||||
)?
|
||||
} else {
|
||||
commands::run(
|
||||
&cli.files,
|
||||
|
||||
@@ -44,7 +44,7 @@ impl<'a> Printer<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
fn pre_text(&self, diagnostics: &Diagnostics) {
|
||||
fn post_text(&self, diagnostics: &Diagnostics, autofix: fixer::Mode) {
|
||||
if self.log_level >= &LogLevel::Default {
|
||||
let fixed = diagnostics.fixed;
|
||||
let remaining = diagnostics.messages.len();
|
||||
@@ -54,13 +54,16 @@ impl<'a> Printer<'a> {
|
||||
} else if remaining > 0 {
|
||||
println!("Found {remaining} error(s).");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn post_text(&self, num_fixable: usize, autofix: fixer::Mode) {
|
||||
if self.log_level >= &LogLevel::Default {
|
||||
if num_fixable > 0 && !matches!(autofix, fixer::Mode::Apply) {
|
||||
println!("{num_fixable} potentially fixable with the --fix option.");
|
||||
if !matches!(autofix, fixer::Mode::Apply) {
|
||||
let num_fixable = diagnostics
|
||||
.messages
|
||||
.iter()
|
||||
.filter(|message| message.kind.fixable())
|
||||
.count();
|
||||
if num_fixable > 0 {
|
||||
println!("{num_fixable} potentially fixable with the --fix option.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -70,12 +73,6 @@ impl<'a> Printer<'a> {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let num_fixable = diagnostics
|
||||
.messages
|
||||
.iter()
|
||||
.filter(|message| message.kind.fixable())
|
||||
.count();
|
||||
|
||||
match self.format {
|
||||
SerializationFormat::Json => {
|
||||
println!(
|
||||
@@ -141,18 +138,13 @@ impl<'a> Printer<'a> {
|
||||
println!("{}", report.to_string().unwrap());
|
||||
}
|
||||
SerializationFormat::Text => {
|
||||
self.pre_text(diagnostics);
|
||||
|
||||
for message in &diagnostics.messages {
|
||||
print_message(message);
|
||||
}
|
||||
|
||||
self.post_text(num_fixable, autofix);
|
||||
self.post_text(diagnostics, autofix);
|
||||
}
|
||||
SerializationFormat::Grouped => {
|
||||
self.pre_text(diagnostics);
|
||||
println!();
|
||||
|
||||
// Group by filename.
|
||||
let mut grouped_messages = BTreeMap::default();
|
||||
for message in &diagnostics.messages {
|
||||
@@ -190,11 +182,9 @@ impl<'a> Printer<'a> {
|
||||
println!();
|
||||
}
|
||||
|
||||
self.post_text(num_fixable, autofix);
|
||||
self.post_text(diagnostics, autofix);
|
||||
}
|
||||
SerializationFormat::Github => {
|
||||
self.pre_text(diagnostics);
|
||||
|
||||
// Generate error workflow command in GitHub Actions format
|
||||
// https://docs.github.com/en/actions/reference/workflow-commands-for-github-actions#setting-an-error-message
|
||||
diagnostics.messages.iter().for_each(|message| {
|
||||
|
||||
@@ -13,24 +13,26 @@ mod tests {
|
||||
use crate::linter::test_path;
|
||||
use crate::settings;
|
||||
|
||||
#[test_case(CheckCode::E402, Path::new("E402.py"); "E402")]
|
||||
#[test_case(CheckCode::E501, Path::new("E501.py"); "E501")]
|
||||
#[test_case(CheckCode::E711, Path::new("E711.py"); "E711")]
|
||||
#[test_case(CheckCode::E712, Path::new("E712.py"); "E712")]
|
||||
#[test_case(CheckCode::E713, Path::new("E713.py"); "E713")]
|
||||
#[test_case(CheckCode::E714, Path::new("E714.py"); "E714")]
|
||||
#[test_case(CheckCode::E721, Path::new("E721.py"); "E721")]
|
||||
#[test_case(CheckCode::E722, Path::new("E722.py"); "E722")]
|
||||
#[test_case(CheckCode::E731, Path::new("E731.py"); "E731")]
|
||||
#[test_case(CheckCode::E741, Path::new("E741.py"); "E741")]
|
||||
#[test_case(CheckCode::E742, Path::new("E742.py"); "E742")]
|
||||
#[test_case(CheckCode::E743, Path::new("E743.py"); "E743")]
|
||||
#[test_case(CheckCode::E999, Path::new("E999.py"); "E999")]
|
||||
#[test_case(CheckCode::W292, Path::new("W292_0.py"); "W292_0")]
|
||||
#[test_case(CheckCode::W292, Path::new("W292_1.py"); "W292_1")]
|
||||
#[test_case(CheckCode::W292, Path::new("W292_2.py"); "W292_2")]
|
||||
#[test_case(CheckCode::W605, Path::new("W605_0.py"); "W605_0")]
|
||||
#[test_case(CheckCode::W605, Path::new("W605_1.py"); "W605_1")]
|
||||
#[test_case(CheckCode::E401, Path::new("E40.py"))]
|
||||
#[test_case(CheckCode::E402, Path::new("E40.py"))]
|
||||
#[test_case(CheckCode::E402, Path::new("E402.py"))]
|
||||
#[test_case(CheckCode::E501, Path::new("E501.py"))]
|
||||
#[test_case(CheckCode::E711, Path::new("E711.py"))]
|
||||
#[test_case(CheckCode::E712, Path::new("E712.py"))]
|
||||
#[test_case(CheckCode::E713, Path::new("E713.py"))]
|
||||
#[test_case(CheckCode::E714, Path::new("E714.py"))]
|
||||
#[test_case(CheckCode::E721, Path::new("E721.py"))]
|
||||
#[test_case(CheckCode::E722, Path::new("E722.py"))]
|
||||
#[test_case(CheckCode::E731, Path::new("E731.py"))]
|
||||
#[test_case(CheckCode::E741, Path::new("E741.py"))]
|
||||
#[test_case(CheckCode::E742, Path::new("E742.py"))]
|
||||
#[test_case(CheckCode::E743, Path::new("E743.py"))]
|
||||
#[test_case(CheckCode::E999, Path::new("E999.py"))]
|
||||
#[test_case(CheckCode::W292, Path::new("W292_0.py"))]
|
||||
#[test_case(CheckCode::W292, Path::new("W292_1.py"))]
|
||||
#[test_case(CheckCode::W292, Path::new("W292_2.py"))]
|
||||
#[test_case(CheckCode::W605, Path::new("W605_0.py"))]
|
||||
#[test_case(CheckCode::W605, Path::new("W605_1.py"))]
|
||||
fn checks(check_code: CheckCode, path: &Path) -> Result<()> {
|
||||
let snapshot = format!("{}_{}", check_code.as_ref(), path.to_string_lossy());
|
||||
let mut checks = test_path(
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
---
|
||||
source: src/pycodestyle/mod.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind: MultipleImportsOnOneLine
|
||||
location:
|
||||
row: 2
|
||||
column: 0
|
||||
end_location:
|
||||
row: 2
|
||||
column: 14
|
||||
fix: ~
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
---
|
||||
source: src/pycodestyle/mod.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind: ModuleImportNotAtTopOfFile
|
||||
location:
|
||||
row: 55
|
||||
column: 0
|
||||
end_location:
|
||||
row: 55
|
||||
column: 10
|
||||
fix: ~
|
||||
- kind: ModuleImportNotAtTopOfFile
|
||||
location:
|
||||
row: 57
|
||||
column: 0
|
||||
end_location:
|
||||
row: 57
|
||||
column: 10
|
||||
fix: ~
|
||||
- kind: ModuleImportNotAtTopOfFile
|
||||
location:
|
||||
row: 61
|
||||
column: 0
|
||||
end_location:
|
||||
row: 61
|
||||
column: 10
|
||||
fix: ~
|
||||
|
||||
@@ -105,4 +105,18 @@ mod tests {
|
||||
insta::assert_yaml_snapshot!(checks);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn datetime_utc_alias_py311() -> Result<()> {
|
||||
let mut checks = test_path(
|
||||
Path::new("./resources/test/fixtures/pyupgrade/UP017.py"),
|
||||
&settings::Settings {
|
||||
target_version: PythonVersion::Py311,
|
||||
..settings::Settings::for_rule(CheckCode::UP017)
|
||||
},
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!(checks);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
25
src/pyupgrade/plugins/datetime_utc_alias.rs
Normal file
25
src/pyupgrade/plugins/datetime_utc_alias.rs
Normal file
@@ -0,0 +1,25 @@
|
||||
use rustpython_ast::Expr;
|
||||
|
||||
use crate::ast::helpers::{collect_call_paths, compose_call_path, dealias_call_path};
|
||||
use crate::ast::types::Range;
|
||||
use crate::autofix::Fix;
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::checks::{Check, CheckCode, CheckKind};
|
||||
|
||||
/// UP017
|
||||
pub fn datetime_utc_alias(checker: &mut Checker, expr: &Expr) {
|
||||
let dealiased_call_path = dealias_call_path(collect_call_paths(expr), &checker.import_aliases);
|
||||
if dealiased_call_path == ["datetime", "timezone", "utc"] {
|
||||
let mut check = Check::new(CheckKind::DatetimeTimezoneUTC, Range::from_located(expr));
|
||||
if checker.patch(&CheckCode::UP017) {
|
||||
check.amend(Fix::replacement(
|
||||
compose_call_path(expr)
|
||||
.unwrap()
|
||||
.replace("timezone.utc", "UTC"),
|
||||
expr.location,
|
||||
expr.end_location.unwrap(),
|
||||
));
|
||||
}
|
||||
checker.add_check(check);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
pub use convert_named_tuple_functional_to_class::convert_named_tuple_functional_to_class;
|
||||
pub use convert_typed_dict_functional_to_class::convert_typed_dict_functional_to_class;
|
||||
pub use datetime_utc_alias::datetime_utc_alias;
|
||||
pub use deprecated_unittest_alias::deprecated_unittest_alias;
|
||||
pub use redundant_open_modes::redundant_open_modes;
|
||||
pub use remove_six_compat::remove_six_compat;
|
||||
@@ -15,6 +16,7 @@ pub use useless_object_inheritance::useless_object_inheritance;
|
||||
|
||||
mod convert_named_tuple_functional_to_class;
|
||||
mod convert_typed_dict_functional_to_class;
|
||||
mod datetime_utc_alias;
|
||||
mod deprecated_unittest_alias;
|
||||
mod redundant_open_modes;
|
||||
mod remove_six_compat;
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
---
|
||||
source: src/pyupgrade/mod.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind: DatetimeTimezoneUTC
|
||||
location:
|
||||
row: 10
|
||||
column: 6
|
||||
end_location:
|
||||
row: 10
|
||||
column: 27
|
||||
fix:
|
||||
content: datetime.UTC
|
||||
location:
|
||||
row: 10
|
||||
column: 6
|
||||
end_location:
|
||||
row: 10
|
||||
column: 27
|
||||
- kind: DatetimeTimezoneUTC
|
||||
location:
|
||||
row: 11
|
||||
column: 6
|
||||
end_location:
|
||||
row: 11
|
||||
column: 21
|
||||
fix:
|
||||
content: dt.UTC
|
||||
location:
|
||||
row: 11
|
||||
column: 6
|
||||
end_location:
|
||||
row: 11
|
||||
column: 21
|
||||
|
||||
106
src/resolver.rs
106
src/resolver.rs
@@ -164,8 +164,8 @@ pub fn resolve_settings(
|
||||
|
||||
/// Return `true` if the given file should be ignored based on the exclusion
|
||||
/// criteria.
|
||||
fn is_excluded(file_path: &str, file_basename: &str, exclude: &globset::GlobSet) -> bool {
|
||||
exclude.is_match(file_path) || exclude.is_match(file_basename)
|
||||
fn match_exclusion(file_path: &str, file_basename: &str, exclusion: &globset::GlobSet) -> bool {
|
||||
exclusion.is_match(file_path) || exclusion.is_match(file_basename)
|
||||
}
|
||||
|
||||
/// Return `true` if the `Path` appears to be that of a Python file.
|
||||
@@ -190,7 +190,7 @@ pub fn python_files_in_path(
|
||||
overrides: &Overrides,
|
||||
) -> Result<(Vec<Result<DirEntry, ignore::Error>>, Resolver)> {
|
||||
// Normalize every path (e.g., convert from relative to absolute).
|
||||
let paths: Vec<PathBuf> = paths.iter().map(fs::normalize_path).collect();
|
||||
let mut paths: Vec<PathBuf> = paths.iter().map(fs::normalize_path).collect();
|
||||
|
||||
// Search for `pyproject.toml` files in all parent directories.
|
||||
let mut resolver = Resolver::default();
|
||||
@@ -207,6 +207,14 @@ pub fn python_files_in_path(
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the paths themselves are excluded.
|
||||
if file_strategy.force_exclude {
|
||||
paths.retain(|path| !is_file_excluded(path, &resolver, pyproject_strategy));
|
||||
if paths.is_empty() {
|
||||
return Ok((vec![], resolver));
|
||||
}
|
||||
}
|
||||
|
||||
// Create the `WalkBuilder`.
|
||||
let mut builder = WalkBuilder::new(
|
||||
paths
|
||||
@@ -264,19 +272,23 @@ pub fn python_files_in_path(
|
||||
|
||||
// Respect our own exclusion behavior.
|
||||
if let Ok(entry) = &result {
|
||||
if file_strategy.force_exclude || entry.depth() > 0 {
|
||||
if entry.depth() > 0 {
|
||||
let path = entry.path();
|
||||
let resolver = resolver.read().unwrap();
|
||||
let settings = resolver.resolve(path, pyproject_strategy);
|
||||
match fs::extract_path_names(path) {
|
||||
Ok((file_path, file_basename)) => {
|
||||
if !settings.exclude.is_empty()
|
||||
&& is_excluded(file_path, file_basename, &settings.exclude)
|
||||
&& match_exclusion(file_path, file_basename, &settings.exclude)
|
||||
{
|
||||
debug!("Ignored path via `exclude`: {:?}", path);
|
||||
return WalkState::Skip;
|
||||
} else if !settings.extend_exclude.is_empty()
|
||||
&& is_excluded(file_path, file_basename, &settings.extend_exclude)
|
||||
&& match_exclusion(
|
||||
file_path,
|
||||
file_basename,
|
||||
&settings.extend_exclude,
|
||||
)
|
||||
{
|
||||
debug!("Ignored path via `extend-exclude`: {:?}", path);
|
||||
return WalkState::Skip;
|
||||
@@ -303,6 +315,72 @@ pub fn python_files_in_path(
|
||||
Ok((files.into_inner().unwrap(), resolver.into_inner().unwrap()))
|
||||
}
|
||||
|
||||
/// Return `true` if the Python file at `Path` is _not_ excluded.
|
||||
pub fn python_file_at_path(
|
||||
path: &Path,
|
||||
pyproject_strategy: &PyprojectDiscovery,
|
||||
file_strategy: &FileDiscovery,
|
||||
overrides: &Overrides,
|
||||
) -> Result<bool> {
|
||||
if !file_strategy.force_exclude {
|
||||
return Ok(true);
|
||||
}
|
||||
|
||||
// Normalize the path (e.g., convert from relative to absolute).
|
||||
let path = fs::normalize_path(path);
|
||||
|
||||
// Search for `pyproject.toml` files in all parent directories.
|
||||
let mut resolver = Resolver::default();
|
||||
for ancestor in path.ancestors() {
|
||||
let pyproject = ancestor.join("pyproject.toml");
|
||||
if pyproject.is_file() {
|
||||
if has_ruff_section(&pyproject)? {
|
||||
let (root, settings) =
|
||||
resolve_scoped_settings(&pyproject, &Relativity::Parent, Some(overrides))?;
|
||||
resolver.add(root, settings);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check exclusions.
|
||||
Ok(!is_file_excluded(&path, &resolver, pyproject_strategy))
|
||||
}
|
||||
|
||||
/// Return `true` if the given top-level `Path` should be excluded.
|
||||
fn is_file_excluded(
|
||||
path: &Path,
|
||||
resolver: &Resolver,
|
||||
pyproject_strategy: &PyprojectDiscovery,
|
||||
) -> bool {
|
||||
// TODO(charlie): Respect gitignore.
|
||||
for path in path.ancestors() {
|
||||
if path.file_name().is_none() {
|
||||
break;
|
||||
}
|
||||
let settings = resolver.resolve(path, pyproject_strategy);
|
||||
match fs::extract_path_names(path) {
|
||||
Ok((file_path, file_basename)) => {
|
||||
if !settings.exclude.is_empty()
|
||||
&& match_exclusion(file_path, file_basename, &settings.exclude)
|
||||
{
|
||||
debug!("Ignored path via `exclude`: {:?}", path);
|
||||
return true;
|
||||
} else if !settings.extend_exclude.is_empty()
|
||||
&& match_exclusion(file_path, file_basename, &settings.extend_exclude)
|
||||
{
|
||||
debug!("Ignored path via `extend-exclude`: {:?}", path);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
debug!("Ignored path due to error in parsing: {:?}: {}", path, err);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::path::Path;
|
||||
@@ -312,7 +390,7 @@ mod tests {
|
||||
use path_absolutize::Absolutize;
|
||||
|
||||
use crate::fs;
|
||||
use crate::resolver::{is_excluded, is_python_path};
|
||||
use crate::resolver::{is_python_path, match_exclusion};
|
||||
use crate::settings::types::FilePattern;
|
||||
|
||||
#[test]
|
||||
@@ -349,7 +427,7 @@ mod tests {
|
||||
.to_path_buf(),
|
||||
);
|
||||
let (file_path, file_basename) = fs::extract_path_names(&path)?;
|
||||
assert!(is_excluded(
|
||||
assert!(match_exclusion(
|
||||
file_path,
|
||||
file_basename,
|
||||
&make_exclusion(exclude,)
|
||||
@@ -364,7 +442,7 @@ mod tests {
|
||||
.to_path_buf(),
|
||||
);
|
||||
let (file_path, file_basename) = fs::extract_path_names(&path)?;
|
||||
assert!(is_excluded(
|
||||
assert!(match_exclusion(
|
||||
file_path,
|
||||
file_basename,
|
||||
&make_exclusion(exclude,)
|
||||
@@ -381,7 +459,7 @@ mod tests {
|
||||
.to_path_buf(),
|
||||
);
|
||||
let (file_path, file_basename) = fs::extract_path_names(&path)?;
|
||||
assert!(is_excluded(
|
||||
assert!(match_exclusion(
|
||||
file_path,
|
||||
file_basename,
|
||||
&make_exclusion(exclude,)
|
||||
@@ -396,7 +474,7 @@ mod tests {
|
||||
.to_path_buf(),
|
||||
);
|
||||
let (file_path, file_basename) = fs::extract_path_names(&path)?;
|
||||
assert!(is_excluded(
|
||||
assert!(match_exclusion(
|
||||
file_path,
|
||||
file_basename,
|
||||
&make_exclusion(exclude,)
|
||||
@@ -413,7 +491,7 @@ mod tests {
|
||||
.to_path_buf(),
|
||||
);
|
||||
let (file_path, file_basename) = fs::extract_path_names(&path)?;
|
||||
assert!(is_excluded(
|
||||
assert!(match_exclusion(
|
||||
file_path,
|
||||
file_basename,
|
||||
&make_exclusion(exclude,)
|
||||
@@ -430,7 +508,7 @@ mod tests {
|
||||
.to_path_buf(),
|
||||
);
|
||||
let (file_path, file_basename) = fs::extract_path_names(&path)?;
|
||||
assert!(is_excluded(
|
||||
assert!(match_exclusion(
|
||||
file_path,
|
||||
file_basename,
|
||||
&make_exclusion(exclude,)
|
||||
@@ -447,7 +525,7 @@ mod tests {
|
||||
.to_path_buf(),
|
||||
);
|
||||
let (file_path, file_basename) = fs::extract_path_names(&path)?;
|
||||
assert!(!is_excluded(
|
||||
assert!(!match_exclusion(
|
||||
file_path,
|
||||
file_basename,
|
||||
&make_exclusion(exclude,)
|
||||
|
||||
@@ -2,11 +2,15 @@
|
||||
//! command-line options. Structure mirrors the user-facing representation of
|
||||
//! the various parameters.
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::env::VarError;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
use glob::{glob, GlobError, Paths, PatternError};
|
||||
use regex::Regex;
|
||||
use shellexpand;
|
||||
use shellexpand::LookupError;
|
||||
|
||||
use crate::checks_gen::CheckCodePrefix;
|
||||
use crate::cli::{collect_per_file_ignores, Overrides};
|
||||
@@ -78,7 +82,14 @@ impl Configuration {
|
||||
})
|
||||
.collect()
|
||||
}),
|
||||
extend: options.extend.map(PathBuf::from),
|
||||
extend: options
|
||||
.extend
|
||||
.map(|extend| {
|
||||
let extend = shellexpand::full(&extend);
|
||||
extend.map(|extend| PathBuf::from(extend.as_ref()))
|
||||
})
|
||||
.transpose()
|
||||
.map_err(|e| anyhow!("Invalid `extend` value: {e}"))?,
|
||||
extend_exclude: options
|
||||
.extend_exclude
|
||||
.map(|paths| {
|
||||
@@ -266,9 +277,13 @@ impl Configuration {
|
||||
/// Given a list of source paths, which could include glob patterns, resolve the
|
||||
/// matching paths.
|
||||
pub fn resolve_src(src: &[String], project_root: &Path) -> Result<Vec<PathBuf>> {
|
||||
let globs = src
|
||||
let expansions = src
|
||||
.iter()
|
||||
.map(Path::new)
|
||||
.map(shellexpand::full)
|
||||
.collect::<Result<Vec<Cow<'_, str>>, LookupError<VarError>>>()?;
|
||||
let globs = expansions
|
||||
.iter()
|
||||
.map(|path| Path::new(path.as_ref()))
|
||||
.map(|path| fs::normalize_path_to(path, project_root))
|
||||
.map(|path| glob(&path.to_string_lossy()))
|
||||
.collect::<Result<Vec<Paths>, PatternError>>()?;
|
||||
|
||||
@@ -283,6 +283,7 @@ impl Hash for Settings {
|
||||
}
|
||||
}
|
||||
self.show_source.hash(state);
|
||||
self.src.hash(state);
|
||||
self.target_version.hash(state);
|
||||
// Add plugin properties in alphabetical order.
|
||||
self.flake8_annotations.hash(state);
|
||||
|
||||
@@ -67,7 +67,8 @@ pub struct Options {
|
||||
pub exclude: Option<Vec<String>>,
|
||||
#[option(
|
||||
doc = r#"
|
||||
A path to a local `pyproject.toml` file to merge into this configuration.
|
||||
A path to a local `pyproject.toml` file to merge into this configuration. User home
|
||||
directory and environment variables will be expanded.
|
||||
|
||||
To resolve the current `pyproject.toml` file, Ruff will first resolve this base
|
||||
configuration file, then merge in any properties defined in the current configuration
|
||||
@@ -285,7 +286,8 @@ pub struct Options {
|
||||
|
||||
This field supports globs. For example, if you have a series of Python packages in
|
||||
a `python_modules` directory, `src = ["python_modules/*"]` would expand to incorporate
|
||||
all of the packages in that directory.
|
||||
all of the packages in that directory. User home directory and environment variables
|
||||
will also be expanded.
|
||||
"#,
|
||||
default = r#"["."]"#,
|
||||
value_type = "Vec<PathBuf>",
|
||||
|
||||
@@ -13,7 +13,7 @@ use crate::checks::CheckCode;
|
||||
use crate::checks_gen::CheckCodePrefix;
|
||||
use crate::fs;
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialOrd, PartialEq, Eq, Serialize, Deserialize, Hash)]
|
||||
#[derive(Clone, Copy, Debug, PartialOrd, Ord, PartialEq, Eq, Serialize, Deserialize, Hash)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum PythonVersion {
|
||||
Py33,
|
||||
|
||||
@@ -23,7 +23,7 @@ fn test_stdin_error() -> Result<()> {
|
||||
.failure();
|
||||
assert_eq!(
|
||||
str::from_utf8(&output.get_output().stdout)?,
|
||||
"Found 1 error(s).\n-:1:8: F401 `os` imported but unused\n1 potentially fixable with the \
|
||||
"-:1:8: F401 `os` imported but unused\nFound 1 error(s).\n1 potentially fixable with the \
|
||||
--fix option.\n"
|
||||
);
|
||||
Ok(())
|
||||
@@ -39,7 +39,7 @@ fn test_stdin_filename() -> Result<()> {
|
||||
.failure();
|
||||
assert_eq!(
|
||||
str::from_utf8(&output.get_output().stdout)?,
|
||||
"Found 1 error(s).\nF401.py:1:8: F401 `os` imported but unused\n1 potentially fixable \
|
||||
"F401.py:1:8: F401 `os` imported but unused\nFound 1 error(s).\n1 potentially fixable \
|
||||
with the --fix option.\n"
|
||||
);
|
||||
Ok(())
|
||||
|
||||
Reference in New Issue
Block a user