Compare commits

...

17 Commits

Author SHA1 Message Date
Charlie Marsh
9551a045ec Add BaseExceptionGroup, EncodingWarning 2023-02-23 07:31:39 -05:00
Jonathan Plasse
b6445de34d Fix ExceptionGroup F821 false positive 2023-02-23 09:58:36 +01:00
Jeong YunWon
4357f2be0f Add Autofix::is_enabled() to remove repeative patterns (#3159) 2023-02-22 23:52:07 -05:00
Charlie Marsh
e5c1f95545 Check-in updated snapshot (#3161) 2023-02-23 03:42:27 +00:00
Charlie Marsh
227ff62a4e Don't touch tuple brackets after in (#3160) 2023-02-23 03:10:24 +00:00
Charlie Marsh
d8e4902516 Un-modify tupleassign and function2 tests (#3158)
I manually changed these in #3080 and #3083 to get the tests passing (with notes around the deviations) -- but that's no longer necessary, now that we have proper testing that takes deviations into account.
2023-02-23 02:37:25 +00:00
Matthew Lloyd
e66739884f Add note about prioritizing naming convention over preservation (#3157) 2023-02-23 02:32:46 +00:00
Charlie Marsh
5fd827545b Add a trailing newline to all .py.expect files (#3156)
This just re-formats all the `.py.expect` files with Black, both to add a trailing newline and be doubly-certain that they're correctly formatted.

I also ensured that we add a hard line break after each statement, and that we avoid including an extra newline in the generated Markdown (since the code should contain the exact expected newlines).
2023-02-23 02:29:27 +00:00
Matthew Lloyd
c1ddcb8a60 [flake8-pie] Unnecessary list comprehension, with autofix (PIE802) (#3149) 2023-02-22 20:58:45 -05:00
Charlie Marsh
48a317d5f6 Change via to using (#3155) 2023-02-23 01:47:15 +00:00
Charlie Marsh
74e18b6cff Split up some docs sections (#3154) 2023-02-22 20:18:10 -05:00
Charlie Marsh
21d02cd51f Omit non-.py[i] files from module naming rules (#3153) 2023-02-23 00:38:46 +00:00
Charlie Marsh
049e77b939 Follow-up with some small doc changes (#3152) 2023-02-23 00:35:22 +00:00
Charlie Marsh
b9bfb81e36 Move configuration out of README and into permanent docs (#3150) 2023-02-22 19:25:53 -05:00
Charlie Marsh
2d4fae45d9 Avoid flagging unfixable TypedDict and NamedTuple definitions (#3148) 2023-02-22 23:23:25 +00:00
Charlie Marsh
726adb7efc Avoid suggesting 'is' for constant literals (#3146) 2023-02-22 22:37:22 +00:00
Charlie Marsh
dbdfdeb0e1 Add pre-commit note to docs (#3145) 2023-02-22 17:22:47 -05:00
155 changed files with 1352 additions and 2163 deletions

View File

@@ -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
View File

@@ -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
```
[![Packaging status](https://repology.org/badge/vertical-allrepos/ruff-python-linter.svg?exclude_unsupported=1)](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:

View 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])

View 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:

View File

@@ -0,0 +1,4 @@
try:
pass
except ExceptionGroup:
pass

View File

@@ -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})

View File

@@ -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, ...])])

View File

@@ -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) {

View File

@@ -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(

View File

@@ -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);
}

View File

@@ -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),
));
}
}

View File

@@ -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,

View File

@@ -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,

View File

@@ -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)

View File

@@ -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);

View 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(),
))
}

View File

@@ -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(

View File

@@ -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 {

View File

@@ -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: ~

View File

@@ -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(&quotes_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(&quotes_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(&quotes_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(&quotes_settings.inline_quotes);
let mut fixed_contents =
String::with_capacity(trivia.prefix.len() + string_contents.len() + 2);

View File

@@ -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);

View File

@@ -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,

View File

@@ -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(

View File

@@ -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"

View File

@@ -0,0 +1,6 @@
---
source: crates/ruff/src/rules/pep8_naming/mod.rs
expression: diagnostics
---
[]

View File

@@ -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);

View File

@@ -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);

View File

@@ -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: ~

View File

@@ -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")]

View File

@@ -0,0 +1,6 @@
---
source: crates/ruff/src/rules/pyflakes/mod.rs
expression: diagnostics
---
[]

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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(),

View File

@@ -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: ~

View File

@@ -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: ~

View File

@@ -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(),

View File

@@ -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 {

View File

@@ -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,

View File

@@ -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"), "/../../");

View File

@@ -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(())
}

View File

@@ -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",

View File

@@ -19,4 +19,4 @@ if (10).real:
...
y = 100[no]
y = 100(no)
y = 100(no)

View File

@@ -1 +1 @@
print("hello, world")
print("hello, world")

View File

@@ -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

View File

@@ -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

View File

@@ -162,4 +162,4 @@ class ClassWithDecoInitAndVarsAndDocstringWithInner2:
@deco
def __init__(self):
pass
pass

View File

@@ -96,4 +96,4 @@ if True:
WaiterConfig={
"Delay": 5,
},
)
)

View File

@@ -3,4 +3,4 @@ def bob(): # pylint: disable=W9016
def bobtwo(): # some comment here
pass
pass

View File

@@ -170,4 +170,4 @@ class Test:
instruction() # comment with bad spacing
# END COMMENTS
# MORE END COMMENTS
# MORE END COMMENTS

View File

@@ -45,4 +45,4 @@ def func():
)
# %%
# %%

View File

@@ -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))
)
)

View File

@@ -70,4 +70,4 @@ def g():
if __name__ == "__main__":
main()
main()

View File

@@ -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]

View File

@@ -3,4 +3,4 @@
# to the latter.
# %%
# %%
# %%

View File

@@ -158,4 +158,4 @@ def foo():
@decorator1
# A standalone comment
def bar():
pass
pass

View File

@@ -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

View File

@@ -178,4 +178,4 @@ class C:
key8: value8,
key9: value9,
}
)
)

View File

@@ -178,4 +178,4 @@ class C:
key8: value8,
key9: value9,
}
)
)

View File

@@ -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>
"""
"""

View File

@@ -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."""

View File

@@ -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)."

View File

@@ -86,4 +86,4 @@ def g():
syms.arglist,
syms.argument,
}:
return NO
return NO

View File

@@ -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

View File

@@ -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}

View File

@@ -37,4 +37,4 @@ def test_calculate_fades():
(None, 4, 0, 0, 10, 0, 0, 6, 10),
]
# fmt: on
# fmt: on

View File

@@ -12,4 +12,4 @@ x = [
]
# fmt: on
x = [1, 2, 3, 4]
x = [1, 2, 3, 4]

View File

@@ -17,4 +17,4 @@ def f():
]
)
def f():
pass
pass

View File

@@ -84,4 +84,4 @@ if x:
# fmt: off
elif unformatted:
# fmt: on
will_be_formatted()
will_be_formatted()

View File

@@ -1,3 +1,3 @@
a, b = 1, 2
c = 6 # fmt: skip
d = 5
d = 5

View File

@@ -8,4 +8,4 @@ l3 = [
"I have",
"trailing comma",
"so I should be braked",
]
]

View File

@@ -7,4 +7,4 @@ e = 5
f = [
"This is a very long line that should be formatted into a clearer line ",
"by rearranging.",
]
]

View File

@@ -6,4 +6,4 @@ if (
):
print("I'm good!")
else:
print("I'm bad")
print("I'm bad")

View File

@@ -2,4 +2,4 @@ class A:
def f(self):
for line in range(10):
if True:
pass # fmt: skip
pass # fmt: skip

View File

@@ -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

View File

@@ -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")

View File

@@ -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"}\''

View File

@@ -145,4 +145,4 @@ def f(
def __await__():
return (yield)
return (yield)

View File

@@ -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

View File

@@ -111,4 +111,4 @@ some_module.some_function(
argument4,
argument5,
argument6,
)
)

View File

@@ -61,4 +61,4 @@ __all__ = (
+ queues.__all__
+ streams.__all__
+ tasks.__all__
)
)

View File

@@ -19,4 +19,4 @@ small_list = [
]
list_of_types = [
tuple[int,],
]
]

View File

@@ -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
)
)

View File

@@ -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

View File

@@ -90,4 +90,4 @@ async def main():
async def main():
await (yield)
await (yield)

View File

@@ -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

View File

@@ -24,4 +24,4 @@ for (
# Test deeply nested brackets
for k, v in d.items():
print(k, v)
print(k, v)

View File

@@ -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())

View File

@@ -82,4 +82,4 @@ def example7():
def example8():
return None
return None

View File

@@ -117,4 +117,4 @@ def foo() -> (
int,
]
):
return 2
return 2

View File

@@ -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)

View File

@@ -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]

View File

@@ -17,4 +17,4 @@ def docstring_singleline():
def docstring_multiline():
R"""
clear out all of the issues opened in that time :p
"""
"""

View File

@@ -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"}

View File

@@ -31,4 +31,4 @@ class A:
4,
3,
) < self.connection.mysql_version < (10, 5, 2):
pass
pass

View File

@@ -3,4 +3,4 @@ if e123456.get_tk_patchlevel() >= (8, 6, 0, "final") or (
5,
8,
) <= get_tk_patchlevel() < (8, 6):
pass
pass

View File

@@ -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}

View File

@@ -47,4 +47,4 @@ assert xxxxxxxxx.xxxxxxxxx.xxxxxxxxx(
xxxxxxxxx
).xxxxxxxxxxxxxxxxxx(), (
"xxx {xxxxxxxxx} xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
)
)

View File

@@ -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