Compare commits

...

28 Commits

Author SHA1 Message Date
Charlie Marsh
1028ed3565 Bump version to 0.0.186 2022-12-18 14:30:30 -05:00
Anders Kaseorg
98897db6ac Add packaging status badge from repology (#1276) 2022-12-18 14:27:29 -05:00
Honkertonken
5ce4262112 Readme : Fix incorrect exmaple. (#1277) 2022-12-18 12:04:48 -05:00
Charlie Marsh
6b2359384d Print redirect warnings exactly once per code (#1280) 2022-12-18 12:03:49 -05:00
Harutaka Kawamura
d3443d7c19 Update RustPython to use correct Tuple location (#1278) 2022-12-18 08:53:57 -05:00
Anders Kaseorg
04b1e1de6f README: Add missing backtick (#1274) 2022-12-18 00:29:33 -05:00
Anders Kaseorg
c93c85300f Repair corrupted PDV007, PDV009 messages (#1273) 2022-12-18 00:29:12 -05:00
Charlie Marsh
73ed6f8654 Touch-up README 2022-12-17 21:38:45 -05:00
Charlie Marsh
eb183645f3 Add ruff-lsp to README (#1272)
Add ruff-lsp to README
2022-12-17 21:37:11 -05:00
Charlie Marsh
ef17aa93da Add ruff-lsp to README (#1272) 2022-12-17 21:37:00 -05:00
Charlie Marsh
f366b0147f Add ruff-lsp to README (#1272) 2022-12-17 21:35:44 -05:00
Charlie Marsh
fc88fa35ff Add instructions for Sublime Text installation (#1271) 2022-12-17 16:22:50 -05:00
Charlie Marsh
a2806eb8ef Bump version to 0.0.185 2022-12-16 23:47:56 -05:00
Charlie Marsh
89d919eac5 Re-remove W605_1.py from Black compatibility test 2022-12-16 23:17:27 -05:00
Charlie Marsh
5ad77fbc8d Move checkers into their own module (#1268) 2022-12-16 22:55:47 -05:00
Charlie Marsh
9cb18a481b Separate line-based checker from noqa enforcement (#1267) 2022-12-16 22:49:27 -05:00
Charlie Marsh
2393e270ed Change a few more methods to take AsRef<Path> 2022-12-16 21:38:52 -05:00
Charlie Marsh
f36e6035c8 Change a few methods to take AsRef<Path> 2022-12-16 21:28:19 -05:00
Charlie Marsh
ecf0dd05d6 Auto-detect same-package imports (#1266) 2022-12-16 21:19:11 -05:00
Charlie Marsh
5f67ee93f7 Replace cache bool with an enum 2022-12-16 15:45:30 -05:00
Charlie Marsh
1e19142d0e Bump version to 0.0.184 2022-12-16 14:36:25 -05:00
Charlie Marsh
6a95dade6d Actually check-in snapshots for #1265 2022-12-16 14:36:00 -05:00
Charlie Marsh
d6e765877e Enable autofix for __init__ method with missing None-return (#1265) 2022-12-16 14:28:56 -05:00
Charlie Marsh
e4d36bae57 Replace ignore_noqa and autofix booleans with enums (#1264) 2022-12-16 14:01:25 -05:00
Harutaka Kawamura
e3531276a7 Fix F501 (line-too-long) start location (#1262) 2022-12-16 11:29:47 -05:00
Charlie Marsh
634553f188 Add ignore-variadic-names options to flake8-unused-arguments (#1261) 2022-12-16 00:22:38 -05:00
Edgar R. M
4ff0b75045 test: Fix flake8-errmsg snapshots (#1260) 2022-12-15 23:53:15 -05:00
Charlie Marsh
481d668511 Add flake8-errmsg to README 2022-12-15 23:16:38 -05:00
162 changed files with 1641 additions and 880 deletions

View File

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

16
Cargo.lock generated
View File

@@ -724,7 +724,7 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
[[package]]
name = "flake8-to-ruff"
version = "0.0.183-dev.0"
version = "0.0.186-dev.0"
dependencies = [
"anyhow",
"clap 4.0.29",
@@ -1845,7 +1845,7 @@ dependencies = [
[[package]]
name = "ruff"
version = "0.0.183"
version = "0.0.186"
dependencies = [
"annotate-snippets 0.9.1",
"anyhow",
@@ -1901,7 +1901,7 @@ dependencies = [
[[package]]
name = "ruff_dev"
version = "0.0.183"
version = "0.0.186"
dependencies = [
"anyhow",
"clap 4.0.29",
@@ -1919,7 +1919,7 @@ dependencies = [
[[package]]
name = "ruff_macros"
version = "0.0.183"
version = "0.0.186"
dependencies = [
"proc-macro2",
"quote",
@@ -1962,7 +1962,7 @@ dependencies = [
[[package]]
name = "rustpython-ast"
version = "0.1.0"
source = "git+https://github.com/RustPython/RustPython.git?rev=8d879a53197f9c73062f6160410bdba796a71cbf#8d879a53197f9c73062f6160410bdba796a71cbf"
source = "git+https://github.com/RustPython/RustPython.git?rev=c01f014b1269eedcf4bdb45d5fbc62ae2beecf31#c01f014b1269eedcf4bdb45d5fbc62ae2beecf31"
dependencies = [
"num-bigint",
"rustpython-common",
@@ -1972,7 +1972,7 @@ dependencies = [
[[package]]
name = "rustpython-common"
version = "0.0.0"
source = "git+https://github.com/RustPython/RustPython.git?rev=8d879a53197f9c73062f6160410bdba796a71cbf#8d879a53197f9c73062f6160410bdba796a71cbf"
source = "git+https://github.com/RustPython/RustPython.git?rev=c01f014b1269eedcf4bdb45d5fbc62ae2beecf31#c01f014b1269eedcf4bdb45d5fbc62ae2beecf31"
dependencies = [
"ascii",
"cfg-if 1.0.0",
@@ -1995,7 +1995,7 @@ dependencies = [
[[package]]
name = "rustpython-compiler-core"
version = "0.1.2"
source = "git+https://github.com/RustPython/RustPython.git?rev=8d879a53197f9c73062f6160410bdba796a71cbf#8d879a53197f9c73062f6160410bdba796a71cbf"
source = "git+https://github.com/RustPython/RustPython.git?rev=c01f014b1269eedcf4bdb45d5fbc62ae2beecf31#c01f014b1269eedcf4bdb45d5fbc62ae2beecf31"
dependencies = [
"bincode",
"bitflags",
@@ -2012,7 +2012,7 @@ dependencies = [
[[package]]
name = "rustpython-parser"
version = "0.1.2"
source = "git+https://github.com/RustPython/RustPython.git?rev=8d879a53197f9c73062f6160410bdba796a71cbf#8d879a53197f9c73062f6160410bdba796a71cbf"
source = "git+https://github.com/RustPython/RustPython.git?rev=c01f014b1269eedcf4bdb45d5fbc62ae2beecf31#c01f014b1269eedcf4bdb45d5fbc62ae2beecf31"
dependencies = [
"ahash",
"anyhow",

View File

@@ -6,7 +6,7 @@ members = [
[package]
name = "ruff"
version = "0.0.183"
version = "0.0.186"
edition = "2021"
rust-version = "1.65.0"
@@ -43,11 +43,11 @@ 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.183", path = "ruff_macros" }
ruff_macros = { version = "0.0.186", path = "ruff_macros" }
rustc-hash = { version = "1.1.0" }
rustpython-ast = { features = ["unparse"], git = "https://github.com/RustPython/RustPython.git", rev = "8d879a53197f9c73062f6160410bdba796a71cbf" }
rustpython-common = { git = "https://github.com/RustPython/RustPython.git", rev = "8d879a53197f9c73062f6160410bdba796a71cbf" }
rustpython-parser = { features = ["lalrpop"], git = "https://github.com/RustPython/RustPython.git", rev = "8d879a53197f9c73062f6160410bdba796a71cbf" }
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" }
strum = { version = "0.24.1", features = ["strum_macros"] }

146
README.md
View File

@@ -118,6 +118,8 @@ Ruff is available as [`ruff`](https://pypi.org/project/ruff/) on PyPI:
pip install ruff
```
[![Packaging status](https://repology.org/badge/vertical-allrepos/ruff-python-linter.svg?exclude_unsupported=1)](https://repology.org/project/ruff-python-linter/versions)
For **macOS Homebrew** and **Linuxbrew** users, Ruff is also available as [`ruff`](https://formulae.brew.sh/formula/ruff) on Homebrew:
```shell
@@ -157,7 +159,7 @@ Ruff also works with [pre-commit](https://pre-commit.com):
```yaml
repos:
- repo: https://github.com/charliermarsh/ruff-pre-commit
rev: v0.0.183
rev: v0.0.186
hooks:
- id: ruff
```
@@ -354,7 +356,7 @@ There are a few exceptions to these rules:
a default configuration. If a user-specific configuration file exists
at `${config_dir}/ruff/pyproject.toml`,
that file will be used instead of the default configuration, with `${config_dir}` being
determined via the [`dirs](https://docs.rs/dirs/4.0.0/dirs/fn.config_dir.html) crate, and all
determined via the [`dirs`](https://docs.rs/dirs/4.0.0/dirs/fn.config_dir.html) crate, and all
relative paths being again resolved relative to the _current working directory_.
4. Any `pyproject.toml`-supported settings that are provided on the command-line (e.g., via
`--select`) will override the settings in _every_ resolved configuration file.
@@ -673,7 +675,7 @@ For more, see [flake8-annotations](https://pypi.org/project/flake8-annotations/2
| ANN102 | MissingTypeCls | Missing type annotation for `...` in classmethod | |
| ANN201 | MissingReturnTypePublicFunction | Missing return type annotation for public function `...` | |
| ANN202 | MissingReturnTypePrivateFunction | Missing return type annotation for private function `...` | |
| ANN204 | MissingReturnTypeMagicMethod | Missing return type annotation for magic method `...` | |
| ANN204 | MissingReturnTypeSpecialMethod | Missing return type annotation for special method `...` | 🛠 |
| ANN205 | MissingReturnTypeStaticMethod | Missing return type annotation for staticmethod `...` | |
| ANN206 | MissingReturnTypeClassMethod | Missing return type annotation for classmethod `...` | |
| ANN401 | DynamicallyTypedExpression | Dynamically typed expressions (typing.Any) are disallowed in `...` | |
@@ -881,9 +883,9 @@ For more, see [pandas-vet](https://pypi.org/project/pandas-vet/0.2.3/) on PyPI.
| PDV002 | UseOfInplaceArgument | `inplace=True` should be avoided; it has inconsistent behavior | |
| PDV003 | UseOfDotIsNull | `.isna` is preferred to `.isnull`; functionality is equivalent | |
| PDV004 | UseOfDotNotNull | `.notna` is preferred to `.notnull`; functionality is equivalent | |
| PDV007 | UseOfDotIx | ``ix` i` deprecated; use more explicit `.loc` o` `.iloc` | |
| PDV007 | UseOfDotIx | `.ix` is deprecated; use more explicit `.loc` or `.iloc` | |
| PDV008 | UseOfDotAt | Use `.loc` instead of `.at`. If speed is important, use numpy. | |
| PDV009 | UseOfDotIat | Use `.iloc` instea` of `.iat`. If speed is important, use numpy. | |
| PDV009 | UseOfDotIat | Use `.iloc` instead of `.iat`. If speed is important, use numpy. | |
| PDV010 | UseOfDotPivotOrUnstack | `.pivot_table` is preferred to `.pivot` or `.unstack`; provides same functionality | |
| PDV011 | UseOfDotValues | Use `.to_numpy()` instead of `.values` | |
| PDV012 | UseOfDotReadTable | `.read_csv` is preferred to `.read_table`; provides same functionality | |
@@ -933,21 +935,100 @@ For more, see [Pylint](https://pypi.org/project/pylint/2.15.7/) on PyPI.
### VS Code (Official)
Download the [Ruff VS Code extension](https://marketplace.visualstudio.com/items?itemName=charliermarsh.ruff).
Download the [Ruff VS Code extension](https://marketplace.visualstudio.com/items?itemName=charliermarsh.ruff),
which supports autofix actions, import sorting, and more.
### Language Server Protocol
![](https://user-images.githubusercontent.com/1309177/205175763-cf34871d-5c05-4abf-9916-440afc82dbf8.gif)
Ruff is available as a [Language Server Protocol (LSP)](https://microsoft.github.io/language-server-protocol/)
server, distributed as the [`python-lsp-ruff`](https://github.com/python-lsp/python-lsp-ruff) plugin
for [`python-lsp-server`](https://github.com/python-lsp/python-lsp-server), both of which are
installable via [PyPI](https://pypi.org/project/python-lsp-ruff/):
### Language Server Protocol (Official)
Ruff supports the [Language Server Protocol](https://microsoft.github.io/language-server-protocol/)
via the [`ruff-lsp`](https://github.com/charliermarsh/ruff-lsp) Python package, available on
[PyPI](https://pypi.org/project/ruff-lsp/).
[`ruff-lsp`](https://github.com/charliermarsh/ruff-lsp) enables Ruff to be used with any editor that
supports the Language Server Protocol, including [Neovim](https://github.com/charliermarsh/ruff-lsp#example-neovim),
[Sublime Text](https://github.com/charliermarsh/ruff-lsp#example-sublime-text), Emacs, and more.
For example, to use `ruff-lsp` with Neovim, install `ruff-lsp` from PyPI along with
[`nvim-lspconfig`](https://github.com/neovim/nvim-lspconfig). Then, add something like the following
to your `init.lua`:
```lua
-- See: https://github.com/neovim/nvim-lspconfig/tree/54eb2a070a4f389b1be0f98070f81d23e2b1a715#suggested-configuration
local opts = { noremap=true, silent=true }
vim.keymap.set('n', '<space>e', vim.diagnostic.open_float, opts)
vim.keymap.set('n', '[d', vim.diagnostic.goto_prev, opts)
vim.keymap.set('n', ']d', vim.diagnostic.goto_next, opts)
vim.keymap.set('n', '<space>q', vim.diagnostic.setloclist, opts)
-- Use an on_attach function to only map the following keys
-- after the language server attaches to the current buffer
local on_attach = function(client, bufnr)
-- Enable completion triggered by <c-x><c-o>
vim.api.nvim_buf_set_option(bufnr, 'omnifunc', 'v:lua.vim.lsp.omnifunc')
-- Mappings.
-- See `:help vim.lsp.*` for documentation on any of the below functions
local bufopts = { noremap=true, silent=true, buffer=bufnr }
vim.keymap.set('n', 'gD', vim.lsp.buf.declaration, bufopts)
vim.keymap.set('n', 'gd', vim.lsp.buf.definition, bufopts)
vim.keymap.set('n', 'K', vim.lsp.buf.hover, bufopts)
vim.keymap.set('n', 'gi', vim.lsp.buf.implementation, bufopts)
vim.keymap.set('n', '<C-k>', vim.lsp.buf.signature_help, bufopts)
vim.keymap.set('n', '<space>wa', vim.lsp.buf.add_workspace_folder, bufopts)
vim.keymap.set('n', '<space>wr', vim.lsp.buf.remove_workspace_folder, bufopts)
vim.keymap.set('n', '<space>wl', function()
print(vim.inspect(vim.lsp.buf.list_workspace_folders()))
end, bufopts)
vim.keymap.set('n', '<space>D', vim.lsp.buf.type_definition, bufopts)
vim.keymap.set('n', '<space>rn', vim.lsp.buf.rename, bufopts)
vim.keymap.set('n', '<space>ca', vim.lsp.buf.code_action, bufopts)
vim.keymap.set('n', 'gr', vim.lsp.buf.references, bufopts)
vim.keymap.set('n', '<space>f', function() vim.lsp.buf.format { async = true } end, bufopts)
end
-- Configure `ruff-lsp`.
local configs = require 'lspconfig.configs'
if not configs.ruff_lsp then
configs.ruff_lsp = {
default_config = {
cmd = { "ruff-lsp" },
filetypes = {'python'},
root_dir = require('lspconfig').util.find_git_ancestor,
settings = {
ruff_lsp = {
-- Any extra CLI arguments for `ruff` go here.
args = {}
}
}
}
}
end
require('lspconfig').ruff_lsp.setup {
on_attach = on_attach,
}
```
Upon successful installation, you should see Ruff's diagnostics surfaced directly in your editor:
![Code Actions available in Neovim](https://user-images.githubusercontent.com/1309177/208278707-25fa37e4-079d-4597-ad35-b95dba066960.png)
### Language Server Protocol (Unofficial)
Ruff is also available as the [`python-lsp-ruff`](https://github.com/python-lsp/python-lsp-ruff)
plugin for [`python-lsp-server`](https://github.com/python-lsp/python-lsp-ruff), both of which are
installable from PyPI:
```shell
pip install python-lsp-server python-lsp-ruff
```
The LSP server can be used with any editor that supports the Language Server Protocol. For example,
to use it with Neovim, you would add something like the following to your `init.lua`:
The LSP server can then be used with any editor that supports the Language Server Protocol.
For example, to use `python-lsp-ruff` with Neovim, add something like the following to your
`init.lua`:
```lua
require'lspconfig'.pylsp.setup {
@@ -956,6 +1037,15 @@ require'lspconfig'.pylsp.setup {
plugins = {
ruff = {
enabled = true
},
pycodestyle = {
enabled = false
},
pyflakes = {
enabled = false
},
mccabe = {
enabled = false
}
}
}
@@ -963,9 +1053,6 @@ require'lspconfig'.pylsp.setup {
}
```
[`ruffd`](https://github.com/Seamooo/ruffd) is another implementation of the Language Server
Protocol (LSP) for Ruff, written in Rust.
### PyCharm
Ruff can be installed as an [External Tool](https://www.jetbrains.com/help/pycharm/configuring-third-party-tools.html)
@@ -980,8 +1067,8 @@ Ruff should then appear as a runnable action:
### Vim & Neovim
Ruff can be integrated into any editor that supports the Language Server Protocol (LSP) (see:
[Language Server Protocol](#language-server-protocol)).
Ruff can be integrated into any editor that supports the Language Server Protocol via [`ruff-lsp`](https://github.com/charliermarsh/ruff-lsp)
(see: [Language Server Protocol](#language-server-protocol-official)).
Ruff is also available as part of the [coc-pyright](https://github.com/fannheyward/coc-pyright)
extension for `coc.nvim`.
@@ -1100,6 +1187,7 @@ natively, including:
- [`flake8-debugger`](https://pypi.org/project/flake8-debugger/)
- [`flake8-docstrings`](https://pypi.org/project/flake8-docstrings/)
- [`flake8-eradicate`](https://pypi.org/project/flake8-eradicate/)
- [`flake8-errmsg`](https://pypi.org/project/flake8-errmsg/)
- [`flake8-import-conventions`](https://github.com/joaopalmeiro/flake8-import-conventions)
- [`flake8-print`](https://pypi.org/project/flake8-print/)
- [`flake8-quotes`](https://pypi.org/project/flake8-quotes/)
@@ -1153,6 +1241,7 @@ Today, Ruff can be used to replace Flake8 when used with any of the following pl
- [`flake8-debugger`](https://pypi.org/project/flake8-debugger/)
- [`flake8-docstrings`](https://pypi.org/project/flake8-docstrings/)
- [`flake8-eradicate`](https://pypi.org/project/flake8-eradicate/)
- [`flake8-errmsg`](https://pypi.org/project/flake8-errmsg/)
- [`flake8-import-conventions`](https://github.com/joaopalmeiro/flake8-import-conventions)
- [`flake8-print`](https://pypi.org/project/flake8-print/)
- [`flake8-quotes`](https://pypi.org/project/flake8-quotes/)
@@ -1486,7 +1575,7 @@ ignored when evaluating (e.g.) unused-variable checks. The default expression ma
```toml
[tool.ruff]
# Only ignore variables named "_".
dummy_variable_rgx = "^_$"
dummy-variable-rgx = "^_$"
```
---
@@ -2142,6 +2231,25 @@ ban-relative-imports = "all"
---
### `flake8-unused-arguments`
#### [`ignore-variadic-names`](#ignore-variadic-names)
Whether to allow unused variadic arguments, like `*args` and `**kwargs`.
**Default value**: `false`
**Type**: `bool`
**Example usage**:
```toml
[tool.ruff.flake8-unused-arguments]
ignore-variadic-names = true
```
---
### `isort`
#### [`combine-as-imports`](#combine-as-imports)

View File

@@ -771,7 +771,7 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
[[package]]
name = "flake8_to_ruff"
version = "0.0.183"
version = "0.0.186"
dependencies = [
"anyhow",
"clap",
@@ -1975,7 +1975,7 @@ dependencies = [
[[package]]
name = "ruff"
version = "0.0.183"
version = "0.0.186"
dependencies = [
"anyhow",
"bincode",
@@ -2028,7 +2028,7 @@ dependencies = [
[[package]]
name = "rustpython-ast"
version = "0.1.0"
source = "git+https://github.com/RustPython/RustPython.git?rev=8d879a53197f9c73062f6160410bdba796a71cbf#8d879a53197f9c73062f6160410bdba796a71cbf"
source = "git+https://github.com/RustPython/RustPython.git?rev=c01f014b1269eedcf4bdb45d5fbc62ae2beecf31#c01f014b1269eedcf4bdb45d5fbc62ae2beecf31"
dependencies = [
"num-bigint",
"rustpython-common",
@@ -2038,7 +2038,7 @@ dependencies = [
[[package]]
name = "rustpython-common"
version = "0.0.0"
source = "git+https://github.com/RustPython/RustPython.git?rev=8d879a53197f9c73062f6160410bdba796a71cbf#8d879a53197f9c73062f6160410bdba796a71cbf"
source = "git+https://github.com/RustPython/RustPython.git?rev=c01f014b1269eedcf4bdb45d5fbc62ae2beecf31#c01f014b1269eedcf4bdb45d5fbc62ae2beecf31"
dependencies = [
"ascii",
"cfg-if 1.0.0",
@@ -2061,7 +2061,7 @@ dependencies = [
[[package]]
name = "rustpython-compiler-core"
version = "0.1.2"
source = "git+https://github.com/RustPython/RustPython.git?rev=8d879a53197f9c73062f6160410bdba796a71cbf#8d879a53197f9c73062f6160410bdba796a71cbf"
source = "git+https://github.com/RustPython/RustPython.git?rev=c01f014b1269eedcf4bdb45d5fbc62ae2beecf31#c01f014b1269eedcf4bdb45d5fbc62ae2beecf31"
dependencies = [
"bincode",
"bitflags",
@@ -2078,7 +2078,7 @@ dependencies = [
[[package]]
name = "rustpython-parser"
version = "0.1.2"
source = "git+https://github.com/RustPython/RustPython.git?rev=8d879a53197f9c73062f6160410bdba796a71cbf#8d879a53197f9c73062f6160410bdba796a71cbf"
source = "git+https://github.com/RustPython/RustPython.git?rev=c01f014b1269eedcf4bdb45d5fbc62ae2beecf31#c01f014b1269eedcf4bdb45d5fbc62ae2beecf31"
dependencies = [
"ahash",
"anyhow",

View File

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

View File

@@ -288,6 +288,7 @@ mod tests {
flake8_quotes: None,
flake8_tidy_imports: None,
flake8_import_conventions: None,
flake8_unused_arguments: None,
isort: None,
mccabe: None,
pep8_naming: None,
@@ -336,6 +337,7 @@ mod tests {
flake8_quotes: None,
flake8_tidy_imports: None,
flake8_import_conventions: None,
flake8_unused_arguments: None,
isort: None,
mccabe: None,
pep8_naming: None,
@@ -384,6 +386,7 @@ mod tests {
flake8_quotes: None,
flake8_tidy_imports: None,
flake8_import_conventions: None,
flake8_unused_arguments: None,
isort: None,
mccabe: None,
pep8_naming: None,
@@ -432,6 +435,7 @@ mod tests {
flake8_quotes: None,
flake8_tidy_imports: None,
flake8_import_conventions: None,
flake8_unused_arguments: None,
isort: None,
mccabe: None,
pep8_naming: None,
@@ -485,6 +489,7 @@ mod tests {
}),
flake8_tidy_imports: None,
flake8_import_conventions: None,
flake8_unused_arguments: None,
isort: None,
mccabe: None,
pep8_naming: None,
@@ -572,6 +577,7 @@ mod tests {
flake8_quotes: None,
flake8_tidy_imports: None,
flake8_import_conventions: None,
flake8_unused_arguments: None,
isort: None,
mccabe: None,
pep8_naming: None,
@@ -626,6 +632,7 @@ mod tests {
}),
flake8_tidy_imports: None,
flake8_import_conventions: None,
flake8_unused_arguments: None,
isort: None,
mccabe: None,
pep8_naming: None,

View File

@@ -5,6 +5,10 @@ def f_a():
raise RuntimeError("This is an example exception")
def f_a_short():
raise RuntimeError("Error")
def f_b():
example = "example"
raise RuntimeError(f"This is an {example} exception")

View File

@@ -27,7 +27,7 @@ def f(cls, x):
lambda x: print("Hello, world!")
class X:
class C:
###
# Unused arguments.
###

View File

@@ -0,0 +1,14 @@
def f(a, b):
print("Hello, world!")
def f(a, b, *args, **kwargs):
print("Hello, world!")
class C:
def f(self, a, b):
print("Hello, world!")
def f(self, a, b, *args, **kwargs):
print("Hello, world!")

View File

@@ -0,0 +1,2 @@
[tool.ruff]
src = ["."]

View File

@@ -0,0 +1,4 @@
from package.core import method
if __name__ == "__main__":
method()

View File

@@ -1,6 +1,6 @@
[package]
name = "ruff_dev"
version = "0.0.183"
version = "0.0.186"
edition = "2021"
[dependencies]
@@ -11,8 +11,8 @@ 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 = "8d879a53197f9c73062f6160410bdba796a71cbf" }
rustpython-common = { git = "https://github.com/RustPython/RustPython.git", rev = "8d879a53197f9c73062f6160410bdba796a71cbf" }
rustpython-parser = { features = ["lalrpop"], git = "https://github.com/RustPython/RustPython.git", rev = "8d879a53197f9c73062f6160410bdba796a71cbf" }
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" }
strum = { version = "0.24.1", features = ["strum_macros"] }
strum_macros = { version = "0.24.3" }

View File

@@ -107,7 +107,7 @@ pub fn main(cli: &Cli) -> Result<()> {
for (prefix, codes) in &prefix_to_codes {
if let Some(target) = CODE_REDIRECTS.get(&prefix.as_str()) {
gen = gen.line(format!(
"CheckCodePrefix::{prefix} => {{ eprintln!(\"{{}}{{}} {{}}\", \
"CheckCodePrefix::{prefix} => {{ one_time_warning!(\"{{}}{{}} {{}}\", \
\"warning\".yellow().bold(), \":\".bold(), \"`{}` has been remapped to \
`{}`\".bold()); \n vec![{}] }}",
prefix,
@@ -119,7 +119,7 @@ pub fn main(cli: &Cli) -> Result<()> {
));
} else if let Some(target) = PREFIX_REDIRECTS.get(&prefix.as_str()) {
gen = gen.line(format!(
"CheckCodePrefix::{prefix} => {{ eprintln!(\"{{}}{{}} {{}}\", \
"CheckCodePrefix::{prefix} => {{ one_time_warning!(\"{{}}{{}} {{}}\", \
\"warning\".yellow().bold(), \":\".bold(), \"`{}` has been remapped to \
`{}`\".bold()); \n vec![{}] }}",
prefix,
@@ -182,6 +182,8 @@ pub fn main(cli: &Cli) -> Result<()> {
output.push('\n');
output.push_str("use crate::checks::CheckCode;");
output.push('\n');
output.push_str("use crate::one_time_warning;");
output.push('\n');
output.push('\n');
output.push_str(&scope.to_string());
output.push('\n');

View File

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

View File

@@ -4,7 +4,7 @@ use std::str::Lines;
use rustpython_ast::{Located, Location};
use crate::ast::types::Range;
use crate::check_ast::Checker;
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> {

View File

@@ -10,7 +10,7 @@ use crate::autofix::Fix;
use crate::checks::Check;
use crate::source_code_locator::SourceCodeLocator;
#[derive(Debug, Hash)]
#[derive(Debug, Copy, Clone, Hash)]
pub enum Mode {
Generate,
Apply,
@@ -27,15 +27,6 @@ impl From<bool> for Mode {
}
}
impl From<&Mode> for bool {
fn from(value: &Mode) -> Self {
match value {
Mode::Generate | Mode::Apply => true,
Mode::None => false,
}
}
}
/// Auto-fix errors in a file, and write the fixed source code to disk.
pub fn fix_file<'a>(
checks: &'a [Check],

View File

@@ -13,7 +13,7 @@ use serde::{Deserialize, Serialize};
use crate::autofix::fixer;
use crate::message::Message;
use crate::settings::Settings;
use crate::settings::{flags, Settings};
const CARGO_PKG_VERSION: &str = env!("CARGO_PKG_VERSION");
@@ -34,43 +34,6 @@ struct CheckResult {
messages: Vec<Message>,
}
pub enum Mode {
ReadWrite,
ReadOnly,
WriteOnly,
None,
}
impl Mode {
fn allow_read(&self) -> bool {
match self {
Mode::ReadWrite => true,
Mode::ReadOnly => true,
Mode::WriteOnly => false,
Mode::None => false,
}
}
fn allow_write(&self) -> bool {
match self {
Mode::ReadWrite => true,
Mode::ReadOnly => false,
Mode::WriteOnly => true,
Mode::None => false,
}
}
}
impl From<bool> for Mode {
fn from(value: bool) -> Self {
if value {
Mode::ReadWrite
} else {
Mode::None
}
}
}
fn cache_dir() -> &'static str {
"./.ruff_cache"
}
@@ -79,10 +42,10 @@ fn content_dir() -> &'static str {
"content"
}
fn cache_key(path: &Path, settings: &Settings, autofix: &fixer::Mode) -> u64 {
fn cache_key<P: AsRef<Path>>(path: P, settings: &Settings, autofix: fixer::Mode) -> u64 {
let mut hasher = DefaultHasher::new();
CARGO_PKG_VERSION.hash(&mut hasher);
path.absolutize().unwrap().hash(&mut hasher);
path.as_ref().absolutize().unwrap().hash(&mut hasher);
settings.hash(&mut hasher);
autofix.hash(&mut hasher);
hasher.finish()
@@ -128,14 +91,14 @@ fn read_sync(key: u64) -> Result<Vec<u8>, std::io::Error> {
}
/// Get a value from the cache.
pub fn get(
path: &Path,
pub fn get<P: AsRef<Path>>(
path: P,
metadata: &Metadata,
settings: &Settings,
autofix: &fixer::Mode,
mode: &Mode,
autofix: fixer::Mode,
cache: flags::Cache,
) -> Option<Vec<Message>> {
if !mode.allow_read() {
if matches!(cache, flags::Cache::Disabled) {
return None;
};
@@ -157,15 +120,15 @@ pub fn get(
}
/// Set a value in the cache.
pub fn set(
path: &Path,
pub fn set<P: AsRef<Path>>(
path: P,
metadata: &Metadata,
settings: &Settings,
autofix: &fixer::Mode,
autofix: fixer::Mode,
messages: &[Message],
mode: &Mode,
cache: flags::Cache,
) {
if !mode.allow_write() {
if matches!(cache, flags::Cache::Disabled) {
return;
};

View File

@@ -1,288 +0,0 @@
//! Lint rules based on checking raw physical lines.
use nohash_hasher::IntMap;
use once_cell::sync::Lazy;
use regex::Regex;
use rustpython_parser::ast::Location;
use crate::ast::types::Range;
use crate::autofix::Fix;
use crate::checks::{Check, CheckCode, CheckKind, CODE_REDIRECTS};
use crate::noqa;
use crate::noqa::{is_file_exempt, Directive};
use crate::settings::Settings;
// Regex from PEP263.
static CODING_COMMENT_REGEX: Lazy<Regex> =
Lazy::new(|| Regex::new(r"^[ \t\f]*#.*?coding[:=][ \t]*utf-?8").unwrap());
static URL_REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r"^https?://\S+$").unwrap());
/// Whether the given line is too long and should be reported.
fn should_enforce_line_length(line: &str, length: usize, limit: usize) -> bool {
if length <= limit {
return false;
}
let mut chunks = line.split_whitespace();
let (Some(first), Some(_)) = (chunks.next(), chunks.next()) else {
// Single word / no printable chars - no way to make the line shorter
return false;
};
// Do not enforce the line length for commented lines that end with a URL
// or contain only a single word.
!(first == "#" && chunks.last().map_or(true, |c| URL_REGEX.is_match(c)))
}
pub fn check_lines(
checks: &mut Vec<Check>,
contents: &str,
noqa_line_for: &IntMap<usize, usize>,
settings: &Settings,
autofix: bool,
ignore_noqa: bool,
) {
let enforce_unnecessary_coding_comment = settings.enabled.contains(&CheckCode::UP009);
let enforce_line_too_long = settings.enabled.contains(&CheckCode::E501);
let enforce_noqa = settings.enabled.contains(&CheckCode::RUF100);
let mut noqa_directives: IntMap<usize, (Directive, Vec<&str>)> = IntMap::default();
let mut line_checks = vec![];
let mut ignored = vec![];
checks.sort_by_key(|check| check.location);
let mut checks_iter = checks.iter().enumerate().peekable();
if let Some((_index, check)) = checks_iter.peek() {
assert!(check.location.row() >= 1);
}
macro_rules! add_if {
($check:expr, $noqa_lineno:expr, $line:expr) => {{
match noqa_directives
.entry($noqa_lineno)
.or_insert_with(|| (noqa::extract_noqa_directive($line), vec![]))
{
(Directive::All(..), matches) => {
matches.push($check.kind.code().as_ref());
if ignore_noqa {
line_checks.push($check);
}
}
(Directive::Codes(.., codes), matches) => {
if noqa::includes($check.kind.code(), codes) {
matches.push($check.kind.code().as_ref());
if ignore_noqa {
line_checks.push($check);
}
} else {
line_checks.push($check);
}
}
(Directive::None, ..) => line_checks.push($check),
}
}};
}
let lines: Vec<&str> = contents.lines().collect();
for (lineno, line) in lines.iter().enumerate() {
// If we hit an exemption for the entire file, bail.
if is_file_exempt(line) {
checks.drain(..);
return;
}
// Grab the noqa (logical) line number for the current (physical) line.
// If there are newlines at the end of the file, they won't be represented in
// `noqa_line_for`, so fallback to the current line.
let noqa_lineno = noqa_line_for.get(&(lineno + 1)).unwrap_or(&(lineno + 1)) - 1;
// Enforce unnecessary coding comments (UP009).
if enforce_unnecessary_coding_comment {
if lineno < 2 {
// PEP3120 makes utf-8 the default encoding.
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),
},
);
if autofix && settings.fixable.contains(check.kind.code()) {
check.amend(Fix::deletion(
Location::new(lineno + 1, 0),
Location::new(lineno + 2, 0),
));
}
add_if!(check, noqa_lineno, lines[noqa_lineno]);
}
}
}
if enforce_noqa {
noqa_directives
.entry(noqa_lineno)
.or_insert_with(|| (noqa::extract_noqa_directive(lines[noqa_lineno]), vec![]));
}
// Remove any ignored checks.
while let Some((index, check)) =
checks_iter.next_if(|(_index, check)| check.location.row() == lineno + 1)
{
let noqa = noqa_directives
.entry(noqa_lineno)
.or_insert_with(|| (noqa::extract_noqa_directive(lines[noqa_lineno]), vec![]));
match noqa {
(Directive::All(..), matches) => {
matches.push(check.kind.code().as_ref());
ignored.push(index);
}
(Directive::Codes(.., codes), matches) => {
if noqa::includes(check.kind.code(), codes) {
matches.push(check.kind.code().as_ref());
ignored.push(index);
}
}
(Directive::None, ..) => {}
}
}
// Enforce line length violations (E501).
if enforce_line_too_long {
let line_length = line.chars().count();
if should_enforce_line_length(line, line_length, settings.line_length) {
let check = Check::new(
CheckKind::LineTooLong(line_length, settings.line_length),
Range {
location: Location::new(lineno + 1, 0),
end_location: Location::new(lineno + 1, line_length),
},
);
add_if!(check, noqa_lineno, lines[noqa_lineno]);
}
}
}
// Enforce newlines at end of files (W292).
if settings.enabled.contains(&CheckCode::W292) && !contents.ends_with('\n') {
// Note: if `lines.last()` is `None`, then `contents` is empty (and so we don't
// want to raise W292 anyway).
if let Some(line) = lines.last() {
let check = Check::new(
CheckKind::NoNewLineAtEndOfFile,
Range {
location: Location::new(lines.len(), line.len() + 1),
end_location: Location::new(lines.len(), line.len() + 1),
},
);
let lineno = lines.len() - 1;
let noqa_lineno = noqa_line_for.get(&(lineno + 1)).unwrap_or(&(lineno + 1)) - 1;
add_if!(check, noqa_lineno, lines[noqa_lineno]);
}
}
// Enforce that the noqa directive was actually used (RUF100).
if enforce_noqa {
for (row, (directive, matches)) in noqa_directives {
match directive {
Directive::All(spaces, start, end) => {
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),
},
);
if autofix && settings.fixable.contains(check.kind.code()) {
check.amend(Fix::deletion(
Location::new(row + 1, start - spaces),
Location::new(row + 1, lines[row].chars().count()),
));
}
line_checks.push(check);
}
}
Directive::Codes(spaces, start, end, codes) => {
let mut invalid_codes = vec![];
let mut valid_codes = vec![];
for code in codes {
let code = CODE_REDIRECTS.get(code).map_or(code, AsRef::as_ref);
if matches.contains(&code) || settings.external.contains(code) {
valid_codes.push(code.to_string());
} else {
invalid_codes.push(code.to_string());
}
}
if !invalid_codes.is_empty() {
let mut check = Check::new(
CheckKind::UnusedNOQA(Some(invalid_codes)),
Range {
location: Location::new(row + 1, start),
end_location: Location::new(row + 1, end),
},
);
if autofix && settings.fixable.contains(check.kind.code()) {
if valid_codes.is_empty() {
check.amend(Fix::deletion(
Location::new(row + 1, start - spaces),
Location::new(row + 1, lines[row].chars().count()),
));
} else {
check.amend(Fix::replacement(
format!("# noqa: {}", valid_codes.join(", ")),
Location::new(row + 1, start),
Location::new(row + 1, lines[row].chars().count()),
));
}
}
line_checks.push(check);
}
}
Directive::None => {}
}
}
}
if !ignore_noqa {
ignored.sort_unstable();
for index in ignored.iter().rev() {
checks.swap_remove(*index);
}
}
checks.extend(line_checks);
}
#[cfg(test)]
mod tests {
use nohash_hasher::IntMap;
use super::check_lines;
use crate::checks::{Check, CheckCode};
use crate::settings::Settings;
#[test]
fn e501_non_ascii_char() {
let line = "'\u{4e9c}' * 2"; // 7 in UTF-32, 9 in UTF-8.
let check_with_max_line_length = |line_length: usize| {
let mut checks: Vec<Check> = vec![];
check_lines(
&mut checks,
line,
&IntMap::default(),
&Settings {
line_length,
..Settings::for_rule(CheckCode::E501)
},
true,
false,
);
checks
};
assert!(!check_with_max_line_length(6).is_empty());
assert!(check_with_max_line_length(7).is_empty());
}
}

View File

@@ -31,7 +31,7 @@ use crate::python::future::ALL_FEATURE_NAMES;
use crate::python::typing;
use crate::python::typing::SubscriptKind;
use crate::settings::types::PythonVersion;
use crate::settings::Settings;
use crate::settings::{flags, Settings};
use crate::source_code_locator::SourceCodeLocator;
use crate::vendored::cformat::{CFormatError, CFormatErrorType};
use crate::visibility::{module_visibility, transition_scope, Modifier, Visibility, VisibleScope};
@@ -51,8 +51,8 @@ type DeferralContext<'a> = (Vec<usize>, Vec<RefEquality<'a, Stmt>>);
pub struct Checker<'a> {
// Input data.
path: &'a Path,
autofix: bool,
ignore_noqa: bool,
autofix: flags::Autofix,
noqa: flags::Noqa,
pub(crate) settings: &'a Settings,
pub(crate) noqa_line_for: &'a IntMap<usize, usize>,
pub(crate) locator: &'a SourceCodeLocator<'a>,
@@ -102,8 +102,8 @@ impl<'a> Checker<'a> {
pub fn new(
settings: &'a Settings,
noqa_line_for: &'a IntMap<usize, usize>,
autofix: bool,
ignore_noqa: bool,
autofix: flags::Autofix,
noqa: flags::Noqa,
path: &'a Path,
locator: &'a SourceCodeLocator,
) -> Checker<'a> {
@@ -111,7 +111,7 @@ impl<'a> Checker<'a> {
settings,
noqa_line_for,
autofix,
ignore_noqa,
noqa,
path,
locator,
checks: vec![],
@@ -182,7 +182,9 @@ impl<'a> Checker<'a> {
pub fn patch(&self, code: &CheckCode) -> bool {
// TODO(charlie): We can't fix errors in f-strings until RustPython adds
// location data.
self.autofix && self.in_f_string.is_none() && self.settings.fixable.contains(code)
matches!(self.autofix, flags::Autofix::Enabled)
&& self.in_f_string.is_none()
&& self.settings.fixable.contains(code)
}
/// Return `true` if the `Expr` is a reference to `typing.${target}`.
@@ -216,7 +218,7 @@ impl<'a> Checker<'a> {
// members from the fix that will eventually be excluded by a `noqa`.
// Unfortunately, we _do_ want to register a `Check` for each eventually-ignored
// import, so that our `noqa` counts are accurate.
if self.ignore_noqa {
if matches!(self.noqa, flags::Noqa::Disabled) {
return false;
}
let noqa_lineno = self.noqa_line_for.get(&lineno).unwrap_or(&lineno);
@@ -3758,11 +3760,11 @@ pub fn check_ast(
locator: &SourceCodeLocator,
noqa_line_for: &IntMap<usize, usize>,
settings: &Settings,
autofix: bool,
ignore_noqa: bool,
autofix: flags::Autofix,
noqa: flags::Noqa,
path: &Path,
) -> Vec<Check> {
let mut checker = Checker::new(settings, noqa_line_for, autofix, ignore_noqa, path, locator);
let mut checker = Checker::new(settings, noqa_line_for, autofix, noqa, path, locator);
checker.push_scope(Scope::new(ScopeKind::Module));
checker.bind_builtins();

View File

@@ -9,19 +9,22 @@ use crate::checks::Check;
use crate::directives::IsortDirectives;
use crate::isort;
use crate::isort::track::ImportTracker;
use crate::settings::Settings;
use crate::settings::{flags, Settings};
use crate::source_code_locator::SourceCodeLocator;
fn check_import_blocks(
tracker: ImportTracker,
locator: &SourceCodeLocator,
settings: &Settings,
autofix: bool,
autofix: flags::Autofix,
package: Option<&Path>,
) -> Vec<Check> {
let mut checks = vec![];
for block in tracker.into_iter() {
if !block.imports.is_empty() {
if let Some(check) = isort::plugins::check_imports(&block, locator, settings, autofix) {
if let Some(check) =
isort::plugins::check_imports(&block, locator, settings, autofix, package)
{
checks.push(check);
}
}
@@ -34,12 +37,13 @@ pub fn check_imports(
locator: &SourceCodeLocator,
directives: &IsortDirectives,
settings: &Settings,
autofix: bool,
autofix: flags::Autofix,
path: &Path,
package: Option<&Path>,
) -> Vec<Check> {
let mut tracker = ImportTracker::new(locator, directives, path);
for stmt in python_ast {
tracker.visit_stmt(stmt);
}
check_import_blocks(tracker, locator, settings, autofix)
check_import_blocks(tracker, locator, settings, autofix, package)
}

71
src/checkers/lines.rs Normal file
View File

@@ -0,0 +1,71 @@
//! Lint rules based on checking raw physical lines.
use crate::checks::{Check, CheckCode};
use crate::pycodestyle::checks::{line_too_long, no_newline_at_end_of_file};
use crate::pyupgrade::checks::unnecessary_coding_comment;
use crate::settings::{flags, Settings};
pub fn check_lines(contents: &str, settings: &Settings, autofix: flags::Autofix) -> Vec<Check> {
let mut checks: Vec<Check> = vec![];
let enforce_unnecessary_coding_comment = settings.enabled.contains(&CheckCode::UP009);
let enforce_line_too_long = settings.enabled.contains(&CheckCode::E501);
let enforce_no_newline_at_end_of_file = settings.enabled.contains(&CheckCode::W292);
for (lineno, line) in contents.lines().enumerate() {
// Enforce unnecessary coding comments (UP009).
if enforce_unnecessary_coding_comment {
if lineno < 2 {
if let Some(check) = unnecessary_coding_comment(
lineno,
line,
matches!(autofix, flags::Autofix::Enabled)
&& settings.fixable.contains(&CheckCode::UP009),
) {
checks.push(check);
}
}
}
// Enforce line length violations (E501).
if enforce_line_too_long {
if let Some(check) = line_too_long(lineno, line, settings.line_length) {
checks.push(check);
}
}
}
// Enforce newlines at end of files (W292).
if enforce_no_newline_at_end_of_file {
if let Some(check) = no_newline_at_end_of_file(contents) {
checks.push(check);
}
}
checks
}
#[cfg(test)]
mod tests {
use super::check_lines;
use crate::checks::CheckCode;
use crate::settings::{flags, Settings};
#[test]
fn e501_non_ascii_char() {
let line = "'\u{4e9c}' * 2"; // 7 in UTF-32, 9 in UTF-8.
let check_with_max_line_length = |line_length: usize| {
check_lines(
line,
&Settings {
line_length,
..Settings::for_rule(CheckCode::E501)
},
flags::Autofix::Enabled,
)
};
assert!(!check_with_max_line_length(6).is_empty());
assert!(check_with_max_line_length(7).is_empty());
}
}

5
src/checkers/mod.rs Normal file
View File

@@ -0,0 +1,5 @@
pub mod ast;
pub mod imports;
pub mod lines;
pub mod noqa;
pub mod tokens;

148
src/checkers/noqa.rs Normal file
View File

@@ -0,0 +1,148 @@
//! `NoQA` enforcement and validation.
use nohash_hasher::IntMap;
use rustpython_parser::ast::Location;
use crate::ast::types::Range;
use crate::autofix::Fix;
use crate::checks::{Check, CheckCode, CheckKind, CODE_REDIRECTS};
use crate::noqa;
use crate::noqa::{is_file_exempt, Directive};
use crate::settings::{flags, Settings};
pub fn check_noqa(
checks: &mut Vec<Check>,
contents: &str,
commented_lines: &[usize],
noqa_line_for: &IntMap<usize, usize>,
settings: &Settings,
autofix: flags::Autofix,
) {
let mut noqa_directives: IntMap<usize, (Directive, Vec<&str>)> = IntMap::default();
let mut ignored = vec![];
let enforce_noqa = settings.enabled.contains(&CheckCode::RUF100);
checks.sort_by_key(|check| check.location);
let mut checks_iter = checks.iter().enumerate().peekable();
if let Some((_index, check)) = checks_iter.peek() {
assert!(check.location.row() >= 1);
}
let lines: Vec<&str> = contents.lines().collect();
for lineno in commented_lines {
// If we hit an exemption for the entire file, bail.
if is_file_exempt(lines[lineno - 1]) {
checks.drain(..);
return;
}
if enforce_noqa {
noqa_directives
.entry(lineno - 1)
.or_insert_with(|| (noqa::extract_noqa_directive(lines[lineno - 1]), vec![]));
}
// Remove any ignored checks.
while let Some((index, check)) =
checks_iter.next_if(|(_index, check)| check.location.row() <= *lineno)
{
// Grab the noqa (logical) line number for the current (physical) line.
// If there are newlines at the end of the file, they won't be represented in
// `noqa_line_for`, so fallback to the current line.
let check_lineno = check.location.row();
let noqa_lineno = noqa_line_for.get(&check_lineno).unwrap_or(&check_lineno);
if noqa_lineno == lineno {
let noqa = noqa_directives.entry(noqa_lineno - 1).or_insert_with(|| {
(noqa::extract_noqa_directive(lines[noqa_lineno - 1]), vec![])
});
match noqa {
(Directive::All(..), matches) => {
matches.push(check.kind.code().as_ref());
ignored.push(index);
}
(Directive::Codes(.., codes), matches) => {
if noqa::includes(check.kind.code(), codes) {
matches.push(check.kind.code().as_ref());
ignored.push(index);
}
}
(Directive::None, ..) => {}
}
}
}
}
// Enforce that the noqa directive was actually used (RUF100).
if enforce_noqa {
for (row, (directive, matches)) in noqa_directives {
match directive {
Directive::All(spaces, start, end) => {
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),
},
);
if matches!(autofix, flags::Autofix::Enabled)
&& settings.fixable.contains(check.kind.code())
{
check.amend(Fix::deletion(
Location::new(row + 1, start - spaces),
Location::new(row + 1, lines[row].chars().count()),
));
}
checks.push(check);
}
}
Directive::Codes(spaces, start, end, codes) => {
let mut invalid_codes = vec![];
let mut valid_codes = vec![];
for code in codes {
let code = CODE_REDIRECTS.get(code).map_or(code, AsRef::as_ref);
if matches.contains(&code) || settings.external.contains(code) {
valid_codes.push(code.to_string());
} else {
invalid_codes.push(code.to_string());
}
}
if !invalid_codes.is_empty() {
let mut check = Check::new(
CheckKind::UnusedNOQA(Some(invalid_codes)),
Range {
location: Location::new(row + 1, start),
end_location: Location::new(row + 1, end),
},
);
if matches!(autofix, flags::Autofix::Enabled)
&& settings.fixable.contains(check.kind.code())
{
if valid_codes.is_empty() {
check.amend(Fix::deletion(
Location::new(row + 1, start - spaces),
Location::new(row + 1, lines[row].chars().count()),
));
} else {
check.amend(Fix::replacement(
format!("# noqa: {}", valid_codes.join(", ")),
Location::new(row + 1, start),
Location::new(row + 1, lines[row].chars().count()),
));
}
}
checks.push(check);
}
}
Directive::None => {}
}
}
}
ignored.sort_unstable();
for index in ignored.iter().rev() {
checks.swap_remove(*index);
}
}

View File

@@ -5,6 +5,7 @@ use rustpython_parser::lexer::{LexResult, Tok};
use crate::checks::{Check, CheckCode};
use crate::lex::docstring_detection::StateMachine;
use crate::ruff::checks::Context;
use crate::settings::flags;
use crate::source_code_locator::SourceCodeLocator;
use crate::{eradicate, flake8_quotes, pycodestyle, ruff, Settings};
@@ -12,7 +13,7 @@ pub fn check_tokens(
locator: &SourceCodeLocator,
tokens: &[LexResult],
settings: &Settings,
autofix: bool,
autofix: flags::Autofix,
) -> Vec<Check> {
let mut checks: Vec<Check> = vec![];

View File

@@ -573,6 +573,7 @@ pub enum LintSource {
Lines,
Tokens,
Imports,
NoQA,
}
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
@@ -772,7 +773,7 @@ pub enum CheckKind {
MissingTypeCls(String),
MissingReturnTypePublicFunction(String),
MissingReturnTypePrivateFunction(String),
MissingReturnTypeMagicMethod(String),
MissingReturnTypeSpecialMethod(String),
MissingReturnTypeStaticMethod(String),
MissingReturnTypeClassMethod(String),
DynamicallyTypedExpression(String),
@@ -923,9 +924,8 @@ impl CheckCode {
/// physical lines).
pub fn lint_source(&self) -> &'static LintSource {
match self {
CheckCode::E501 | CheckCode::W292 | CheckCode::RUF100 | CheckCode::UP009 => {
&LintSource::Lines
}
CheckCode::RUF100 => &LintSource::NoQA,
CheckCode::E501 | CheckCode::W292 | CheckCode::UP009 => &LintSource::Lines,
CheckCode::ERA001
| CheckCode::Q000
| CheckCode::Q001
@@ -1126,7 +1126,7 @@ impl CheckCode {
CheckCode::ANN102 => CheckKind::MissingTypeCls("...".to_string()),
CheckCode::ANN201 => CheckKind::MissingReturnTypePublicFunction("...".to_string()),
CheckCode::ANN202 => CheckKind::MissingReturnTypePrivateFunction("...".to_string()),
CheckCode::ANN204 => CheckKind::MissingReturnTypeMagicMethod("...".to_string()),
CheckCode::ANN204 => CheckKind::MissingReturnTypeSpecialMethod("...".to_string()),
CheckCode::ANN205 => CheckKind::MissingReturnTypeStaticMethod("...".to_string()),
CheckCode::ANN206 => CheckKind::MissingReturnTypeClassMethod("...".to_string()),
CheckCode::ANN401 => CheckKind::DynamicallyTypedExpression("...".to_string()),
@@ -1733,7 +1733,7 @@ impl CheckKind {
CheckKind::MissingTypeCls(_) => &CheckCode::ANN102,
CheckKind::MissingReturnTypePublicFunction(_) => &CheckCode::ANN201,
CheckKind::MissingReturnTypePrivateFunction(_) => &CheckCode::ANN202,
CheckKind::MissingReturnTypeMagicMethod(_) => &CheckCode::ANN204,
CheckKind::MissingReturnTypeSpecialMethod(_) => &CheckCode::ANN204,
CheckKind::MissingReturnTypeStaticMethod(_) => &CheckCode::ANN205,
CheckKind::MissingReturnTypeClassMethod(_) => &CheckCode::ANN206,
CheckKind::DynamicallyTypedExpression(_) => &CheckCode::ANN401,
@@ -2390,8 +2390,8 @@ impl CheckKind {
CheckKind::MissingReturnTypePrivateFunction(name) => {
format!("Missing return type annotation for private function `{name}`")
}
CheckKind::MissingReturnTypeMagicMethod(name) => {
format!("Missing return type annotation for magic method `{name}`")
CheckKind::MissingReturnTypeSpecialMethod(name) => {
format!("Missing return type annotation for special method `{name}`")
}
CheckKind::MissingReturnTypeStaticMethod(name) => {
format!("Missing return type annotation for staticmethod `{name}`")
@@ -2711,13 +2711,13 @@ impl CheckKind {
"`.notna` is preferred to `.notnull`; functionality is equivalent".to_string()
}
CheckKind::UseOfDotIx => {
"``ix` i` deprecated; use more explicit `.loc` o` `.iloc`".to_string()
"`.ix` is deprecated; use more explicit `.loc` or `.iloc`".to_string()
}
CheckKind::UseOfDotAt => {
"Use `.loc` instead of `.at`. If speed is important, use numpy.".to_string()
}
CheckKind::UseOfDotIat => {
"Use `.iloc` instea` of `.iat`. If speed is important, use numpy.".to_string()
"Use `.iloc` instead of `.iat`. If speed is important, use numpy.".to_string()
}
CheckKind::UseOfDotPivotOrUnstack => "`.pivot_table` is preferred to `.pivot` or \
`.unstack`; provides same functionality"
@@ -2817,7 +2817,6 @@ impl CheckKind {
| CheckKind::CommentedOutCode
| CheckKind::ConvertNamedTupleFunctionalToClass(..)
| CheckKind::ConvertTypedDictFunctionalToClass(..)
| CheckKind::RemoveSixCompat
| CheckKind::DashedUnderlineAfterSection(..)
| CheckKind::DeprecatedUnittestAlias(..)
| CheckKind::DoNotAssertFalse
@@ -2831,6 +2830,7 @@ impl CheckKind {
| CheckKind::IsLiteral
| CheckKind::KeyInDict(..)
| CheckKind::MisplacedComparisonConstant(..)
| CheckKind::MissingReturnTypeSpecialMethod(..)
| CheckKind::NewLineAfterLastParagraph
| CheckKind::NewLineAfterSectionName(..)
| CheckKind::NoBlankLineAfterFunction(..)
@@ -2845,13 +2845,14 @@ impl CheckKind {
| CheckKind::NotIsTest
| CheckKind::OneBlankLineAfterClass(..)
| CheckKind::OneBlankLineBeforeClass(..)
| CheckKind::PercentFormatExtraNamedArguments(..)
| CheckKind::PEP3120UnnecessaryCodingComment
| CheckKind::PPrintFound
| CheckKind::PercentFormatExtraNamedArguments(..)
| CheckKind::PrintFound
| CheckKind::RaiseNotImplemented
| CheckKind::RedundantOpenModes
| CheckKind::RedundantTupleInExceptionHandler(..)
| CheckKind::RemoveSixCompat
| CheckKind::SectionNameEndsInColon(..)
| CheckKind::SectionNotOverIndented(..)
| CheckKind::SectionUnderlineAfterName(..)

View File

@@ -5,6 +5,7 @@ use serde::{Deserialize, Serialize};
use strum_macros::{AsRefStr, EnumString};
use crate::checks::CheckCode;
use crate::one_time_warning;
#[derive(
EnumString, AsRefStr, Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Serialize, Deserialize,
@@ -1243,7 +1244,7 @@ impl CheckCodePrefix {
CheckCodePrefix::I00 => vec![CheckCode::I001],
CheckCodePrefix::I001 => vec![CheckCode::I001],
CheckCodePrefix::I2 => {
eprintln!(
one_time_warning!(
"{}{} {}",
"warning".yellow().bold(),
":".bold(),
@@ -1252,7 +1253,7 @@ impl CheckCodePrefix {
vec![CheckCode::TID252]
}
CheckCodePrefix::I25 => {
eprintln!(
one_time_warning!(
"{}{} {}",
"warning".yellow().bold(),
":".bold(),
@@ -1261,7 +1262,7 @@ impl CheckCodePrefix {
vec![CheckCode::TID252]
}
CheckCodePrefix::I252 => {
eprintln!(
one_time_warning!(
"{}{} {}",
"warning".yellow().bold(),
":".bold(),
@@ -1274,7 +1275,7 @@ impl CheckCodePrefix {
CheckCodePrefix::ICN00 => vec![CheckCode::ICN001],
CheckCodePrefix::ICN001 => vec![CheckCode::ICN001],
CheckCodePrefix::M => {
eprintln!(
one_time_warning!(
"{}{} {}",
"warning".yellow().bold(),
":".bold(),
@@ -1283,7 +1284,7 @@ impl CheckCodePrefix {
vec![CheckCode::RUF100]
}
CheckCodePrefix::M0 => {
eprintln!(
one_time_warning!(
"{}{} {}",
"warning".yellow().bold(),
":".bold(),
@@ -1292,7 +1293,7 @@ impl CheckCodePrefix {
vec![CheckCode::RUF100]
}
CheckCodePrefix::M001 => {
eprintln!(
one_time_warning!(
"{}{} {}",
"warning".yellow().bold(),
":".bold(),
@@ -1603,7 +1604,7 @@ impl CheckCodePrefix {
CheckCodePrefix::TID25 => vec![CheckCode::TID252],
CheckCodePrefix::TID252 => vec![CheckCode::TID252],
CheckCodePrefix::U => {
eprintln!(
one_time_warning!(
"{}{} {}",
"warning".yellow().bold(),
":".bold(),
@@ -1628,7 +1629,7 @@ impl CheckCodePrefix {
]
}
CheckCodePrefix::U0 => {
eprintln!(
one_time_warning!(
"{}{} {}",
"warning".yellow().bold(),
":".bold(),
@@ -1653,7 +1654,7 @@ impl CheckCodePrefix {
]
}
CheckCodePrefix::U00 => {
eprintln!(
one_time_warning!(
"{}{} {}",
"warning".yellow().bold(),
":".bold(),
@@ -1671,7 +1672,7 @@ impl CheckCodePrefix {
]
}
CheckCodePrefix::U001 => {
eprintln!(
one_time_warning!(
"{}{} {}",
"warning".yellow().bold(),
":".bold(),
@@ -1680,7 +1681,7 @@ impl CheckCodePrefix {
vec![CheckCode::UP001]
}
CheckCodePrefix::U003 => {
eprintln!(
one_time_warning!(
"{}{} {}",
"warning".yellow().bold(),
":".bold(),
@@ -1689,7 +1690,7 @@ impl CheckCodePrefix {
vec![CheckCode::UP003]
}
CheckCodePrefix::U004 => {
eprintln!(
one_time_warning!(
"{}{} {}",
"warning".yellow().bold(),
":".bold(),
@@ -1698,7 +1699,7 @@ impl CheckCodePrefix {
vec![CheckCode::UP004]
}
CheckCodePrefix::U005 => {
eprintln!(
one_time_warning!(
"{}{} {}",
"warning".yellow().bold(),
":".bold(),
@@ -1707,7 +1708,7 @@ impl CheckCodePrefix {
vec![CheckCode::UP005]
}
CheckCodePrefix::U006 => {
eprintln!(
one_time_warning!(
"{}{} {}",
"warning".yellow().bold(),
":".bold(),
@@ -1716,7 +1717,7 @@ impl CheckCodePrefix {
vec![CheckCode::UP006]
}
CheckCodePrefix::U007 => {
eprintln!(
one_time_warning!(
"{}{} {}",
"warning".yellow().bold(),
":".bold(),
@@ -1725,7 +1726,7 @@ impl CheckCodePrefix {
vec![CheckCode::UP007]
}
CheckCodePrefix::U008 => {
eprintln!(
one_time_warning!(
"{}{} {}",
"warning".yellow().bold(),
":".bold(),
@@ -1734,7 +1735,7 @@ impl CheckCodePrefix {
vec![CheckCode::UP008]
}
CheckCodePrefix::U009 => {
eprintln!(
one_time_warning!(
"{}{} {}",
"warning".yellow().bold(),
":".bold(),
@@ -1743,7 +1744,7 @@ impl CheckCodePrefix {
vec![CheckCode::UP009]
}
CheckCodePrefix::U01 => {
eprintln!(
one_time_warning!(
"{}{} {}",
"warning".yellow().bold(),
":".bold(),
@@ -1760,7 +1761,7 @@ impl CheckCodePrefix {
]
}
CheckCodePrefix::U010 => {
eprintln!(
one_time_warning!(
"{}{} {}",
"warning".yellow().bold(),
":".bold(),
@@ -1769,7 +1770,7 @@ impl CheckCodePrefix {
vec![CheckCode::UP010]
}
CheckCodePrefix::U011 => {
eprintln!(
one_time_warning!(
"{}{} {}",
"warning".yellow().bold(),
":".bold(),
@@ -1778,7 +1779,7 @@ impl CheckCodePrefix {
vec![CheckCode::UP011]
}
CheckCodePrefix::U012 => {
eprintln!(
one_time_warning!(
"{}{} {}",
"warning".yellow().bold(),
":".bold(),
@@ -1787,7 +1788,7 @@ impl CheckCodePrefix {
vec![CheckCode::UP012]
}
CheckCodePrefix::U013 => {
eprintln!(
one_time_warning!(
"{}{} {}",
"warning".yellow().bold(),
":".bold(),
@@ -1796,7 +1797,7 @@ impl CheckCodePrefix {
vec![CheckCode::UP013]
}
CheckCodePrefix::U014 => {
eprintln!(
one_time_warning!(
"{}{} {}",
"warning".yellow().bold(),
":".bold(),
@@ -1805,7 +1806,7 @@ impl CheckCodePrefix {
vec![CheckCode::UP014]
}
CheckCodePrefix::U015 => {
eprintln!(
one_time_warning!(
"{}{} {}",
"warning".yellow().bold(),
":".bold(),
@@ -1814,7 +1815,7 @@ impl CheckCodePrefix {
vec![CheckCode::UP015]
}
CheckCodePrefix::U016 => {
eprintln!(
one_time_warning!(
"{}{} {}",
"warning".yellow().bold(),
":".bold(),

View File

@@ -17,9 +17,10 @@ use crate::cli::Overrides;
use crate::iterators::par_iter;
use crate::linter::{add_noqa_to_path, autoformat_path, lint_path, lint_stdin, Diagnostics};
use crate::message::Message;
use crate::resolver;
use crate::resolver::{FileDiscovery, PyprojectDiscovery};
use crate::settings::flags;
use crate::settings::types::SerializationFormat;
use crate::{packages, resolver};
/// Run the linter over a collection of files.
pub fn run(
@@ -27,24 +28,37 @@ pub fn run(
pyproject_strategy: &PyprojectDiscovery,
file_strategy: &FileDiscovery,
overrides: &Overrides,
cache: bool,
autofix: &fixer::Mode,
cache: flags::Cache,
autofix: fixer::Mode,
) -> Result<Diagnostics> {
// Collect all the files to check.
// Collect all the Python files to check.
let start = Instant::now();
let (paths, resolver) =
resolver::python_files_in_path(files, pyproject_strategy, file_strategy, overrides)?;
let duration = start.elapsed();
debug!("Identified files to lint in: {:?}", duration);
// Discover the package root for each Python file.
let package_roots = packages::detect_package_roots(
&paths
.iter()
.flatten()
.map(ignore::DirEntry::path)
.collect::<Vec<_>>(),
);
let start = Instant::now();
let mut diagnostics: Diagnostics = par_iter(&paths)
.map(|entry| {
match entry {
Ok(entry) => {
let path = entry.path();
let package = path
.parent()
.and_then(|parent| package_roots.get(parent))
.and_then(|package| *package);
let settings = resolver.resolve(path, pyproject_strategy);
lint_path(path, settings, &cache.into(), autofix)
lint_path(path, package, settings, cache, autofix)
.map_err(|e| (Some(path.to_owned()), e.to_string()))
}
Err(e) => Err((
@@ -102,7 +116,7 @@ fn read_from_stdin() -> Result<String> {
pub fn run_stdin(
strategy: &PyprojectDiscovery,
filename: &Path,
autofix: &fixer::Mode,
autofix: fixer::Mode,
) -> Result<Diagnostics> {
let stdin = read_from_stdin()?;
let settings = match strategy {

View File

@@ -37,6 +37,7 @@ pub struct IsortDirectives {
}
pub struct Directives {
pub commented_lines: Vec<usize>,
pub noqa_line_for: IntMap<usize, usize>,
pub isort: IsortDirectives,
}
@@ -47,6 +48,7 @@ pub fn extract_directives(
flags: Flags,
) -> Directives {
Directives {
commented_lines: extract_commented_lines(lxr),
noqa_line_for: if flags.contains(Flags::NOQA) {
extract_noqa_line_for(lxr)
} else {
@@ -60,6 +62,16 @@ pub fn extract_directives(
}
}
pub fn extract_commented_lines(lxr: &[LexResult]) -> Vec<usize> {
let mut commented_lines = Vec::new();
for (start, tok, ..) in lxr.iter().flatten() {
if matches!(tok, Tok::Comment) {
commented_lines.push(start.row());
}
}
commented_lines
}
/// Extract a mapping from logical line to noqa line.
pub fn extract_noqa_line_for(lxr: &[LexResult]) -> IntMap<usize, usize> {
let mut noqa_line_for: IntMap<usize, usize> = IntMap::default();

View File

@@ -4,6 +4,7 @@ use crate::ast::types::Range;
use crate::autofix::Fix;
use crate::checks::{CheckCode, CheckKind};
use crate::eradicate::detection::comment_contains_code;
use crate::settings::flags;
use crate::{Check, Settings, SourceCodeLocator};
fn is_standalone_comment(line: &str) -> bool {
@@ -23,7 +24,7 @@ pub fn commented_out_code(
start: Location,
end: Location,
settings: &Settings,
autofix: bool,
autofix: flags::Autofix,
) -> Option<Check> {
let location = Location::new(start.row(), 0);
let end_location = Location::new(end.row() + 1, 0);
@@ -41,7 +42,9 @@ pub fn commented_out_code(
end_location: end,
},
);
if autofix && settings.fixable.contains(&CheckCode::ERA001) {
if matches!(autofix, flags::Autofix::Enabled)
&& settings.fixable.contains(&CheckCode::ERA001)
{
check.amend(Fix::deletion(location, end_location));
}
Some(check)

View File

@@ -21,7 +21,6 @@ mod tests {
.join(path)
.as_path(),
&settings::Settings::for_rule(check_code),
true,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(snapshot, checks);

View File

@@ -29,7 +29,6 @@ mod tests {
.join(path)
.as_path(),
&settings::Settings::for_rule(check_code),
true,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(snapshot, checks);

View File

@@ -3,7 +3,7 @@ use rustpython_ast::{Cmpop, Constant, Expr, ExprKind, Located};
use crate::ast::helpers::match_module_member;
use crate::ast::types::Range;
use crate::check_ast::Checker;
use crate::checkers::ast::Checker;
use crate::checks::{Check, CheckCode, CheckKind};
fn is_sys(checker: &Checker, expr: &Expr, target: &str) -> bool {

View File

@@ -0,0 +1,45 @@
use anyhow::{bail, Result};
use rustpython_ast::Stmt;
use rustpython_parser::lexer;
use rustpython_parser::lexer::Tok;
use crate::ast::helpers;
use crate::ast::types::Range;
use crate::autofix::Fix;
use crate::source_code_locator::SourceCodeLocator;
/// ANN204
pub fn add_return_none_annotation(locator: &SourceCodeLocator, stmt: &Stmt) -> Result<Fix> {
let range = Range::from_located(stmt);
let contents = locator.slice_source_code_range(&range);
// Find the colon (following the `def` keyword).
let mut seen_lpar = false;
let mut seen_rpar = false;
let mut count: usize = 0;
for (start, tok, ..) in lexer::make_tokenizer(&contents).flatten() {
if seen_lpar && seen_rpar {
if matches!(tok, Tok::Colon) {
return Ok(Fix::insertion(
" -> None".to_string(),
helpers::to_absolute(start, range.location),
));
}
}
if matches!(tok, Tok::Lpar) {
if count == 0 {
seen_lpar = true;
}
count += 1;
}
if matches!(tok, Tok::Rpar) {
count -= 1;
if count == 0 {
seen_rpar = true;
}
}
}
bail!("Unable to locate colon in function definition");
}

View File

@@ -1,7 +1,7 @@
use rustpython_ast::{Arguments, Expr, Stmt, StmtKind};
use crate::ast::cast;
use crate::check_ast::Checker;
use crate::checkers::ast::Checker;
use crate::docstrings::definition::{Definition, DefinitionKind};
use crate::visibility;

View File

@@ -1,3 +1,4 @@
mod fixes;
pub mod helpers;
pub mod plugins;
pub mod settings;
@@ -31,7 +32,6 @@ mod tests {
CheckCode::ANN401,
])
},
true,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(checks);
@@ -57,7 +57,6 @@ mod tests {
CheckCode::ANN102,
])
},
true,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(checks);
@@ -83,7 +82,6 @@ mod tests {
CheckCode::ANN206,
])
},
true,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(checks);
@@ -109,7 +107,6 @@ mod tests {
CheckCode::ANN206,
])
},
true,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(checks);
@@ -129,7 +126,6 @@ mod tests {
},
..Settings::for_rules(vec![CheckCode::ANN401])
},
true,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(checks);
@@ -149,7 +145,6 @@ mod tests {
CheckCode::ANN206,
])
},
true,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(checks);

View File

@@ -1,11 +1,13 @@
use log::error;
use rustpython_ast::{Constant, Expr, ExprKind, Stmt, StmtKind};
use crate::ast::types::Range;
use crate::ast::visitor::Visitor;
use crate::ast::{cast, visitor};
use crate::check_ast::Checker;
use crate::checkers::ast::Checker;
use crate::checks::{CheckCode, CheckKind};
use crate::docstrings::definition::{Definition, DefinitionKind};
use crate::flake8_annotations::fixes;
use crate::flake8_annotations::helpers::match_function_def;
use crate::visibility::Visibility;
use crate::{visibility, Check};
@@ -295,8 +297,8 @@ pub fn definition(checker: &mut Checker, definition: &Definition, visibility: &V
check_dynamically_typed(checker, expr, || name.to_string());
}
} else {
// Allow omission of return annotation in `__init__` functions, if the function
// only returns `None` (explicitly or implicitly).
// Allow omission of return annotation if the function only returns `None`
// (explicitly or implicitly).
if checker.settings.flake8_annotations.suppress_none_returning
&& is_none_returning(body)
{
@@ -317,13 +319,6 @@ pub fn definition(checker: &mut Checker, definition: &Definition, visibility: &V
Range::from_located(stmt),
));
}
} else if visibility::is_magic(stmt) {
if checker.settings.enabled.contains(&CheckCode::ANN204) {
checker.add_check(Check::new(
CheckKind::MissingReturnTypeMagicMethod(name.to_string()),
Range::from_located(stmt),
));
}
} else if visibility::is_init(stmt) {
// Allow omission of return annotation in `__init__` functions, as long as at
// least one argument is typed.
@@ -331,12 +326,26 @@ pub fn definition(checker: &mut Checker, definition: &Definition, visibility: &V
if !(checker.settings.flake8_annotations.mypy_init_return
&& has_any_typed_arg)
{
checker.add_check(Check::new(
CheckKind::MissingReturnTypeMagicMethod(name.to_string()),
let mut check = Check::new(
CheckKind::MissingReturnTypeSpecialMethod(name.to_string()),
Range::from_located(stmt),
));
);
if checker.patch(check.kind.code()) {
match fixes::add_return_none_annotation(checker.locator, stmt) {
Ok(fix) => check.amend(fix),
Err(e) => error!("Failed to generate fix: {e}"),
}
}
checker.add_check(check);
}
}
} else if visibility::is_magic(stmt) {
if checker.settings.enabled.contains(&CheckCode::ANN204) {
checker.add_check(Check::new(
CheckKind::MissingReturnTypeSpecialMethod(name.to_string()),
Range::from_located(stmt),
));
}
} else {
match visibility {
Visibility::Public => {

View File

@@ -3,23 +3,37 @@ source: src/flake8_annotations/mod.rs
expression: checks
---
- kind:
MissingReturnTypeMagicMethod: __init__
MissingReturnTypeSpecialMethod: __init__
location:
row: 5
column: 4
end_location:
row: 6
column: 11
fix: ~
fix:
content: " -> None"
location:
row: 5
column: 22
end_location:
row: 5
column: 22
- kind:
MissingReturnTypeMagicMethod: __init__
MissingReturnTypeSpecialMethod: __init__
location:
row: 11
column: 4
end_location:
row: 12
column: 11
fix: ~
fix:
content: " -> None"
location:
row: 11
column: 27
end_location:
row: 11
column: 27
- kind:
MissingReturnTypePrivateFunction: __init__
location:

View File

@@ -26,7 +26,6 @@ mod tests {
.join(path)
.as_path(),
&settings::Settings::for_rule(check_code),
true,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(snapshot, checks);

View File

@@ -20,7 +20,6 @@ mod tests {
.join(path)
.as_path(),
&settings::Settings::for_rule(check_code),
true,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(snapshot, checks);

View File

@@ -1,7 +1,7 @@
use rustpython_ast::{Expr, ExprKind, Stmt, StmtKind};
use crate::ast::types::Range;
use crate::check_ast::Checker;
use crate::checkers::ast::Checker;
use crate::checks::{Check, CheckKind};
/// BLE001

View File

@@ -22,7 +22,6 @@ mod tests {
.join(path)
.as_path(),
&settings::Settings::for_rule(check_code),
true,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(snapshot, checks);

View File

@@ -2,7 +2,7 @@ use rustpython_ast::{Arguments, ExprKind};
use rustpython_parser::ast::{Constant, Expr};
use crate::ast::types::Range;
use crate::check_ast::Checker;
use crate::checkers::ast::Checker;
use crate::checks::{Check, CheckKind};
const FUNC_NAME_ALLOWLIST: &[&str] = &["get", "setdefault", "pop", "fromkeys"];

View File

@@ -47,7 +47,6 @@ mod tests {
.join(path)
.as_path(),
&Settings::for_rule(check_code),
true,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(snapshot, checks);
@@ -68,7 +67,6 @@ mod tests {
},
..Settings::for_rules(vec![CheckCode::B008])
},
true,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(snapshot, checks);

View File

@@ -3,7 +3,7 @@ use rustpython_ast::{Constant, Expr, ExprKind, Keyword, Stmt, StmtKind};
use crate::ast::helpers::match_module_member;
use crate::ast::types::Range;
use crate::check_ast::Checker;
use crate::checkers::ast::Checker;
use crate::checks::{Check, CheckCode, CheckKind};
fn is_abc_class(

View File

@@ -2,7 +2,7 @@ use rustpython_ast::{Constant, Expr, ExprContext, ExprKind, Location, Stmt, Stmt
use crate::ast::types::Range;
use crate::autofix::Fix;
use crate::check_ast::Checker;
use crate::checkers::ast::Checker;
use crate::checks::{Check, CheckKind};
use crate::code_gen::SourceGenerator;

View File

@@ -2,7 +2,7 @@ use rustpython_ast::{ExprKind, Stmt, Withitem};
use crate::ast::helpers::match_module_member;
use crate::ast::types::Range;
use crate::check_ast::Checker;
use crate::checkers::ast::Checker;
use crate::checks::{Check, CheckKind};
/// B017

View File

@@ -1,7 +1,7 @@
use rustpython_ast::{Expr, ExprKind};
use crate::ast::types::Range;
use crate::check_ast::Checker;
use crate::checkers::ast::Checker;
use crate::checks::{Check, CheckKind};
/// B003

View File

@@ -2,7 +2,7 @@ use rustpython_ast::{Expr, ExprKind};
use crate::ast::helpers::{collect_call_paths, dealias_call_path, match_call_path};
use crate::ast::types::{Range, ScopeKind};
use crate::check_ast::Checker;
use crate::checkers::ast::Checker;
use crate::checks::{Check, CheckKind};
fn is_cache_func(checker: &Checker, expr: &Expr) -> bool {

View File

@@ -1,7 +1,7 @@
use rustpython_ast::{Expr, ExprKind};
use crate::ast::types::Range;
use crate::check_ast::Checker;
use crate::checkers::ast::Checker;
use crate::checks::{Check, CheckKind};
/// B016

View File

@@ -7,7 +7,7 @@ use rustpython_ast::{
use crate::ast::helpers;
use crate::ast::types::Range;
use crate::autofix::Fix;
use crate::check_ast::Checker;
use crate::checkers::ast::Checker;
use crate::checks::{Check, CheckCode, CheckKind};
use crate::code_gen::SourceGenerator;
@@ -56,9 +56,12 @@ fn duplicate_handler_exceptions<'a>(
Range::from_located(expr),
);
if checker.patch(check.kind.code()) {
// TODO(charlie): If we have a single element, remove the tuple.
let mut generator = SourceGenerator::new();
generator.unparse_expr(&type_pattern(unique_elts), 0);
if unique_elts.len() == 1 {
generator.unparse_expr(unique_elts[0], 0);
} else {
generator.unparse_expr(&type_pattern(unique_elts), 0);
}
if let Ok(content) = generator.generate() {
check.amend(Fix::replacement(
content,

View File

@@ -1,7 +1,7 @@
use rustpython_ast::{ExprKind, Stmt, StmtKind};
use crate::ast::types::Range;
use crate::check_ast::Checker;
use crate::checkers::ast::Checker;
use crate::checks::{Check, CheckKind};
/// B021

View File

@@ -7,7 +7,7 @@ use crate::ast::helpers::{
use crate::ast::types::Range;
use crate::ast::visitor;
use crate::ast::visitor::Visitor;
use crate::check_ast::Checker;
use crate::checkers::ast::Checker;
use crate::checks::{Check, CheckKind};
use crate::flake8_bugbear::plugins::mutable_argument_default::is_mutable_func;

View File

@@ -5,7 +5,7 @@ use crate::ast::helpers::collect_arg_names;
use crate::ast::types::{Node, Range};
use crate::ast::visitor;
use crate::ast::visitor::Visitor;
use crate::check_ast::Checker;
use crate::checkers::ast::Checker;
use crate::checks::{Check, CheckKind};
#[derive(Default)]

View File

@@ -2,7 +2,7 @@ use rustpython_ast::{Constant, Expr, ExprContext, ExprKind, Location};
use crate::ast::types::Range;
use crate::autofix::Fix;
use crate::check_ast::Checker;
use crate::checkers::ast::Checker;
use crate::checks::{Check, CheckKind};
use crate::code_gen::SourceGenerator;
use crate::python::identifiers::IDENTIFIER_REGEX;

View File

@@ -1,7 +1,7 @@
use rustpython_ast::{Stmt, StmtKind};
use crate::ast::types::Range;
use crate::check_ast::Checker;
use crate::checkers::ast::Checker;
use crate::checks::{Check, CheckKind};
fn walk_stmt(checker: &mut Checker, body: &[Stmt], f: fn(&Stmt) -> bool) {

View File

@@ -4,7 +4,7 @@ use rustpython_ast::{Expr, ExprKind};
use crate::ast::types::Range;
use crate::ast::visitor;
use crate::ast::visitor::Visitor;
use crate::check_ast::Checker;
use crate::checkers::ast::Checker;
use crate::checks::{Check, CheckKind};
#[derive(Default)]

View File

@@ -3,7 +3,7 @@ use rustpython_ast::{Arguments, Constant, Expr, ExprKind, Operator};
use crate::ast::helpers::{collect_call_paths, dealias_call_path, match_call_path};
use crate::ast::types::Range;
use crate::check_ast::Checker;
use crate::checkers::ast::Checker;
use crate::checks::{Check, CheckKind};
const MUTABLE_FUNCS: &[(&str, &str)] = &[

View File

@@ -2,7 +2,7 @@ use rustpython_ast::{ExprKind, Stmt, StmtKind};
use crate::ast::types::Range;
use crate::ast::visitor::Visitor;
use crate::check_ast::Checker;
use crate::checkers::ast::Checker;
use crate::checks::{Check, CheckKind};
use crate::python::string::is_lower;

View File

@@ -1,52 +1,10 @@
use anyhow::{bail, Result};
use log::error;
use rustpython_ast::{Excepthandler, ExcepthandlerKind, ExprKind, Located};
use rustpython_parser::lexer;
use rustpython_parser::lexer::Tok;
use rustpython_ast::{Excepthandler, ExcepthandlerKind, ExprKind};
use crate::ast::helpers;
use crate::ast::types::Range;
use crate::autofix::Fix;
use crate::check_ast::Checker;
use crate::checkers::ast::Checker;
use crate::checks::{Check, CheckKind};
use crate::code_gen::SourceGenerator;
use crate::SourceCodeLocator;
/// Given a statement like `except (ValueError,)`, find the range of the
/// parenthesized expression.
fn match_tuple_range<T>(located: &Located<T>, locator: &SourceCodeLocator) -> Result<Range> {
// Extract contents from the source code.
let range = Range::from_located(located);
let contents = locator.slice_source_code_range(&range);
// Find the left (opening) and right (closing) parentheses.
let mut location = None;
let mut end_location = None;
let mut count: usize = 0;
for (start, tok, end) in lexer::make_tokenizer(&contents).flatten() {
if matches!(tok, Tok::Lpar) {
if count == 0 {
location = Some(helpers::to_absolute(start, range.location));
}
count += 1;
}
if matches!(tok, Tok::Rpar) {
count -= 1;
if count == 0 {
end_location = Some(helpers::to_absolute(end, range.location));
break;
}
}
}
let (Some(location), Some(end_location)) = (location, end_location) else {
bail!("Unable to find left and right parentheses");
};
Ok(Range {
location,
end_location,
})
}
/// B013
pub fn redundant_tuple_in_exception_handler(checker: &mut Checker, handlers: &[Excepthandler]) {
@@ -68,16 +26,11 @@ pub fn redundant_tuple_in_exception_handler(checker: &mut Checker, handlers: &[E
let mut generator = SourceGenerator::new();
generator.unparse_expr(elt, 0);
if let Ok(content) = generator.generate() {
match match_tuple_range(handler, checker.locator) {
Ok(range) => {
check.amend(Fix::replacement(
content,
range.location,
range.end_location,
));
}
Err(e) => error!("Failed to locate parentheses: {e}"),
}
check.amend(Fix::replacement(
content,
type_.location,
type_.end_location.unwrap(),
));
}
}
checker.add_check(check);

View File

@@ -4,7 +4,7 @@ use rustpython_ast::{Constant, Expr, ExprContext, ExprKind, Location, Stmt, Stmt
use crate::ast::types::Range;
use crate::autofix::Fix;
use crate::check_ast::Checker;
use crate::checkers::ast::Checker;
use crate::checks::{Check, CheckKind};
use crate::code_gen::SourceGenerator;
use crate::python::identifiers::IDENTIFIER_REGEX;

View File

@@ -1,7 +1,7 @@
use rustpython_ast::{Expr, ExprKind, Keyword};
use crate::ast::types::Range;
use crate::check_ast::Checker;
use crate::checkers::ast::Checker;
use crate::checks::{Check, CheckKind};
/// B026

View File

@@ -2,7 +2,7 @@ use itertools::Itertools;
use rustpython_ast::{Constant, Expr, ExprKind};
use crate::ast::types::Range;
use crate::check_ast::Checker;
use crate::checkers::ast::Checker;
use crate::checks::{Check, CheckKind};
/// B005

View File

@@ -1,7 +1,7 @@
use rustpython_ast::{Expr, ExprKind, Unaryop};
use crate::ast::types::Range;
use crate::check_ast::Checker;
use crate::checkers::ast::Checker;
use crate::checks::{Check, CheckKind};
/// B002

View File

@@ -1,7 +1,7 @@
use rustpython_ast::{Constant, Expr, ExprKind};
use crate::ast::types::Range;
use crate::check_ast::Checker;
use crate::checkers::ast::Checker;
use crate::checks::{Check, CheckKind};
/// B004

View File

@@ -5,7 +5,7 @@ use crate::ast::types::Range;
use crate::ast::visitor;
use crate::ast::visitor::Visitor;
use crate::autofix::Fix;
use crate::check_ast::Checker;
use crate::checkers::ast::Checker;
use crate::checks::{Check, CheckKind};
/// Identify all `ExprKind::Name` nodes in an AST.

View File

@@ -1,7 +1,7 @@
use rustpython_ast::{Expr, ExprKind};
use crate::ast::types::Range;
use crate::check_ast::Checker;
use crate::checkers::ast::Checker;
use crate::checks::{Check, CheckKind};
pub fn useless_comparison(checker: &mut Checker, expr: &Expr) {

View File

@@ -2,7 +2,7 @@ use rustpython_ast::Expr;
use crate::ast::helpers::{collect_call_paths, match_call_path};
use crate::ast::types::Range;
use crate::check_ast::Checker;
use crate::checkers::ast::Checker;
use crate::checks::{Check, CheckKind};
/// B005

View File

@@ -1,7 +1,7 @@
use rustpython_ast::{Constant, ExprKind, Stmt, StmtKind};
use crate::ast::types::Range;
use crate::check_ast::Checker;
use crate::checkers::ast::Checker;
use crate::checks::{Check, CheckKind};
// B018

View File

@@ -1,7 +1,7 @@
use rustpython_ast::{Expr, ExprKind, Keyword};
use crate::ast::types::Range;
use crate::check_ast::Checker;
use crate::checkers::ast::Checker;
use crate::checks::{Check, CheckKind};
/// B905

View File

@@ -6,10 +6,10 @@ expression: checks
RedundantTupleInExceptionHandler: ValueError
location:
row: 3
column: 8
column: 7
end_location:
row: 3
column: 19
column: 20
fix:
content: ValueError
location:

View File

@@ -7,50 +7,50 @@ expression: checks
- OSError
location:
row: 17
column: 8
column: 7
end_location:
row: 17
column: 24
column: 25
fix:
content: "OSError,"
content: OSError
location:
row: 17
column: 8
column: 7
end_location:
row: 17
column: 24
column: 25
- kind:
DuplicateHandlerException:
- MyError
location:
row: 28
column: 8
column: 7
end_location:
row: 28
column: 24
column: 25
fix:
content: "MyError,"
content: MyError
location:
row: 28
column: 8
column: 7
end_location:
row: 28
column: 24
column: 25
- kind:
DuplicateHandlerException:
- re.error
location:
row: 49
column: 8
column: 7
end_location:
row: 49
column: 26
column: 27
fix:
content: "re.error,"
content: re.error
location:
row: 49
column: 8
column: 7
end_location:
row: 49
column: 26
column: 27

View File

@@ -23,7 +23,6 @@ mod tests {
.join(path)
.as_path(),
&settings::Settings::for_rule(check_code),
true,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(snapshot, checks);

View File

@@ -37,7 +37,6 @@ mod tests {
.join(path)
.as_path(),
&settings::Settings::for_rule(check_code),
true,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(snapshot, checks);

View File

@@ -20,7 +20,6 @@ mod tests {
CheckCode::EM102,
CheckCode::EM103,
]),
false,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!("defaults", checks);
@@ -41,7 +40,6 @@ mod tests {
CheckCode::EM103,
])
},
false,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!("custom", checks);

View File

@@ -12,18 +12,18 @@ expression: checks
fix: ~
- kind: FStringInException
location:
row: 10
row: 14
column: 23
end_location:
row: 10
row: 14
column: 56
fix: ~
- kind: DotFormatInException
location:
row: 14
row: 18
column: 23
end_location:
row: 14
row: 18
column: 81
fix: ~

View File

@@ -10,20 +10,28 @@ expression: checks
row: 5
column: 53
fix: ~
- kind: FStringInException
- kind: RawStringInException
location:
row: 10
row: 9
column: 23
end_location:
row: 10
row: 9
column: 30
fix: ~
- kind: FStringInException
location:
row: 14
column: 23
end_location:
row: 14
column: 56
fix: ~
- kind: DotFormatInException
location:
row: 14
row: 18
column: 23
end_location:
row: 14
row: 18
column: 81
fix: ~

View File

@@ -18,7 +18,6 @@ mod tests {
let mut checks = test_path(
Path::new("./resources/test/fixtures/flake8_import_conventions/defaults.py"),
&Settings::for_rule(CheckCode::ICN001),
true,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!("defaults", checks);
@@ -42,7 +41,6 @@ mod tests {
),
..Settings::for_rule(CheckCode::ICN001)
},
true,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!("custom", checks);
@@ -68,7 +66,6 @@ mod tests {
),
..Settings::for_rule(CheckCode::ICN001)
},
true,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!("remove_default", checks);
@@ -92,7 +89,6 @@ mod tests {
),
..Settings::for_rule(CheckCode::ICN001)
},
true,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!("override_default", checks);

View File

@@ -23,7 +23,6 @@ mod tests {
.join(path)
.as_path(),
&settings::Settings::for_rule(check_code),
true,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(snapshot, checks);

View File

@@ -3,7 +3,7 @@ use rustpython_ast::{Expr, Stmt, StmtKind};
use crate::ast::types::Range;
use crate::autofix::helpers;
use crate::check_ast::Checker;
use crate::checkers::ast::Checker;
use crate::checks::CheckCode;
use crate::flake8_print::checks;

View File

@@ -38,7 +38,6 @@ mod tests {
CheckCode::Q003,
])
},
true,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(snapshot, checks);
@@ -70,7 +69,6 @@ mod tests {
CheckCode::Q003,
])
},
true,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(snapshot, checks);
@@ -107,7 +105,6 @@ mod tests {
CheckCode::Q003,
])
},
true,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(snapshot, checks);
@@ -144,7 +141,6 @@ mod tests {
CheckCode::Q003,
])
},
true,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(snapshot, checks);

View File

@@ -28,7 +28,6 @@ mod tests {
.join(path)
.as_path(),
&Settings::for_rule(check_code),
true,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(snapshot, checks);

View File

@@ -5,7 +5,7 @@ use crate::ast::types::Range;
use crate::ast::visitor::Visitor;
use crate::ast::whitespace::indentation;
use crate::autofix::Fix;
use crate::check_ast::Checker;
use crate::checkers::ast::Checker;
use crate::checks::{Branch, CheckCode, CheckKind};
use crate::flake8_return::helpers::result_exists;
use crate::flake8_return::visitor::{ReturnVisitor, Stack};

View File

@@ -20,7 +20,6 @@ mod tests {
.join(path)
.as_path(),
&settings::Settings::for_rule(check_code),
true,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(snapshot, checks);

View File

@@ -2,7 +2,7 @@ use rustpython_ast::{Cmpop, Expr, ExprKind};
use crate::ast::types::Range;
use crate::autofix::Fix;
use crate::check_ast::Checker;
use crate::checkers::ast::Checker;
use crate::checks::{Check, CheckCode, CheckKind};
/// SIM118

View File

@@ -22,7 +22,6 @@ mod tests {
},
..Settings::for_rules(vec![CheckCode::TID252])
},
true,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(checks);
@@ -39,7 +38,6 @@ mod tests {
},
..Settings::for_rules(vec![CheckCode::TID252])
},
true,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(checks);

View File

@@ -1,5 +1,6 @@
mod helpers;
pub mod plugins;
pub mod settings;
mod types;
#[cfg(test)]
@@ -12,7 +13,7 @@ mod tests {
use crate::checks::CheckCode;
use crate::linter::test_path;
use crate::settings;
use crate::{flake8_unused_arguments, settings};
#[test_case(CheckCode::ARG001, Path::new("ARG.py"); "ARG001")]
#[test_case(CheckCode::ARG002, Path::new("ARG.py"); "ARG002")]
@@ -26,10 +27,53 @@ mod tests {
.join(path)
.as_path(),
&settings::Settings::for_rule(check_code),
true,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(snapshot, checks);
Ok(())
}
#[test]
fn ignore_variadic_names() -> Result<()> {
let mut checks = test_path(
Path::new("./resources/test/fixtures/flake8_unused_arguments/ignore_variadic_names.py"),
&settings::Settings {
flake8_unused_arguments: flake8_unused_arguments::settings::Settings {
ignore_variadic_names: true,
},
..settings::Settings::for_rules(vec![
CheckCode::ARG001,
CheckCode::ARG002,
CheckCode::ARG003,
CheckCode::ARG004,
CheckCode::ARG005,
])
},
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(checks);
Ok(())
}
#[test]
fn enforce_variadic_names() -> Result<()> {
let mut checks = test_path(
Path::new("./resources/test/fixtures/flake8_unused_arguments/ignore_variadic_names.py"),
&settings::Settings {
flake8_unused_arguments: flake8_unused_arguments::settings::Settings {
ignore_variadic_names: false,
},
..settings::Settings::for_rules(vec![
CheckCode::ARG001,
CheckCode::ARG002,
CheckCode::ARG003,
CheckCode::ARG004,
CheckCode::ARG005,
])
},
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(checks);
Ok(())
}
}

View File

@@ -6,9 +6,8 @@ use rustpython_ast::{Arg, Arguments};
use crate::ast::function_type;
use crate::ast::function_type::FunctionType;
use crate::ast::helpers::collect_arg_names;
use crate::ast::types::{Binding, BindingKind, FunctionDef, Lambda, Scope, ScopeKind};
use crate::check_ast::Checker;
use crate::checkers::ast::Checker;
use crate::flake8_unused_arguments::helpers;
use crate::flake8_unused_arguments::types::Argumentable;
use crate::{visibility, Check};
@@ -20,16 +19,35 @@ fn function(
values: &FxHashMap<&str, usize>,
bindings: &[Binding],
dummy_variable_rgx: &Regex,
ignore_variadic_names: bool,
) -> Vec<Check> {
let mut checks: Vec<Check> = vec![];
for arg_name in collect_arg_names(args) {
if let Some(binding) = values.get(arg_name).map(|index| &bindings[*index]) {
for arg in args
.posonlyargs
.iter()
.chain(args.args.iter())
.chain(args.kwonlyargs.iter())
.chain(
iter::once::<Option<&Arg>>(args.vararg.as_deref())
.flatten()
.skip(usize::from(ignore_variadic_names)),
)
.chain(
iter::once::<Option<&Arg>>(args.kwarg.as_deref())
.flatten()
.skip(usize::from(ignore_variadic_names)),
)
{
if let Some(binding) = values
.get(&arg.node.arg.as_str())
.map(|index| &bindings[*index])
{
if binding.used.is_none()
&& matches!(binding.kind, BindingKind::Argument)
&& !dummy_variable_rgx.is_match(arg_name)
&& !dummy_variable_rgx.is_match(arg.node.arg.as_str())
{
checks.push(Check::new(
argumentable.check_for(arg_name.to_string()),
argumentable.check_for(arg.node.arg.to_string()),
binding.range,
));
}
@@ -45,6 +63,7 @@ fn method(
values: &FxHashMap<&str, usize>,
bindings: &[Binding],
dummy_variable_rgx: &Regex,
ignore_variadic_names: bool,
) -> Vec<Check> {
let mut checks: Vec<Check> = vec![];
for arg in args
@@ -53,8 +72,16 @@ fn method(
.chain(args.args.iter())
.skip(1)
.chain(args.kwonlyargs.iter())
.chain(iter::once::<Option<&Arg>>(args.vararg.as_deref()).flatten())
.chain(iter::once::<Option<&Arg>>(args.kwarg.as_deref()).flatten())
.chain(
iter::once::<Option<&Arg>>(args.vararg.as_deref())
.flatten()
.skip(usize::from(ignore_variadic_names)),
)
.chain(
iter::once::<Option<&Arg>>(args.kwarg.as_deref())
.flatten()
.skip(usize::from(ignore_variadic_names)),
)
{
if let Some(binding) = values
.get(&arg.node.arg.as_str())
@@ -110,6 +137,10 @@ pub fn unused_arguments(
&scope.values,
bindings,
&checker.settings.dummy_variable_rgx,
checker
.settings
.flake8_unused_arguments
.ignore_variadic_names,
)
} else {
vec![]
@@ -130,6 +161,10 @@ pub fn unused_arguments(
&scope.values,
bindings,
&checker.settings.dummy_variable_rgx,
checker
.settings
.flake8_unused_arguments
.ignore_variadic_names,
)
} else {
vec![]
@@ -150,6 +185,10 @@ pub fn unused_arguments(
&scope.values,
bindings,
&checker.settings.dummy_variable_rgx,
checker
.settings
.flake8_unused_arguments
.ignore_variadic_names,
)
} else {
vec![]
@@ -170,6 +209,10 @@ pub fn unused_arguments(
&scope.values,
bindings,
&checker.settings.dummy_variable_rgx,
checker
.settings
.flake8_unused_arguments
.ignore_variadic_names,
)
} else {
vec![]
@@ -189,6 +232,10 @@ pub fn unused_arguments(
&scope.values,
bindings,
&checker.settings.dummy_variable_rgx,
checker
.settings
.flake8_unused_arguments
.ignore_variadic_names,
)
} else {
vec![]

View File

@@ -0,0 +1,32 @@
//! Settings for the `flake8-unused-arguments` plugin.
use ruff_macros::ConfigurationOptions;
use serde::{Deserialize, Serialize};
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Default, ConfigurationOptions)]
#[serde(deny_unknown_fields, rename_all = "kebab-case")]
pub struct Options {
#[option(
doc = r#"
Whether to allow unused variadic arguments, like `*args` and `**kwargs`.
"#,
default = "false",
value_type = "bool",
example = "ignore-variadic-names = true"
)]
pub ignore_variadic_names: Option<bool>,
}
#[derive(Debug, Hash, Default)]
pub struct Settings {
pub ignore_variadic_names: bool,
}
impl Settings {
#[allow(clippy::needless_pass_by_value)]
pub fn from_options(options: Options) -> Self {
Self {
ignore_variadic_names: options.ignore_variadic_names.unwrap_or_default(),
}
}
}

View File

@@ -0,0 +1,113 @@
---
source: src/flake8_unused_arguments/mod.rs
expression: checks
---
- kind:
UnusedFunctionArgument: a
location:
row: 1
column: 6
end_location:
row: 1
column: 7
fix: ~
- kind:
UnusedFunctionArgument: b
location:
row: 1
column: 9
end_location:
row: 1
column: 10
fix: ~
- kind:
UnusedFunctionArgument: a
location:
row: 5
column: 6
end_location:
row: 5
column: 7
fix: ~
- kind:
UnusedFunctionArgument: b
location:
row: 5
column: 9
end_location:
row: 5
column: 10
fix: ~
- kind:
UnusedFunctionArgument: args
location:
row: 5
column: 13
end_location:
row: 5
column: 17
fix: ~
- kind:
UnusedFunctionArgument: kwargs
location:
row: 5
column: 21
end_location:
row: 5
column: 27
fix: ~
- kind:
UnusedMethodArgument: a
location:
row: 10
column: 16
end_location:
row: 10
column: 17
fix: ~
- kind:
UnusedMethodArgument: b
location:
row: 10
column: 19
end_location:
row: 10
column: 20
fix: ~
- kind:
UnusedMethodArgument: a
location:
row: 13
column: 16
end_location:
row: 13
column: 17
fix: ~
- kind:
UnusedMethodArgument: b
location:
row: 13
column: 19
end_location:
row: 13
column: 20
fix: ~
- kind:
UnusedMethodArgument: args
location:
row: 13
column: 23
end_location:
row: 13
column: 27
fix: ~
- kind:
UnusedMethodArgument: kwargs
location:
row: 13
column: 31
end_location:
row: 13
column: 37
fix: ~

View File

@@ -0,0 +1,77 @@
---
source: src/flake8_unused_arguments/mod.rs
expression: checks
---
- kind:
UnusedFunctionArgument: a
location:
row: 1
column: 6
end_location:
row: 1
column: 7
fix: ~
- kind:
UnusedFunctionArgument: b
location:
row: 1
column: 9
end_location:
row: 1
column: 10
fix: ~
- kind:
UnusedFunctionArgument: a
location:
row: 5
column: 6
end_location:
row: 5
column: 7
fix: ~
- kind:
UnusedFunctionArgument: b
location:
row: 5
column: 9
end_location:
row: 5
column: 10
fix: ~
- kind:
UnusedMethodArgument: a
location:
row: 10
column: 16
end_location:
row: 10
column: 17
fix: ~
- kind:
UnusedMethodArgument: b
location:
row: 10
column: 19
end_location:
row: 10
column: 20
fix: ~
- kind:
UnusedMethodArgument: a
location:
row: 13
column: 16
end_location:
row: 13
column: 17
fix: ~
- kind:
UnusedMethodArgument: b
location:
row: 13
column: 19
end_location:
row: 13
column: 20
fix: ~

View File

@@ -40,7 +40,8 @@ pub(crate) fn ignores_from_path<'a>(
/// Convert any path to an absolute path (based on the current working
/// directory).
pub fn normalize_path(path: &Path) -> PathBuf {
pub fn normalize_path<P: AsRef<Path>>(path: P) -> PathBuf {
let path = path.as_ref();
if let Ok(path) = path.absolutize() {
return path.to_path_buf();
}
@@ -48,8 +49,9 @@ pub fn normalize_path(path: &Path) -> PathBuf {
}
/// Convert any path to an absolute path (based on the specified project root).
pub fn normalize_path_to(path: &Path, project_root: &Path) -> PathBuf {
if let Ok(path) = path.absolutize_from(project_root) {
pub fn normalize_path_to<P: AsRef<Path>, R: AsRef<Path>>(path: P, project_root: R) -> PathBuf {
let path = path.as_ref();
if let Ok(path) = path.absolutize_from(project_root.as_ref()) {
return path.to_path_buf();
}
path.to_path_buf()
@@ -64,7 +66,7 @@ pub fn relativize_path(path: &Path) -> Cow<str> {
}
/// Read a file's contents from disk.
pub(crate) fn read_file(path: &Path) -> Result<String> {
pub(crate) fn read_file<P: AsRef<Path>>(path: P) -> Result<String> {
let file = File::open(path)?;
let mut buf_reader = BufReader::new(file);
let mut contents = String::new();

View File

@@ -1,6 +1,8 @@
use std::collections::BTreeSet;
use std::fs;
use std::path::PathBuf;
use std::path::{Path, PathBuf};
use log::debug;
use crate::python::sys::KNOWN_STANDARD_LIBRARY;
@@ -13,45 +15,72 @@ pub enum ImportType {
LocalFolder,
}
#[derive(Debug)]
enum Reason<'a> {
NonZeroLevel,
KnownFirstParty,
KnownThirdParty,
ExtraStandardLibrary,
Future,
KnownStandardLibrary,
SamePackage,
SourceMatch(&'a Path),
NoMatch,
}
pub fn categorize(
module_base: &str,
level: Option<&usize>,
src: &[PathBuf],
package: Option<&Path>,
known_first_party: &BTreeSet<String>,
known_third_party: &BTreeSet<String>,
extra_standard_library: &BTreeSet<String>,
) -> ImportType {
if level.map_or(false, |level| *level > 0) {
ImportType::LocalFolder
} else if known_first_party.contains(module_base) {
ImportType::FirstParty
} else if known_third_party.contains(module_base) {
ImportType::ThirdParty
} else if extra_standard_library.contains(module_base) {
ImportType::StandardLibrary
} else if module_base == "__future__" {
ImportType::Future
} else if KNOWN_STANDARD_LIBRARY.contains(module_base) {
ImportType::StandardLibrary
} else if find_local(src, module_base) {
ImportType::FirstParty
} else {
ImportType::ThirdParty
}
let (import_type, reason) = {
if level.map_or(false, |level| *level > 0) {
(ImportType::LocalFolder, Reason::NonZeroLevel)
} else if known_first_party.contains(module_base) {
(ImportType::FirstParty, Reason::KnownFirstParty)
} else if known_third_party.contains(module_base) {
(ImportType::ThirdParty, Reason::KnownThirdParty)
} else if extra_standard_library.contains(module_base) {
(ImportType::StandardLibrary, Reason::ExtraStandardLibrary)
} else if module_base == "__future__" {
(ImportType::Future, Reason::Future)
} else if KNOWN_STANDARD_LIBRARY.contains(module_base) {
(ImportType::StandardLibrary, Reason::KnownStandardLibrary)
} else if same_package(package, module_base) {
(ImportType::FirstParty, Reason::SamePackage)
} else if let Some(src) = match_sources(src, module_base) {
(ImportType::FirstParty, Reason::SourceMatch(src))
} else {
(ImportType::ThirdParty, Reason::NoMatch)
}
};
debug!(
"Categorized '{}' as {:?} ({:?})",
module_base, import_type, reason
);
import_type
}
fn find_local(paths: &[PathBuf], base: &str) -> bool {
fn same_package(package: Option<&Path>, module_base: &str) -> bool {
package.map_or(false, |package| package.ends_with(module_base))
}
fn match_sources<'a>(paths: &'a [PathBuf], base: &str) -> Option<&'a Path> {
for path in paths {
if let Ok(metadata) = fs::metadata(path.join(base)) {
if metadata.is_dir() {
return true;
return Some(path);
}
}
if let Ok(metadata) = fs::metadata(path.join(format!("{base}.py"))) {
if metadata.is_file() {
return true;
return Some(path);
}
}
}
false
None
}

View File

@@ -1,6 +1,6 @@
use std::cmp::Reverse;
use std::collections::{BTreeMap, BTreeSet};
use std::path::PathBuf;
use std::path::{Path, PathBuf};
use itertools::Itertools;
use ropey::RopeBuilder;
@@ -294,6 +294,7 @@ fn normalize_imports(imports: Vec<AnnotatedImport>, combine_as_imports: bool) ->
fn categorize_imports<'a>(
block: ImportBlock<'a>,
src: &[PathBuf],
package: Option<&Path>,
known_first_party: &BTreeSet<String>,
known_third_party: &BTreeSet<String>,
extra_standard_library: &BTreeSet<String>,
@@ -305,6 +306,7 @@ fn categorize_imports<'a>(
&alias.module_base(),
None,
src,
package,
known_first_party,
known_third_party,
extra_standard_library,
@@ -321,6 +323,7 @@ fn categorize_imports<'a>(
&import_from.module_base(),
import_from.level,
src,
package,
known_first_party,
known_third_party,
extra_standard_library,
@@ -337,6 +340,7 @@ fn categorize_imports<'a>(
&import_from.module_base(),
import_from.level,
src,
package,
known_first_party,
known_third_party,
extra_standard_library,
@@ -353,6 +357,7 @@ fn categorize_imports<'a>(
&import_from.module_base(),
import_from.level,
src,
package,
known_first_party,
known_third_party,
extra_standard_library,
@@ -470,6 +475,7 @@ pub fn format_imports(
comments: Vec<Comment>,
line_length: usize,
src: &[PathBuf],
package: Option<&Path>,
known_first_party: &BTreeSet<String>,
known_third_party: &BTreeSet<String>,
extra_standard_library: &BTreeSet<String>,
@@ -486,6 +492,7 @@ pub fn format_imports(
let block_by_type = categorize_imports(
block,
src,
package,
known_first_party,
known_third_party,
extra_standard_library,
@@ -590,7 +597,6 @@ mod tests {
src: vec![Path::new("resources/test/fixtures/isort").to_path_buf()],
..Settings::for_rule(CheckCode::I001)
},
true,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(snapshot, checks);
@@ -612,7 +618,6 @@ mod tests {
src: vec![Path::new("resources/test/fixtures/isort").to_path_buf()],
..Settings::for_rule(CheckCode::I001)
},
true,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(snapshot, checks);
@@ -635,7 +640,6 @@ mod tests {
src: vec![Path::new("resources/test/fixtures/isort").to_path_buf()],
..Settings::for_rule(CheckCode::I001)
},
true,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(snapshot, checks);

View File

@@ -1,3 +1,5 @@
use std::path::Path;
use rustpython_ast::{Location, Stmt};
use textwrap::{dedent, indent};
@@ -10,6 +12,7 @@ use crate::autofix::Fix;
use crate::checks::CheckKind;
use crate::isort::track::Block;
use crate::isort::{comments, format_imports};
use crate::settings::flags;
use crate::{Check, Settings, SourceCodeLocator};
fn extract_range(body: &[&Stmt]) -> Range {
@@ -34,7 +37,8 @@ pub fn check_imports(
block: &Block,
locator: &SourceCodeLocator,
settings: &Settings,
autofix: bool,
autofix: flags::Autofix,
package: Option<&Path>,
) -> Option<Check> {
let indentation = locator.slice_source_code_range(&extract_indentation_range(&block.imports));
let indentation = leading_space(&indentation);
@@ -70,6 +74,7 @@ pub fn check_imports(
comments,
settings.line_length - indentation.len(),
&settings.src,
package,
&settings.isort.known_first_party,
&settings.isort.known_third_party,
&settings.isort.extra_standard_library,
@@ -87,7 +92,9 @@ pub fn check_imports(
None
} else {
let mut check = Check::new(CheckKind::UnsortedImports, range);
if autofix && settings.fixable.contains(check.kind.code()) {
if matches!(autofix, flags::Autofix::Enabled)
&& settings.fixable.contains(check.kind.code())
{
check.amend(Fix::replacement(
indent(&expected, indentation),
range.location,

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