Compare commits
17 Commits
v0.0.252
...
fix-except
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9551a045ec | ||
|
|
b6445de34d | ||
|
|
4357f2be0f | ||
|
|
e5c1f95545 | ||
|
|
227ff62a4e | ||
|
|
d8e4902516 | ||
|
|
e66739884f | ||
|
|
5fd827545b | ||
|
|
c1ddcb8a60 | ||
|
|
48a317d5f6 | ||
|
|
74e18b6cff | ||
|
|
21d02cd51f | ||
|
|
049e77b939 | ||
|
|
b9bfb81e36 | ||
|
|
2d4fae45d9 | ||
|
|
726adb7efc | ||
|
|
dbdfdeb0e1 |
@@ -152,6 +152,9 @@ This implies that rule names:
|
||||
* should not contain instructions on what you what you should use instead
|
||||
(these belong in the rule documentation and the `autofix_title` for rules that have autofix)
|
||||
|
||||
When re-implementing rules from other linters, this convention is given more importance than
|
||||
preserving the original rule name.
|
||||
|
||||
### Example: Adding a new configuration option
|
||||
|
||||
Ruff's user-facing settings live in a few different places.
|
||||
|
||||
489
README.md
489
README.md
@@ -97,21 +97,19 @@ developer of [Zulip](https://github.com/zulip/zulip):
|
||||
|
||||
For more, see the [documentation](https://beta.ruff.rs/docs/).
|
||||
|
||||
1. [Installation and Usage](#installation-and-usage)
|
||||
1. [Getting Started](#getting-started)
|
||||
2. [Configuration](#configuration)
|
||||
3. [Supported Rules](#supported-rules)
|
||||
3. [Rules](#rules)
|
||||
4. [Contributing](#contributing)
|
||||
5. [Support](#support)
|
||||
6. [Acknowledgements](#acknowledgements)
|
||||
7. [Who's Using Ruff?](#whos-using-ruff)
|
||||
8. [License](#license)
|
||||
|
||||
## Installation and Usage
|
||||
## Getting Started
|
||||
|
||||
For more, see the [documentation](https://beta.ruff.rs/docs/).
|
||||
|
||||
<!-- Begin section: Installation and Usage -->
|
||||
|
||||
### Installation
|
||||
|
||||
Ruff is available as [`ruff`](https://pypi.org/project/ruff/) on PyPI:
|
||||
@@ -120,31 +118,8 @@ Ruff is available as [`ruff`](https://pypi.org/project/ruff/) on PyPI:
|
||||
pip install ruff
|
||||
```
|
||||
|
||||
For **macOS Homebrew** and **Linuxbrew** users, Ruff is also available as [`ruff`](https://formulae.brew.sh/formula/ruff) on Homebrew:
|
||||
|
||||
```shell
|
||||
brew install ruff
|
||||
```
|
||||
|
||||
For **Conda** users, Ruff is also available as [`ruff`](https://anaconda.org/conda-forge/ruff) on `conda-forge`:
|
||||
|
||||
```shell
|
||||
conda install -c conda-forge ruff
|
||||
```
|
||||
|
||||
For **Arch Linux** users, Ruff is also available as [`ruff`](https://archlinux.org/packages/community/x86_64/ruff/) on the official repositories:
|
||||
|
||||
```shell
|
||||
pacman -S ruff
|
||||
```
|
||||
|
||||
For **Alpine** users, Ruff is also available as [`ruff`](https://pkgs.alpinelinux.org/package/edge/testing/x86_64/ruff) on the testing repositories:
|
||||
|
||||
```shell
|
||||
apk add ruff
|
||||
```
|
||||
|
||||
[](https://repology.org/project/ruff-python-linter/versions)
|
||||
You can also install Ruff via [Homebrew](https://formulae.brew.sh/formula/ruff), [Conda](https://anaconda.org/conda-forge/ruff),
|
||||
and with [a variety of other package managers](https://beta.ruff.rs/docs/installation/).
|
||||
|
||||
### Usage
|
||||
|
||||
@@ -157,13 +132,7 @@ ruff check path/to/code/*.py # Lint all `.py` files in `/path/to/code`
|
||||
ruff check path/to/code/to/file.py # Lint `file.py`
|
||||
```
|
||||
|
||||
You can run Ruff in `--watch` mode to automatically re-run on-change:
|
||||
|
||||
```shell
|
||||
ruff check path/to/code/ --watch
|
||||
```
|
||||
|
||||
Ruff also works with [pre-commit](https://pre-commit.com):
|
||||
Ruff can also be used as a [pre-commit](https://pre-commit.com) hook:
|
||||
|
||||
```yaml
|
||||
- repo: https://github.com/charliermarsh/ruff-pre-commit
|
||||
@@ -173,35 +142,20 @@ Ruff also works with [pre-commit](https://pre-commit.com):
|
||||
- id: ruff
|
||||
```
|
||||
|
||||
Or, to enable autofix:
|
||||
Ruff can also be used as a [VS Code extension](https://github.com/charliermarsh/ruff-vscode) or
|
||||
alongside any other editor through the [Ruff LSP](https://github.com/charliermarsh/ruff-lsp).
|
||||
|
||||
```yaml
|
||||
- repo: https://github.com/charliermarsh/ruff-pre-commit
|
||||
# Ruff version.
|
||||
rev: 'v0.0.252'
|
||||
hooks:
|
||||
- id: ruff
|
||||
args: [--fix, --exit-non-zero-on-fix]
|
||||
```
|
||||
### Configuration
|
||||
|
||||
<!-- End section: Installation and Usage -->
|
||||
|
||||
## Configuration
|
||||
|
||||
<!-- Begin section: Configuration -->
|
||||
|
||||
Ruff can be configured via a `pyproject.toml` file, a `ruff.toml` file, or through the command line.
|
||||
|
||||
For a complete enumeration of the available configuration options, see the
|
||||
[documentation](https://beta.ruff.rs/docs/settings/).
|
||||
|
||||
### Configure via `pyproject.toml`
|
||||
Ruff can be configured via a `pyproject.toml` file, a `ruff.toml` file, or through the command line
|
||||
(see: [_Configuration_](https://beta.ruff.rs/docs/configuration/), or
|
||||
[_Settings_](https://beta.ruff.rs/docs/settings/) for a complete list of all configuration options).
|
||||
|
||||
If left unspecified, the default configuration is equivalent to:
|
||||
|
||||
```toml
|
||||
[tool.ruff]
|
||||
# Enable Pyflakes `E` and `F` codes by default.
|
||||
# Enable pycodestyle (`E`) and Pyflakes (`F`) codes by default.
|
||||
select = ["E", "F"]
|
||||
ignore = []
|
||||
|
||||
@@ -248,428 +202,39 @@ target-version = "py310"
|
||||
max-complexity = 10
|
||||
```
|
||||
|
||||
As an example, the following would configure Ruff to: (1) enforce flake8-bugbear rules, in addition
|
||||
to the defaults; (2) avoid enforcing line-length violations (`E501`); (3) avoid attempting to fix
|
||||
flake8-bugbear (`B`) violations; and (3) ignore import-at-top-of-file violations (`E402`) in
|
||||
`__init__.py` files:
|
||||
|
||||
```toml
|
||||
[tool.ruff]
|
||||
# Enable flake8-bugbear (`B`) rules.
|
||||
select = ["E", "F", "B"]
|
||||
|
||||
# Never enforce `E501` (line length violations).
|
||||
ignore = ["E501"]
|
||||
|
||||
# Avoid trying to fix flake8-bugbear (`B`) violations.
|
||||
unfixable = ["B"]
|
||||
|
||||
# Ignore `E402` (import violations) in all `__init__.py` files, and in `path/to/file.py`.
|
||||
[tool.ruff.per-file-ignores]
|
||||
"__init__.py" = ["E402"]
|
||||
"path/to/file.py" = ["E402"]
|
||||
```
|
||||
|
||||
Plugin configurations should be expressed as subsections, e.g.:
|
||||
|
||||
```toml
|
||||
[tool.ruff]
|
||||
# Add "Q" to the list of enabled codes.
|
||||
select = ["E", "F", "Q"]
|
||||
|
||||
[tool.ruff.flake8-quotes]
|
||||
docstring-quotes = "double"
|
||||
```
|
||||
|
||||
Ruff mirrors Flake8's rule code system, in which each rule code consists of a one-to-three letter
|
||||
prefix, followed by three digits (e.g., `F401`). The prefix indicates that "source" of the rule
|
||||
(e.g., `F` for Pyflakes, `E` for pycodestyle, `ANN` for flake8-annotations). The set of enabled
|
||||
rules is determined by the `select` and `ignore` options, which support both the full code (e.g.,
|
||||
`F401`) and the prefix (e.g., `F`).
|
||||
|
||||
As a special-case, Ruff also supports the `ALL` code, which enables all rules. Note that some of the
|
||||
pydocstyle rules conflict (e.g., `D203` and `D211`) as they represent alternative docstring
|
||||
formats. Enabling `ALL` without further configuration may result in suboptimal behavior, especially
|
||||
for the pydocstyle plugin.
|
||||
|
||||
If you're wondering how to configure Ruff, here are some **recommended guidelines**:
|
||||
|
||||
* Prefer `select` and `ignore` over `extend-select` and `extend-ignore`, to make your rule set
|
||||
explicit.
|
||||
* Use `ALL` with discretion. Enabling `ALL` will implicitly enable new rules whenever you upgrade.
|
||||
* Start with a small set of rules (`select = ["E", "F"]`) and add a category at-a-time. For example,
|
||||
you might consider expanding to `select = ["E", "F", "B"]` to enable the popular flake8-bugbear
|
||||
extension.
|
||||
* By default, Ruff's autofix is aggressive. If you find that it's too aggressive for your liking,
|
||||
consider turning off autofix for specific rules or categories (see: [FAQ](https://beta.ruff.rs/docs/faq/#ruff-tried-to-fix-something-but-it-broke-my-code-what-should-i-do)).
|
||||
|
||||
### Configure via `ruff.toml`
|
||||
|
||||
As an alternative to `pyproject.toml`, Ruff will also respect a `ruff.toml` file, which implements
|
||||
an equivalent schema (though the `[tool.ruff]` hierarchy can be omitted). For example, the
|
||||
`pyproject.toml` described above would be represented via the following `ruff.toml`:
|
||||
|
||||
```toml
|
||||
# Enable flake8-bugbear (`B`) rules.
|
||||
select = ["E", "F", "B"]
|
||||
|
||||
# Never enforce `E501` (line length violations).
|
||||
ignore = ["E501"]
|
||||
|
||||
# Avoid trying to fix flake8-bugbear (`B`) violations.
|
||||
unfixable = ["B"]
|
||||
|
||||
# Ignore `E402` (import violations) in all `__init__.py` files, and in `path/to/file.py`.
|
||||
[per-file-ignores]
|
||||
"__init__.py" = ["E402"]
|
||||
"path/to/file.py" = ["E402"]
|
||||
```
|
||||
|
||||
For a full list of configurable options, see the [list of all options](https://beta.ruff.rs/docs/settings/).
|
||||
|
||||
### Command-line interface
|
||||
|
||||
Some configuration settings can be provided via the command-line, such as those related to
|
||||
Some configuration options can be provided via the command-line, such as those related to
|
||||
rule enablement and disablement, file discovery, logging level, and more:
|
||||
|
||||
```shell
|
||||
ruff check path/to/code/ --select F401 --select F403 --quiet
|
||||
```
|
||||
|
||||
See `ruff help` for more on Ruff's top-level commands:
|
||||
See `ruff help` for more on Ruff's top-level commands, or `ruff help check` for more on the
|
||||
linting command.
|
||||
|
||||
<!-- Begin auto-generated command help. -->
|
||||
|
||||
```text
|
||||
Ruff: An extremely fast Python linter.
|
||||
|
||||
Usage: ruff [OPTIONS] <COMMAND>
|
||||
|
||||
Commands:
|
||||
check Run Ruff on the given files or directories (default)
|
||||
rule Explain a rule
|
||||
config List or describe the available configuration options
|
||||
linter List all supported upstream linters
|
||||
clean Clear any caches in the current directory and any subdirectories
|
||||
help Print this message or the help of the given subcommand(s)
|
||||
|
||||
Options:
|
||||
-h, --help Print help
|
||||
-V, --version Print version
|
||||
|
||||
Log levels:
|
||||
-v, --verbose Enable verbose logging
|
||||
-q, --quiet Print lint violations, but nothing else
|
||||
-s, --silent Disable all logging (but still exit with status code "1" upon detecting lint violations)
|
||||
|
||||
For help with a specific command, see: `ruff help <command>`.
|
||||
```
|
||||
|
||||
<!-- End auto-generated command help. -->
|
||||
|
||||
Or `ruff help check` for more on the linting command:
|
||||
|
||||
<!-- Begin auto-generated subcommand help. -->
|
||||
|
||||
```text
|
||||
Run Ruff on the given files or directories (default)
|
||||
|
||||
Usage: ruff check [OPTIONS] [FILES]...
|
||||
|
||||
Arguments:
|
||||
[FILES]... List of files or directories to check
|
||||
|
||||
Options:
|
||||
--fix
|
||||
Attempt to automatically fix lint violations
|
||||
--show-source
|
||||
Show violations with source code
|
||||
--show-fixes
|
||||
Show an enumeration of all autofixed lint violations
|
||||
--diff
|
||||
Avoid writing any fixed files back; instead, output a diff for each changed file to stdout
|
||||
-w, --watch
|
||||
Run in watch mode by re-running whenever files change
|
||||
--fix-only
|
||||
Fix any fixable lint violations, but don't report on leftover violations. Implies `--fix`
|
||||
--format <FORMAT>
|
||||
Output serialization format for violations [env: RUFF_FORMAT=] [possible values: text, json, junit, grouped, github, gitlab, pylint]
|
||||
--target-version <TARGET_VERSION>
|
||||
The minimum Python version that should be supported
|
||||
--config <CONFIG>
|
||||
Path to the `pyproject.toml` or `ruff.toml` file to use for configuration
|
||||
--statistics
|
||||
Show counts for every rule with at least one violation
|
||||
--add-noqa
|
||||
Enable automatic additions of `noqa` directives to failing lines
|
||||
--show-files
|
||||
See the files Ruff will be run against with the current settings
|
||||
--show-settings
|
||||
See the settings Ruff will use to lint a given Python file
|
||||
-h, --help
|
||||
Print help
|
||||
|
||||
Rule selection:
|
||||
--select <RULE_CODE>
|
||||
Comma-separated list of rule codes to enable (or ALL, to enable all rules)
|
||||
--ignore <RULE_CODE>
|
||||
Comma-separated list of rule codes to disable
|
||||
--extend-select <RULE_CODE>
|
||||
Like --select, but adds additional rule codes on top of the selected ones
|
||||
--per-file-ignores <PER_FILE_IGNORES>
|
||||
List of mappings from file pattern to code to exclude
|
||||
--fixable <RULE_CODE>
|
||||
List of rule codes to treat as eligible for autofix. Only applicable when autofix itself is enabled (e.g., via `--fix`)
|
||||
--unfixable <RULE_CODE>
|
||||
List of rule codes to treat as ineligible for autofix. Only applicable when autofix itself is enabled (e.g., via `--fix`)
|
||||
|
||||
File selection:
|
||||
--exclude <FILE_PATTERN> List of paths, used to omit files and/or directories from analysis
|
||||
--extend-exclude <FILE_PATTERN> Like --exclude, but adds additional files and directories on top of those already excluded
|
||||
--respect-gitignore Respect file exclusions via `.gitignore` and other standard ignore files
|
||||
--force-exclude Enforce exclusions, even for paths passed to Ruff directly on the command-line
|
||||
|
||||
Miscellaneous:
|
||||
-n, --no-cache
|
||||
Disable cache reads
|
||||
--isolated
|
||||
Ignore all configuration files
|
||||
--cache-dir <CACHE_DIR>
|
||||
Path to the cache directory [env: RUFF_CACHE_DIR=]
|
||||
--stdin-filename <STDIN_FILENAME>
|
||||
The name of the file when passing it through stdin
|
||||
-e, --exit-zero
|
||||
Exit with status code "0", even upon detecting lint violations
|
||||
--exit-non-zero-on-fix
|
||||
Exit with a non-zero status code if any files were modified via autofix, even if no lint violations remain
|
||||
|
||||
Log levels:
|
||||
-v, --verbose Enable verbose logging
|
||||
-q, --quiet Print lint violations, but nothing else
|
||||
-s, --silent Disable all logging (but still exit with status code "1" upon detecting lint violations)
|
||||
```
|
||||
|
||||
<!-- End auto-generated subcommand help. -->
|
||||
|
||||
### `pyproject.toml` discovery
|
||||
|
||||
Similar to [ESLint](https://eslint.org/docs/latest/user-guide/configuring/configuration-files#cascading-and-hierarchy),
|
||||
Ruff supports hierarchical configuration, such that the "closest" `pyproject.toml` file in the
|
||||
directory hierarchy is used for every individual file, with all paths in the `pyproject.toml` file
|
||||
(e.g., `exclude` globs, `src` paths) being resolved relative to the directory containing the
|
||||
`pyproject.toml` file.
|
||||
|
||||
There are a few exceptions to these rules:
|
||||
|
||||
1. In locating the "closest" `pyproject.toml` file for a given path, Ruff ignores any
|
||||
`pyproject.toml` files that lack a `[tool.ruff]` section.
|
||||
2. If a configuration file is passed directly via `--config`, those settings are used for across
|
||||
files. Any relative paths in that configuration file (like `exclude` globs or `src` paths) are
|
||||
resolved relative to the _current working directory_.
|
||||
3. If no `pyproject.toml` file is found in the filesystem hierarchy, Ruff will fall back to using
|
||||
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 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.
|
||||
|
||||
Unlike [ESLint](https://eslint.org/docs/latest/user-guide/configuring/configuration-files#cascading-and-hierarchy),
|
||||
Ruff does not merge settings across configuration files; instead, the "closest" configuration file
|
||||
is used, and any parent configuration files are ignored. In lieu of this implicit cascade, Ruff
|
||||
supports an [`extend`](https://beta.ruff.rs/docs/settings#extend) field, which allows you to inherit the settings from another
|
||||
`pyproject.toml` file, like so:
|
||||
|
||||
```toml
|
||||
# Extend the `pyproject.toml` file in the parent directory.
|
||||
extend = "../pyproject.toml"
|
||||
# But use a different line length.
|
||||
line-length = 100
|
||||
```
|
||||
|
||||
All of the above rules apply equivalently to `ruff.toml` files. If Ruff detects both a `ruff.toml`
|
||||
and `pyproject.toml` file, it will defer to the `ruff.toml`.
|
||||
|
||||
### Python file discovery
|
||||
|
||||
When passed a path on the command-line, Ruff will automatically discover all Python files in that
|
||||
path, taking into account the [`exclude`](https://beta.ruff.rs/docs/settings#exclude) and
|
||||
[`extend-exclude`](https://beta.ruff.rs/docs/settings#extend-exclude) settings in each directory's
|
||||
`pyproject.toml` file.
|
||||
|
||||
By default, Ruff will also skip any files that are omitted via `.ignore`, `.gitignore`,
|
||||
`.git/info/exclude`, and global `gitignore` files (see: [`respect-gitignore`](https://beta.ruff.rs/docs/settings#respect-gitignore)).
|
||||
|
||||
Files that are passed to `ruff` directly are always linted, regardless of the above criteria.
|
||||
For example, `ruff check /path/to/excluded/file.py` will always lint `file.py`.
|
||||
|
||||
### Rule resolution
|
||||
|
||||
The set of enabled rules is controlled via the [`select`](https://beta.ruff.rs/docs/settings#select)
|
||||
and [`ignore`](https://beta.ruff.rs/docs/settings#ignore) settings, along with the
|
||||
[`extend-select`](https://beta.ruff.rs/docs/settings#extend-select) and
|
||||
[`extend-ignore`](https://beta.ruff.rs/docs/settings#extend-ignore) modifiers.
|
||||
|
||||
To resolve the enabled rule set, Ruff may need to reconcile `select` and `ignore` from a variety
|
||||
of sources, including the current `pyproject.toml`, any inherited `pyproject.toml` files, and the
|
||||
CLI (e.g., `--select`).
|
||||
|
||||
In those scenarios, Ruff uses the "highest-priority" `select` as the basis for the rule set, and
|
||||
then applies any `extend-select`, `ignore`, and `extend-ignore` adjustments. CLI options are given
|
||||
higher priority than `pyproject.toml` options, and the current `pyproject.toml` file is given higher
|
||||
priority than any inherited `pyproject.toml` files.
|
||||
|
||||
For example, given the following `pyproject.toml` file:
|
||||
|
||||
```toml
|
||||
[tool.ruff]
|
||||
select = ["E", "F"]
|
||||
ignore = ["F401"]
|
||||
```
|
||||
|
||||
Running `ruff check --select F401` would result in Ruff enforcing `F401`, and no other rules.
|
||||
|
||||
Running `ruff check --extend-select B` would result in Ruff enforcing the `E`, `F`, and `B` rules, with
|
||||
the exception of `F401`.
|
||||
|
||||
### Suppressing errors
|
||||
|
||||
To omit a lint rule entirely, add it to the "ignore" list via [`ignore`](https://beta.ruff.rs/docs/settings#ignore)
|
||||
or [`extend-ignore`](https://beta.ruff.rs/docs/settings#extend-ignore), either on the command-line
|
||||
or in your `pyproject.toml` file.
|
||||
|
||||
To ignore a violation inline, Ruff uses a `noqa` system similar to [Flake8](https://flake8.pycqa.org/en/3.1.1/user/ignoring-errors.html).
|
||||
To ignore an individual violation, add `# noqa: {code}` to the end of the line, like so:
|
||||
|
||||
```python
|
||||
# Ignore F841.
|
||||
x = 1 # noqa: F841
|
||||
|
||||
# Ignore E741 and F841.
|
||||
i = 1 # noqa: E741, F841
|
||||
|
||||
# Ignore _all_ violations.
|
||||
x = 1 # noqa
|
||||
```
|
||||
|
||||
Note that, for multi-line strings, the `noqa` directive should come at the end of the string, and
|
||||
will apply to the entire string, like so:
|
||||
|
||||
```python
|
||||
"""Lorem ipsum dolor sit amet.
|
||||
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor.
|
||||
""" # noqa: E501
|
||||
```
|
||||
|
||||
To ignore all violations across an entire file, add `# ruff: noqa` to any line in the file, like so:
|
||||
|
||||
```python
|
||||
# ruff: noqa
|
||||
```
|
||||
|
||||
To ignore a specific rule across an entire file, add `# ruff: noqa: {code}` to any line in the file,
|
||||
like so:
|
||||
|
||||
```python
|
||||
# ruff: noqa: F841
|
||||
```
|
||||
|
||||
Or see the [`per-file-ignores`](https://beta.ruff.rs/docs/settings#per-file-ignores) configuration
|
||||
setting, which enables the same functionality via a `pyproject.toml` file.
|
||||
|
||||
Note that Ruff will also respect Flake8's `# flake8: noqa` directive, and will treat it as
|
||||
equivalent to `# ruff: noqa`.
|
||||
|
||||
#### Automatic error suppression
|
||||
|
||||
Ruff supports several workflows to aid in `noqa` management.
|
||||
|
||||
First, Ruff provides a special rule code, `RUF100`, to enforce that your `noqa` directives are
|
||||
"valid", in that the violations they _say_ they ignore are actually being triggered on that line (and
|
||||
thus suppressed). You can run `ruff check /path/to/file.py --extend-select RUF100` to flag unused `noqa`
|
||||
directives.
|
||||
|
||||
Second, Ruff can _automatically remove_ unused `noqa` directives via its autofix functionality.
|
||||
You can run `ruff check /path/to/file.py --extend-select RUF100 --fix` to automatically remove unused
|
||||
`noqa` directives.
|
||||
|
||||
Third, Ruff can _automatically add_ `noqa` directives to all failing lines. This is useful when
|
||||
migrating a new codebase to Ruff. You can run `ruff check /path/to/file.py --add-noqa` to automatically
|
||||
add `noqa` directives to all failing lines, with the appropriate rule codes.
|
||||
|
||||
#### Action comments
|
||||
|
||||
Ruff respects `isort`'s [action comments](https://pycqa.github.io/isort/docs/configuration/action_comments.html)
|
||||
(`# isort: skip_file`, `# isort: on`, `# isort: off`, `# isort: skip`, and `# isort: split`), which
|
||||
enable selectively enabling and disabling import sorting for blocks of code and other inline
|
||||
configuration.
|
||||
|
||||
See the [`isort` documentation](https://pycqa.github.io/isort/docs/configuration/action_comments.html)
|
||||
for more.
|
||||
|
||||
### Exit codes
|
||||
|
||||
By default, Ruff exits with the following status codes:
|
||||
|
||||
* `0` if no violations were found, or if all present violations were fixed automatically.
|
||||
* `1` if violations were found.
|
||||
* `2` if Ruff terminates abnormally due to invalid configuration, invalid CLI options, or an internal error.
|
||||
|
||||
This convention mirrors that of tools like ESLint, Prettier, and RuboCop.
|
||||
|
||||
Ruff supports two command-line flags that alter its exit code behavior:
|
||||
|
||||
* `--exit-zero` will cause Ruff to exit with a status code of `0` even if violations were found.
|
||||
Note that Ruff will still exit with a status code of `2` if it terminates abnormally.
|
||||
* `--exit-non-zero-on-fix` will cause Ruff to exit with a status code of `1` if violations were
|
||||
found, _even if_ all such violations were fixed automatically. Note that the use of
|
||||
`--exit-non-zero-on-fix` can result in a non-zero exit code even if no violations remain after
|
||||
autofixing.
|
||||
|
||||
### Autocompletion
|
||||
|
||||
Ruff supports autocompletion for most shells. A shell-specific completion script can be generated
|
||||
by `ruff generate-shell-completion <SHELL>`, where `<SHELL>` is one of `bash`, `elvish`, `fig`, `fish`,
|
||||
`powershell`, or `zsh`.
|
||||
|
||||
The exact steps required to enable autocompletion will vary by shell. For example instructions,
|
||||
see the [Poetry](https://python-poetry.org/docs/#enable-tab-completion-for-bash-fish-or-zsh) or
|
||||
[ripgrep](https://github.com/BurntSushi/ripgrep/blob/master/FAQ.md#complete) documentation.
|
||||
|
||||
As an example: to enable autocompletion for Zsh, run
|
||||
`ruff generate-shell-completion zsh > ~/.zfunc/_ruff`. Then add the following line to your
|
||||
`~/.zshrc` file, if they're not already present:
|
||||
|
||||
```zsh
|
||||
fpath+=~/.zfunc
|
||||
autoload -Uz compinit && compinit
|
||||
```
|
||||
|
||||
<!-- End section: Configuration -->
|
||||
|
||||
## Supported Rules
|
||||
## Rules
|
||||
|
||||
<!-- Begin section: Rules -->
|
||||
|
||||
Ruff supports over 400 lint rules, many of which are inspired by popular tools like Flake8, isort,
|
||||
pyupgrade, and others. Regardless of the rule's origin, Ruff re-implements every rule in
|
||||
**Ruff supports over 400 lint rules**, many of which are inspired by popular tools like Flake8,
|
||||
isort, pyupgrade, and others. Regardless of the rule's origin, Ruff re-implements every rule in
|
||||
Rust as a first-party feature.
|
||||
|
||||
By default, Ruff enables Flake8's `E` and `F` rules. Ruff supports all rules from the `F` category,
|
||||
and a [subset](https://beta.ruff.rs/docs/rules/#error-e) of the `E` category, omitting those
|
||||
stylistic rules made obsolete by the use of an autoformatter, like [Black](https://github.com/psf/black).
|
||||
stylistic rules made obsolete by the use of an autoformatter, like
|
||||
[Black](https://github.com/psf/black).
|
||||
|
||||
<!-- End section: Rules -->
|
||||
|
||||
For a complete enumeration, see the [list of rules](https://beta.ruff.rs/docs/rules/) in the
|
||||
Ruff documentation.
|
||||
For a complete enumeration of the supported rules, see [_Rules_](https://beta.ruff.rs/docs/rules/).
|
||||
|
||||
## Contributing
|
||||
|
||||
Contributions are welcome and highly appreciated. To get started, check out the
|
||||
[**contributing guidelines**](https://github.com/charliermarsh/ruff/blob/main/CONTRIBUTING.md). You
|
||||
can also join us on [**Discord**](https://discord.gg/c9MhzV8aU5).
|
||||
[**contributing guidelines**](https://beta.ruff.rs/docs/contributing/).
|
||||
|
||||
You can also join us on [**Discord**](https://discord.gg/c9MhzV8aU5).
|
||||
|
||||
## Support
|
||||
|
||||
@@ -678,8 +243,6 @@ or feel free to [**open a new one**](https://github.com/charliermarsh/ruff/issue
|
||||
|
||||
You can also ask for help on [**Discord**](https://discord.gg/c9MhzV8aU5).
|
||||
|
||||
<!-- Begin section: Acknowledgements -->
|
||||
|
||||
## Acknowledgements
|
||||
|
||||
Ruff's linter draws on both the APIs and implementation details of many other
|
||||
@@ -702,8 +265,6 @@ Ruff is the beneficiary of a large number of [contributors](https://github.com/c
|
||||
|
||||
Ruff is released under the MIT license.
|
||||
|
||||
<!-- End section: Acknowledgements -->
|
||||
|
||||
## Who's Using Ruff?
|
||||
|
||||
Ruff is used in a number of major open-source projects, including:
|
||||
|
||||
10
crates/ruff/resources/test/fixtures/flake8_pie/PIE802.py
vendored
Normal file
10
crates/ruff/resources/test/fixtures/flake8_pie/PIE802.py
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
# no error
|
||||
all((x.id for x in bar))
|
||||
all(x.id for x in bar)
|
||||
all(x.id for x in bar)
|
||||
any(x.id for x in bar)
|
||||
any({x.id for x in bar})
|
||||
|
||||
# PIE 802
|
||||
any([x.id for x in bar])
|
||||
all([x.id for x in bar])
|
||||
0
crates/ruff/resources/test/fixtures/pep8_naming/N999/module/valid_name/file-with-dashes
vendored
Normal file
0
crates/ruff/resources/test/fixtures/pep8_naming/N999/module/valid_name/file-with-dashes
vendored
Normal file
@@ -16,21 +16,17 @@ if False == None: # E711, E712 (fix)
|
||||
if None == False: # E711, E712 (fix)
|
||||
pass
|
||||
|
||||
###
|
||||
# Unfixable errors
|
||||
###
|
||||
if "abc" == None: # E711
|
||||
pass
|
||||
if None == "abc": # E711
|
||||
pass
|
||||
if "abc" == False: # E712
|
||||
pass
|
||||
if False == "abc": # E712
|
||||
pass
|
||||
|
||||
###
|
||||
# Non-errors
|
||||
###
|
||||
if "abc" == None:
|
||||
pass
|
||||
if None == "abc":
|
||||
pass
|
||||
if "abc" == False:
|
||||
pass
|
||||
if False == "abc":
|
||||
pass
|
||||
if "def" == "abc":
|
||||
pass
|
||||
if False is None:
|
||||
|
||||
4
crates/ruff/resources/test/fixtures/pyflakes/F821_10.py
vendored
Normal file
4
crates/ruff/resources/test/fixtures/pyflakes/F821_10.py
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
try:
|
||||
pass
|
||||
except ExceptionGroup:
|
||||
pass
|
||||
@@ -2,36 +2,36 @@ from typing import TypedDict, NotRequired, Literal
|
||||
import typing
|
||||
|
||||
# dict literal
|
||||
MyType1 = TypedDict("MyType1", {"a": int, "b": str})
|
||||
MyType = TypedDict("MyType", {"a": int, "b": str})
|
||||
|
||||
# dict call
|
||||
MyType2 = TypedDict("MyType2", dict(a=int, b=str))
|
||||
MyType = TypedDict("MyType", dict(a=int, b=str))
|
||||
|
||||
# kwargs
|
||||
MyType3 = TypedDict("MyType3", a=int, b=str)
|
||||
MyType = TypedDict("MyType", a=int, b=str)
|
||||
|
||||
# Empty TypedDict
|
||||
MyType4 = TypedDict("MyType4")
|
||||
MyType = TypedDict("MyType")
|
||||
|
||||
# Literal values
|
||||
MyType5 = TypedDict("MyType5", {"a": "hello"})
|
||||
MyType6 = TypedDict("MyType6", a="hello")
|
||||
MyType = TypedDict("MyType", {"a": "hello"})
|
||||
MyType = TypedDict("MyType", a="hello")
|
||||
|
||||
# NotRequired
|
||||
MyType7 = TypedDict("MyType7", {"a": NotRequired[dict]})
|
||||
MyType = TypedDict("MyType", {"a": NotRequired[dict]})
|
||||
|
||||
# total
|
||||
MyType8 = TypedDict("MyType8", {"x": int, "y": int}, total=False)
|
||||
|
||||
# invalid identifiers
|
||||
MyType9 = TypedDict("MyType9", {"in": int, "x-y": int})
|
||||
MyType = TypedDict("MyType", {"x": int, "y": int}, total=False)
|
||||
|
||||
# using Literal type
|
||||
MyType10 = TypedDict("MyType10", {"key": Literal["value"]})
|
||||
MyType = TypedDict("MyType", {"key": Literal["value"]})
|
||||
|
||||
# using namespace TypedDict
|
||||
MyType11 = typing.TypedDict("MyType11", {"key": int})
|
||||
MyType = typing.TypedDict("MyType", {"key": int})
|
||||
|
||||
# unpacking
|
||||
# invalid identifiers (OK)
|
||||
MyType = TypedDict("MyType", {"in": int, "x-y": int})
|
||||
|
||||
# unpacking (OK)
|
||||
c = {"c": float}
|
||||
MyType12 = TypedDict("MyType1", {"a": int, "b": str, **c})
|
||||
MyType = TypedDict("MyType", {"a": int, "b": str, **c})
|
||||
|
||||
@@ -2,21 +2,24 @@ from typing import NamedTuple
|
||||
import typing
|
||||
|
||||
# with complex annotations
|
||||
NT1 = NamedTuple("NT1", [("a", int), ("b", tuple[str, ...])])
|
||||
MyType = NamedTuple("MyType", [("a", int), ("b", tuple[str, ...])])
|
||||
|
||||
# with default values as list
|
||||
NT2 = NamedTuple(
|
||||
"NT2",
|
||||
MyType = NamedTuple(
|
||||
"MyType",
|
||||
[("a", int), ("b", str), ("c", list[bool])],
|
||||
defaults=["foo", [True]],
|
||||
)
|
||||
|
||||
# with namespace
|
||||
NT3 = typing.NamedTuple("NT3", [("a", int), ("b", str)])
|
||||
MyType = typing.NamedTuple("MyType", [("a", int), ("b", str)])
|
||||
|
||||
# with too many default values
|
||||
NT4 = NamedTuple(
|
||||
"NT4",
|
||||
# too many default values (OK)
|
||||
MyType = NamedTuple(
|
||||
"MyType",
|
||||
[("a", int), ("b", str)],
|
||||
defaults=[1, "bar", "baz"],
|
||||
)
|
||||
|
||||
# invalid identifiers (OK)
|
||||
MyType = NamedTuple("MyType", [("x-y", int), ("b", tuple[str, ...])])
|
||||
|
||||
@@ -187,7 +187,7 @@ impl<'a> Checker<'a> {
|
||||
/// Return `true` if a patch should be generated under the given autofix
|
||||
/// `Mode`.
|
||||
pub fn patch(&self, code: &Rule) -> bool {
|
||||
matches!(self.autofix, flags::Autofix::Enabled) && self.settings.rules.should_fix(code)
|
||||
self.autofix.is_enabled() && self.settings.rules.should_fix(code)
|
||||
}
|
||||
|
||||
/// Return `true` if the `Expr` is a reference to `typing.${target}`.
|
||||
@@ -2490,6 +2490,13 @@ where
|
||||
if self.settings.rules.enabled(&Rule::UnnecessaryDictKwargs) {
|
||||
flake8_pie::rules::no_unnecessary_dict_kwargs(self, expr, keywords);
|
||||
}
|
||||
if self
|
||||
.settings
|
||||
.rules
|
||||
.enabled(&Rule::UnnecessaryComprehensionAnyAll)
|
||||
{
|
||||
flake8_pie::rules::unnecessary_comprehension_any_all(self, expr, func, args);
|
||||
}
|
||||
|
||||
// flake8-bandit
|
||||
if self.settings.rules.enabled(&Rule::ExecBuiltin) {
|
||||
|
||||
@@ -148,8 +148,7 @@ pub fn check_noqa(
|
||||
UnusedNOQA { codes: None },
|
||||
Range::new(Location::new(row + 1, start), Location::new(row + 1, end)),
|
||||
);
|
||||
if matches!(autofix, flags::Autofix::Enabled)
|
||||
&& settings.rules.should_fix(diagnostic.kind.rule())
|
||||
if autofix.is_enabled() && settings.rules.should_fix(diagnostic.kind.rule())
|
||||
{
|
||||
diagnostic.amend(Fix::deletion(
|
||||
Location::new(row + 1, start - spaces),
|
||||
@@ -217,8 +216,7 @@ pub fn check_noqa(
|
||||
},
|
||||
Range::new(Location::new(row + 1, start), Location::new(row + 1, end)),
|
||||
);
|
||||
if matches!(autofix, flags::Autofix::Enabled)
|
||||
&& settings.rules.should_fix(diagnostic.kind.rule())
|
||||
if autofix.is_enabled() && settings.rules.should_fix(diagnostic.kind.rule())
|
||||
{
|
||||
if valid_codes.is_empty() {
|
||||
diagnostic.amend(Fix::deletion(
|
||||
|
||||
@@ -42,10 +42,10 @@ pub fn check_physical_lines(
|
||||
let enforce_mixed_spaces_and_tabs = settings.rules.enabled(&Rule::MixedSpacesAndTabs);
|
||||
let enforce_bidirectional_unicode = settings.rules.enabled(&Rule::BidirectionalUnicode);
|
||||
|
||||
let fix_unnecessary_coding_comment = matches!(autofix, flags::Autofix::Enabled)
|
||||
&& settings.rules.should_fix(&Rule::UTF8EncodingDeclaration);
|
||||
let fix_shebang_whitespace = matches!(autofix, flags::Autofix::Enabled)
|
||||
&& settings.rules.should_fix(&Rule::ShebangWhitespace);
|
||||
let fix_unnecessary_coding_comment =
|
||||
autofix.is_enabled() && settings.rules.should_fix(&Rule::UTF8EncodingDeclaration);
|
||||
let fix_shebang_whitespace =
|
||||
autofix.is_enabled() && settings.rules.should_fix(&Rule::ShebangWhitespace);
|
||||
|
||||
let mut commented_lines_iter = commented_lines.iter().peekable();
|
||||
let mut doc_lines_iter = doc_lines.iter().peekable();
|
||||
@@ -145,8 +145,7 @@ pub fn check_physical_lines(
|
||||
if let Some(diagnostic) = no_newline_at_end_of_file(
|
||||
stylist,
|
||||
contents,
|
||||
matches!(autofix, flags::Autofix::Enabled)
|
||||
&& settings.rules.should_fix(&Rule::NoNewLineAtEndOfFile),
|
||||
autofix.is_enabled() && settings.rules.should_fix(&Rule::NoNewLineAtEndOfFile),
|
||||
) {
|
||||
diagnostics.push(diagnostic);
|
||||
}
|
||||
|
||||
@@ -108,8 +108,7 @@ pub fn check_tokens(
|
||||
locator,
|
||||
*start,
|
||||
*end,
|
||||
matches!(autofix, flags::Autofix::Enabled)
|
||||
&& settings.rules.should_fix(&Rule::InvalidEscapeSequence),
|
||||
autofix.is_enabled() && settings.rules.should_fix(&Rule::InvalidEscapeSequence),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -516,6 +516,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<Rule> {
|
||||
(Flake8Pie, "794") => Rule::DupeClassFieldDefinitions,
|
||||
(Flake8Pie, "796") => Rule::PreferUniqueEnums,
|
||||
(Flake8Pie, "800") => Rule::UnnecessarySpread,
|
||||
(Flake8Pie, "802") => Rule::UnnecessaryComprehensionAnyAll,
|
||||
(Flake8Pie, "804") => Rule::UnnecessaryDictKwargs,
|
||||
(Flake8Pie, "807") => Rule::PreferListBuiltin,
|
||||
(Flake8Pie, "810") => Rule::SingleStartsEndsWith,
|
||||
|
||||
@@ -491,6 +491,7 @@ ruff_macros::register_rules!(
|
||||
rules::flake8_pie::rules::UnnecessaryDictKwargs,
|
||||
rules::flake8_pie::rules::PreferListBuiltin,
|
||||
rules::flake8_pie::rules::SingleStartsEndsWith,
|
||||
rules::flake8_pie::rules::UnnecessaryComprehensionAnyAll,
|
||||
// flake8-commas
|
||||
rules::flake8_commas::rules::TrailingCommaMissing,
|
||||
rules::flake8_commas::rules::TrailingCommaOnBareTupleProhibited,
|
||||
|
||||
@@ -60,9 +60,7 @@ pub fn commented_out_code(
|
||||
// Verify that the comment is on its own line, and that it contains code.
|
||||
if is_standalone_comment(line) && comment_contains_code(line, &settings.task_tags[..]) {
|
||||
let mut diagnostic = Diagnostic::new(CommentedOutCode, Range::new(start, end));
|
||||
if matches!(autofix, flags::Autofix::Enabled)
|
||||
&& settings.rules.should_fix(&Rule::CommentedOutCode)
|
||||
{
|
||||
if autofix.is_enabled() && settings.rules.should_fix(&Rule::CommentedOutCode) {
|
||||
diagnostic.amend(Fix::deletion(location, end_location));
|
||||
}
|
||||
Some(diagnostic)
|
||||
|
||||
@@ -257,9 +257,7 @@ pub fn trailing_commas(
|
||||
end_location: comma.2,
|
||||
},
|
||||
);
|
||||
if matches!(autofix, flags::Autofix::Enabled)
|
||||
&& settings.rules.should_fix(&Rule::TrailingCommaProhibited)
|
||||
{
|
||||
if autofix.is_enabled() && settings.rules.should_fix(&Rule::TrailingCommaProhibited) {
|
||||
diagnostic.amend(Fix::deletion(comma.0, comma.2));
|
||||
}
|
||||
diagnostics.push(diagnostic);
|
||||
@@ -303,9 +301,7 @@ pub fn trailing_commas(
|
||||
end_location: missing_comma.2,
|
||||
},
|
||||
);
|
||||
if matches!(autofix, flags::Autofix::Enabled)
|
||||
&& settings.rules.should_fix(&Rule::TrailingCommaMissing)
|
||||
{
|
||||
if autofix.is_enabled() && settings.rules.should_fix(&Rule::TrailingCommaMissing) {
|
||||
diagnostic.amend(Fix::insertion(",".to_owned(), missing_comma.2));
|
||||
}
|
||||
diagnostics.push(diagnostic);
|
||||
|
||||
45
crates/ruff/src/rules/flake8_pie/fixes.rs
Normal file
45
crates/ruff/src/rules/flake8_pie/fixes.rs
Normal file
@@ -0,0 +1,45 @@
|
||||
use anyhow::{bail, Result};
|
||||
use libcst_native::{Codegen, CodegenState, Expression, GeneratorExp};
|
||||
|
||||
use crate::ast::types::Range;
|
||||
use crate::cst::matchers::{match_expr, match_module};
|
||||
use crate::fix::Fix;
|
||||
use crate::source_code::{Locator, Stylist};
|
||||
|
||||
/// (PIE802) Convert `[i for i in a]` into `i for i in a`
|
||||
pub fn fix_unnecessary_comprehension_any_all(
|
||||
locator: &Locator,
|
||||
stylist: &Stylist,
|
||||
expr: &rustpython_parser::ast::Expr,
|
||||
) -> Result<Fix> {
|
||||
// Expr(ListComp) -> Expr(GeneratorExp)
|
||||
let module_text = locator.slice(&Range::from_located(expr));
|
||||
let mut tree = match_module(module_text)?;
|
||||
let mut body = match_expr(&mut tree)?;
|
||||
|
||||
let Expression::ListComp(list_comp) = &body.value else {
|
||||
bail!(
|
||||
"Expected Expression::ListComp"
|
||||
);
|
||||
};
|
||||
|
||||
body.value = Expression::GeneratorExp(Box::new(GeneratorExp {
|
||||
elt: list_comp.elt.clone(),
|
||||
for_in: list_comp.for_in.clone(),
|
||||
lpar: list_comp.lpar.clone(),
|
||||
rpar: list_comp.rpar.clone(),
|
||||
}));
|
||||
|
||||
let mut state = CodegenState {
|
||||
default_newline: stylist.line_ending(),
|
||||
default_indent: stylist.indentation(),
|
||||
..CodegenState::default()
|
||||
};
|
||||
tree.codegen(&mut state);
|
||||
|
||||
Ok(Fix::replacement(
|
||||
state.to_string(),
|
||||
expr.location,
|
||||
expr.end_location.unwrap(),
|
||||
))
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
//! Rules from [flake8-pie](https://pypi.org/project/flake8-pie/).
|
||||
mod fixes;
|
||||
pub(crate) mod rules;
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -20,6 +21,7 @@ mod tests {
|
||||
#[test_case(Rule::UnnecessarySpread, Path::new("PIE800.py"); "PIE800")]
|
||||
#[test_case(Rule::PreferListBuiltin, Path::new("PIE807.py"); "PIE807")]
|
||||
#[test_case(Rule::PreferUniqueEnums, Path::new("PIE796.py"); "PIE796")]
|
||||
#[test_case(Rule::UnnecessaryComprehensionAnyAll, Path::new("PIE802.py"); "PIE802")]
|
||||
fn rules(rule_code: Rule, path: &Path) -> Result<()> {
|
||||
let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy());
|
||||
let diagnostics = test_path(
|
||||
|
||||
@@ -15,6 +15,8 @@ use crate::message::Location;
|
||||
use crate::registry::Diagnostic;
|
||||
use crate::violation::{AlwaysAutofixableViolation, Violation};
|
||||
|
||||
use super::fixes;
|
||||
|
||||
define_violation!(
|
||||
pub struct UnnecessaryPass;
|
||||
);
|
||||
@@ -58,6 +60,50 @@ impl Violation for PreferUniqueEnums {
|
||||
}
|
||||
}
|
||||
|
||||
define_violation!(
|
||||
/// ## What it does
|
||||
/// Checks for unnecessary list comprehensions passed to `any` and `all`.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// `any` and `all` take any iterators, including generators. Converting a generator to a list
|
||||
/// by way of a list comprehension is unnecessary and reduces performance due to the
|
||||
/// overhead of creating the list.
|
||||
///
|
||||
/// For example, compare the performance of `all` with a list comprehension against that
|
||||
/// of a generator (~40x faster here):
|
||||
///
|
||||
/// ```python
|
||||
/// In [1]: %timeit all([i for i in range(1000)])
|
||||
/// 8.14 µs ± 25.4 ns per loop (mean ± std. dev. of 7 runs, 100,000 loops each)
|
||||
///
|
||||
/// In [2]: %timeit all(i for i in range(1000))
|
||||
/// 212 ns ± 0.892 ns per loop (mean ± std. dev. of 7 runs, 1,000,000 loops each)
|
||||
/// ```
|
||||
///
|
||||
/// ## Examples
|
||||
/// ```python
|
||||
/// any([x.id for x in bar])
|
||||
/// all([x.id for x in bar])
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// any(x.id for x in bar)
|
||||
/// all(x.id for x in bar)
|
||||
/// ```
|
||||
pub struct UnnecessaryComprehensionAnyAll;
|
||||
);
|
||||
impl AlwaysAutofixableViolation for UnnecessaryComprehensionAnyAll {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
format!("Unnecessary list comprehension.")
|
||||
}
|
||||
|
||||
fn autofix_title(&self) -> String {
|
||||
"Remove unnecessary list comprehension".to_string()
|
||||
}
|
||||
}
|
||||
|
||||
define_violation!(
|
||||
pub struct UnnecessarySpread;
|
||||
);
|
||||
@@ -282,6 +328,39 @@ pub fn no_unnecessary_spread(checker: &mut Checker, keys: &[Option<Expr>], value
|
||||
}
|
||||
}
|
||||
|
||||
/// PIE802
|
||||
pub fn unnecessary_comprehension_any_all(
|
||||
checker: &mut Checker,
|
||||
expr: &Expr,
|
||||
func: &Expr,
|
||||
args: &[Expr],
|
||||
) {
|
||||
if let ExprKind::Name { id, .. } = &func.node {
|
||||
if (id == "all" || id == "any") && args.len() == 1 {
|
||||
if !checker.is_builtin(id) {
|
||||
return;
|
||||
}
|
||||
if let ExprKind::ListComp { .. } = args[0].node {
|
||||
let mut diagnostic =
|
||||
Diagnostic::new(UnnecessaryComprehensionAnyAll, Range::from_located(expr));
|
||||
if checker.patch(diagnostic.kind.rule()) {
|
||||
match fixes::fix_unnecessary_comprehension_any_all(
|
||||
checker.locator,
|
||||
checker.stylist,
|
||||
&args[0],
|
||||
) {
|
||||
Ok(fix) => {
|
||||
diagnostic.amend(fix);
|
||||
}
|
||||
Err(e) => error!("Failed to generate fix: {e}"),
|
||||
}
|
||||
}
|
||||
checker.diagnostics.push(diagnostic);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Return `true` if a key is a valid keyword argument name.
|
||||
fn is_valid_kwarg_name(key: &Expr) -> bool {
|
||||
if let ExprKind::Constant {
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
---
|
||||
source: crates/ruff/src/rules/flake8_pie/mod.rs
|
||||
expression: diagnostics
|
||||
---
|
||||
- kind:
|
||||
UnnecessaryComprehensionAnyAll: ~
|
||||
location:
|
||||
row: 9
|
||||
column: 0
|
||||
end_location:
|
||||
row: 9
|
||||
column: 24
|
||||
fix:
|
||||
content: x.id for x in bar
|
||||
location:
|
||||
row: 9
|
||||
column: 4
|
||||
end_location:
|
||||
row: 9
|
||||
column: 23
|
||||
parent: ~
|
||||
- kind:
|
||||
UnnecessaryComprehensionAnyAll: ~
|
||||
location:
|
||||
row: 10
|
||||
column: 0
|
||||
end_location:
|
||||
row: 10
|
||||
column: 24
|
||||
fix:
|
||||
content: x.id for x in bar
|
||||
location:
|
||||
row: 10
|
||||
column: 4
|
||||
end_location:
|
||||
row: 10
|
||||
column: 23
|
||||
parent: ~
|
||||
|
||||
@@ -280,9 +280,7 @@ fn docstring(
|
||||
},
|
||||
Range::new(start, end),
|
||||
);
|
||||
if matches!(autofix, flags::Autofix::Enabled)
|
||||
&& settings.rules.should_fix(&Rule::BadQuotesDocstring)
|
||||
{
|
||||
if autofix.is_enabled() && settings.rules.should_fix(&Rule::BadQuotesDocstring) {
|
||||
let quote_count = if trivia.is_multiline { 3 } else { 1 };
|
||||
let string_contents = &trivia.raw_text[quote_count..trivia.raw_text.len() - quote_count];
|
||||
let quote = good_docstring("es_settings.docstring_quotes).repeat(quote_count);
|
||||
@@ -357,9 +355,7 @@ fn strings(
|
||||
Range::new(*start, *end),
|
||||
);
|
||||
|
||||
if matches!(autofix, flags::Autofix::Enabled)
|
||||
&& settings.rules.should_fix(&Rule::BadQuotesMultilineString)
|
||||
{
|
||||
if autofix.is_enabled() && settings.rules.should_fix(&Rule::BadQuotesMultilineString) {
|
||||
let string_contents = &trivia.raw_text[3..trivia.raw_text.len() - 3];
|
||||
let quote = good_multiline("es_settings.multiline_quotes);
|
||||
let mut fixed_contents = String::with_capacity(
|
||||
@@ -389,7 +385,7 @@ fn strings(
|
||||
{
|
||||
let mut diagnostic =
|
||||
Diagnostic::new(AvoidableEscapedQuote, Range::new(*start, *end));
|
||||
if matches!(autofix, flags::Autofix::Enabled)
|
||||
if autofix.is_enabled()
|
||||
&& settings.rules.should_fix(&Rule::AvoidableEscapedQuote)
|
||||
{
|
||||
let quote = bad_single("es_settings.inline_quotes);
|
||||
@@ -450,9 +446,7 @@ fn strings(
|
||||
},
|
||||
Range::new(*start, *end),
|
||||
);
|
||||
if matches!(autofix, flags::Autofix::Enabled)
|
||||
&& settings.rules.should_fix(&Rule::BadQuotesInlineString)
|
||||
{
|
||||
if autofix.is_enabled() && settings.rules.should_fix(&Rule::BadQuotesInlineString) {
|
||||
let quote = good_single("es_settings.inline_quotes);
|
||||
let mut fixed_contents =
|
||||
String::with_capacity(trivia.prefix.len() + string_contents.len() + 2);
|
||||
|
||||
@@ -166,9 +166,7 @@ fn add_required_import(
|
||||
MissingRequiredImport(required_import.clone()),
|
||||
Range::new(Location::default(), Location::default()),
|
||||
);
|
||||
if matches!(autofix, flags::Autofix::Enabled)
|
||||
&& settings.rules.should_fix(&Rule::MissingRequiredImport)
|
||||
{
|
||||
if autofix.is_enabled() && settings.rules.should_fix(&Rule::MissingRequiredImport) {
|
||||
// Determine the location at which the import should be inserted.
|
||||
let splice = helpers::find_splice_location(python_ast, locator);
|
||||
|
||||
|
||||
@@ -153,9 +153,7 @@ pub fn organize_imports(
|
||||
None
|
||||
} else {
|
||||
let mut diagnostic = Diagnostic::new(UnsortedImports, range);
|
||||
if matches!(autofix, flags::Autofix::Enabled)
|
||||
&& settings.rules.should_fix(diagnostic.kind.rule())
|
||||
{
|
||||
if autofix.is_enabled() && settings.rules.should_fix(diagnostic.kind.rule()) {
|
||||
diagnostic.amend(Fix::replacement(
|
||||
indent(&expected, indentation),
|
||||
range.location,
|
||||
|
||||
@@ -43,6 +43,7 @@ mod tests {
|
||||
#[test_case(Rule::InvalidModuleName, Path::new("N999/module/valid_name/__main__.py"); "N999_10")]
|
||||
#[test_case(Rule::InvalidModuleName, Path::new("N999/module/valid_name/0001_initial.py"); "N999_11")]
|
||||
#[test_case(Rule::InvalidModuleName, Path::new("N999/module/valid_name/__setup__.py"); "N999_12")]
|
||||
#[test_case(Rule::InvalidModuleName, Path::new("N999/module/valid_name/file-with-dashes"); "N999_13")]
|
||||
fn rules(rule_code: Rule, path: &Path) -> Result<()> {
|
||||
let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy());
|
||||
let diagnostics = test_path(
|
||||
|
||||
@@ -43,6 +43,13 @@ impl Violation for InvalidModuleName {
|
||||
|
||||
/// N999
|
||||
pub fn invalid_module_name(path: &Path, package: Option<&Path>) -> Option<Diagnostic> {
|
||||
if !path
|
||||
.extension()
|
||||
.map_or(false, |ext| ext == "py" || ext == "pyi")
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
||||
if let Some(package) = package {
|
||||
let module_name = if path.file_name().map_or(false, |file_name| {
|
||||
file_name == "__init__.py"
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
source: crates/ruff/src/rules/pep8_naming/mod.rs
|
||||
expression: diagnostics
|
||||
---
|
||||
[]
|
||||
|
||||
@@ -105,9 +105,7 @@ pub fn compound_statements(
|
||||
Tok::Newline => {
|
||||
if let Some((start, end)) = semi {
|
||||
let mut diagnostic = Diagnostic::new(UselessSemicolon, Range::new(start, end));
|
||||
if matches!(autofix, flags::Autofix::Enabled)
|
||||
&& settings.rules.should_fix(&Rule::UselessSemicolon)
|
||||
{
|
||||
if autofix.is_enabled() && settings.rules.should_fix(&Rule::UselessSemicolon) {
|
||||
diagnostic.amend(Fix::deletion(start, end));
|
||||
};
|
||||
diagnostics.push(diagnostic);
|
||||
|
||||
@@ -102,68 +102,72 @@ pub fn literal_comparisons(
|
||||
// Check `left`.
|
||||
let mut comparator = left;
|
||||
let next = &comparators[0];
|
||||
if check_none_comparisons
|
||||
&& matches!(
|
||||
comparator.node,
|
||||
ExprKind::Constant {
|
||||
value: Constant::None,
|
||||
kind: None
|
||||
}
|
||||
)
|
||||
{
|
||||
if matches!(op, Cmpop::Eq) {
|
||||
let diagnostic =
|
||||
Diagnostic::new(NoneComparison(op.into()), Range::from_located(comparator));
|
||||
if checker.patch(diagnostic.kind.rule()) && !helpers::is_constant_non_singleton(next) {
|
||||
bad_ops.insert(0, Cmpop::Is);
|
||||
}
|
||||
diagnostics.push(diagnostic);
|
||||
}
|
||||
if matches!(op, Cmpop::NotEq) {
|
||||
let diagnostic =
|
||||
Diagnostic::new(NoneComparison(op.into()), Range::from_located(comparator));
|
||||
if checker.patch(diagnostic.kind.rule()) && !helpers::is_constant_non_singleton(next) {
|
||||
bad_ops.insert(0, Cmpop::IsNot);
|
||||
}
|
||||
diagnostics.push(diagnostic);
|
||||
}
|
||||
}
|
||||
|
||||
if check_true_false_comparisons {
|
||||
if let ExprKind::Constant {
|
||||
value: Constant::Bool(value),
|
||||
kind: None,
|
||||
} = comparator.node
|
||||
if !helpers::is_constant_non_singleton(next) {
|
||||
if check_none_comparisons
|
||||
&& matches!(
|
||||
comparator.node,
|
||||
ExprKind::Constant {
|
||||
value: Constant::None,
|
||||
kind: None
|
||||
}
|
||||
)
|
||||
{
|
||||
if matches!(op, Cmpop::Eq) {
|
||||
let diagnostic = Diagnostic::new(
|
||||
TrueFalseComparison(value, op.into()),
|
||||
Range::from_located(comparator),
|
||||
);
|
||||
if checker.patch(diagnostic.kind.rule())
|
||||
&& !helpers::is_constant_non_singleton(next)
|
||||
{
|
||||
let diagnostic =
|
||||
Diagnostic::new(NoneComparison(op.into()), Range::from_located(comparator));
|
||||
if checker.patch(diagnostic.kind.rule()) {
|
||||
bad_ops.insert(0, Cmpop::Is);
|
||||
}
|
||||
diagnostics.push(diagnostic);
|
||||
}
|
||||
if matches!(op, Cmpop::NotEq) {
|
||||
let diagnostic = Diagnostic::new(
|
||||
TrueFalseComparison(value, op.into()),
|
||||
Range::from_located(comparator),
|
||||
);
|
||||
if checker.patch(diagnostic.kind.rule())
|
||||
&& !helpers::is_constant_non_singleton(next)
|
||||
{
|
||||
let diagnostic =
|
||||
Diagnostic::new(NoneComparison(op.into()), Range::from_located(comparator));
|
||||
if checker.patch(diagnostic.kind.rule()) {
|
||||
bad_ops.insert(0, Cmpop::IsNot);
|
||||
}
|
||||
diagnostics.push(diagnostic);
|
||||
}
|
||||
}
|
||||
|
||||
if check_true_false_comparisons {
|
||||
if let ExprKind::Constant {
|
||||
value: Constant::Bool(value),
|
||||
kind: None,
|
||||
} = comparator.node
|
||||
{
|
||||
if matches!(op, Cmpop::Eq) {
|
||||
let diagnostic = Diagnostic::new(
|
||||
TrueFalseComparison(value, op.into()),
|
||||
Range::from_located(comparator),
|
||||
);
|
||||
if checker.patch(diagnostic.kind.rule()) {
|
||||
bad_ops.insert(0, Cmpop::Is);
|
||||
}
|
||||
diagnostics.push(diagnostic);
|
||||
}
|
||||
if matches!(op, Cmpop::NotEq) {
|
||||
let diagnostic = Diagnostic::new(
|
||||
TrueFalseComparison(value, op.into()),
|
||||
Range::from_located(comparator),
|
||||
);
|
||||
if checker.patch(diagnostic.kind.rule()) {
|
||||
bad_ops.insert(0, Cmpop::IsNot);
|
||||
}
|
||||
diagnostics.push(diagnostic);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check each comparator in order.
|
||||
for (idx, (op, next)) in izip!(ops, comparators).enumerate() {
|
||||
if helpers::is_constant_non_singleton(comparator) {
|
||||
comparator = next;
|
||||
continue;
|
||||
}
|
||||
|
||||
if check_none_comparisons
|
||||
&& matches!(
|
||||
next.node,
|
||||
@@ -176,9 +180,7 @@ pub fn literal_comparisons(
|
||||
if matches!(op, Cmpop::Eq) {
|
||||
let diagnostic =
|
||||
Diagnostic::new(NoneComparison(op.into()), Range::from_located(next));
|
||||
if checker.patch(diagnostic.kind.rule())
|
||||
&& !helpers::is_constant_non_singleton(comparator)
|
||||
{
|
||||
if checker.patch(diagnostic.kind.rule()) {
|
||||
bad_ops.insert(idx, Cmpop::Is);
|
||||
}
|
||||
diagnostics.push(diagnostic);
|
||||
@@ -186,9 +188,7 @@ pub fn literal_comparisons(
|
||||
if matches!(op, Cmpop::NotEq) {
|
||||
let diagnostic =
|
||||
Diagnostic::new(NoneComparison(op.into()), Range::from_located(next));
|
||||
if checker.patch(diagnostic.kind.rule())
|
||||
&& !helpers::is_constant_non_singleton(comparator)
|
||||
{
|
||||
if checker.patch(diagnostic.kind.rule()) {
|
||||
bad_ops.insert(idx, Cmpop::IsNot);
|
||||
}
|
||||
diagnostics.push(diagnostic);
|
||||
@@ -206,9 +206,7 @@ pub fn literal_comparisons(
|
||||
TrueFalseComparison(value, op.into()),
|
||||
Range::from_located(next),
|
||||
);
|
||||
if checker.patch(diagnostic.kind.rule())
|
||||
&& !helpers::is_constant_non_singleton(comparator)
|
||||
{
|
||||
if checker.patch(diagnostic.kind.rule()) {
|
||||
bad_ops.insert(idx, Cmpop::Is);
|
||||
}
|
||||
diagnostics.push(diagnostic);
|
||||
@@ -218,9 +216,7 @@ pub fn literal_comparisons(
|
||||
TrueFalseComparison(value, op.into()),
|
||||
Range::from_located(next),
|
||||
);
|
||||
if checker.patch(diagnostic.kind.rule())
|
||||
&& !helpers::is_constant_non_singleton(comparator)
|
||||
{
|
||||
if checker.patch(diagnostic.kind.rule()) {
|
||||
bad_ops.insert(idx, Cmpop::IsNot);
|
||||
}
|
||||
diagnostics.push(diagnostic);
|
||||
|
||||
@@ -164,48 +164,4 @@ expression: diagnostics
|
||||
row: 16
|
||||
column: 16
|
||||
parent: ~
|
||||
- kind:
|
||||
NoneComparison: Eq
|
||||
location:
|
||||
row: 22
|
||||
column: 12
|
||||
end_location:
|
||||
row: 22
|
||||
column: 16
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
NoneComparison: Eq
|
||||
location:
|
||||
row: 24
|
||||
column: 3
|
||||
end_location:
|
||||
row: 24
|
||||
column: 7
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
TrueFalseComparison:
|
||||
- false
|
||||
- Eq
|
||||
location:
|
||||
row: 26
|
||||
column: 12
|
||||
end_location:
|
||||
row: 26
|
||||
column: 17
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
TrueFalseComparison:
|
||||
- false
|
||||
- Eq
|
||||
location:
|
||||
row: 28
|
||||
column: 3
|
||||
end_location:
|
||||
row: 28
|
||||
column: 8
|
||||
fix: ~
|
||||
parent: ~
|
||||
|
||||
|
||||
@@ -102,6 +102,7 @@ mod tests {
|
||||
#[test_case(Rule::UndefinedName, Path::new("F821_7.py"); "F821_7")]
|
||||
#[test_case(Rule::UndefinedName, Path::new("F821_8.pyi"); "F821_8")]
|
||||
#[test_case(Rule::UndefinedName, Path::new("F821_9.py"); "F821_9")]
|
||||
#[test_case(Rule::UndefinedName, Path::new("F821_10.py"); "F821_10")]
|
||||
#[test_case(Rule::UndefinedExport, Path::new("F822_0.py"); "F822_0")]
|
||||
#[test_case(Rule::UndefinedExport, Path::new("F822_1.py"); "F822_1")]
|
||||
#[test_case(Rule::UndefinedExport, Path::new("F822_2.py"); "F822_2")]
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
source: crates/ruff/src/rules/pyflakes/mod.rs
|
||||
expression: diagnostics
|
||||
---
|
||||
[]
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
use anyhow::{bail, Result};
|
||||
use log::debug;
|
||||
use rustpython_parser::ast::{Constant, Expr, ExprContext, ExprKind, Keyword, Stmt, StmtKind};
|
||||
|
||||
use ruff_macros::{define_violation, derive_message_formats};
|
||||
use ruff_python::identifiers::is_identifier;
|
||||
use ruff_python::keyword::KWLIST;
|
||||
use rustpython_parser::ast::{Constant, Expr, ExprContext, ExprKind, Keyword, Stmt, StmtKind};
|
||||
|
||||
use crate::ast::helpers::{create_expr, create_stmt, unparse_stmt};
|
||||
use crate::ast::types::Range;
|
||||
@@ -11,23 +12,29 @@ use crate::checkers::ast::Checker;
|
||||
use crate::fix::Fix;
|
||||
use crate::registry::Diagnostic;
|
||||
use crate::source_code::Stylist;
|
||||
use crate::violation::AlwaysAutofixableViolation;
|
||||
use crate::violation::{Availability, Violation};
|
||||
use crate::AutofixKind;
|
||||
|
||||
define_violation!(
|
||||
pub struct ConvertNamedTupleFunctionalToClass {
|
||||
pub name: String,
|
||||
pub fixable: bool,
|
||||
}
|
||||
);
|
||||
impl AlwaysAutofixableViolation for ConvertNamedTupleFunctionalToClass {
|
||||
impl Violation for ConvertNamedTupleFunctionalToClass {
|
||||
const AUTOFIX: Option<AutofixKind> = Some(AutofixKind::new(Availability::Sometimes));
|
||||
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
let ConvertNamedTupleFunctionalToClass { name } = self;
|
||||
let ConvertNamedTupleFunctionalToClass { name, .. } = self;
|
||||
format!("Convert `{name}` from `NamedTuple` functional to class syntax")
|
||||
}
|
||||
|
||||
fn autofix_title(&self) -> String {
|
||||
let ConvertNamedTupleFunctionalToClass { name } = self;
|
||||
format!("Convert `{name}` to class syntax")
|
||||
fn autofix_title_formatter(&self) -> Option<fn(&Self) -> String> {
|
||||
self.fixable
|
||||
.then_some(|ConvertNamedTupleFunctionalToClass { name, .. }| {
|
||||
format!("Convert `{name}` to class syntax")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -172,28 +179,33 @@ pub fn convert_named_tuple_functional_to_class(
|
||||
{
|
||||
return;
|
||||
};
|
||||
|
||||
let properties = match match_defaults(keywords)
|
||||
.and_then(|defaults| create_properties_from_args(args, defaults))
|
||||
{
|
||||
Ok(properties) => properties,
|
||||
Err(err) => {
|
||||
debug!("Skipping `NamedTuple` \"{typename}\": {err}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
// TODO(charlie): Preserve indentation, to remove the first-column requirement.
|
||||
let fixable = stmt.location.column() == 0;
|
||||
let mut diagnostic = Diagnostic::new(
|
||||
ConvertNamedTupleFunctionalToClass {
|
||||
name: typename.to_string(),
|
||||
fixable,
|
||||
},
|
||||
Range::from_located(stmt),
|
||||
);
|
||||
// TODO(charlie): Preserve indentation, to remove the first-column requirement.
|
||||
if checker.patch(diagnostic.kind.rule()) && stmt.location.column() == 0 {
|
||||
match match_defaults(keywords)
|
||||
.and_then(|defaults| create_properties_from_args(args, defaults))
|
||||
{
|
||||
Ok(properties) => {
|
||||
diagnostic.amend(convert_to_class(
|
||||
stmt,
|
||||
typename,
|
||||
properties,
|
||||
base_class,
|
||||
checker.stylist,
|
||||
));
|
||||
}
|
||||
Err(err) => debug!("Skipping ineligible `NamedTuple` \"{typename}\": {err}"),
|
||||
};
|
||||
if fixable && checker.patch(diagnostic.kind.rule()) {
|
||||
diagnostic.amend(convert_to_class(
|
||||
stmt,
|
||||
typename,
|
||||
properties,
|
||||
base_class,
|
||||
checker.stylist,
|
||||
));
|
||||
}
|
||||
checker.diagnostics.push(diagnostic);
|
||||
}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
use anyhow::{bail, Result};
|
||||
use log::debug;
|
||||
use rustpython_parser::ast::{Constant, Expr, ExprContext, ExprKind, Keyword, Stmt, StmtKind};
|
||||
|
||||
use ruff_macros::{define_violation, derive_message_formats};
|
||||
use ruff_python::identifiers::is_identifier;
|
||||
use ruff_python::keyword::KWLIST;
|
||||
use rustpython_parser::ast::{Constant, Expr, ExprContext, ExprKind, Keyword, Stmt, StmtKind};
|
||||
|
||||
use crate::ast::helpers::{create_expr, create_stmt, unparse_stmt};
|
||||
use crate::ast::types::Range;
|
||||
@@ -11,23 +12,29 @@ use crate::checkers::ast::Checker;
|
||||
use crate::fix::Fix;
|
||||
use crate::registry::Diagnostic;
|
||||
use crate::source_code::Stylist;
|
||||
use crate::violation::AlwaysAutofixableViolation;
|
||||
use crate::violation::{Availability, Violation};
|
||||
use crate::AutofixKind;
|
||||
|
||||
define_violation!(
|
||||
pub struct ConvertTypedDictFunctionalToClass {
|
||||
pub name: String,
|
||||
pub fixable: bool,
|
||||
}
|
||||
);
|
||||
impl AlwaysAutofixableViolation for ConvertTypedDictFunctionalToClass {
|
||||
impl Violation for ConvertTypedDictFunctionalToClass {
|
||||
const AUTOFIX: Option<AutofixKind> = Some(AutofixKind::new(Availability::Sometimes));
|
||||
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
let ConvertTypedDictFunctionalToClass { name } = self;
|
||||
let ConvertTypedDictFunctionalToClass { name, .. } = self;
|
||||
format!("Convert `{name}` from `TypedDict` functional to class syntax")
|
||||
}
|
||||
|
||||
fn autofix_title(&self) -> String {
|
||||
let ConvertTypedDictFunctionalToClass { name } = self;
|
||||
format!("Convert `{name}` to class syntax")
|
||||
fn autofix_title_formatter(&self) -> Option<fn(&Self) -> String> {
|
||||
self.fixable
|
||||
.then_some(|ConvertTypedDictFunctionalToClass { name, .. }| {
|
||||
format!("Convert `{name}` to class syntax")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -219,27 +226,31 @@ pub fn convert_typed_dict_functional_to_class(
|
||||
return;
|
||||
};
|
||||
|
||||
let (body, total_keyword) = match match_properties_and_total(args, keywords) {
|
||||
Ok((body, total_keyword)) => (body, total_keyword),
|
||||
Err(err) => {
|
||||
debug!("Skipping ineligible `TypedDict` \"{class_name}\": {err}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
// TODO(charlie): Preserve indentation, to remove the first-column requirement.
|
||||
let fixable = stmt.location.column() == 0;
|
||||
let mut diagnostic = Diagnostic::new(
|
||||
ConvertTypedDictFunctionalToClass {
|
||||
name: class_name.to_string(),
|
||||
fixable,
|
||||
},
|
||||
Range::from_located(stmt),
|
||||
);
|
||||
// TODO(charlie): Preserve indentation, to remove the first-column requirement.
|
||||
if checker.patch(diagnostic.kind.rule()) && stmt.location.column() == 0 {
|
||||
match match_properties_and_total(args, keywords) {
|
||||
Ok((body, total_keyword)) => {
|
||||
diagnostic.amend(convert_to_class(
|
||||
stmt,
|
||||
class_name,
|
||||
body,
|
||||
total_keyword,
|
||||
base_class,
|
||||
checker.stylist,
|
||||
));
|
||||
}
|
||||
Err(err) => debug!("Skipping ineligible `TypedDict` \"{class_name}\": {err}"),
|
||||
};
|
||||
if fixable && checker.patch(diagnostic.kind.rule()) {
|
||||
diagnostic.amend(convert_to_class(
|
||||
stmt,
|
||||
class_name,
|
||||
body,
|
||||
total_keyword,
|
||||
base_class,
|
||||
checker.stylist,
|
||||
));
|
||||
}
|
||||
checker.diagnostics.push(diagnostic);
|
||||
}
|
||||
|
||||
@@ -137,9 +137,7 @@ pub fn extraneous_parentheses(
|
||||
};
|
||||
let mut diagnostic =
|
||||
Diagnostic::new(ExtraneousParentheses, Range::new(*start, *end));
|
||||
if matches!(autofix, flags::Autofix::Enabled)
|
||||
&& settings.rules.should_fix(&Rule::ExtraneousParentheses)
|
||||
{
|
||||
if autofix.is_enabled() && settings.rules.should_fix(&Rule::ExtraneousParentheses) {
|
||||
let contents = locator.slice(&Range::new(*start, *end));
|
||||
diagnostic.amend(Fix::replacement(
|
||||
contents[1..contents.len() - 1].to_string(),
|
||||
|
||||
@@ -4,204 +4,192 @@ expression: diagnostics
|
||||
---
|
||||
- kind:
|
||||
ConvertTypedDictFunctionalToClass:
|
||||
name: MyType1
|
||||
name: MyType
|
||||
fixable: true
|
||||
location:
|
||||
row: 5
|
||||
column: 0
|
||||
end_location:
|
||||
row: 5
|
||||
column: 52
|
||||
fix:
|
||||
content: "class MyType1(TypedDict):\n a: int\n b: str"
|
||||
location:
|
||||
row: 5
|
||||
column: 0
|
||||
end_location:
|
||||
row: 5
|
||||
column: 52
|
||||
parent: ~
|
||||
- kind:
|
||||
ConvertTypedDictFunctionalToClass:
|
||||
name: MyType2
|
||||
location:
|
||||
row: 8
|
||||
column: 0
|
||||
end_location:
|
||||
row: 8
|
||||
column: 50
|
||||
fix:
|
||||
content: "class MyType2(TypedDict):\n a: int\n b: str"
|
||||
content: "class MyType(TypedDict):\n a: int\n b: str"
|
||||
location:
|
||||
row: 8
|
||||
row: 5
|
||||
column: 0
|
||||
end_location:
|
||||
row: 8
|
||||
row: 5
|
||||
column: 50
|
||||
parent: ~
|
||||
- kind:
|
||||
ConvertTypedDictFunctionalToClass:
|
||||
name: MyType3
|
||||
name: MyType
|
||||
fixable: true
|
||||
location:
|
||||
row: 8
|
||||
column: 0
|
||||
end_location:
|
||||
row: 8
|
||||
column: 48
|
||||
fix:
|
||||
content: "class MyType(TypedDict):\n a: int\n b: str"
|
||||
location:
|
||||
row: 8
|
||||
column: 0
|
||||
end_location:
|
||||
row: 8
|
||||
column: 48
|
||||
parent: ~
|
||||
- kind:
|
||||
ConvertTypedDictFunctionalToClass:
|
||||
name: MyType
|
||||
fixable: true
|
||||
location:
|
||||
row: 11
|
||||
column: 0
|
||||
end_location:
|
||||
row: 11
|
||||
column: 44
|
||||
column: 42
|
||||
fix:
|
||||
content: "class MyType3(TypedDict):\n a: int\n b: str"
|
||||
content: "class MyType(TypedDict):\n a: int\n b: str"
|
||||
location:
|
||||
row: 11
|
||||
column: 0
|
||||
end_location:
|
||||
row: 11
|
||||
column: 42
|
||||
parent: ~
|
||||
- kind:
|
||||
ConvertTypedDictFunctionalToClass:
|
||||
name: MyType
|
||||
fixable: true
|
||||
location:
|
||||
row: 14
|
||||
column: 0
|
||||
end_location:
|
||||
row: 14
|
||||
column: 28
|
||||
fix:
|
||||
content: "class MyType(TypedDict):\n pass"
|
||||
location:
|
||||
row: 14
|
||||
column: 0
|
||||
end_location:
|
||||
row: 14
|
||||
column: 28
|
||||
parent: ~
|
||||
- kind:
|
||||
ConvertTypedDictFunctionalToClass:
|
||||
name: MyType
|
||||
fixable: true
|
||||
location:
|
||||
row: 17
|
||||
column: 0
|
||||
end_location:
|
||||
row: 17
|
||||
column: 44
|
||||
fix:
|
||||
content: "class MyType(TypedDict):\n a: \"hello\""
|
||||
location:
|
||||
row: 17
|
||||
column: 0
|
||||
end_location:
|
||||
row: 17
|
||||
column: 44
|
||||
parent: ~
|
||||
- kind:
|
||||
ConvertTypedDictFunctionalToClass:
|
||||
name: MyType4
|
||||
location:
|
||||
row: 14
|
||||
column: 0
|
||||
end_location:
|
||||
row: 14
|
||||
column: 30
|
||||
fix:
|
||||
content: "class MyType4(TypedDict):\n pass"
|
||||
location:
|
||||
row: 14
|
||||
column: 0
|
||||
end_location:
|
||||
row: 14
|
||||
column: 30
|
||||
parent: ~
|
||||
- kind:
|
||||
ConvertTypedDictFunctionalToClass:
|
||||
name: MyType5
|
||||
location:
|
||||
row: 17
|
||||
column: 0
|
||||
end_location:
|
||||
row: 17
|
||||
column: 46
|
||||
fix:
|
||||
content: "class MyType5(TypedDict):\n a: \"hello\""
|
||||
location:
|
||||
row: 17
|
||||
column: 0
|
||||
end_location:
|
||||
row: 17
|
||||
column: 46
|
||||
parent: ~
|
||||
- kind:
|
||||
ConvertTypedDictFunctionalToClass:
|
||||
name: MyType6
|
||||
name: MyType
|
||||
fixable: true
|
||||
location:
|
||||
row: 18
|
||||
column: 0
|
||||
end_location:
|
||||
row: 18
|
||||
column: 41
|
||||
column: 39
|
||||
fix:
|
||||
content: "class MyType6(TypedDict):\n a: \"hello\""
|
||||
content: "class MyType(TypedDict):\n a: \"hello\""
|
||||
location:
|
||||
row: 18
|
||||
column: 0
|
||||
end_location:
|
||||
row: 18
|
||||
column: 41
|
||||
column: 39
|
||||
parent: ~
|
||||
- kind:
|
||||
ConvertTypedDictFunctionalToClass:
|
||||
name: MyType7
|
||||
name: MyType
|
||||
fixable: true
|
||||
location:
|
||||
row: 21
|
||||
column: 0
|
||||
end_location:
|
||||
row: 21
|
||||
column: 56
|
||||
column: 54
|
||||
fix:
|
||||
content: "class MyType7(TypedDict):\n a: NotRequired[dict]"
|
||||
content: "class MyType(TypedDict):\n a: NotRequired[dict]"
|
||||
location:
|
||||
row: 21
|
||||
column: 0
|
||||
end_location:
|
||||
row: 21
|
||||
column: 56
|
||||
column: 54
|
||||
parent: ~
|
||||
- kind:
|
||||
ConvertTypedDictFunctionalToClass:
|
||||
name: MyType8
|
||||
name: MyType
|
||||
fixable: true
|
||||
location:
|
||||
row: 24
|
||||
column: 0
|
||||
end_location:
|
||||
row: 24
|
||||
column: 65
|
||||
column: 63
|
||||
fix:
|
||||
content: "class MyType8(TypedDict, total=False):\n x: int\n y: int"
|
||||
content: "class MyType(TypedDict, total=False):\n x: int\n y: int"
|
||||
location:
|
||||
row: 24
|
||||
column: 0
|
||||
end_location:
|
||||
row: 24
|
||||
column: 65
|
||||
column: 63
|
||||
parent: ~
|
||||
- kind:
|
||||
ConvertTypedDictFunctionalToClass:
|
||||
name: MyType9
|
||||
name: MyType
|
||||
fixable: true
|
||||
location:
|
||||
row: 27
|
||||
column: 0
|
||||
end_location:
|
||||
row: 27
|
||||
column: 55
|
||||
fix: ~
|
||||
fix:
|
||||
content: "class MyType(TypedDict):\n key: Literal[\"value\"]"
|
||||
location:
|
||||
row: 27
|
||||
column: 0
|
||||
end_location:
|
||||
row: 27
|
||||
column: 55
|
||||
parent: ~
|
||||
- kind:
|
||||
ConvertTypedDictFunctionalToClass:
|
||||
name: MyType10
|
||||
name: MyType
|
||||
fixable: true
|
||||
location:
|
||||
row: 30
|
||||
column: 0
|
||||
end_location:
|
||||
row: 30
|
||||
column: 59
|
||||
column: 49
|
||||
fix:
|
||||
content: "class MyType10(TypedDict):\n key: Literal[\"value\"]"
|
||||
content: "class MyType(typing.TypedDict):\n key: int"
|
||||
location:
|
||||
row: 30
|
||||
column: 0
|
||||
end_location:
|
||||
row: 30
|
||||
column: 59
|
||||
parent: ~
|
||||
- kind:
|
||||
ConvertTypedDictFunctionalToClass:
|
||||
name: MyType11
|
||||
location:
|
||||
row: 33
|
||||
column: 0
|
||||
end_location:
|
||||
row: 33
|
||||
column: 53
|
||||
fix:
|
||||
content: "class MyType11(typing.TypedDict):\n key: int"
|
||||
location:
|
||||
row: 33
|
||||
column: 0
|
||||
end_location:
|
||||
row: 33
|
||||
column: 53
|
||||
parent: ~
|
||||
- kind:
|
||||
ConvertTypedDictFunctionalToClass:
|
||||
name: MyType12
|
||||
location:
|
||||
row: 37
|
||||
column: 0
|
||||
end_location:
|
||||
row: 37
|
||||
column: 58
|
||||
fix: ~
|
||||
column: 49
|
||||
parent: ~
|
||||
|
||||
|
||||
@@ -4,25 +4,27 @@ expression: diagnostics
|
||||
---
|
||||
- kind:
|
||||
ConvertNamedTupleFunctionalToClass:
|
||||
name: NT1
|
||||
name: MyType
|
||||
fixable: true
|
||||
location:
|
||||
row: 5
|
||||
column: 0
|
||||
end_location:
|
||||
row: 5
|
||||
column: 61
|
||||
column: 67
|
||||
fix:
|
||||
content: "class NT1(NamedTuple):\n a: int\n b: tuple[str, ...]"
|
||||
content: "class MyType(NamedTuple):\n a: int\n b: tuple[str, ...]"
|
||||
location:
|
||||
row: 5
|
||||
column: 0
|
||||
end_location:
|
||||
row: 5
|
||||
column: 61
|
||||
column: 67
|
||||
parent: ~
|
||||
- kind:
|
||||
ConvertNamedTupleFunctionalToClass:
|
||||
name: NT2
|
||||
name: MyType
|
||||
fixable: true
|
||||
location:
|
||||
row: 8
|
||||
column: 0
|
||||
@@ -30,7 +32,7 @@ expression: diagnostics
|
||||
row: 12
|
||||
column: 1
|
||||
fix:
|
||||
content: "class NT2(NamedTuple):\n a: int\n b: str = \"foo\"\n c: list[bool] = [True]"
|
||||
content: "class MyType(NamedTuple):\n a: int\n b: str = \"foo\"\n c: list[bool] = [True]"
|
||||
location:
|
||||
row: 8
|
||||
column: 0
|
||||
@@ -40,31 +42,21 @@ expression: diagnostics
|
||||
parent: ~
|
||||
- kind:
|
||||
ConvertNamedTupleFunctionalToClass:
|
||||
name: NT3
|
||||
name: MyType
|
||||
fixable: true
|
||||
location:
|
||||
row: 15
|
||||
column: 0
|
||||
end_location:
|
||||
row: 15
|
||||
column: 56
|
||||
column: 62
|
||||
fix:
|
||||
content: "class NT3(typing.NamedTuple):\n a: int\n b: str"
|
||||
content: "class MyType(typing.NamedTuple):\n a: int\n b: str"
|
||||
location:
|
||||
row: 15
|
||||
column: 0
|
||||
end_location:
|
||||
row: 15
|
||||
column: 56
|
||||
parent: ~
|
||||
- kind:
|
||||
ConvertNamedTupleFunctionalToClass:
|
||||
name: NT4
|
||||
location:
|
||||
row: 18
|
||||
column: 0
|
||||
end_location:
|
||||
row: 22
|
||||
column: 1
|
||||
fix: ~
|
||||
column: 62
|
||||
parent: ~
|
||||
|
||||
|
||||
@@ -1730,8 +1730,7 @@ pub fn ambiguous_unicode_character(
|
||||
Range::new(location, end_location),
|
||||
);
|
||||
if settings.rules.enabled(diagnostic.kind.rule()) {
|
||||
if matches!(autofix, flags::Autofix::Enabled)
|
||||
&& settings.rules.should_fix(diagnostic.kind.rule())
|
||||
if autofix.is_enabled() && settings.rules.should_fix(diagnostic.kind.rule())
|
||||
{
|
||||
diagnostic.amend(Fix::replacement(
|
||||
representant.to_string(),
|
||||
|
||||
@@ -6,6 +6,12 @@ pub enum Autofix {
|
||||
Disabled,
|
||||
}
|
||||
|
||||
impl Autofix {
|
||||
pub const fn is_enabled(self) -> bool {
|
||||
matches!(self, Self::Enabled)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<bool> for Autofix {
|
||||
fn from(value: bool) -> Self {
|
||||
if value {
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
//! Generate CLI help.
|
||||
#![allow(clippy::print_stdout, clippy::print_stderr)]
|
||||
|
||||
use std::str;
|
||||
use std::fs::OpenOptions;
|
||||
use std::io::Write;
|
||||
use std::path::PathBuf;
|
||||
use std::{fs, str};
|
||||
|
||||
use anyhow::Result;
|
||||
|
||||
use crate::utils::replace_readme_section;
|
||||
use crate::ROOT_DIR;
|
||||
|
||||
const COMMAND_HELP_BEGIN_PRAGMA: &str = "<!-- Begin auto-generated command help. -->\n";
|
||||
const COMMAND_HELP_END_PRAGMA: &str = "<!-- End auto-generated command help. -->";
|
||||
@@ -15,7 +18,7 @@ const SUBCOMMAND_HELP_END_PRAGMA: &str = "<!-- End auto-generated subcommand hel
|
||||
|
||||
#[derive(clap::Args)]
|
||||
pub struct Args {
|
||||
/// Write the generated help to stdout (rather than to `README.md`).
|
||||
/// Write the generated help to stdout (rather than to `docs/configuration.md`).
|
||||
#[arg(long)]
|
||||
pub(crate) dry_run: bool,
|
||||
}
|
||||
@@ -24,6 +27,32 @@ fn trim_lines(s: &str) -> String {
|
||||
s.lines().map(str::trim_end).collect::<Vec<_>>().join("\n")
|
||||
}
|
||||
|
||||
fn replace_docs_section(content: &str, begin_pragma: &str, end_pragma: &str) -> Result<()> {
|
||||
// Read the existing file.
|
||||
let file = PathBuf::from(ROOT_DIR).join("docs/configuration.md");
|
||||
let existing = fs::read_to_string(&file)?;
|
||||
|
||||
// Extract the prefix.
|
||||
let index = existing
|
||||
.find(begin_pragma)
|
||||
.expect("Unable to find begin pragma");
|
||||
let prefix = &existing[..index + begin_pragma.len()];
|
||||
|
||||
// Extract the suffix.
|
||||
let index = existing
|
||||
.find(end_pragma)
|
||||
.expect("Unable to find end pragma");
|
||||
let suffix = &existing[index..];
|
||||
|
||||
// Write the prefix, new contents, and suffix.
|
||||
let mut f = OpenOptions::new().write(true).truncate(true).open(&file)?;
|
||||
writeln!(f, "{prefix}")?;
|
||||
write!(f, "{content}")?;
|
||||
write!(f, "{suffix}")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn main(args: &Args) -> Result<()> {
|
||||
// Generate `ruff help`.
|
||||
let command_help = trim_lines(ruff_cli::command_help().trim());
|
||||
@@ -35,12 +64,12 @@ pub fn main(args: &Args) -> Result<()> {
|
||||
print!("{command_help}");
|
||||
print!("{subcommand_help}");
|
||||
} else {
|
||||
replace_readme_section(
|
||||
replace_docs_section(
|
||||
&format!("```text\n{command_help}\n```\n\n"),
|
||||
COMMAND_HELP_BEGIN_PRAGMA,
|
||||
COMMAND_HELP_END_PRAGMA,
|
||||
)?;
|
||||
replace_readme_section(
|
||||
replace_docs_section(
|
||||
&format!("```text\n{subcommand_help}\n```\n\n"),
|
||||
SUBCOMMAND_HELP_BEGIN_PRAGMA,
|
||||
SUBCOMMAND_HELP_END_PRAGMA,
|
||||
|
||||
@@ -15,7 +15,6 @@ mod print_ast;
|
||||
mod print_cst;
|
||||
mod print_tokens;
|
||||
mod round_trip;
|
||||
mod utils;
|
||||
|
||||
const ROOT_DIR: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/../../");
|
||||
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
use std::fs;
|
||||
use std::fs::OpenOptions;
|
||||
use std::io::Write;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use anyhow::Result;
|
||||
|
||||
use crate::ROOT_DIR;
|
||||
|
||||
pub fn replace_readme_section(content: &str, begin_pragma: &str, end_pragma: &str) -> Result<()> {
|
||||
// Read the existing file.
|
||||
let file = PathBuf::from(ROOT_DIR).join("README.md");
|
||||
let existing = fs::read_to_string(&file)?;
|
||||
|
||||
// Extract the prefix.
|
||||
let index = existing
|
||||
.find(begin_pragma)
|
||||
.expect("Unable to find begin pragma");
|
||||
let prefix = &existing[..index + begin_pragma.len()];
|
||||
|
||||
// Extract the suffix.
|
||||
let index = existing
|
||||
.find(end_pragma)
|
||||
.expect("Unable to find end pragma");
|
||||
let suffix = &existing[index..];
|
||||
|
||||
// Write the prefix, new contents, and suffix.
|
||||
let mut f = OpenOptions::new().write(true).truncate(true).open(&file)?;
|
||||
writeln!(f, "{prefix}")?;
|
||||
write!(f, "{content}")?;
|
||||
write!(f, "{suffix}")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -3,6 +3,7 @@ pub const BUILTINS: &[&str] = &[
|
||||
"AssertionError",
|
||||
"AttributeError",
|
||||
"BaseException",
|
||||
"BaseExceptionGroup",
|
||||
"BlockingIOError",
|
||||
"BrokenPipeError",
|
||||
"BufferError",
|
||||
@@ -15,8 +16,10 @@ pub const BUILTINS: &[&str] = &[
|
||||
"DeprecationWarning",
|
||||
"EOFError",
|
||||
"Ellipsis",
|
||||
"EncodingWarning",
|
||||
"EnvironmentError",
|
||||
"Exception",
|
||||
"ExceptionGroup",
|
||||
"False",
|
||||
"FileExistsError",
|
||||
"FileNotFoundError",
|
||||
|
||||
@@ -19,4 +19,4 @@ if (10).real:
|
||||
...
|
||||
|
||||
y = 100[no]
|
||||
y = 100(no)
|
||||
y = 100(no)
|
||||
|
||||
@@ -1 +1 @@
|
||||
print("hello, world")
|
||||
print("hello, world")
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
for ((x in {}) or {})["a"] in x:
|
||||
pass
|
||||
pem_spam = lambda l, spam={"x": 3}: not spam.get(l.strip())
|
||||
lambda x=lambda y={1: 3}: y["x" : lambda y: {1: 2}]: x
|
||||
lambda x=lambda y={1: 3}: y["x" : lambda y: {1: 2}]: x
|
||||
|
||||
@@ -27,4 +27,4 @@ def class_under_the_func_with_blank_parentheses():
|
||||
class NormalClass:
|
||||
def func_for_testing(self, first, second):
|
||||
sum = first + second
|
||||
return sum
|
||||
return sum
|
||||
|
||||
@@ -162,4 +162,4 @@ class ClassWithDecoInitAndVarsAndDocstringWithInner2:
|
||||
|
||||
@deco
|
||||
def __init__(self):
|
||||
pass
|
||||
pass
|
||||
|
||||
@@ -96,4 +96,4 @@ if True:
|
||||
WaiterConfig={
|
||||
"Delay": 5,
|
||||
},
|
||||
)
|
||||
)
|
||||
|
||||
@@ -3,4 +3,4 @@ def bob(): # pylint: disable=W9016
|
||||
|
||||
|
||||
def bobtwo(): # some comment here
|
||||
pass
|
||||
pass
|
||||
|
||||
@@ -170,4 +170,4 @@ class Test:
|
||||
instruction() # comment with bad spacing
|
||||
|
||||
# END COMMENTS
|
||||
# MORE END COMMENTS
|
||||
# MORE END COMMENTS
|
||||
|
||||
@@ -45,4 +45,4 @@ def func():
|
||||
)
|
||||
|
||||
|
||||
# %%
|
||||
# %%
|
||||
|
||||
@@ -91,4 +91,4 @@ def foo3(list_a, list_b):
|
||||
db.or_(User.field_a.astext.in_(list_a), User.field_b.astext.in_(list_b))
|
||||
)
|
||||
.filter(User.xyz.is_(None))
|
||||
)
|
||||
)
|
||||
|
||||
@@ -70,4 +70,4 @@ def g():
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
main()
|
||||
|
||||
@@ -115,4 +115,4 @@ call_to_some_function_asdf(
|
||||
[AAAAAAAAAAAAAAAAAAAAAAA, AAAAAAAAAAAAAAAAAAAAAAA, AAAAAAAAAAAAAAAAAAAAAAA, BBBBBBBBBBBB], # type: ignore
|
||||
)
|
||||
|
||||
aaaaaaaaaaaaa, bbbbbbbbb = map(list, map(itertools.chain.from_iterable, zip(*items))) # type: ignore[arg-type]
|
||||
aaaaaaaaaaaaa, bbbbbbbbb = map(list, map(itertools.chain.from_iterable, zip(*items))) # type: ignore[arg-type]
|
||||
|
||||
@@ -3,4 +3,4 @@
|
||||
# to the latter.
|
||||
|
||||
# %%
|
||||
# %%
|
||||
# %%
|
||||
|
||||
@@ -158,4 +158,4 @@ def foo():
|
||||
@decorator1
|
||||
# A standalone comment
|
||||
def bar():
|
||||
pass
|
||||
pass
|
||||
|
||||
@@ -20,4 +20,4 @@ def function(a: int = 42):
|
||||
"""
|
||||
# There's a NBSP + 3 spaces before
|
||||
# And 4 spaces on the next line
|
||||
pass
|
||||
pass
|
||||
|
||||
@@ -178,4 +178,4 @@ class C:
|
||||
key8: value8,
|
||||
key9: value9,
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
@@ -178,4 +178,4 @@ class C:
|
||||
key8: value8,
|
||||
key9: value9,
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
@@ -216,4 +216,4 @@ def stable_quote_normalization_with_immediate_inner_single_quote(self):
|
||||
"""'<text here>
|
||||
|
||||
<text here, since without another non-empty line black is stable>
|
||||
"""
|
||||
"""
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Make sure when the file ends with class's docstring,
|
||||
# It doesn't add extra blank lines.
|
||||
class ClassWithDocstring:
|
||||
"""A docstring."""
|
||||
"""A docstring."""
|
||||
|
||||
@@ -45,4 +45,4 @@ def single_quote_docstring_over_line_limit():
|
||||
|
||||
|
||||
def single_quote_docstring_over_line_limit2():
|
||||
"We do not want to put the closing quote on a new line as that is invalid (see GH-3141)."
|
||||
"We do not want to put the closing quote on a new line as that is invalid (see GH-3141)."
|
||||
|
||||
@@ -86,4 +86,4 @@ def g():
|
||||
syms.arglist,
|
||||
syms.argument,
|
||||
}:
|
||||
return NO
|
||||
return NO
|
||||
|
||||
@@ -114,7 +114,7 @@ call(
|
||||
arg,
|
||||
another,
|
||||
kwarg="hey",
|
||||
**kwargs
|
||||
**kwargs,
|
||||
) # note: no trailing comma pre-3.6
|
||||
call(*gidgets[:2])
|
||||
call(a, *gidgets[:2])
|
||||
@@ -367,4 +367,4 @@ bbbb >> bbbb * bbbb
|
||||
^ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||
)
|
||||
last_call()
|
||||
# standalone comment at ENDMARKER
|
||||
# standalone comment at ENDMARKER
|
||||
|
||||
@@ -221,4 +221,4 @@ yield 'hello'
|
||||
# No formatting to the end of the file
|
||||
l=[1,2,3]
|
||||
d={'a':1,
|
||||
'b':2}
|
||||
'b':2}
|
||||
|
||||
@@ -37,4 +37,4 @@ def test_calculate_fades():
|
||||
(None, 4, 0, 0, 10, 0, 0, 6, 10),
|
||||
]
|
||||
|
||||
# fmt: on
|
||||
# fmt: on
|
||||
|
||||
@@ -12,4 +12,4 @@ x = [
|
||||
]
|
||||
# fmt: on
|
||||
|
||||
x = [1, 2, 3, 4]
|
||||
x = [1, 2, 3, 4]
|
||||
|
||||
@@ -17,4 +17,4 @@ def f():
|
||||
]
|
||||
)
|
||||
def f():
|
||||
pass
|
||||
pass
|
||||
|
||||
@@ -84,4 +84,4 @@ if x:
|
||||
# fmt: off
|
||||
elif unformatted:
|
||||
# fmt: on
|
||||
will_be_formatted()
|
||||
will_be_formatted()
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
a, b = 1, 2
|
||||
c = 6 # fmt: skip
|
||||
d = 5
|
||||
d = 5
|
||||
|
||||
@@ -8,4 +8,4 @@ l3 = [
|
||||
"I have",
|
||||
"trailing comma",
|
||||
"so I should be braked",
|
||||
]
|
||||
]
|
||||
|
||||
@@ -7,4 +7,4 @@ e = 5
|
||||
f = [
|
||||
"This is a very long line that should be formatted into a clearer line ",
|
||||
"by rearranging.",
|
||||
]
|
||||
]
|
||||
|
||||
@@ -4,4 +4,4 @@ l = [
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
]
|
||||
]
|
||||
|
||||
@@ -6,4 +6,4 @@ if (
|
||||
):
|
||||
print("I'm good!")
|
||||
else:
|
||||
print("I'm bad")
|
||||
print("I'm bad")
|
||||
|
||||
@@ -2,4 +2,4 @@ class A:
|
||||
def f(self):
|
||||
for line in range(10):
|
||||
if True:
|
||||
pass # fmt: skip
|
||||
pass # fmt: skip
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
a = "this is some code"
|
||||
b = 5 # fmt:skip
|
||||
c = 9 # fmt: skip
|
||||
d = "thisisasuperlongstringthisisasuperlongstringthisisasuperlongstringthisisasuperlongstring" # fmt:skip
|
||||
d = "thisisasuperlongstringthisisasuperlongstringthisisasuperlongstringthisisasuperlongstring" # fmt:skip
|
||||
|
||||
@@ -59,4 +59,4 @@ with give_me_context( unformatted, args ): # fmt: skip
|
||||
|
||||
async def test_async_with():
|
||||
async with give_me_async_context( unformatted, args ): # fmt: skip
|
||||
print("Do something")
|
||||
print("Do something")
|
||||
|
||||
@@ -6,4 +6,4 @@ f'some f-string with {a} {few(""):.2f} {formatted.values!r}'
|
||||
f"{f'''{'nested'} inner'''} outer"
|
||||
f"\"{f'{nested} inner'}\" outer"
|
||||
f"space between opening braces: { {a for a in (1, 2, 3)}}"
|
||||
f'Hello \'{tricky + "example"}\''
|
||||
f'Hello \'{tricky + "example"}\''
|
||||
|
||||
@@ -145,4 +145,4 @@ def f(
|
||||
|
||||
|
||||
def __await__():
|
||||
return (yield)
|
||||
return (yield)
|
||||
|
||||
@@ -5,8 +5,7 @@ def f(
|
||||
with cache_dir():
|
||||
if something:
|
||||
result = CliRunner().invoke(
|
||||
black.main,
|
||||
[str(src1), str(src2), "--diff", "--check"],
|
||||
black.main, [str(src1), str(src2), "--diff", "--check"]
|
||||
)
|
||||
limited.append(-limited.pop()) # negate top
|
||||
return A(
|
||||
@@ -63,4 +62,4 @@ else:
|
||||
|
||||
|
||||
with hmm_but_this_should_get_two_preceding_newlines():
|
||||
pass
|
||||
pass
|
||||
|
||||
@@ -111,4 +111,4 @@ some_module.some_function(
|
||||
argument4,
|
||||
argument5,
|
||||
argument6,
|
||||
)
|
||||
)
|
||||
|
||||
@@ -61,4 +61,4 @@ __all__ = (
|
||||
+ queues.__all__
|
||||
+ streams.__all__
|
||||
+ tasks.__all__
|
||||
)
|
||||
)
|
||||
|
||||
@@ -19,4 +19,4 @@ small_list = [
|
||||
]
|
||||
list_of_types = [
|
||||
tuple[int,],
|
||||
]
|
||||
]
|
||||
|
||||
@@ -60,4 +60,4 @@ if hasattr(view, "sum_of_weights"):
|
||||
|
||||
return np.divide(
|
||||
where=view.sum_of_weights_of_weight_long**2 > view.sum_of_weights_squared, # type: ignore
|
||||
)
|
||||
)
|
||||
|
||||
@@ -18,4 +18,4 @@
|
||||
# exactly line length limit + 1, it won't be split like that.
|
||||
xxxxxxxxx_yyy_zzzzzzzz[
|
||||
xx.xxxxxx(x_yyy_zzzzzz.xxxxx[0]), x_yyy_zzzzzz.xxxxxx(xxxx=1)
|
||||
] = 1
|
||||
] = 1
|
||||
|
||||
@@ -90,4 +90,4 @@ async def main():
|
||||
|
||||
|
||||
async def main():
|
||||
await (yield)
|
||||
await (yield)
|
||||
|
||||
@@ -39,4 +39,4 @@ except (
|
||||
some.really.really.really.looooooooooooooooooooooooooooooooong.module.over89.chars.Error,
|
||||
some.really.really.really.looooooooooooooooooooooooooooooooong.module.over89.chars.Error,
|
||||
) as err:
|
||||
raise err
|
||||
raise err
|
||||
|
||||
@@ -24,4 +24,4 @@ for (
|
||||
|
||||
# Test deeply nested brackets
|
||||
for k, v in d.items():
|
||||
print(k, v)
|
||||
print(k, v)
|
||||
|
||||
@@ -75,4 +75,4 @@ with open("/path/to/file.txt", mode="w") as file:
|
||||
|
||||
with open("/path/to/file.txt", mode="r") as read_file:
|
||||
with open("/path/to/output_file.txt", mode="w") as write_file:
|
||||
write_file.writelines(read_file.readlines())
|
||||
write_file.writelines(read_file.readlines())
|
||||
|
||||
@@ -82,4 +82,4 @@ def example7():
|
||||
|
||||
|
||||
def example8():
|
||||
return None
|
||||
return None
|
||||
|
||||
@@ -117,4 +117,4 @@ def foo() -> (
|
||||
int,
|
||||
]
|
||||
):
|
||||
return 2
|
||||
return 2
|
||||
|
||||
@@ -22,4 +22,4 @@ func1(arg1).func2(arg2).func3(arg3).func4(arg4).func5(arg5)
|
||||
|
||||
(a, b, c, d) = func1(arg1) and func2(arg2)
|
||||
|
||||
func(argument1, (one, two), argument4, argument5, argument6)
|
||||
func(argument1, (one, two), argument4, argument5, argument6)
|
||||
|
||||
@@ -28,4 +28,4 @@ ham[1:9], ham[1:9:3], ham[:9:3], ham[1::3], ham[1:9:]
|
||||
ham[lower:upper], ham[lower:upper:], ham[lower::step]
|
||||
# ham[lower+offset : upper+offset]
|
||||
ham[: upper_fn(x) : step_fn(x)], ham[:: step_fn(x)]
|
||||
ham[lower + offset : upper + offset]
|
||||
ham[lower + offset : upper + offset]
|
||||
|
||||
@@ -17,4 +17,4 @@ def docstring_singleline():
|
||||
def docstring_multiline():
|
||||
R"""
|
||||
clear out all of the issues opened in that time :p
|
||||
"""
|
||||
"""
|
||||
|
||||
@@ -55,4 +55,4 @@ def test(self, othr):
|
||||
assert a_function(
|
||||
very_long_arguments_that_surpass_the_limit,
|
||||
which_is_eighty_eight_in_this_case_plus_a_bit_more,
|
||||
) == {"x": "this need to pass the line limit as well", "b": "but only by a little bit"}
|
||||
) == {"x": "this need to pass the line limit as well", "b": "but only by a little bit"}
|
||||
|
||||
@@ -31,4 +31,4 @@ class A:
|
||||
4,
|
||||
3,
|
||||
) < self.connection.mysql_version < (10, 5, 2):
|
||||
pass
|
||||
pass
|
||||
|
||||
@@ -3,4 +3,4 @@ if e123456.get_tk_patchlevel() >= (8, 6, 0, "final") or (
|
||||
5,
|
||||
8,
|
||||
) <= get_tk_patchlevel() < (8, 6):
|
||||
pass
|
||||
pass
|
||||
|
||||
@@ -5,4 +5,4 @@ if True:
|
||||
"qweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweas "
|
||||
+ "qweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqwegqweasdzxcqweasdzxc.",
|
||||
"qweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqwe",
|
||||
) % {"reported_username": reported_username, "report_reason": report_reason}
|
||||
) % {"reported_username": reported_username, "report_reason": report_reason}
|
||||
|
||||
@@ -47,4 +47,4 @@ assert xxxxxxxxx.xxxxxxxxx.xxxxxxxxx(
|
||||
xxxxxxxxx
|
||||
).xxxxxxxxxxxxxxxxxx(), (
|
||||
"xxx {xxxxxxxxx} xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
||||
)
|
||||
)
|
||||
|
||||
@@ -6,4 +6,4 @@ x󠄀 = 4
|
||||
Q̇_per_meter = 4
|
||||
|
||||
A᧚ = 3
|
||||
A፩ = 8
|
||||
A፩ = 8
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user