Compare commits

...

74 Commits

Author SHA1 Message Date
Dhruv Manilawala
c0fc84fd78 Refactor boolean expression parsing 2024-04-21 11:53:15 +05:30
Dhruv Manilawala
060141b1de Make associativity a property of precedence 2024-04-20 23:55:20 +05:30
Dhruv Manilawala
b04cb9f92a Add ExpressionContext for expression parsing
This commit adds a new `ExpressionContext` struct which is used in
expression parsing.

This solves the following problem:
1. Allowing starred expression with different precedence
2. Allowing yield expression in certain context
3. Remove ambiguity with `in` keyword when parsing a `for ... in`
   statement
2024-04-20 15:25:13 +05:30
Carl Meyer
c80b9a4a90 Reduce size of Stmt from 144 to 120 bytes (#11051)
## Summary

I happened to notice that we box `TypeParams` on `StmtClassDef` but not
on `StmtFunctionDef` and wondered why, since `StmtFunctionDef` is bigger
and sets the size of `Stmt`.

@charliermarsh found that at the time we started boxing type params on
classes, classes were the largest statement type (see #6275), but that's
no longer true.

So boxing type-params also on functions reduces the overall size of
`Stmt`.

## Test Plan

The `<=` size tests are a bit irritating (since their failure doesn't
tell you the actual size), but I manually confirmed that the size is
actually 120 now.
2024-04-19 17:02:17 -06:00
Charlie Marsh
99f7f94538 Improve documentation around custom isort sections (#11050)
## Summary

Closes https://github.com/astral-sh/ruff/issues/11047.
2024-04-19 22:26:55 +00:00
James Frost
7b3c92a979 [flake8-bugbear] Document explicitly disabling strict zip (B905) (#11040)
Occasionally you intentionally have iterables of differing lengths. The
rule permits this by explicitly adding `strict=False`, but this was not
documented.

## Summary

The rule does not currently document how to avoid it when having
differing length iterables is intentional. This PR adds that to the rule
documentation.
2024-04-19 13:50:18 +00:00
Alex Waygood
fdbcb62adc scripts/fuzz-parser: work around race condition from running cargo build concurrently (#11039) 2024-04-19 14:42:28 +01:00
Dhruv Manilawala
0ff25a540c Bump version to 0.4.1 (#11035)
Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
2024-04-19 17:42:02 +05:30
Alex Waygood
34873ec009 Add a script to fuzz the parser (courtesy of pysource-codegen) (#11015) 2024-04-19 12:40:36 +01:00
Dhruv Manilawala
d3cd61f804 Use empty range when there's "gap" in token source (#11032)
## Summary

This fixes a bug where the parser would panic when there is a "gap" in
the token source.

What's a gap?

The reason it's `<=` instead of just `==` is because there could be
whitespaces between
the two tokens. For example:

```python
#     last token end
#     | current token (newline) start
#     v v
def foo \n
#      ^
#      assume there's trailing whitespace here
```

Or, there could tokens that are considered "trivia" and thus aren't
emitted by the token
source. These are comments and non-logical newlines. For example:

```python
#     last token end
#     v
def foo # comment\n
#                ^ current token (newline) start
```

In either of the above cases, there's a "gap" between the end of the
last token and start
of the current token.

## Test Plan

Add test cases and update the snapshots.
2024-04-19 11:36:26 +00:00
Alex Waygood
9b80cc09ee Select fewer ruff rules when linting Python files in scripts/ (#11034) 2024-04-19 12:33:36 +01:00
Dhruv Manilawala
9bb23b0a38 Expect indented case block instead of match stmt (#11033)
## Summary

This PR adds a new `Clause::Case` and uses it to parse the body of a
`case` block. Earlier, it was using `Match` which would give an
incorrect error message like:

```
  |
1 | match subject:
2 |     case 1:
3 |     case 2: ...
  |     ^^^^ Syntax Error: Expected an indented block after `match` statement
  |
```

## Test Plan

Add test case and update the snapshot.
2024-04-19 16:46:15 +05:30
Charlie Marsh
06c248a126 [ruff] Ignore stub functions in unused-async (RUF029) (#11026)
## Summary

We should ignore methods that appear to be stubs, e.g.:

```python
async def foo() -> int: ...
```

Closes https://github.com/astral-sh/ruff/issues/11018.
2024-04-19 00:03:52 -04:00
Tibor Reiss
27902b7130 [pylint] Implement invalid-index-returned (PLE0305) (#10962)
Add pylint rule invalid-index-returned (PLE0305)

See https://github.com/astral-sh/ruff/issues/970 for rules

Test Plan: `cargo test`
2024-04-19 03:44:05 +00:00
Henry Asa
97acf1d59b ENH: Bump ruff dependency versions to support the latest release of v0.4.0 and Python 3.12 (#11025)
## Summary

With the release of
[`v0.4.0`](https://github.com/astral-sh/ruff/releases/tag/v0.4.0) of
`ruff`, I noticed that some of `ruff`'s dependencies were not updated to
their latest versions. The
[`ruff-pre-commit`](https://github.com/astral-sh/ruff-pre-commit)
package released
[`v0.4.0`](https://github.com/astral-sh/ruff-pre-commit/releases/tag/v0.4.0)
at the same time `ruff` was updated, but `ruff` still referenced
`v0.3.7` of the package, not the newly updated version. I updated the
`ruff-pre-commit` reference to be `v0.4.0`.

In a similar light, I noticed that the version of the
[`dill`](https://github.com/uqfoundation/dill) package being used was
not the latest version. I bumped `dill` from version `0.3.7` to `0.3.8`,
which now [fully supports Python
3.12](https://github.com/uqfoundation/dill/releases/tag/0.3.8).

## Related Issues

Resolves #11024
2024-04-19 03:37:54 +00:00
Tibor Reiss
adf63d9013 [pylint] Implement invalid-hash-returned (PLE0309) (#10961)
Add pylint rule invalid-hash-returned (PLE0309)

See https://github.com/astral-sh/ruff/issues/970 for rules

Test Plan: `cargo test`

TBD: from the description: "Strictly speaking `bool` is a subclass of
`int`, thus returning `True`/`False` is valid. To be consistent with
other rules (e.g.
[PLE0305](https://github.com/astral-sh/ruff/pull/10962)
invalid-index-returned), ruff will raise, compared to pylint which will
not raise."
2024-04-19 03:33:52 +00:00
MithicSpirit
5d3c9f2637 ruff server: fix Neovim setup guide command (#11021) 2024-04-19 08:24:09 +05:30
Charlie Marsh
33529c049e Allow NoReturn-like functions for __str__, __len__, etc. (#11017)
## Summary

If the method always raises, we shouldn't raise a diagnostic for
"returning a value of the wrong type".

Closes https://github.com/astral-sh/ruff/issues/11016.
2024-04-18 22:55:15 +00:00
Zanie Blue
e751b4ea82 Bump version to 0.4.0 (#11011)
Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
Co-authored-by: Dhruv Manilawala <dhruvmanila@gmail.com>
2024-04-18 19:10:28 +00:00
Dhruv Manilawala
25a9131109 Add myself as codeowner for the parser (#11013) 2024-04-18 16:37:52 +00:00
Dhruv Manilawala
b7066e64e7 Consider binary expr for parenthesized with items parsing (#11012)
## Summary

This PR fixes the bug in with items parsing where it would fail to
recognize that the parenthesized expression is part of a large binary
expression.

## Test Plan

Add test cases and verified the snapshots.
2024-04-18 21:39:30 +05:30
Dhruv Manilawala
6c4d779140 Consider if expression for parenthesized with items parsing (#11010)
## Summary

This PR fixes the bug in parenthesized with items parsing where the `if`
expression would result into a syntax error.

The reason being that once we identify that the ambiguous left
parenthesis belongs to the context expression, the parser converts the
parsed with item into an equivalent expression. Then, the parser
continuous to parse any postfix expressions. Now, attribute, subscript,
and call are taken into account as they're grouped in
`parse_postfix_expression` but `if` expression has it's own parsing
function.

Use `parse_if_expression` once all postfix expressions have been parsed.
Ideally, I think that `if` could be included in postfix expression
parsing as they can be chained as well (`x if True else y if True else
z`).

## Test Plan

Add test cases and verified the snapshots.
2024-04-18 14:30:15 +00:00
Dhruv Manilawala
8020d486f6 Reset FOR_TARGET context for all kinds of parentheses (#11009)
## Summary

This PR fixes a bug in the new parser which involves the parser context
w.r.t. for statement. This is specifically around the `in` keyword which
can be present in the target expression and shouldn't be considered to
be part of the `for` statement header. Ideally it should use a context
which is passed between functions, thus using a call stack to set /
unset a specific variant which will be done in a follow-up PR as it
requires some amount of refactor.

## Test Plan

Add test cases and update the snapshots.
2024-04-18 19:37:50 +05:30
Dhruv Manilawala
13ffb5bc19 Replace LALRPOP parser with hand-written parser (#10036)
(Supersedes #9152, authored by @LaBatata101)

## Summary

This PR replaces the current parser generated from LALRPOP to a
hand-written recursive descent parser.

It also updates the grammar for [PEP
646](https://peps.python.org/pep-0646/) so that the parser outputs the
correct AST. For example, in `data[*x]`, the index expression is now a
tuple with a single starred expression instead of just a starred
expression.

Beyond the performance improvements, the parser is also error resilient
and can provide better error messages. The behavior as seen by any
downstream tools isn't changed. That is, the linter and formatter can
still assume that the parser will _stop_ at the first syntax error. This
will be updated in the following months.

For more details about the change here, refer to the PR corresponding to
the individual commits and the release blog post.

## Test Plan

Write _lots_ and _lots_ of tests for both valid and invalid syntax and
verify the output.

## Acknowledgements

- @MichaReiser for reviewing 100+ parser PRs and continuously providing
guidance throughout the project
- @LaBatata101 for initiating the transition to a hand-written parser in
#9152
- @addisoncrump for implementing the fuzzer which helped
[catch](https://github.com/astral-sh/ruff/pull/10903)
[a](https://github.com/astral-sh/ruff/pull/10910)
[lot](https://github.com/astral-sh/ruff/pull/10966)
[of](https://github.com/astral-sh/ruff/pull/10896)
[bugs](https://github.com/astral-sh/ruff/pull/10877)

---------

Co-authored-by: Victor Hugo Gomes <labatata101@linuxmail.org>
Co-authored-by: Micha Reiser <micha@reiser.io>
2024-04-18 17:57:39 +05:30
Alex Waygood
e09180b1df Rename SemanticModel::is_builtin to SemanticModel::has_builtin_binding (#10991) 2024-04-18 11:11:42 +01:00
Jane Lewis
2cc487eb22 ruff server: Introduce settings for directly configuring the linter and formatter (#10984)
## Summary

The following client settings have been introduced to the language
server:
* `lint.preview`
* `format.preview`
* `lint.select`
* `lint.extendSelect`
* `lint.ignore`
* `exclude`
* `lineLength`

`exclude` and `lineLength` apply to both the linter and formatter.

This does not actually use the settings yet, but makes them available
for future use.

## Test Plan

Snapshot tests have been updated.
2024-04-18 07:53:48 +00:00
Jane Lewis
5da7299b32 ruff server: Write a setup guide for Neovim (#10987)
## Summary

A setup guide has been written for NeoVim under a new
`crates/ruff_server/docs/setup` folder, where future setup guides will
also go. This setup guide was adapted from the [`ruff-lsp`
guide](https://github.com/astral-sh/ruff-lsp?tab=readme-ov-file#example-neovim).

---------

Co-authored-by: Dhruv Manilawala <dhruvmanila@gmail.com>
2024-04-18 02:46:30 +00:00
Charlie Marsh
4d8890eef5 [pylint] Omit stubs from invalid-bool and invalid-str-return-type (#11008)
## Summary

Reflecting some improvements that were made in
https://github.com/astral-sh/ruff/pull/10959.
2024-04-18 01:57:20 +00:00
Tibor Reiss
9f01ac3f87 [pylint] Implement invalid-length-returned (E0303) (#10963)
Add pylint rule invalid-length-returned (PLE0303)

See https://github.com/astral-sh/ruff/issues/970 for rules

Test Plan: `cargo test`

TBD: from the description: "Strictly speaking `bool` is a subclass of
`int`, thus returning `True`/`False` is valid. To be consistent with
other rules (e.g.
[PLE0305](https://github.com/astral-sh/ruff/pull/10962)
invalid-index-returned), ruff will raise, compared to pylint which will
not raise."
2024-04-18 01:54:52 +00:00
Charlie Marsh
b23414e3cc Resolve classes and functions relative to script name (#10965)
## Summary

If the user is analyzing a script (i.e., we have no module path), it
seems reasonable to use the script name when trying to identify paths to
objects defined _within_ the script.

Closes https://github.com/astral-sh/ruff/issues/10960.

## Test Plan

Ran:

```shell
check --isolated --select=B008 \
    --config 'lint.flake8-bugbear.extend-immutable-calls=["test.A"]' \
    test.py
```

On:

```python
class A: pass

def f(a=A()):
    pass
```
2024-04-18 01:42:50 +00:00
Tibor Reiss
1480d72643 [pylint] Implement invalid-bytes-returned (E0308) (#10959)
Add pylint rule invalid-bytes-returned (PLE0308)

See https://github.com/astral-sh/ruff/issues/970 for rules

Test Plan: `cargo test`
2024-04-18 01:38:14 +00:00
Charlie Marsh
06b3e376ac Improve documentation for block comment rules (#11007)
Closes https://github.com/astral-sh/ruff/issues/10632.
2024-04-18 01:22:19 +00:00
Charlie Marsh
e8b1125b30 [flake8-slots] Respect same-file Enum subclasses (#11006)
Closes https://github.com/astral-sh/ruff/issues/9890.
2024-04-17 21:15:52 -04:00
Zanie Blue
16cc9bd78d Improve display of rules in --show-settings (#11003)
Closes https://github.com/astral-sh/ruff/issues/11002
2024-04-17 20:18:41 +00:00
Jane Lewis
0a6327418d ruff server refreshes diagnostics for open files when file configuration is changed (#10988)
## Summary

The server now requests a [workspace diagnostic
refresh](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#diagnostic_refresh)
when a configuration file gets changed. This means that diagnostics for
all open files will be automatically re-requested by the client on a
config change.

## Test Plan

You can test this by opening several files in VS Code, setting `select`
in your file configuration to `[]`, and observing that the diagnostics
go away once the file is saved (besides any `Pylance` diagnostics).
Restore it to what it was before, and you should see the diagnostics
automatically return once a save happens.
2024-04-17 09:14:45 -07:00
Sigurd Spieckermann
518b29a9ef Add RUFF_OUTPUT_FILE environment variable support (#10992)
## Summary

I've added support for configuring the `ruff check` output file via the
environment variable `RUFF_OUTPUT_FILE` akin to #1731.

This is super useful when, e.g., generating a [GitLab code quality
report](https://docs.gitlab.com/ee/ci/testing/code_quality.html#implement-a-custom-tool)
while running Ruff as a pre-commit hook. Usually, `ruff check` should
print its human-readable output to `stdout`, but when run through
`pre-commit` _in a GitLab CI job_ it should write its output in `gitlab`
format to a file. So, to override these two settings only during CI,
environment variables come handy, and `RUFF_OUTPUT_FORMAT` already
exists but `RUFF_OUTPUT_FILE` has been missing.

A (simplified) GitLab CI job config for this scenario might look like
this:

```yaml
pre-commit:
  stage: test
  image: python
  variables:
    RUFF_OUTPUT_FILE: gl-code-quality-report.json
    RUFF_OUTPUT_FORMAT: gitlab
  before_script:
    - pip install pre-commit
  script:
    - pre-commit run --all-files --show-diff-on-failure
  artifacts:
    reports:
      codequality: gl-code-quality-report.json
```

## Test Plan

I tested it manually.
2024-04-17 11:42:45 -04:00
Alex Waygood
caae8d2c68 Optimise SemanticModel::match_builtin_expr (#11001) 2024-04-17 15:54:41 +01:00
Philipp Thiel
2971655b28 [flake8-bugbear] Treat raise NotImplemented-only bodies as stub functions (#10990)
## Summary

As discussed in
https://github.com/astral-sh/ruff/issues/10083#issuecomment-1969653610,
stubs detection now also covers the case where the function body raises
NotImplementedError and does nothing else.

## Test Plan

Tests for the relevant cases were added in B006_8.py
2024-04-17 14:06:40 +00:00
Alex Waygood
f48a794125 Change more usages of SemanticModel::is_builtin to use resolve_builtin_symbol or match_builtin_expr (#10982)
## Summary

This PR switches more callsites of `SemanticModel::is_builtin` to move
over to the new methods I introduced in #10919, which are more concise
and more accurate. I missed these calls in the first PR.
2024-04-17 07:50:10 +01:00
Jane Lewis
2882604451 ruff server: Important errors are now shown as popups (#10951)
## Summary

Fixes #10866.

Introduces the `show_err_msg!` macro which will send a message to be
shown as a popup to the client via the `window/showMessage` LSP method.

## Test Plan

Insert various `show_err_msg!` calls in common code paths (for example,
at the beginning of `event_loop`) and confirm that these messages appear
in your editor.

To test that panicking works correctly, add this to the top of the `fn
run` definition in
`crates/ruff_server/src/server/api/requests/execute_command.rs`:

```rust
panic!("This should appear");
```

Then, try running a command like `Ruff: Format document` from the
command palette (`Ctrl/Cmd+Shift+P`). You should see the following
messages appear:


![Screenshot 2024-04-16 at 11 20
57 AM](https://github.com/astral-sh/ruff/assets/19577865/ae430da6-82c3-4841-a419-664ff34034e8)
2024-04-16 18:32:53 +00:00
Jane Lewis
eab3c4e334 Enable ruff-specific source actions (#10916)
## Summary

Fixes #10780.

The server now send code actions to the client with a Ruff-specific
kind, `source.*.ruff`. The kind filtering logic has also been reworked
to support this.

## Test Plan

Add this to your `settings.json` in VS Code:

```json
{
  "[python]": {
    "editor.codeActionsOnSave": {
      "source.organizeImports.ruff": "explicit",
    },
  }
}
```

Imports should be automatically organized when you manually save with
`Ctrl/Cmd+S`.
2024-04-16 18:21:08 +00:00
Jane Lewis
cffc55576f ruff server: Resolve configuration for each document individually (#10950)
## Summary

Configuration is no longer the property of a workspace but rather of
individual documents. Just like the Ruff CLI, each document is
configured based on the 'nearest' project configuration. See [the Ruff
documentation](https://docs.astral.sh/ruff/configuration/#config-file-discovery)
for more details.

To reduce the amount of times we resolve configuration for a file, we
have an index for each workspace that stores a reference-counted pointer
to a configuration for a given folder. If another file in the same
folder is opened, the configuration is simply re-used rather than us
re-resolving it.

## Guide for reviewing

The first commit is just the restructuring work, which adds some noise
to the diff. If you want to quickly understand what's actually changed,
I recommend looking at the two commits that come after it.
f7c073d441 makes configuration a property
of `DocumentController`/`DocumentRef`, moving it out of `Workspace`, and
it also sets up the `ConfigurationIndex`, though it doesn't implement
its key function, `get_or_insert`. In the commit after it,
fc35618f17, we implement `get_or_insert`.

## Test Plan

The best way to test this would be to ensure that the behavior matches
the Ruff CLI. Open a project with multiple configuration files (or add
them yourself), and then introduce problems in certain files that won't
show due to their configuration. Add those same problems to a section of
the project where those rules are run. Confirm that the lint rules are
run as expected with `ruff check`. Then, open your editor and confirm
that the diagnostics shown match the CLI output.

As an example - I have a workspace with two separate folders, `pandas`
and `scipy`. I created a `pyproject.toml` file in `pandas/pandas/io` and
a `ruff.toml` file in `pandas/pandas/api`. I changed the `select` and
`preview` settings in the sub-folder configuration files and confirmed
that these were reflected in the diagnostics. I also confirmed that this
did not change the diagnostics for the `scipy` folder whatsoever.
2024-04-16 18:15:02 +00:00
Alex Waygood
4284e079b5 Improve inference capabilities of the BuiltinTypeChecker (#10976) 2024-04-16 18:53:22 +01:00
plredmond
65edbfe62f Detect unneeded async keywords on functions (#9966)
## Summary

This change adds a rule to detect functions declared `async` but lacking
any of `await`, `async with`, or `async for`. This resolves #9951.

## Test Plan

This change was tested by following
https://docs.astral.sh/ruff/contributing/#rule-testing-fixtures-and-snapshots
and adding positive and negative cases for each of `await` vs nothing,
`async with` vs `with`, and `async for` vs `for`.
2024-04-16 10:32:29 -07:00
Max Muoto
45db695c47 Fix Typo for extend-aliases Option (#10978) 2024-04-16 12:23:09 -04:00
Micha Reiser
1801798e85 Bump the size of RuleSet (#10972) 2024-04-16 14:20:46 +02:00
Micha Reiser
d4e140d47f perf: RuleTable::any_enabled (#10971) 2024-04-16 12:20:27 +00:00
Alex Waygood
f779babc5f Improve handling of builtin symbols in linter rules (#10919)
Add a new method to the semantic model to simplify and improve the correctness of a common pattern
2024-04-16 11:37:31 +01:00
Charlie Marsh
effd5188c9 [flake8-bandit] Allow urllib.request.urlopen calls with static Request argument (#10964)
## Summary

Allows, e.g.:

```python
import urllib

urllib.request.urlopen(urllib.request.Request("https://example.com/"))
```

...in
[`suspicious-url-open-usage`](https://docs.astral.sh/ruff/rules/suspicious-url-open-usage/).

See:
https://github.com/astral-sh/ruff/issues/7918#issuecomment-2057661054
2024-04-16 02:30:23 +00:00
renovate[bot]
6dccbd2b58 Update NPM Development dependencies to v7.7.0 (#10958) 2024-04-15 17:38:45 +00:00
Alex Waygood
0c8ba32819 Minor improvements to renovate config (#10957) 2024-04-15 17:33:06 +00:00
renovate[bot]
4ac523c19d fix(deps): update dependency react-resizable-panels to v2.0.17 (#10956)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-04-15 19:30:09 +02:00
StevenMia
f9214f95bb chore: remove repetitive words (#10952) 2024-04-15 12:57:48 +00:00
renovate[bot]
49d9ad4c7e Update Rust crate chrono to v0.4.38 (#10953)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-04-15 14:50:22 +02:00
Steve C
c2210359e7 [pylint] Implement self-cls-assignment (W0642) (#9267)
## Summary

This PR implements [`W0642`/`self-cls-assignment`](https://pylint.readthedocs.io/en/stable/user_guide/messages/warning/self-cls-assignment.html)

See: #970 

## Test Plan

Add test cases and verified the updated snapshots.

---------

Co-authored-by: Dhruv Manilawala <dhruvmanila@gmail.com>
2024-04-15 09:06:01 +00:00
Hoël Bagard
670d66f54c [pycodestyle] Do not trigger E3 rules on defs following a function/method with a dummy body (#10704) 2024-04-15 10:23:49 +02:00
renovate[bot]
cbd500141f Update NPM Development dependencies (#10947)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-04-15 09:03:19 +02:00
Micha Reiser
b62aeb39d2 Configure Renovate to ignore ESLint 9 (#10946) 2024-04-15 06:52:43 +00:00
renovate[bot]
cdbd754870 Update Rust crate syn to v2.0.59 (#10941) 2024-04-15 06:38:18 +01:00
renovate[bot]
91efca1837 Update Rust crate codspeed-criterion-compat to v2.4.1 (#10931) 2024-04-14 21:49:31 -04:00
renovate[bot]
09ae2341e9 Update pre-commit dependencies (#10934) 2024-04-14 21:49:11 -04:00
renovate[bot]
f07af6fb63 Update Rust crate insta-cmd to 0.6.0 (#10937) 2024-04-14 21:48:46 -04:00
renovate[bot]
b11d17f65c Update Rust crate pep440_rs to 0.6.0 (#10938) 2024-04-14 21:48:37 -04:00
renovate[bot]
b4c7c55ddd Update Rust crate argfile to 0.2.0 (#10936) 2024-04-14 21:48:30 -04:00
renovate[bot]
0f01713257 Update Rust crate proc-macro2 to v1.0.80 (#10932) 2024-04-14 21:48:15 -04:00
renovate[bot]
ed9a92d915 Update Rust crate quote to v1.0.36 (#10933) 2024-04-14 21:48:09 -04:00
renovate[bot]
6da4ea6116 Update Rust crate anyhow to v1.0.82 (#10930) 2024-04-14 21:47:54 -04:00
Dhruv Manilawala
f9a828f493 Move Q003 to AST checker (#10923)
## Summary

This PR moves the `Q003` rule to AST checker.

This is the final rule that used the docstring detection state machine
and thus this PR removes it as well.

resolves: #7595 
resolves: #7808 

## Test Plan

- [x] `cargo test`
- [x] Make sure there are no changes in the ecosystem
2024-04-14 23:44:12 +05:30
Steve C
812b0976a9 [pylint] Support inverted comparisons (PLR1730) (#10920)
## Summary

Adds more aggressive logic to PLR1730, `if-stmt-min-max`

Closes #10907 

## Test Plan

`cargo test`

---------

Co-authored-by: Charlie Marsh <charlie.r.marsh@gmail.com>
2024-04-13 22:57:20 +00:00
Hoël Bagard
b356c4376c Fix S310 suspicious-url-open-usage description (#10917)
## Summary

The "What it does" section of the docstring is missing a verb, this PR
adds it.
2024-04-13 12:52:04 +01:00
Sebastian Pipping
85ca5b7eed Fix last example of flake8-bugbear rule B023 "function uses loop variable" (#10913)
<!--
Thank you for contributing to Ruff! To help us out with reviewing,
please consider the following:

- Does this pull request include a summary of the change? (See below.)
- Does this pull request include a descriptive title?
- Does this pull request include references to any relevant issues?
-->

## Summary

Hi! 👋 

Thanks for sharing ruff as software libre — it helps me keep Python code
quality up with pre-commit, both locally and CI 🙏

While studying the examples at
https://docs.astral.sh/ruff/rules/function-uses-loop-variable/#example I
noticed that the last of the examples had a bug: prior to this fix, `ì`
was passed to the lambda for `x` rather than for `i` — the two are
mixed-up. The reason it's easy to overlook is because addition is an
commutative operation and so `x + i` and `i + x` give the same result
(and least with integers), despite the mix-up. For proof, let me demo
the relevant part with before and after:

```python
In [1]: from functools import partial

In [2]: [partial(lambda x, i: (x, i), i)(123) for i in range(3)]
Out[2]: [(0, 123), (1, 123), (2, 123)]

In [3]: [partial(lambda x, i: (x, i), i=i)(123) for i in range(3)]
Out[3]: [(123, 0), (123, 1), (123, 2)]
```

Does that make sense?

## Test Plan

<!-- How was it tested? -->
Was manually tested using IPython.


CC @r4f @grandchild
2024-04-12 20:07:52 +00:00
Charlie Marsh
c2421068bc Limit commutative non-augmented-assignments to primitive data types (#10912)
## Summary

I think this is the best we can do without type inference. At least it
will still catch some common cases.

Closes #10911.
2024-04-12 15:02:29 -04:00
Charlie Marsh
e9870fe468 Avoid non-augmented-assignment for reversed, non-commutative operators (#10909)
Closes https://github.com/astral-sh/ruff/issues/10900.
2024-04-12 10:04:57 -04:00
Charlie Marsh
a013050c11 Respect per-file-ignores for RUF100 on blanket # noqa (#10908)
## Summary

If `RUF100` was included in a per-file-ignore, we respected it on cases
like `# noqa: F401`, but not the blanket variant (`# noqa`).

Closes https://github.com/astral-sh/ruff/issues/10906.
2024-04-12 13:45:29 +00:00
1140 changed files with 121481 additions and 106767 deletions

2
.gitattributes vendored
View File

@@ -8,5 +8,7 @@ crates/ruff_linter/resources/test/fixtures/pycodestyle/W391_3.py text eol=crlf
crates/ruff_python_formatter/resources/test/fixtures/ruff/docstring_code_examples_crlf.py text eol=crlf
crates/ruff_python_formatter/tests/snapshots/format@docstring_code_examples_crlf.py.snap text eol=crlf
crates/ruff_python_parser/resources/inline linguist-generated=true
ruff.schema.json linguist-generated=true text=auto eol=lf
*.md.snap linguist-language=Markdown

5
.github/CODEOWNERS vendored
View File

@@ -5,11 +5,10 @@
# - The '*' pattern is global owners.
# - Order is important. The last matching pattern has the most precedence.
# Jupyter
/crates/ruff_linter/src/jupyter/ @dhruvmanila
/crates/ruff_notebook/ @dhruvmanila
/crates/ruff_formatter/ @MichaReiser
/crates/ruff_python_formatter/ @MichaReiser
/crates/ruff_python_parser/ @MichaReiser
/crates/ruff_python_parser/ @MichaReiser @dhruvmanila
# flake8-pyi
/crates/ruff_linter/src/rules/flake8_pyi/ @AlexWaygood

View File

@@ -4,7 +4,8 @@
suppressNotifications: ["prEditedNotification"],
extends: ["config:recommended"],
labels: ["internal"],
schedule: ["on Monday"],
schedule: ["before 4am on Monday"],
semanticCommits: "disabled",
separateMajorMinor: false,
prHourlyLimit: 10,
enabledManagers: ["github-actions", "pre-commit", "cargo", "pep621", "npm"],
@@ -52,6 +53,13 @@
matchPackagePatterns: ["strum"],
description: "Weekly update of strum dependencies",
},
{
groupName: "ESLint",
matchManagers: ["npm"],
matchPackageNames: ["eslint"],
allowedVersions: "<9",
description: "Constraint ESLint to version 8 until TypeScript-eslint supports ESLint 9", // https://github.com/typescript-eslint/typescript-eslint/issues/8211
},
],
vulnerabilityAlerts: {
commitMessageSuffix: "",

View File

@@ -41,7 +41,7 @@ repos:
)$
- repo: https://github.com/crate-ci/typos
rev: v1.20.4
rev: v1.20.8
hooks:
- id: typos
@@ -55,7 +55,7 @@ repos:
pass_filenames: false # This makes it a lot faster
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.3.5
rev: v0.4.0
hooks:
- id: ruff-format
- id: ruff

View File

@@ -1,5 +1,94 @@
# Changelog
## 0.4.1
### Preview features
- \[`pylint`\] Implement `invalid-hash-returned` (`PLE0309`) ([#10961](https://github.com/astral-sh/ruff/pull/10961))
- \[`pylint`\] Implement `invalid-index-returned` (`PLE0305`) ([#10962](https://github.com/astral-sh/ruff/pull/10962))
### Bug fixes
- \[`pylint`\] Allow `NoReturn`-like functions for `__str__`, `__len__`, etc. (`PLE0307`) ([#11017](https://github.com/astral-sh/ruff/pull/11017))
- Parser: Use empty range when there's "gap" in token source ([#11032](https://github.com/astral-sh/ruff/pull/11032))
- \[`ruff`\] Ignore stub functions in `unused-async` (`RUF029`) ([#11026](https://github.com/astral-sh/ruff/pull/11026))
- Parser: Expect indented case block instead of match stmt ([#11033](https://github.com/astral-sh/ruff/pull/11033))
## 0.4.0
### A new, hand-written parser
Ruff's new parser is **>2x faster**, which translates to a **20-40% speedup** for all linting and formatting invocations.
There's a lot to say about this exciting change, so check out the [blog post](https://astral.sh/blog/ruff-v0.4.0) for more details!
See [#10036](https://github.com/astral-sh/ruff/pull/10036) for implementation details.
### A new language server in Rust
With this release, we also want to highlight our new language server. `ruff server` is a Rust-powered language
server that comes built-in with Ruff. It can be used with any editor that supports the [Language Server Protocol](https://microsoft.github.io/language-server-protocol/) (LSP).
It uses a multi-threaded, lock-free architecture inspired by `rust-analyzer` and it will open the door for a lot
of exciting features. Its also faster than our previous [Python-based language server](https://github.com/astral-sh/ruff-lsp)
-- but you probably guessed that already.
`ruff server` is only in alpha, but it has a lot of features that you can try out today:
- Lints Python files automatically and shows quick-fixes when available
- Formats Python files, with support for range formatting
- Comes with commands for quickly performing actions: `ruff.applyAutofix`, `ruff.applyFormat`, and `ruff.applyOrganizeImports`
- Supports `source.fixAll` and `source.organizeImports` source actions
- Automatically reloads your project configuration when you change it
To setup `ruff server` with your editor, refer to the [README.md](https://github.com/astral-sh/ruff/blob/main/crates/ruff_server/README.md).
### Preview features
- \[`pycodestyle`\] Do not trigger `E3` rules on `def`s following a function/method with a dummy body ([#10704](https://github.com/astral-sh/ruff/pull/10704))
- \[`pylint`\] Implement `invalid-bytes-returned` (`E0308`) ([#10959](https://github.com/astral-sh/ruff/pull/10959))
- \[`pylint`\] Implement `invalid-length-returned` (`E0303`) ([#10963](https://github.com/astral-sh/ruff/pull/10963))
- \[`pylint`\] Implement `self-cls-assignment` (`W0642`) ([#9267](https://github.com/astral-sh/ruff/pull/9267))
- \[`pylint`\] Omit stubs from `invalid-bool` and `invalid-str-return-type` ([#11008](https://github.com/astral-sh/ruff/pull/11008))
- \[`ruff`\] New rule `unused-async` (`RUF029`) to detect unneeded `async` keywords on functions ([#9966](https://github.com/astral-sh/ruff/pull/9966))
### Rule changes
- \[`flake8-bandit`\] Allow `urllib.request.urlopen` calls with static `Request` argument (`S310`) ([#10964](https://github.com/astral-sh/ruff/pull/10964))
- \[`flake8-bugbear`\] Treat `raise NotImplemented`-only bodies as stub functions (`B006`) ([#10990](https://github.com/astral-sh/ruff/pull/10990))
- \[`flake8-slots`\] Respect same-file `Enum` subclasses (`SLOT000`) ([#11006](https://github.com/astral-sh/ruff/pull/11006))
- \[`pylint`\] Support inverted comparisons (`PLR1730`) ([#10920](https://github.com/astral-sh/ruff/pull/10920))
### Linter
- Improve handling of builtin symbols in linter rules ([#10919](https://github.com/astral-sh/ruff/pull/10919))
- Improve display of rules in `--show-settings` ([#11003](https://github.com/astral-sh/ruff/pull/11003))
- Improve inference capabilities of the `BuiltinTypeChecker` ([#10976](https://github.com/astral-sh/ruff/pull/10976))
- Resolve classes and functions relative to script name ([#10965](https://github.com/astral-sh/ruff/pull/10965))
- Improve performance of `RuleTable::any_enabled` ([#10971](https://github.com/astral-sh/ruff/pull/10971))
### Server
*This section is devoted to updates for our new language server, written in Rust.*
- Enable ruff-specific source actions ([#10916](https://github.com/astral-sh/ruff/pull/10916))
- Refreshes diagnostics for open files when file configuration is changed ([#10988](https://github.com/astral-sh/ruff/pull/10988))
- Important errors are now shown as popups ([#10951](https://github.com/astral-sh/ruff/pull/10951))
- Introduce settings for directly configuring the linter and formatter ([#10984](https://github.com/astral-sh/ruff/pull/10984))
- Resolve configuration for each document individually ([#10950](https://github.com/astral-sh/ruff/pull/10950))
- Write a setup guide for Neovim ([#10987](https://github.com/astral-sh/ruff/pull/10987))
### Configuration
- Add `RUFF_OUTPUT_FILE` environment variable support ([#10992](https://github.com/astral-sh/ruff/pull/10992))
### Bug fixes
- Avoid `non-augmented-assignment` for reversed, non-commutative operators (`PLR6104`) ([#10909](https://github.com/astral-sh/ruff/pull/10909))
- Limit commutative non-augmented-assignments to primitive data types (`PLR6104`) ([#10912](https://github.com/astral-sh/ruff/pull/10912))
- Respect `per-file-ignores` for `RUF100` on blanket `# noqa` ([#10908](https://github.com/astral-sh/ruff/pull/10908))
- Consider `if` expression for parenthesized with items parsing ([#11010](https://github.com/astral-sh/ruff/pull/11010))
- Consider binary expr for parenthesized with items parsing ([#11012](https://github.com/astral-sh/ruff/pull/11012))
- Reset `FOR_TARGET` context for all kinds of parentheses ([#11009](https://github.com/astral-sh/ruff/pull/11009))
## 0.3.7
### Preview features
@@ -1385,7 +1474,7 @@ Read Ruff's new [versioning policy](https://docs.astral.sh/ruff/versioning/).
- \[`refurb`\] Add `single-item-membership-test` (`FURB171`) ([#7815](https://github.com/astral-sh/ruff/pull/7815))
- \[`pylint`\] Add `and-or-ternary` (`R1706`) ([#7811](https://github.com/astral-sh/ruff/pull/7811))
_New rules are added in [preview](https://docs.astral.sh/ruff/preview/)._
*New rules are added in [preview](https://docs.astral.sh/ruff/preview/).*
### Configuration

311
Cargo.lock generated
View File

@@ -123,15 +123,15 @@ dependencies = [
[[package]]
name = "anyhow"
version = "1.0.81"
version = "1.0.82"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0952808a6c2afd1aa8947271f3a60f1a6763c7b912d210184c5149b5cf147247"
checksum = "f538837af36e6f6a9be0faa67f9a314f8119e4e4b5867c6ab40ed60360142519"
[[package]]
name = "argfile"
version = "0.1.6"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1287c4f82a41c5085e65ee337c7934d71ab43d5187740a81fb69129013f6a5f6"
checksum = "b7c5c8e418080ef8aa932039d12eda7b6f5043baf48f1523c166fbc32d004534"
dependencies = [
"fs-err",
"os_str_bytes",
@@ -143,15 +143,6 @@ version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711"
[[package]]
name = "ascii-canvas"
version = "3.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8824ecca2e851cec16968d54a01dd372ef8f95b244fb84b84e70128be347c3c6"
dependencies = [
"term",
]
[[package]]
name = "autocfg"
version = "1.1.0"
@@ -173,21 +164,6 @@ dependencies = [
"serde",
]
[[package]]
name = "bit-set"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1"
dependencies = [
"bit-vec",
]
[[package]]
name = "bit-vec"
version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb"
[[package]]
name = "bitflags"
version = "1.3.2"
@@ -261,9 +237,9 @@ dependencies = [
[[package]]
name = "chrono"
version = "0.4.37"
version = "0.4.38"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a0d04d43504c61aa6c7531f1871dd0d418d91130162063b789da00fd7057a5e"
checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401"
dependencies = [
"android-tzdata",
"iana-time-zone",
@@ -371,7 +347,7 @@ dependencies = [
"heck 0.5.0",
"proc-macro2",
"quote",
"syn 2.0.58",
"syn 2.0.59",
]
[[package]]
@@ -395,9 +371,9 @@ dependencies = [
[[package]]
name = "codspeed"
version = "2.4.0"
version = "2.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4b85b056aa0541d1975ebc524149dde72803a5d7352b6aebf9eabc44f9017246"
checksum = "655a6d8e698ef8de25b6fb160dc904d98cf3642df48fae8bb05711db1893f47d"
dependencies = [
"colored",
"libc",
@@ -406,9 +382,9 @@ dependencies = [
[[package]]
name = "codspeed-criterion-compat"
version = "2.4.0"
version = "2.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02ae9de916d6315a5129bca2fc7957285f0b9f77a2f6a8734a0a146caee2b0b6"
checksum = "cb377d73b85084b1ca5615e42fdbf3b52631d6d8feda4be6dfc2150646f53cea"
dependencies = [
"codspeed",
"colored",
@@ -602,7 +578,7 @@ dependencies = [
"proc-macro2",
"quote",
"strsim 0.10.0",
"syn 2.0.58",
"syn 2.0.59",
]
[[package]]
@@ -613,7 +589,7 @@ checksum = "a668eda54683121533a393014d8692171709ff57a7d61f187b6e782719f8933f"
dependencies = [
"darling_core",
"quote",
"syn 2.0.58",
"syn 2.0.59",
]
[[package]]
@@ -640,16 +616,6 @@ dependencies = [
"dirs-sys 0.4.1",
]
[[package]]
name = "dirs-next"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1"
dependencies = [
"cfg-if",
"dirs-sys-next",
]
[[package]]
name = "dirs-sys"
version = "0.3.7"
@@ -673,17 +639,6 @@ dependencies = [
"windows-sys 0.48.0",
]
[[package]]
name = "dirs-sys-next"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d"
dependencies = [
"libc",
"redox_users",
"winapi",
]
[[package]]
name = "drop_bomb"
version = "0.1.5"
@@ -702,15 +657,6 @@ version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a"
[[package]]
name = "ena"
version = "0.14.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c533630cf40e9caa44bd91aadc88a75d75a4c3a12b4cfde353cbed41daa1e1f1"
dependencies = [
"log",
]
[[package]]
name = "encode_unicode"
version = "0.3.6"
@@ -783,12 +729,6 @@ dependencies = [
"windows-sys 0.52.0",
]
[[package]]
name = "fixedbitset"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
[[package]]
name = "flate2"
version = "1.0.28"
@@ -1078,9 +1018,9 @@ dependencies = [
[[package]]
name = "insta-cmd"
version = "0.5.0"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1980f17994b79f75670aa90cfc8d35edc4aa248f16aa48b5e27835b080e452a2"
checksum = "ffeeefa927925cced49ccb01bf3e57c9d4cd132df21e576eb9415baeab2d3de6"
dependencies = [
"insta",
"serde",
@@ -1114,7 +1054,7 @@ dependencies = [
"Inflector",
"proc-macro2",
"quote",
"syn 2.0.58",
"syn 2.0.59",
]
[[package]]
@@ -1147,15 +1087,6 @@ dependencies = [
"either",
]
[[package]]
name = "itertools"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57"
dependencies = [
"either",
]
[[package]]
name = "itertools"
version = "0.12.1"
@@ -1206,33 +1137,6 @@ dependencies = [
"libc",
]
[[package]]
name = "lalrpop"
version = "0.20.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "55cb077ad656299f160924eb2912aa147d7339ea7d69e1b5517326fdcec3c1ca"
dependencies = [
"ascii-canvas",
"bit-set",
"ena",
"itertools 0.11.0",
"lalrpop-util",
"petgraph",
"regex",
"regex-syntax 0.8.2",
"string_cache",
"term",
"tiny-keccak",
"unicode-xid",
"walkdir",
]
[[package]]
name = "lalrpop-util"
version = "0.20.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "507460a910eb7b32ee961886ff48539633b788a36b65692b95f225b844c82553"
[[package]]
name = "lazy_static"
version = "1.4.0"
@@ -1297,7 +1201,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4a5011f2d59093de14a4a90e01b9d85dee9276e58a25f0107dcee167dd601be0"
dependencies = [
"quote",
"syn 2.0.58",
"syn 2.0.59",
]
[[package]]
@@ -1333,16 +1237,6 @@ version = "0.4.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c"
[[package]]
name = "lock_api"
version = "0.4.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45"
dependencies = [
"autocfg",
"scopeguard",
]
[[package]]
name = "log"
version = "0.4.21"
@@ -1437,12 +1331,6 @@ version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "308d96db8debc727c3fd9744aac51751243420e46edf401010908da7f8d5e57c"
[[package]]
name = "new_debug_unreachable"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54"
[[package]]
name = "nix"
version = "0.28.0"
@@ -1561,29 +1449,6 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
[[package]]
name = "parking_lot"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f"
dependencies = [
"lock_api",
"parking_lot_core",
]
[[package]]
name = "parking_lot_core"
version = "0.9.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e"
dependencies = [
"cfg-if",
"libc",
"redox_syscall",
"smallvec",
"windows-targets 0.48.5",
]
[[package]]
name = "paste"
version = "1.0.14"
@@ -1655,9 +1520,9 @@ dependencies = [
[[package]]
name = "pep440_rs"
version = "0.5.0"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "15efd4d885c29126cc93e12af3087896e2518bd5ca0fb328c19c4ef9cecfa8be"
checksum = "ca0a570e7ec9171250cac57614e901f62408094b54b3798bb920d3cf0d4a0e09"
dependencies = [
"once_cell",
"serde",
@@ -1687,23 +1552,13 @@ version = "2.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
[[package]]
name = "petgraph"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e1d3afd2628e69da2be385eb6f2fd57c8ac7977ceeff6dc166ff1657b0e386a9"
dependencies = [
"fixedbitset",
"indexmap",
]
[[package]]
name = "phf"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc"
dependencies = [
"phf_shared 0.11.2",
"phf_shared",
]
[[package]]
@@ -1713,7 +1568,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8d39688d359e6b34654d328e262234662d16cc0f60ec8dcbe5e718709342a5a"
dependencies = [
"phf_generator",
"phf_shared 0.11.2",
"phf_shared",
]
[[package]]
@@ -1722,19 +1577,10 @@ version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48e4cc64c2ad9ebe670cb8fd69dd50ae301650392e81c05f9bfcb2d5bdbc24b0"
dependencies = [
"phf_shared 0.11.2",
"phf_shared",
"rand",
]
[[package]]
name = "phf_shared"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096"
dependencies = [
"siphasher",
]
[[package]]
name = "phf_shared"
version = "0.11.2"
@@ -1758,7 +1604,7 @@ checksum = "52a40bc70c2c58040d2d8b167ba9a5ff59fc9dab7ad44771cfde3dcfde7a09c6"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.58",
"syn 2.0.59",
]
[[package]]
@@ -1773,12 +1619,6 @@ version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
[[package]]
name = "precomputed-hash"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c"
[[package]]
name = "pretty_assertions"
version = "1.4.0"
@@ -1791,9 +1631,9 @@ dependencies = [
[[package]]
name = "proc-macro2"
version = "1.0.79"
version = "1.0.80"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e"
checksum = "a56dea16b0a29e94408b9aa5e2940a4eedbd128a1ba20e8f7ae60fd3d465af0e"
dependencies = [
"unicode-ident",
]
@@ -1836,9 +1676,9 @@ dependencies = [
[[package]]
name = "quote"
version = "1.0.35"
version = "1.0.36"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef"
checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7"
dependencies = [
"proc-macro2",
]
@@ -1975,7 +1815,7 @@ dependencies = [
"pmutil",
"proc-macro2",
"quote",
"syn 2.0.58",
"syn 2.0.59",
]
[[package]]
@@ -1995,7 +1835,7 @@ dependencies = [
[[package]]
name = "ruff"
version = "0.3.7"
version = "0.4.1"
dependencies = [
"anyhow",
"argfile",
@@ -2157,7 +1997,7 @@ dependencies = [
[[package]]
name = "ruff_linter"
version = "0.3.7"
version = "0.4.1"
dependencies = [
"aho-corasick",
"annotate-snippets 0.9.2",
@@ -2181,7 +2021,7 @@ dependencies = [
"once_cell",
"path-absolutize",
"pathdiff",
"pep440_rs 0.5.0",
"pep440_rs 0.6.0",
"pyproject-toml",
"quick-junit",
"regex",
@@ -2225,7 +2065,7 @@ dependencies = [
"proc-macro2",
"quote",
"ruff_python_trivia",
"syn 2.0.58",
"syn 2.0.59",
]
[[package]]
@@ -2336,22 +2176,23 @@ dependencies = [
name = "ruff_python_parser"
version = "0.0.0"
dependencies = [
"annotate-snippets 0.9.2",
"anyhow",
"bitflags 2.5.0",
"bstr",
"insta",
"is-macro",
"itertools 0.12.1",
"lalrpop",
"lalrpop-util",
"memchr",
"ruff_python_ast",
"ruff_source_file",
"ruff_text_size",
"rustc-hash",
"static_assertions",
"tiny-keccak",
"unicode-ident",
"unicode-normalization",
"unicode_names2",
"walkdir",
]
[[package]]
@@ -2425,11 +2266,12 @@ dependencies = [
"serde",
"serde_json",
"tracing",
"walkdir",
]
[[package]]
name = "ruff_shrinking"
version = "0.3.7"
version = "0.4.1"
dependencies = [
"anyhow",
"clap",
@@ -2502,7 +2344,7 @@ dependencies = [
"itertools 0.12.1",
"log",
"path-absolutize",
"pep440_rs 0.5.0",
"pep440_rs 0.6.0",
"regex",
"ruff_cache",
"ruff_formatter",
@@ -2631,12 +2473,6 @@ version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294"
[[package]]
name = "scopeguard"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "seahash"
version = "4.1.0"
@@ -2671,7 +2507,7 @@ checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.58",
"syn 2.0.59",
]
[[package]]
@@ -2704,7 +2540,7 @@ checksum = "0b2e6b945e9d3df726b65d6ee24060aff8e3533d431f677a9695db04eff9dfdb"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.58",
"syn 2.0.59",
]
[[package]]
@@ -2745,7 +2581,7 @@ dependencies = [
"darling",
"proc-macro2",
"quote",
"syn 2.0.58",
"syn 2.0.59",
]
[[package]]
@@ -2802,19 +2638,6 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
[[package]]
name = "string_cache"
version = "0.8.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f91138e76242f575eb1d3b38b4f1362f10d3a43f47d182a5b359af488a02293b"
dependencies = [
"new_debug_unreachable",
"once_cell",
"parking_lot",
"phf_shared 0.10.0",
"precomputed-hash",
]
[[package]]
name = "strip-ansi-escapes"
version = "0.2.0"
@@ -2855,7 +2678,7 @@ dependencies = [
"proc-macro2",
"quote",
"rustversion",
"syn 2.0.58",
"syn 2.0.59",
]
[[package]]
@@ -2877,9 +2700,9 @@ dependencies = [
[[package]]
name = "syn"
version = "2.0.58"
version = "2.0.59"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44cfb93f38070beee36b3fef7d4f5a16f27751d94b187b666a5cc5e9b0d30687"
checksum = "4a6531ffc7b071655e4ce2e04bd464c4830bb585a61cabb96cf808f05172615a"
dependencies = [
"proc-macro2",
"quote",
@@ -2898,17 +2721,6 @@ dependencies = [
"windows-sys 0.52.0",
]
[[package]]
name = "term"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c59df8ac95d96ff9bede18eb7300b0fda5e5d8d90960e76f8e14ae765eedbf1f"
dependencies = [
"dirs-next",
"rustversion",
"winapi",
]
[[package]]
name = "terminal_size"
version = "0.3.0"
@@ -2950,7 +2762,7 @@ dependencies = [
"cfg-if",
"proc-macro2",
"quote",
"syn 2.0.58",
"syn 2.0.59",
]
[[package]]
@@ -2961,7 +2773,7 @@ checksum = "5c89e72a01ed4c579669add59014b9a524d609c0c88c6a585ce37485879f6ffb"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.58",
"syn 2.0.59",
"test-case-core",
]
@@ -2982,7 +2794,7 @@ checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.58",
"syn 2.0.59",
]
[[package]]
@@ -3015,15 +2827,6 @@ dependencies = [
"tikv-jemalloc-sys",
]
[[package]]
name = "tiny-keccak"
version = "2.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237"
dependencies = [
"crunchy",
]
[[package]]
name = "tinytemplate"
version = "1.2.1"
@@ -3103,7 +2906,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.58",
"syn 2.0.59",
]
[[package]]
@@ -3244,12 +3047,6 @@ version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85"
[[package]]
name = "unicode-xid"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c"
[[package]]
name = "unicode_names2"
version = "1.2.2"
@@ -3339,7 +3136,7 @@ checksum = "9881bea7cbe687e36c9ab3b778c36cd0487402e270304e8b1296d5085303c1a2"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.58",
"syn 2.0.59",
]
[[package]]
@@ -3424,7 +3221,7 @@ dependencies = [
"once_cell",
"proc-macro2",
"quote",
"syn 2.0.58",
"syn 2.0.59",
"wasm-bindgen-shared",
]
@@ -3458,7 +3255,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.58",
"syn 2.0.59",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
@@ -3491,7 +3288,7 @@ checksum = "b7f89739351a2e03cb94beb799d47fb2cac01759b40ec441f7de39b00cbf7ef0"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.58",
"syn 2.0.59",
]
[[package]]
@@ -3753,7 +3550,7 @@ checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.58",
"syn 2.0.59",
]
[[package]]

View File

@@ -15,7 +15,7 @@ license = "MIT"
aho-corasick = { version = "1.1.3" }
annotate-snippets = { version = "0.9.2", features = ["color"] }
anyhow = { version = "1.0.80" }
argfile = { version = "0.1.6" }
argfile = { version = "0.2.0" }
bincode = { version = "1.3.3" }
bitflags = { version = "2.5.0" }
bstr = { version = "1.9.1" }
@@ -46,13 +46,12 @@ imperative = { version = "1.0.4" }
indicatif = { version = "0.17.8" }
indoc = { version = "2.0.4" }
insta = { version = "1.35.1", feature = ["filters", "glob"] }
insta-cmd = { version = "0.5.0" }
insta-cmd = { version = "0.6.0" }
is-macro = { version = "0.3.5" }
is-wsl = { version = "0.4.0" }
itertools = { version = "0.12.1" }
js-sys = { version = "0.3.69" }
jod-thread = { version = "0.1.2" }
lalrpop-util = { version = "0.20.0", default-features = false }
lexical-parse-float = { version = "0.8.0", features = ["format"] }
libc = { version = "0.2.153" }
libcst = { version = "1.1.0", default-features = false }
@@ -67,7 +66,7 @@ num_cpus = { version = "1.16.0" }
once_cell = { version = "1.19.0" }
path-absolutize = { version = "3.1.1" }
pathdiff = { version = "0.2.1" }
pep440_rs = { version = "0.5.0", features = ["serde"] }
pep440_rs = { version = "0.6.0", features = ["serde"] }
pretty_assertions = "1.3.0"
proc-macro2 = { version = "1.0.79" }
pyproject-toml = { version = "0.9.0" }

View File

@@ -151,7 +151,7 @@ Ruff can also be used as a [pre-commit](https://pre-commit.com/) hook via [`ruff
```yaml
- repo: https://github.com/astral-sh/ruff-pre-commit
# Ruff version.
rev: v0.3.7
rev: v0.4.1
hooks:
# Run the linter.
- id: ruff

View File

@@ -11,3 +11,9 @@ ned = "ned"
pn = "pn" # `import panel as pd` is a thing
poit = "poit"
BA = "BA" # acronym for "Bad Allowed", used in testing.
[default]
extend-ignore-re = [
# Line ignore with trailing "spellchecker:disable-line"
"(?Rm)^.*#\\s*spellchecker:disable-line$"
]

View File

@@ -1,6 +1,6 @@
[package]
name = "ruff"
version = "0.3.7"
version = "0.4.1"
publish = false
authors = { workspace = true }
edition = { workspace = true }

View File

@@ -190,7 +190,7 @@ pub struct CheckCommand {
pub output_format: Option<SerializationFormat>,
/// Specify file to write the linter output to (default: stdout).
#[arg(short, long)]
#[arg(short, long, env = "RUFF_OUTPUT_FILE")]
pub output_file: Option<PathBuf>,
/// The minimum Python version that should be supported.
#[arg(long, value_enum)]

View File

@@ -523,7 +523,7 @@ from module import =
----- stdout -----
----- stderr -----
error: Failed to parse main.py:2:20: Unexpected token '='
error: Failed to parse main.py:2:20: Expected an import name
"###);
Ok(())

View File

@@ -731,11 +731,11 @@ fn stdin_parse_error() {
success: false
exit_code: 1
----- stdout -----
-:1:17: E999 SyntaxError: Unexpected token '='
-:1:17: E999 SyntaxError: Expected an import name
Found 1 error.
----- stderr -----
error: Failed to parse at 1:17: Unexpected token '='
error: Failed to parse at 1:17: Expected an import name
"###);
}

View File

@@ -53,6 +53,7 @@ file_resolver.extend_exclude = [
"crates/ruff/resources/",
"crates/ruff_linter/resources/",
"crates/ruff_python_formatter/resources/",
"crates/ruff_python_parser/resources/",
]
file_resolver.force_exclude = false
file_resolver.include = [
@@ -68,128 +69,128 @@ file_resolver.project_root = "[BASEPATH]"
linter.exclude = []
linter.project_root = "[BASEPATH]"
linter.rules.enabled = [
MultipleImportsOnOneLine,
ModuleImportNotAtTopOfFile,
MultipleStatementsOnOneLineColon,
MultipleStatementsOnOneLineSemicolon,
UselessSemicolon,
NoneComparison,
TrueFalseComparison,
NotInTest,
NotIsTest,
TypeComparison,
BareExcept,
LambdaAssignment,
AmbiguousVariableName,
AmbiguousClassName,
AmbiguousFunctionName,
IOError,
SyntaxError,
UnusedImport,
ImportShadowedByLoopVar,
UndefinedLocalWithImportStar,
LateFutureImport,
UndefinedLocalWithImportStarUsage,
UndefinedLocalWithNestedImportStarUsage,
FutureFeatureNotDefined,
PercentFormatInvalidFormat,
PercentFormatExpectedMapping,
PercentFormatExpectedSequence,
PercentFormatExtraNamedArguments,
PercentFormatMissingArgument,
PercentFormatMixedPositionalAndNamed,
PercentFormatPositionalCountMismatch,
PercentFormatStarRequiresSequence,
PercentFormatUnsupportedFormatCharacter,
StringDotFormatInvalidFormat,
StringDotFormatExtraNamedArguments,
StringDotFormatExtraPositionalArguments,
StringDotFormatMissingArguments,
StringDotFormatMixingAutomatic,
FStringMissingPlaceholders,
MultiValueRepeatedKeyLiteral,
MultiValueRepeatedKeyVariable,
ExpressionsInStarAssignment,
MultipleStarredExpressions,
AssertTuple,
IsLiteral,
InvalidPrintSyntax,
IfTuple,
BreakOutsideLoop,
ContinueOutsideLoop,
YieldOutsideFunction,
ReturnOutsideFunction,
DefaultExceptNotLast,
ForwardAnnotationSyntaxError,
RedefinedWhileUnused,
UndefinedName,
UndefinedExport,
UndefinedLocal,
UnusedVariable,
UnusedAnnotation,
RaiseNotImplemented,
multiple-imports-on-one-line (E401),
module-import-not-at-top-of-file (E402),
multiple-statements-on-one-line-colon (E701),
multiple-statements-on-one-line-semicolon (E702),
useless-semicolon (E703),
none-comparison (E711),
true-false-comparison (E712),
not-in-test (E713),
not-is-test (E714),
type-comparison (E721),
bare-except (E722),
lambda-assignment (E731),
ambiguous-variable-name (E741),
ambiguous-class-name (E742),
ambiguous-function-name (E743),
io-error (E902),
syntax-error (E999),
unused-import (F401),
import-shadowed-by-loop-var (F402),
undefined-local-with-import-star (F403),
late-future-import (F404),
undefined-local-with-import-star-usage (F405),
undefined-local-with-nested-import-star-usage (F406),
future-feature-not-defined (F407),
percent-format-invalid-format (F501),
percent-format-expected-mapping (F502),
percent-format-expected-sequence (F503),
percent-format-extra-named-arguments (F504),
percent-format-missing-argument (F505),
percent-format-mixed-positional-and-named (F506),
percent-format-positional-count-mismatch (F507),
percent-format-star-requires-sequence (F508),
percent-format-unsupported-format-character (F509),
string-dot-format-invalid-format (F521),
string-dot-format-extra-named-arguments (F522),
string-dot-format-extra-positional-arguments (F523),
string-dot-format-missing-arguments (F524),
string-dot-format-mixing-automatic (F525),
f-string-missing-placeholders (F541),
multi-value-repeated-key-literal (F601),
multi-value-repeated-key-variable (F602),
expressions-in-star-assignment (F621),
multiple-starred-expressions (F622),
assert-tuple (F631),
is-literal (F632),
invalid-print-syntax (F633),
if-tuple (F634),
break-outside-loop (F701),
continue-outside-loop (F702),
yield-outside-function (F704),
return-outside-function (F706),
default-except-not-last (F707),
forward-annotation-syntax-error (F722),
redefined-while-unused (F811),
undefined-name (F821),
undefined-export (F822),
undefined-local (F823),
unused-variable (F841),
unused-annotation (F842),
raise-not-implemented (F901),
]
linter.rules.should_fix = [
MultipleImportsOnOneLine,
ModuleImportNotAtTopOfFile,
MultipleStatementsOnOneLineColon,
MultipleStatementsOnOneLineSemicolon,
UselessSemicolon,
NoneComparison,
TrueFalseComparison,
NotInTest,
NotIsTest,
TypeComparison,
BareExcept,
LambdaAssignment,
AmbiguousVariableName,
AmbiguousClassName,
AmbiguousFunctionName,
IOError,
SyntaxError,
UnusedImport,
ImportShadowedByLoopVar,
UndefinedLocalWithImportStar,
LateFutureImport,
UndefinedLocalWithImportStarUsage,
UndefinedLocalWithNestedImportStarUsage,
FutureFeatureNotDefined,
PercentFormatInvalidFormat,
PercentFormatExpectedMapping,
PercentFormatExpectedSequence,
PercentFormatExtraNamedArguments,
PercentFormatMissingArgument,
PercentFormatMixedPositionalAndNamed,
PercentFormatPositionalCountMismatch,
PercentFormatStarRequiresSequence,
PercentFormatUnsupportedFormatCharacter,
StringDotFormatInvalidFormat,
StringDotFormatExtraNamedArguments,
StringDotFormatExtraPositionalArguments,
StringDotFormatMissingArguments,
StringDotFormatMixingAutomatic,
FStringMissingPlaceholders,
MultiValueRepeatedKeyLiteral,
MultiValueRepeatedKeyVariable,
ExpressionsInStarAssignment,
MultipleStarredExpressions,
AssertTuple,
IsLiteral,
InvalidPrintSyntax,
IfTuple,
BreakOutsideLoop,
ContinueOutsideLoop,
YieldOutsideFunction,
ReturnOutsideFunction,
DefaultExceptNotLast,
ForwardAnnotationSyntaxError,
RedefinedWhileUnused,
UndefinedName,
UndefinedExport,
UndefinedLocal,
UnusedVariable,
UnusedAnnotation,
RaiseNotImplemented,
multiple-imports-on-one-line (E401),
module-import-not-at-top-of-file (E402),
multiple-statements-on-one-line-colon (E701),
multiple-statements-on-one-line-semicolon (E702),
useless-semicolon (E703),
none-comparison (E711),
true-false-comparison (E712),
not-in-test (E713),
not-is-test (E714),
type-comparison (E721),
bare-except (E722),
lambda-assignment (E731),
ambiguous-variable-name (E741),
ambiguous-class-name (E742),
ambiguous-function-name (E743),
io-error (E902),
syntax-error (E999),
unused-import (F401),
import-shadowed-by-loop-var (F402),
undefined-local-with-import-star (F403),
late-future-import (F404),
undefined-local-with-import-star-usage (F405),
undefined-local-with-nested-import-star-usage (F406),
future-feature-not-defined (F407),
percent-format-invalid-format (F501),
percent-format-expected-mapping (F502),
percent-format-expected-sequence (F503),
percent-format-extra-named-arguments (F504),
percent-format-missing-argument (F505),
percent-format-mixed-positional-and-named (F506),
percent-format-positional-count-mismatch (F507),
percent-format-star-requires-sequence (F508),
percent-format-unsupported-format-character (F509),
string-dot-format-invalid-format (F521),
string-dot-format-extra-named-arguments (F522),
string-dot-format-extra-positional-arguments (F523),
string-dot-format-missing-arguments (F524),
string-dot-format-mixing-automatic (F525),
f-string-missing-placeholders (F541),
multi-value-repeated-key-literal (F601),
multi-value-repeated-key-variable (F602),
expressions-in-star-assignment (F621),
multiple-starred-expressions (F622),
assert-tuple (F631),
is-literal (F632),
invalid-print-syntax (F633),
if-tuple (F634),
break-outside-loop (F701),
continue-outside-loop (F702),
yield-outside-function (F704),
return-outside-function (F706),
default-except-not-last (F707),
forward-annotation-syntax-error (F722),
redefined-while-unused (F811),
undefined-name (F821),
undefined-export (F822),
undefined-local (F823),
unused-variable (F841),
unused-annotation (F842),
raise-not-implemented (F901),
]
linter.per_file_ignores = {}
linter.safety_table.forced_safe = []

View File

@@ -65,7 +65,7 @@ use seahash::SeaHasher;
/// The main reason is that hashes and cache keys have different constraints:
///
/// * Cache keys are less performance sensitive: Hashes must be super fast to compute for performant hashed-collections. That's
/// why some standard types don't implement [`Hash`] where it would be safe to to implement [`CacheKey`], e.g. `HashSet`
/// why some standard types don't implement [`Hash`] where it would be safe to implement [`CacheKey`], e.g. `HashSet`
/// * Cache keys must be deterministic where hash keys do not have this constraint. That's why pointers don't implement [`CacheKey`] but they implement [`Hash`].
/// * Ideally, cache keys are portable
///

View File

@@ -138,7 +138,7 @@ pub const fn empty_line() -> Line {
///
/// # Examples
///
/// The line breaks are emitted as spaces if the enclosing `Group` fits on a a single line:
/// The line breaks are emitted as spaces if the enclosing `Group` fits on a single line:
/// ```
/// use ruff_formatter::{format, format_args};
/// use ruff_formatter::prelude::*;

View File

@@ -1,6 +1,6 @@
[package]
name = "ruff_linter"
version = "0.3.7"
version = "0.4.1"
publish = false
authors = { workspace = true }
edition = { workspace = true }

View File

@@ -17,3 +17,9 @@ urllib.request.URLopener().open(fullurl='http://www.google.com')
urllib.request.URLopener().open('http://www.google.com')
urllib.request.URLopener().open('file:///foo/bar/baz')
urllib.request.URLopener().open(url)
urllib.request.urlopen(url=urllib.request.Request('http://www.google.com'))
urllib.request.urlopen(url=urllib.request.Request('http://www.google.com'), **kwargs)
urllib.request.urlopen(urllib.request.Request('http://www.google.com'))
urllib.request.urlopen(urllib.request.Request('file:///foo/bar/baz'))
urllib.request.urlopen(urllib.request.Request(url))

View File

@@ -6,6 +6,25 @@ def this_is_a_bug():
print("Ooh, callable! Or is it?")
def still_a_bug():
import builtins
o = object()
if builtins.hasattr(o, "__call__"):
print("B U G")
if builtins.getattr(o, "__call__", False):
print("B U G")
def trickier_fix_for_this_one():
o = object()
def callable(x):
return True
if hasattr(o, "__call__"):
print("STILL a bug!")
def this_is_fine():
o = object()
if callable(o):

View File

@@ -0,0 +1,20 @@
def foo(a: list = []):
raise NotImplementedError("")
def bar(a: dict = {}):
""" This one also has a docstring"""
raise NotImplementedError("and has some text in here")
def baz(a: list = []):
"""This one raises a different exception"""
raise IndexError()
def qux(a: list = []):
raise NotImplementedError
def quux(a: list = []):
raise NotImplemented

View File

@@ -23,3 +23,15 @@ def okay(data: custom.ImmutableTypeA = foo()):
def error_due_to_missing_import(data: List[str] = Depends(None)):
...
class Class:
pass
def okay(obj=Class()):
...
def error(obj=OtherClass()):
...

View File

@@ -64,3 +64,6 @@ setattr(*foo, "bar", None)
# Regression test for: https://github.com/astral-sh/ruff/issues/7455#issuecomment-1739800901
getattr(self.
registration.registry, '__name__')
import builtins
builtins.getattr(foo, "bar")

View File

@@ -23,3 +23,7 @@ zip([1, 2, 3], repeat(1, times=None))
# Errors (limited iterators).
zip([1, 2, 3], repeat(1, 1))
zip([1, 2, 3], repeat(1, times=4))
import builtins
# Still an error even though it uses the qualified name
builtins.zip([1, 2, 3])

View File

@@ -1,6 +1,9 @@
# PIE808
range(0, 10)
import builtins
builtins.range(0, 10)
# OK
range(x, 10)
range(-15, 10)

View File

@@ -73,3 +73,10 @@ class BadFive:
class BadSix:
def __exit__(self, typ, exc, tb, weird_extra_arg, extra_arg2 = None) -> None: ... # PYI036: Extra arg must have default
async def __aexit__(self, typ, exc, tb, *, weird_extra_arg) -> None: ... # PYI036: kwargs must have default
def isolated_scope():
from builtins import type as Type
class ShouldNotError:
def __exit__(self, typ: Type[BaseException] | None, exc: BaseException | None, tb: TracebackType | None) -> None: ...

View File

@@ -58,3 +58,8 @@ for key in (
.keys()
):
continue
from builtins import dict as SneakyDict
d = SneakyDict()
key in d.keys() # SIM118

View File

@@ -11,3 +11,11 @@ from enum import Enum
class Fine(str, Enum): # Ok
__slots__ = ["foo"]
class SubEnum(Enum):
pass
class Ok(str, SubEnum): # Ok
pass

View File

@@ -19,3 +19,9 @@ class Bad(Tuple[str, int, float]): # SLOT001
class Good(Tuple[str, int, float]): # OK
__slots__ = ("foo",)
import builtins
class AlsoBad(builtins.tuple[int, int]): # SLOT001
pass

View File

@@ -80,3 +80,8 @@ for i in list(foo_list): # OK
for i in list(foo_list): # OK
if True:
del foo_list[i + 1]
import builtins
for i in builtins.list(nested_tuple): # PERF101
pass

View File

@@ -81,3 +81,11 @@ def foo():
result = {}
for idx, name in enumerate(fruit):
result[name] = idx # PERF403
def foo():
from builtins import dict as SneakyDict
fruit = ["apple", "pear", "orange"]
result = SneakyDict()
for idx, name in enumerate(fruit):
result[name] = idx # PERF403

View File

@@ -445,6 +445,39 @@ def test():
# end
# no error
class Foo:
"""Demo."""
@overload
def bar(self, x: int) -> int: ...
@overload
def bar(self, x: str) -> str: ...
def bar(self, x: int | str) -> int | str:
return x
# end
# no error
@overload
def foo(x: int) -> int: ...
@overload
def foo(x: str) -> str: ...
def foo(x: int | str) -> int | str:
if not isinstance(x, (int, str)):
raise TypeError
return x
# end
# no error
def foo(self, x: int) -> int: ...
def bar(self, x: str) -> str: ...
def baz(self, x: int | str) -> int | str:
return x
# end
# E301
class Class(object):
@@ -489,6 +522,20 @@ class Class:
# end
# E301
class Foo:
"""Demo."""
@overload
def bar(self, x: int) -> int: ...
@overload
def bar(self, x: str) -> str:
...
def bar(self, x: int | str) -> int | str:
return x
# end
# E302
"""Main module."""
def fn():
@@ -580,6 +627,23 @@ class Test:
# end
# E302
class A:...
class B: ...
# end
# E302
@overload
def fn(a: int) -> int: ...
@overload
def fn(a: str) -> str: ...
def fn(a: int | str) -> int | str:
...
# end
# E303
def fn():
_ = None

View File

@@ -138,3 +138,8 @@ np.dtype(int) == float
#: E721
dtype == float
import builtins
if builtins.type(res) == memoryview: # E721
pass

View File

@@ -4,3 +4,8 @@ def f() -> None:
def g() -> None:
raise NotImplemented
def h() -> None:
NotImplementedError = "foo"
raise NotImplemented

View File

@@ -32,3 +32,6 @@ pathlib.Path(NAME).open(mode)
pathlib.Path(NAME).open("rwx") # [bad-open-mode]
pathlib.Path(NAME).open(mode="rwx") # [bad-open-mode]
pathlib.Path(NAME).open("rwx", encoding="utf-8") # [bad-open-mode]
import builtins
builtins.open(NAME, "Ua", encoding="utf-8")

View File

@@ -134,3 +134,28 @@ if value.attr > 3:
value.
attr
) = 3
class Foo:
_min = 0
_max = 0
def foo(self, value) -> None:
if value < self._min:
self._min = value
if value > self._max:
self._max = value
if self._min < value:
self._min = value
if self._max > value:
self._max = value
if value <= self._min:
self._min = value
if value >= self._max:
self._max = value
if self._min <= value:
self._min = value
if self._max >= value:
self._max = value

View File

@@ -0,0 +1,65 @@
# These testcases should raise errors
class Float:
def __bytes__(self):
return 3.05 # [invalid-bytes-return]
class Int:
def __bytes__(self):
return 0 # [invalid-bytes-return]
class Str:
def __bytes__(self):
return "some bytes" # [invalid-bytes-return]
class BytesNoReturn:
def __bytes__(self):
print("ruff") # [invalid-bytes-return]
# TODO: Once Ruff has better type checking
def return_bytes():
return "some string"
class ComplexReturn:
def __bytes__(self):
return return_bytes() # [invalid-bytes-return]
# These testcases should NOT raise errors
class Bytes:
def __bytes__(self):
return b"some bytes"
class Bytes2:
def __bytes__(self):
x = b"some bytes"
return x
class Bytes3:
def __bytes__(self): ...
class Bytes4:
def __bytes__(self):
pass
class Bytes5:
def __bytes__(self):
raise NotImplementedError
class Bytes6:
def __bytes__(self):
print("raise some error")
raise NotImplementedError

View File

@@ -0,0 +1,65 @@
# These testcases should raise errors
class Bool:
def __hash__(self):
return True # [invalid-hash-return]
class Float:
def __hash__(self):
return 3.05 # [invalid-hash-return]
class Str:
def __hash__(self):
return "ruff" # [invalid-hash-return]
class HashNoReturn:
def __hash__(self):
print("ruff") # [invalid-hash-return]
# TODO: Once Ruff has better type checking
def return_int():
return "3"
class ComplexReturn:
def __hash__(self):
return return_int() # [invalid-hash-return]
# These testcases should NOT raise errors
class Hash:
def __hash__(self):
return 7741
class Hash2:
def __hash__(self):
x = 7741
return x
class Hash3:
def __hash__(self): ...
class Has4:
def __hash__(self):
pass
class Hash5:
def __hash__(self):
raise NotImplementedError
class HashWrong6:
def __hash__(self):
print("raise some error")
raise NotImplementedError

View File

@@ -0,0 +1,73 @@
# These testcases should raise errors
class Bool:
"""pylint would not raise, but ruff does - see explanation in the docs"""
def __index__(self):
return True # [invalid-index-return]
class Float:
def __index__(self):
return 3.05 # [invalid-index-return]
class Dict:
def __index__(self):
return {"1": "1"} # [invalid-index-return]
class Str:
def __index__(self):
return "ruff" # [invalid-index-return]
class IndexNoReturn:
def __index__(self):
print("ruff") # [invalid-index-return]
# TODO: Once Ruff has better type checking
def return_index():
return "3"
class ComplexReturn:
def __index__(self):
return return_index() # [invalid-index-return]
# These testcases should NOT raise errors
class Index:
def __index__(self):
return 0
class Index2:
def __index__(self):
x = 1
return x
class Index3:
def __index__(self):
...
class Index4:
def __index__(self):
pass
class Index5:
def __index__(self):
raise NotImplementedError
class Index6:
def __index__(self):
print("raise some error")
raise NotImplementedError

View File

@@ -0,0 +1,70 @@
# These testcases should raise errors
class Bool:
def __len__(self):
return True # [invalid-length-return]
class Float:
def __len__(self):
return 3.05 # [invalid-length-return]
class Str:
def __len__(self):
return "ruff" # [invalid-length-return]
class LengthNoReturn:
def __len__(self):
print("ruff") # [invalid-length-return]
class LengthNegative:
def __len__(self):
return -42 # [invalid-length-return]
# TODO: Once Ruff has better type checking
def return_int():
return "3"
class ComplexReturn:
def __len__(self):
return return_int() # [invalid-length-return]
# These testcases should NOT raise errors
class Length:
def __len__(self):
return 42
class Length2:
def __len__(self):
x = 42
return x
class Length3:
def __len__(self): ...
class Length4:
def __len__(self):
pass
class Length5:
def __len__(self):
raise NotImplementedError
class Length6:
def __len__(self):
print("raise some error")
raise NotImplementedError

View File

@@ -1,36 +1,60 @@
# These testcases should raise errors
class Float:
def __str__(self):
return 3.05
class Int:
def __str__(self):
return 1
class Int2:
def __str__(self):
return 0
class Bool:
def __str__(self):
return False
# TODO: Once Ruff has better type checking
def return_int():
return 3
class ComplexReturn:
def __str__(self):
return return_int()
# These testcases should NOT raise errors
class Str:
def __str__(self):
return "ruff"
class Str2:
def __str__(self):
x = "ruff"
return x
class Str3:
def __str__(self): ...
class Str4:
def __str__(self):
raise RuntimeError("__str__ not allowed")
class Str5:
def __str__(self): # PLE0307 (returns None if x <= 0)
if x > 0:
raise RuntimeError("__str__ not allowed")

View File

@@ -47,6 +47,12 @@ if y == np.nan:
if y == npy_nan:
pass
import builtins
# PLW0117
if x == builtins.float("nan"):
pass
# OK
if math.isnan(x):
pass

View File

@@ -39,3 +39,6 @@ max(max(tuples_list))
# Starred argument should be copied as it is.
max(1, max(*a))
import builtins
builtins.min(1, min(2, 3))

View File

@@ -18,6 +18,8 @@ index = index - 1
a_list = a_list + ["to concat"]
some_set = some_set | {"to concat"}
to_multiply = to_multiply * 5
to_multiply = 5 * to_multiply
to_multiply = to_multiply * to_multiply
to_divide = to_divide / 5
to_divide = to_divide // 5
to_cube = to_cube**3
@@ -53,3 +55,4 @@ a_list[0] = a_list[:] * 3
index = a_number = a_number + 1
a_number = index = a_number + 1
index = index * index + 10
some_string = "a very long start to the string" + some_string

View File

@@ -0,0 +1,43 @@
class Fruit:
@classmethod
def list_fruits(cls) -> None:
cls = "apple" # PLW0642
cls: Fruit = "apple" # PLW0642
cls += "orange" # PLW0642
*cls = "banana" # PLW0642
cls, blah = "apple", "orange" # PLW0642
blah, (cls, blah2) = "apple", ("orange", "banana") # PLW0642
blah, [cls, blah2] = "apple", ("orange", "banana") # PLW0642
@classmethod
def add_fruits(cls, fruits, /) -> None:
cls = fruits # PLW0642
def print_color(self) -> None:
self = "red" # PLW0642
self: Self = "red" # PLW0642
self += "blue" # PLW0642
*self = "blue" # PLW0642
self, blah = "red", "blue" # PLW0642
blah, (self, blah2) = "apple", ("orange", "banana") # PLW0642
blah, [self, blah2] = "apple", ("orange", "banana") # PLW0642
def print_color(self, color, /) -> None:
self = color
def ok(self) -> None:
cls = None # OK because the rule looks for the name in the signature
@classmethod
def ok(cls) -> None:
self = None
@staticmethod
def list_fruits_static(self, cls) -> None:
self = "apple" # Ok
cls = "banana" # Ok
def list_fruits(self, cls) -> None:
self = "apple" # Ok
cls = "banana" # Ok

View File

@@ -152,3 +152,9 @@ object = A
class B(object):
...
import builtins
class Unusual(builtins.object):
...

View File

@@ -59,6 +59,21 @@ with open("file.txt", "w", newline="\r\n") as f:
f.write(foobar)
import builtins
# FURB103
with builtins.open("file.txt", "w", newline="\r\n") as f:
f.write(foobar)
from builtins import open as o
# FURB103
with o("file.txt", "w", newline="\r\n") as f:
f.write(foobar)
# Non-errors.
with open("file.txt", errors="ignore", mode="wb") as f:

View File

@@ -41,6 +41,22 @@ def func():
pass
import builtins
with builtins.open("FURB129.py") as f:
for line in f.readlines():
pass
from builtins import open as o
with o("FURB129.py") as f:
for line in f.readlines():
pass
# False positives
def func(f):
for _line in f.readlines():

View File

@@ -49,6 +49,14 @@ def yes_five(x: Dict[int, str]):
x = 1
from builtins import list as SneakyList
sneaky = SneakyList()
# FURB131
del sneaky[:]
# these should not
del names["key"]

View File

@@ -12,6 +12,8 @@ dict.fromkeys(pierogi_fillings, {})
dict.fromkeys(pierogi_fillings, set())
dict.fromkeys(pierogi_fillings, {"pre": "populated!"})
dict.fromkeys(pierogi_fillings, dict())
import builtins
builtins.dict.fromkeys(pierogi_fillings, dict())
# Okay.
dict.fromkeys(pierogi_fillings)

View File

@@ -0,0 +1,71 @@
import time
import asyncio
async def pass_1a(): # OK: awaits a coroutine
await asyncio.sleep(1)
async def pass_1b(): # OK: awaits a coroutine
def foo(optional_arg=await bar()):
pass
async def pass_2(): # OK: uses an async context manager
async with None as i:
pass
async def pass_3(): # OK: uses an async loop
async for i in []:
pass
class Foo:
async def pass_4(self): # OK: method of a class
pass
def foo():
async def pass_5(): # OK: uses an await
await bla
async def pass_6(): # OK: just a stub
...
async def fail_1a(): # RUF029
time.sleep(1)
async def fail_1b(): # RUF029: yield does not require async
yield "hello"
async def fail_2(): # RUF029
with None as i:
pass
async def fail_3(): # RUF029
for i in []:
pass
return foo
async def fail_4a(): # RUF029: the /outer/ function does not await
async def foo():
await bla
async def fail_4b(): # RUF029: the /outer/ function does not await
class Foo:
async def foo(self):
await bla
def foo():
async def fail_4c(): # RUF029: the /inner/ function does not await
pass

View File

@@ -1,2 +1,3 @@
import os
import foo # noqa: F401
import bar # noqa

View File

@@ -247,7 +247,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
}
}
}
ExprContext::Del => {}
_ => {}
}
if checker.enabled(Rule::SixPY3) {
flake8_2020::rules::name_or_attribute(checker, expr);

View File

@@ -95,10 +95,22 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
}
}
if checker.enabled(Rule::InvalidBoolReturnType) {
pylint::rules::invalid_bool_return(checker, name, body);
pylint::rules::invalid_bool_return(checker, function_def);
}
if checker.enabled(Rule::InvalidLengthReturnType) {
pylint::rules::invalid_length_return(checker, function_def);
}
if checker.enabled(Rule::InvalidBytesReturnType) {
pylint::rules::invalid_bytes_return(checker, function_def);
}
if checker.enabled(Rule::InvalidIndexReturnType) {
pylint::rules::invalid_index_return(checker, function_def);
}
if checker.enabled(Rule::InvalidHashReturnType) {
pylint::rules::invalid_hash_return(checker, function_def);
}
if checker.enabled(Rule::InvalidStrReturnType) {
pylint::rules::invalid_str_return(checker, name, body);
pylint::rules::invalid_str_return(checker, function_def);
}
if checker.enabled(Rule::InvalidFunctionName) {
if let Some(diagnostic) = pep8_naming::rules::invalid_function_name(
@@ -146,7 +158,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
decorator_list,
returns.as_ref().map(AsRef::as_ref),
parameters,
type_params.as_ref(),
type_params.as_deref(),
);
}
if checker.source_type.is_stub() {
@@ -348,6 +360,9 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
if checker.enabled(Rule::SslWithBadDefaults) {
flake8_bandit::rules::ssl_with_bad_defaults(checker, function_def);
}
if checker.enabled(Rule::UnusedAsync) {
ruff::rules::unused_async(checker, function_def);
}
}
Stmt::Return(_) => {
if checker.enabled(Rule::ReturnOutsideFunction) {
@@ -606,7 +621,8 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
pylint::rules::manual_from_import(checker, stmt, alias, names);
}
if checker.enabled(Rule::ImportSelf) {
if let Some(diagnostic) = pylint::rules::import_self(alias, checker.module_path)
if let Some(diagnostic) =
pylint::rules::import_self(alias, checker.module.qualified_name())
{
checker.diagnostics.push(diagnostic);
}
@@ -770,9 +786,11 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
flake8_bandit::rules::suspicious_imports(checker, stmt);
}
if checker.enabled(Rule::BannedApi) {
if let Some(module) =
helpers::resolve_imported_module_path(level, module, checker.module_path)
{
if let Some(module) = helpers::resolve_imported_module_path(
level,
module,
checker.module.qualified_name(),
) {
flake8_tidy_imports::rules::banned_api(
checker,
&flake8_tidy_imports::matchers::NameMatchPolicy::MatchNameOrParent(
@@ -799,9 +817,11 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
}
}
if checker.enabled(Rule::BannedModuleLevelImports) {
if let Some(module) =
helpers::resolve_imported_module_path(level, module, checker.module_path)
{
if let Some(module) = helpers::resolve_imported_module_path(
level,
module,
checker.module.qualified_name(),
) {
flake8_tidy_imports::rules::banned_module_level_imports(
checker,
&flake8_tidy_imports::matchers::NameMatchPolicy::MatchNameOrParent(
@@ -888,7 +908,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
stmt,
level,
module,
checker.module_path,
checker.module.qualified_name(),
checker.settings.flake8_tidy_imports.ban_relative_imports,
) {
checker.diagnostics.push(diagnostic);
@@ -987,9 +1007,12 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
}
}
if checker.enabled(Rule::ImportSelf) {
if let Some(diagnostic) =
pylint::rules::import_from_self(level, module, names, checker.module_path)
{
if let Some(diagnostic) = pylint::rules::import_from_self(
level,
module,
names,
checker.module.qualified_name(),
) {
checker.diagnostics.push(diagnostic);
}
}
@@ -1055,6 +1078,9 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
}
}
Stmt::AugAssign(aug_assign @ ast::StmtAugAssign { target, .. }) => {
if checker.enabled(Rule::SelfOrClsAssignment) {
pylint::rules::self_or_cls_assignment(checker, target);
}
if checker.enabled(Rule::GlobalStatement) {
if let Expr::Name(ast::ExprName { id, .. }) = target.as_ref() {
pylint::rules::global_statement(checker, id);
@@ -1410,6 +1436,11 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
}
}
Stmt::Assign(assign @ ast::StmtAssign { targets, value, .. }) => {
if checker.enabled(Rule::SelfOrClsAssignment) {
for target in targets {
pylint::rules::self_or_cls_assignment(checker, target);
}
}
if checker.enabled(Rule::RedeclaredAssignedName) {
pylint::rules::redeclared_assigned_name(checker, targets);
}
@@ -1547,6 +1578,9 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
);
}
}
if checker.enabled(Rule::SelfOrClsAssignment) {
pylint::rules::self_or_cls_assignment(checker, target);
}
if checker.enabled(Rule::SelfAssigningVariable) {
pylint::rules::self_annotated_assignment(checker, assign_stmt);
}

View File

@@ -33,4 +33,7 @@ pub(crate) fn string_like(string_like: StringLike, checker: &mut Checker) {
if checker.enabled(Rule::UnnecessaryEscapedQuote) {
flake8_quotes::rules::unnecessary_escaped_quote(checker, string_like);
}
if checker.enabled(Rule::AvoidableEscapedQuote) && checker.settings.flake8_quotes.avoid_escape {
flake8_quotes::rules::avoidable_escaped_quote(checker, string_like);
}
}

View File

@@ -50,11 +50,11 @@ use ruff_python_ast::{helpers, str, visitor, PySourceType};
use ruff_python_codegen::{Generator, Stylist};
use ruff_python_index::Indexer;
use ruff_python_parser::typing::{parse_type_annotation, AnnotationKind};
use ruff_python_semantic::analyze::{imports, typing, visibility};
use ruff_python_semantic::analyze::{imports, typing};
use ruff_python_semantic::{
BindingFlags, BindingId, BindingKind, Exceptions, Export, FromImport, Globals, Import, Module,
ModuleKind, NodeId, ScopeId, ScopeKind, SemanticModel, SemanticModelFlags, StarImport,
SubmoduleImport,
ModuleKind, ModuleSource, NodeId, ScopeId, ScopeKind, SemanticModel, SemanticModelFlags,
StarImport, SubmoduleImport,
};
use ruff_python_stdlib::builtins::{IPYTHON_BUILTINS, MAGIC_GLOBALS, PYTHON_BUILTINS};
use ruff_source_file::{Locator, OneIndexed, SourceRow};
@@ -110,7 +110,7 @@ pub(crate) struct Checker<'a> {
/// The [`Path`] to the package containing the current file.
package: Option<&'a Path>,
/// The module representation of the current file (e.g., `foo.bar`).
module_path: Option<&'a [String]>,
module: Module<'a>,
/// The [`PySourceType`] of the current file.
pub(crate) source_type: PySourceType,
/// The [`CellOffsets`] for the current file, if it's a Jupyter notebook.
@@ -174,7 +174,7 @@ impl<'a> Checker<'a> {
noqa,
path,
package,
module_path: module.path(),
module,
source_type,
locator,
stylist,
@@ -784,17 +784,13 @@ impl<'a> Visitor<'a> for Checker<'a> {
}) => {
let mut handled_exceptions = Exceptions::empty();
for type_ in extract_handled_exceptions(handlers) {
if let Some(qualified_name) = self.semantic.resolve_qualified_name(type_) {
match qualified_name.segments() {
["", "NameError"] => {
handled_exceptions |= Exceptions::NAME_ERROR;
}
["", "ModuleNotFoundError"] => {
if let Some(builtins_name) = self.semantic.resolve_builtin_symbol(type_) {
match builtins_name {
"NameError" => handled_exceptions |= Exceptions::NAME_ERROR,
"ModuleNotFoundError" => {
handled_exceptions |= Exceptions::MODULE_NOT_FOUND_ERROR;
}
["", "ImportError"] => {
handled_exceptions |= Exceptions::IMPORT_ERROR;
}
"ImportError" => handled_exceptions |= Exceptions::IMPORT_ERROR,
_ => {}
}
}
@@ -1002,6 +998,7 @@ impl<'a> Visitor<'a> for Checker<'a> {
ExprContext::Load => self.handle_node_load(expr),
ExprContext::Store => self.handle_node_store(id, expr),
ExprContext::Del => self.handle_node_delete(expr),
ExprContext::Invalid => {}
},
_ => {}
}
@@ -1125,7 +1122,8 @@ impl<'a> Visitor<'a> for Checker<'a> {
]
) {
Some(typing::Callable::MypyExtension)
} else if matches!(qualified_name.segments(), ["", "bool"]) {
} else if matches!(qualified_name.segments(), ["" | "builtins", "bool"])
{
Some(typing::Callable::Bool)
} else {
None
@@ -1912,7 +1910,7 @@ impl<'a> Checker<'a> {
}
{
let (all_names, all_flags) =
extract_all_names(parent, |name| self.semantic.is_builtin(name));
extract_all_names(parent, |name| self.semantic.has_builtin_binding(name));
if all_flags.intersects(DunderAllFlags::INVALID_OBJECT) {
flags |= BindingFlags::INVALID_ALL_OBJECT;
@@ -2285,10 +2283,15 @@ pub(crate) fn check_ast(
} else {
ModuleKind::Module
},
source: if let Some(module_path) = module_path.as_ref() {
visibility::ModuleSource::Path(module_path)
name: if let Some(module_path) = &module_path {
module_path.last().map(String::as_str)
} else {
visibility::ModuleSource::File(path)
path.file_stem().and_then(std::ffi::OsStr::to_str)
},
source: if let Some(module_path) = module_path.as_ref() {
ModuleSource::Path(module_path)
} else {
ModuleSource::File(path)
},
python_ast,
};

View File

@@ -111,6 +111,7 @@ pub(crate) fn check_noqa(
FileExemption::All => true,
FileExemption::Codes(codes) => codes.contains(&Rule::UnusedNOQA.noqa_code()),
})
&& !per_file_ignores.contains(Rule::UnusedNOQA)
{
for line in noqa_directives.lines() {
match &line.directive {
@@ -129,7 +130,7 @@ pub(crate) fn check_noqa(
let mut unknown_codes = vec![];
let mut unmatched_codes = vec![];
let mut valid_codes = vec![];
let mut self_ignore = per_file_ignores.contains(Rule::UnusedNOQA);
let mut self_ignore = false;
for code in directive.codes() {
let code = get_redirect_target(code).unwrap_or(code);
if Rule::UnusedNOQA.noqa_code() == code {

View File

@@ -16,7 +16,7 @@ use crate::registry::{AsRule, Rule};
use crate::rules::pycodestyle::rules::BlankLinesChecker;
use crate::rules::{
eradicate, flake8_commas, flake8_executable, flake8_fixme, flake8_implicit_str_concat,
flake8_pyi, flake8_quotes, flake8_todos, pycodestyle, pygrep_hooks, pylint, pyupgrade, ruff,
flake8_pyi, flake8_todos, pycodestyle, pygrep_hooks, pylint, pyupgrade, ruff,
};
use crate::settings::LinterSettings;
@@ -122,10 +122,6 @@ pub(crate) fn check_tokens(
);
}
if settings.rules.enabled(Rule::AvoidableEscapedQuote) && settings.flake8_quotes.avoid_escape {
flake8_quotes::rules::avoidable_escaped_quote(&mut diagnostics, tokens, locator, settings);
}
if settings.rules.any_enabled(&[
Rule::SingleLineImplicitStringConcatenation,
Rule::MultiLineImplicitStringConcatenation,

View File

@@ -241,8 +241,12 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
(Pylint, "E0237") => (RuleGroup::Stable, rules::pylint::rules::NonSlotAssignment),
(Pylint, "E0241") => (RuleGroup::Stable, rules::pylint::rules::DuplicateBases),
(Pylint, "E0302") => (RuleGroup::Stable, rules::pylint::rules::UnexpectedSpecialMethodSignature),
(Pylint, "E0303") => (RuleGroup::Preview, rules::pylint::rules::InvalidLengthReturnType),
(Pylint, "E0304") => (RuleGroup::Preview, rules::pylint::rules::InvalidBoolReturnType),
(Pylint, "E0305") => (RuleGroup::Preview, rules::pylint::rules::InvalidIndexReturnType),
(Pylint, "E0307") => (RuleGroup::Stable, rules::pylint::rules::InvalidStrReturnType),
(Pylint, "E0308") => (RuleGroup::Preview, rules::pylint::rules::InvalidBytesReturnType),
(Pylint, "E0309") => (RuleGroup::Preview, rules::pylint::rules::InvalidHashReturnType),
(Pylint, "E0604") => (RuleGroup::Stable, rules::pylint::rules::InvalidAllObject),
(Pylint, "E0605") => (RuleGroup::Stable, rules::pylint::rules::InvalidAllFormat),
(Pylint, "E0643") => (RuleGroup::Preview, rules::pylint::rules::PotentialIndexError),
@@ -311,6 +315,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
(Pylint, "W0602") => (RuleGroup::Stable, rules::pylint::rules::GlobalVariableNotAssigned),
(Pylint, "W0603") => (RuleGroup::Stable, rules::pylint::rules::GlobalStatement),
(Pylint, "W0604") => (RuleGroup::Preview, rules::pylint::rules::GlobalAtModuleLevel),
(Pylint, "W0642") => (RuleGroup::Preview, rules::pylint::rules::SelfOrClsAssignment),
(Pylint, "W0711") => (RuleGroup::Stable, rules::pylint::rules::BinaryOpException),
(Pylint, "W1501") => (RuleGroup::Preview, rules::pylint::rules::BadOpenMode),
(Pylint, "W1508") => (RuleGroup::Stable, rules::pylint::rules::InvalidEnvvarDefault),
@@ -962,6 +967,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
(Ruff, "026") => (RuleGroup::Preview, rules::ruff::rules::DefaultFactoryKwarg),
(Ruff, "027") => (RuleGroup::Preview, rules::ruff::rules::MissingFStringSyntax),
(Ruff, "028") => (RuleGroup::Preview, rules::ruff::rules::InvalidFormatterSuppressionComment),
(Ruff, "029") => (RuleGroup::Preview, rules::ruff::rules::UnusedAsync),
(Ruff, "100") => (RuleGroup::Stable, rules::ruff::rules::UnusedNOQA),
(Ruff, "200") => (RuleGroup::Stable, rules::ruff::rules::InvalidPyprojectToml),
#[cfg(feature = "test-rules")]

View File

@@ -229,6 +229,31 @@ impl<'a> Importer<'a> {
.map_or_else(|| self.import_symbol(symbol, at, None, semantic), Ok)
}
/// For a given builtin symbol, determine whether an [`Edit`] is necessary to make the symbol
/// available in the current scope. For example, if `zip` has been overridden in the relevant
/// scope, the `builtins` module will need to be imported in order for a `Fix` to reference
/// `zip`; but otherwise, that won't be necessary.
///
/// Returns a two-item tuple. The first item is either `Some(Edit)` (indicating) that an
/// edit is necessary to make the symbol available, or `None`, indicating that the symbol has
/// not been overridden in the current scope. The second item in the tuple is the bound name
/// of the symbol.
///
/// Attempts to reuse existing imports when possible.
pub(crate) fn get_or_import_builtin_symbol(
&self,
symbol: &str,
at: TextSize,
semantic: &SemanticModel,
) -> Result<(Option<Edit>, String), ResolutionError> {
if semantic.has_builtin_binding(symbol) {
return Ok((None, symbol.to_string()));
}
let (import_edit, binding) =
self.get_or_import_symbol(&ImportRequest::import("builtins", symbol), at, semantic)?;
Ok((Some(import_edit), binding))
}
/// Return the [`ImportedName`] to for existing symbol, if it's present in the given [`SemanticModel`].
fn find_symbol(
symbol: &ImportRequest,

View File

@@ -1,122 +0,0 @@
//! Extract docstrings via tokenization.
//!
//! See: <https://github.com/zheller/flake8-quotes/blob/ef0d9a90249a080e460b70ab62bf4b65e5aa5816/flake8_quotes/docstring_detection.py#L29>
//!
//! TODO(charlie): Consolidate with the existing AST-based docstring extraction.
use ruff_python_parser::Tok;
#[derive(Default, Copy, Clone)]
enum State {
// Start of the module: first string gets marked as a docstring.
#[default]
ExpectModuleDocstring,
// After seeing a class definition, we're waiting for the block colon (and do bracket
// counting).
ExpectClassColon,
// After seeing the block colon in a class definition, we expect a docstring.
ExpectClassDocstring,
// Same as ExpectClassColon, but for function definitions.
ExpectFunctionColon,
// Same as ExpectClassDocstring, but for function definitions.
ExpectFunctionDocstring,
// Skip tokens until we observe a `class` or `def`.
Other,
}
#[derive(Default)]
pub(crate) struct StateMachine {
state: State,
bracket_count: usize,
}
impl StateMachine {
pub(crate) fn consume(&mut self, tok: &Tok) -> bool {
match tok {
Tok::NonLogicalNewline
| Tok::Newline
| Tok::Indent
| Tok::Dedent
| Tok::Comment(..) => false,
Tok::String { .. } => {
if matches!(
self.state,
State::ExpectModuleDocstring
| State::ExpectClassDocstring
| State::ExpectFunctionDocstring
) {
self.state = State::Other;
true
} else {
false
}
}
Tok::Class => {
self.state = State::ExpectClassColon;
self.bracket_count = 0;
false
}
Tok::Def => {
self.state = State::ExpectFunctionColon;
self.bracket_count = 0;
false
}
Tok::Colon => {
if self.bracket_count == 0 {
if matches!(self.state, State::ExpectClassColon) {
self.state = State::ExpectClassDocstring;
} else if matches!(self.state, State::ExpectFunctionColon) {
self.state = State::ExpectFunctionDocstring;
}
}
false
}
Tok::Lpar | Tok::Lbrace | Tok::Lsqb => {
self.bracket_count = self.bracket_count.saturating_add(1);
if matches!(
self.state,
State::ExpectModuleDocstring
| State::ExpectClassDocstring
| State::ExpectFunctionDocstring
) {
self.state = State::Other;
}
false
}
Tok::Rpar | Tok::Rbrace | Tok::Rsqb => {
self.bracket_count = self.bracket_count.saturating_sub(1);
if matches!(
self.state,
State::ExpectModuleDocstring
| State::ExpectClassDocstring
| State::ExpectFunctionDocstring
) {
self.state = State::Other;
}
false
}
_ => {
if matches!(
self.state,
State::ExpectModuleDocstring
| State::ExpectClassDocstring
| State::ExpectFunctionDocstring
) {
self.state = State::Other;
}
false
}
}
}
}

View File

@@ -1 +0,0 @@
pub(crate) mod docstring_detection;

View File

@@ -24,7 +24,6 @@ mod docstrings;
mod fix;
pub mod fs;
mod importer;
mod lex;
pub mod line_width;
pub mod linter;
pub mod logging;

View File

@@ -194,7 +194,7 @@ impl DisplayParseError {
// Translate the byte offset to a location in the originating source.
let location =
if let Some(jupyter_index) = source_kind.as_ipy_notebook().map(Notebook::index) {
let source_location = source_code.source_location(error.offset);
let source_location = source_code.source_location(error.location.start());
ErrorLocation::Cell(
jupyter_index
@@ -208,7 +208,7 @@ impl DisplayParseError {
},
)
} else {
ErrorLocation::File(source_code.source_location(error.offset))
ErrorLocation::File(source_code.source_location(error.location.start()))
};
Self {
@@ -275,27 +275,7 @@ impl<'a> DisplayParseErrorType<'a> {
impl Display for DisplayParseErrorType<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self.0 {
ParseErrorType::Eof => write!(f, "Expected token but reached end of file."),
ParseErrorType::ExtraToken(ref tok) => write!(
f,
"Got extraneous token: {tok}",
tok = TruncateAtNewline(&tok)
),
ParseErrorType::InvalidToken => write!(f, "Got invalid token"),
ParseErrorType::UnrecognizedToken(ref tok, ref expected) => {
if let Some(expected) = expected.as_ref() {
write!(
f,
"Expected '{expected}', but got {tok}",
tok = TruncateAtNewline(&tok)
)
} else {
write!(f, "Unexpected token {tok}", tok = TruncateAtNewline(&tok))
}
}
ParseErrorType::Lexical(ref error) => write!(f, "{error}"),
}
write!(f, "{}", TruncateAtNewline(&self.0))
}
}

View File

@@ -256,7 +256,6 @@ impl Rule {
| Rule::MixedSpacesAndTabs
| Rule::TrailingWhitespace => LintSource::PhysicalLines,
Rule::AmbiguousUnicodeCharacterComment
| Rule::AvoidableEscapedQuote
| Rule::BlanketNOQA
| Rule::BlanketTypeIgnore
| Rule::BlankLineAfterDecorator

View File

@@ -3,7 +3,7 @@ use ruff_macros::CacheKey;
use std::fmt::{Debug, Display, Formatter};
use std::iter::FusedIterator;
const RULESET_SIZE: usize = 13;
const RULESET_SIZE: usize = 14;
/// A set of [`Rule`]s.
///
@@ -234,6 +234,7 @@ impl RuleSet {
/// assert!(set.contains(Rule::AmbiguousFunctionName));
/// assert!(!set.contains(Rule::BreakOutsideLoop));
/// ```
#[inline]
pub const fn contains(&self, rule: Rule) -> bool {
let rule = rule as u16;
let index = rule as usize / Self::SLICE_BITS as usize;
@@ -243,6 +244,20 @@ impl RuleSet {
self.0[index] & mask != 0
}
/// Returns `true` if any of the rules in `rules` are in this set.
#[inline]
pub const fn any(&self, rules: &[Rule]) -> bool {
let mut any = false;
let mut i = 0;
while i < rules.len() {
any |= self.contains(rules[i]);
i += 1;
}
any
}
/// Returns an iterator over the rules in this set.
///
/// ## Examples
@@ -276,7 +291,9 @@ impl Display for RuleSet {
} else {
writeln!(f, "[")?;
for rule in self {
writeln!(f, "\t{rule:?},")?;
let name = rule.as_ref();
let code = rule.noqa_code();
writeln!(f, "\t{name} ({code}),")?;
}
write!(f, "]")?;
}

View File

@@ -63,7 +63,7 @@ fn is_open_sleep_or_subprocess_call(func: &Expr, semantic: &SemanticModel) -> bo
.is_some_and(|qualified_name| {
matches!(
qualified_name.segments(),
["", "open"]
["" | "builtins", "open"]
| ["time", "sleep"]
| [
"subprocess",

View File

@@ -24,23 +24,13 @@ pub(super) fn is_untyped_exception(type_: Option<&Expr>, semantic: &SemanticMode
if let Expr::Tuple(ast::ExprTuple { elts, .. }) = &type_ {
elts.iter().any(|type_| {
semantic
.resolve_qualified_name(type_)
.is_some_and(|qualified_name| {
matches!(
qualified_name.segments(),
["", "Exception" | "BaseException"]
)
})
.resolve_builtin_symbol(type_)
.is_some_and(|builtin| matches!(builtin, "Exception" | "BaseException"))
})
} else {
semantic
.resolve_qualified_name(type_)
.is_some_and(|qualified_name| {
matches!(
qualified_name.segments(),
["", "Exception" | "BaseException"]
)
})
.resolve_builtin_symbol(type_)
.is_some_and(|builtin| matches!(builtin, "Exception" | "BaseException"))
}
})
}

View File

@@ -464,7 +464,7 @@ fn find_shell_keyword<'a>(
semantic: &SemanticModel,
) -> Option<ShellKeyword<'a>> {
arguments.find_keyword("shell").map(|keyword| ShellKeyword {
truthiness: Truthiness::from_expr(&keyword.value, |id| semantic.is_builtin(id)),
truthiness: Truthiness::from_expr(&keyword.value, |id| semantic.has_builtin_binding(id)),
keyword,
})
}

View File

@@ -344,7 +344,7 @@ impl Violation for SuspiciousMarkSafeUsage {
}
/// ## What it does
/// Checks for uses of URL open functions that unexpected schemes.
/// Checks for instances where URL open functions are used with unexpected schemes.
///
/// ## Why is this bad?
/// Some URL open functions allow the use of `file:` or custom schemes (for use
@@ -849,15 +849,45 @@ pub(crate) fn suspicious_function_call(checker: &mut Checker, call: &ExprCall) {
["" | "builtins", "eval"] => Some(SuspiciousEvalUsage.into()),
// MarkSafe
["django", "utils", "safestring" | "html", "mark_safe"] => Some(SuspiciousMarkSafeUsage.into()),
// URLOpen (`urlopen`, `urlretrieve`, `Request`)
["urllib", "request", "urlopen" | "urlretrieve" | "Request"] |
["six", "moves", "urllib", "request", "urlopen" | "urlretrieve" | "Request"] => {
// URLOpen (`Request`)
["urllib", "request","Request"] |
["six", "moves", "urllib", "request","Request"] => {
// If the `url` argument is a string literal, allow `http` and `https` schemes.
if call.arguments.args.iter().all(|arg| !arg.is_starred_expr()) && call.arguments.keywords.iter().all(|keyword| keyword.arg.is_some()) {
if let Some(Expr::StringLiteral(ast::ExprStringLiteral { value, .. })) = &call.arguments.find_argument("url", 0) {
let url = value.to_str().trim_start();
if url.starts_with("http://") || url.starts_with("https://") {
return None;
let url = value.to_str().trim_start();
if url.starts_with("http://") || url.starts_with("https://") {
return None;
}
}
}
Some(SuspiciousURLOpenUsage.into())
}
// URLOpen (`urlopen`, `urlretrieve`)
["urllib", "request", "urlopen" | "urlretrieve" ] |
["six", "moves", "urllib", "request", "urlopen" | "urlretrieve" ] => {
if call.arguments.args.iter().all(|arg| !arg.is_starred_expr()) && call.arguments.keywords.iter().all(|keyword| keyword.arg.is_some()) {
if let Some(arg) = &call.arguments.find_argument("url", 0) {
// If the `url` argument is a string literal, allow `http` and `https` schemes.
if let Expr::StringLiteral(ast::ExprStringLiteral { value, .. }) = arg {
let url = value.to_str().trim_start();
if url.starts_with("http://") || url.starts_with("https://") {
return None;
}
}
// If the `url` argument is a `urllib.request.Request` object, allow `http` and `https` schemes.
if let Expr::Call(ExprCall { func, arguments, .. }) = arg {
if checker.semantic().resolve_qualified_name(func.as_ref()).is_some_and(|name| name.segments() == ["urllib", "request", "Request"]) {
if let Some( Expr::StringLiteral(ast::ExprStringLiteral { value, .. })) = arguments.find_argument("url", 0) {
let url = value.to_str().trim_start();
if url.starts_with("http://") || url.starts_with("https://") {
return None;
}
}
}
}
}
}

View File

@@ -102,6 +102,49 @@ S310.py:19:1: S310 Audit URL open for permitted schemes. Allowing use of `file:`
18 | urllib.request.URLopener().open('file:///foo/bar/baz')
19 | urllib.request.URLopener().open(url)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ S310
20 |
21 | urllib.request.urlopen(url=urllib.request.Request('http://www.google.com'))
|
S310.py:22:1: S310 Audit URL open for permitted schemes. Allowing use of `file:` or custom schemes is often unexpected.
|
21 | urllib.request.urlopen(url=urllib.request.Request('http://www.google.com'))
22 | urllib.request.urlopen(url=urllib.request.Request('http://www.google.com'), **kwargs)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ S310
23 | urllib.request.urlopen(urllib.request.Request('http://www.google.com'))
24 | urllib.request.urlopen(urllib.request.Request('file:///foo/bar/baz'))
|
S310.py:24:1: S310 Audit URL open for permitted schemes. Allowing use of `file:` or custom schemes is often unexpected.
|
22 | urllib.request.urlopen(url=urllib.request.Request('http://www.google.com'), **kwargs)
23 | urllib.request.urlopen(urllib.request.Request('http://www.google.com'))
24 | urllib.request.urlopen(urllib.request.Request('file:///foo/bar/baz'))
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ S310
25 | urllib.request.urlopen(urllib.request.Request(url))
|
S310.py:24:24: S310 Audit URL open for permitted schemes. Allowing use of `file:` or custom schemes is often unexpected.
|
22 | urllib.request.urlopen(url=urllib.request.Request('http://www.google.com'), **kwargs)
23 | urllib.request.urlopen(urllib.request.Request('http://www.google.com'))
24 | urllib.request.urlopen(urllib.request.Request('file:///foo/bar/baz'))
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ S310
25 | urllib.request.urlopen(urllib.request.Request(url))
|
S310.py:25:1: S310 Audit URL open for permitted schemes. Allowing use of `file:` or custom schemes is often unexpected.
|
23 | urllib.request.urlopen(urllib.request.Request('http://www.google.com'))
24 | urllib.request.urlopen(urllib.request.Request('file:///foo/bar/baz'))
25 | urllib.request.urlopen(urllib.request.Request(url))
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ S310
|
S310.py:25:24: S310 Audit URL open for permitted schemes. Allowing use of `file:` or custom schemes is often unexpected.
|
23 | urllib.request.urlopen(urllib.request.Request('http://www.google.com'))
24 | urllib.request.urlopen(urllib.request.Request('file:///foo/bar/baz'))
25 | urllib.request.urlopen(urllib.request.Request(url))
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ S310
|

View File

@@ -79,15 +79,12 @@ pub(crate) fn blind_except(
let Some(type_) = type_ else {
return;
};
let Expr::Name(ast::ExprName { id, .. }) = &type_ else {
let semantic = checker.semantic();
let Some(builtin_exception_type) = semantic.resolve_builtin_symbol(type_) else {
return;
};
if !matches!(id.as_str(), "BaseException" | "Exception") {
return;
}
if !checker.semantic().is_builtin(id) {
if !matches!(builtin_exception_type, "BaseException" | "Exception") {
return;
}
@@ -121,7 +118,7 @@ pub(crate) fn blind_except(
Expr::Attribute(ast::ExprAttribute { attr, .. }) => {
if logging::is_logger_candidate(
func,
checker.semantic(),
semantic,
&checker.settings.logger_objects,
) {
match attr.as_str() {
@@ -138,9 +135,8 @@ pub(crate) fn blind_except(
}
}
Expr::Name(ast::ExprName { .. }) => {
if checker
.semantic()
.resolve_qualified_name(func.as_ref())
if semantic
.resolve_qualified_name(func)
.is_some_and(|qualified_name| match qualified_name.segments() {
["logging", "exception"] => true,
["logging", "error"] => {
@@ -170,7 +166,7 @@ pub(crate) fn blind_except(
checker.diagnostics.push(Diagnostic::new(
BlindExcept {
name: id.to_string(),
name: builtin_exception_type.to_string(),
},
type_.range(),
));

View File

@@ -148,7 +148,7 @@ pub(crate) fn boolean_type_hint_positional_argument(
}
// If `bool` isn't actually a reference to the `bool` built-in, return.
if !checker.semantic().is_builtin("bool") {
if !checker.semantic().has_builtin_binding("bool") {
return;
}

View File

@@ -41,6 +41,7 @@ mod tests {
#[test_case(Rule::MutableArgumentDefault, Path::new("B006_5.py"))]
#[test_case(Rule::MutableArgumentDefault, Path::new("B006_6.py"))]
#[test_case(Rule::MutableArgumentDefault, Path::new("B006_7.py"))]
#[test_case(Rule::MutableArgumentDefault, Path::new("B006_8.py"))]
#[test_case(Rule::MutableArgumentDefault, Path::new("B006_B008.py"))]
#[test_case(Rule::NoExplicitStacklevel, Path::new("B028.py"))]
#[test_case(Rule::RaiseLiteral, Path::new("B016.py"))]
@@ -113,6 +114,7 @@ mod tests {
"fastapi.Depends".to_string(),
"fastapi.Query".to_string(),
"custom.ImmutableTypeA".to_string(),
"B008_extended.Class".to_string(),
],
},
..LinterSettings::for_rule(Rule::FunctionCallInDefaultArgument)

View File

@@ -95,24 +95,22 @@ pub(crate) fn assert_raises_exception(checker: &mut Checker, items: &[WithItem])
return;
};
let Some(exception) =
checker
.semantic()
.resolve_qualified_name(arg)
.and_then(|qualified_name| match qualified_name.segments() {
["", "Exception"] => Some(ExceptionKind::Exception),
["", "BaseException"] => Some(ExceptionKind::BaseException),
_ => None,
})
else {
let semantic = checker.semantic();
let Some(builtin_symbol) = semantic.resolve_builtin_symbol(arg) else {
return;
};
let exception = match builtin_symbol {
"Exception" => ExceptionKind::Exception,
"BaseException" => ExceptionKind::BaseException,
_ => return,
};
let assertion = if matches!(func.as_ref(), Expr::Attribute(ast::ExprAttribute { attr, .. }) if attr == "assertRaises")
{
AssertionKind::AssertRaises
} else if checker
.semantic()
} else if semantic
.resolve_qualified_name(func)
.is_some_and(|qualified_name| matches!(qualified_name.segments(), ["pytest", "raises"]))
&& arguments.find_keyword("match").is_none()

View File

@@ -34,7 +34,7 @@ use crate::checkers::ast::Checker;
/// ```python
/// from functools import partial
///
/// adders = [partial(lambda x, i: x + i, i) for i in range(3)]
/// adders = [partial(lambda x, i: x + i, i=i) for i in range(3)]
/// values = [adder(1) for adder in adders] # [1, 2, 3]
/// ```
///
@@ -67,7 +67,7 @@ impl<'a> Visitor<'a> for LoadedNamesVisitor<'a> {
Expr::Name(name) => match &name.ctx {
ExprContext::Load => self.loaded.push(name),
ExprContext::Store => self.stored.push(name),
ExprContext::Del => {}
_ => {}
},
_ => visitor::walk_expr(self, expr),
}

View File

@@ -54,12 +54,6 @@ pub(crate) fn getattr_with_constant(
func: &Expr,
args: &[Expr],
) {
let Expr::Name(ast::ExprName { id, .. }) = func else {
return;
};
if id != "getattr" {
return;
}
let [obj, arg] = args else {
return;
};
@@ -75,7 +69,7 @@ pub(crate) fn getattr_with_constant(
if is_mangled_private(value.to_str()) {
return;
}
if !checker.semantic().is_builtin("getattr") {
if !checker.semantic().match_builtin_expr(func, "getattr") {
return;
}

View File

@@ -2,10 +2,12 @@ use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::helpers::is_docstring_stmt;
use ruff_python_ast::name::QualifiedName;
use ruff_python_ast::{self as ast, Expr, Parameter, ParameterWithDefault, Stmt};
use ruff_python_ast::{self as ast, Expr, Parameter, ParameterWithDefault};
use ruff_python_codegen::{Generator, Stylist};
use ruff_python_index::Indexer;
use ruff_python_semantic::analyze::function_type::is_stub;
use ruff_python_semantic::analyze::typing::{is_immutable_annotation, is_mutable_expr};
use ruff_python_semantic::SemanticModel;
use ruff_python_trivia::{indentation_at_offset, textwrap};
use ruff_source_file::Locator;
use ruff_text_size::Ranged;
@@ -118,6 +120,7 @@ pub(crate) fn mutable_argument_default(checker: &mut Checker, function_def: &ast
function_def,
parameter,
default,
checker.semantic(),
checker.locator(),
checker.stylist(),
checker.indexer(),
@@ -132,10 +135,12 @@ pub(crate) fn mutable_argument_default(checker: &mut Checker, function_def: &ast
/// Generate a [`Fix`] to move a mutable argument default initialization
/// into the function body.
#[allow(clippy::too_many_arguments)]
fn move_initialization(
function_def: &ast::StmtFunctionDef,
parameter: &Parameter,
default: &Expr,
semantic: &SemanticModel,
locator: &Locator,
stylist: &Stylist,
indexer: &Indexer,
@@ -153,7 +158,7 @@ fn move_initialization(
let default_edit = Edit::range_replacement("None".to_string(), default.range());
// If the function is a stub, this is the only necessary edit.
if is_stub(function_def) {
if is_stub(function_def, semantic) {
return Some(Fix::unsafe_edit(default_edit));
}
@@ -209,20 +214,3 @@ fn move_initialization(
let initialization_edit = Edit::insertion(content, pos);
Some(Fix::unsafe_edits(default_edit, [initialization_edit]))
}
/// Returns `true` if a function has an empty body, and is therefore a stub.
///
/// A function body is considered to be empty if it contains only `pass` statements, `...` literals,
/// and docstrings.
fn is_stub(function_def: &ast::StmtFunctionDef) -> bool {
function_def.body.iter().all(|stmt| match stmt {
Stmt::Pass(_) => true,
Stmt::Expr(ast::StmtExpr { value, range: _ }) => {
matches!(
value.as_ref(),
Expr::StringLiteral(_) | Expr::EllipsisLiteral(_)
)
}
_ => false,
})
}

View File

@@ -68,12 +68,6 @@ pub(crate) fn setattr_with_constant(
func: &Expr,
args: &[Expr],
) {
let Expr::Name(ast::ExprName { id, .. }) = func else {
return;
};
if id != "setattr" {
return;
}
let [obj, name, value] = args else {
return;
};
@@ -89,7 +83,7 @@ pub(crate) fn setattr_with_constant(
if is_mangled_private(name.to_str()) {
return;
}
if !checker.semantic().is_builtin("setattr") {
if !checker.semantic().match_builtin_expr(func, "setattr") {
return;
}

View File

@@ -1,7 +1,6 @@
use ruff_python_ast::{self as ast, Expr};
use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::{self as ast, Expr};
use ruff_text_size::Ranged;
use crate::checkers::ast::Checker;
@@ -58,12 +57,6 @@ pub(crate) fn unreliable_callable_check(
func: &Expr,
args: &[Expr],
) {
let Expr::Name(ast::ExprName { id, .. }) = func else {
return;
};
if !matches!(id.as_str(), "hasattr" | "getattr") {
return;
}
let [obj, attr, ..] = args else {
return;
};
@@ -73,15 +66,27 @@ pub(crate) fn unreliable_callable_check(
if value != "__call__" {
return;
}
let Some(builtins_function) = checker.semantic().resolve_builtin_symbol(func) else {
return;
};
if !matches!(builtins_function, "hasattr" | "getattr") {
return;
}
let mut diagnostic = Diagnostic::new(UnreliableCallableCheck, expr.range());
if id == "hasattr" {
if checker.semantic().is_builtin("callable") {
diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
format!("callable({})", checker.locator().slice(obj)),
if builtins_function == "hasattr" {
diagnostic.try_set_fix(|| {
let (import_edit, binding) = checker.importer().get_or_import_builtin_symbol(
"callable",
expr.start(),
checker.semantic(),
)?;
let binding_edit = Edit::range_replacement(
format!("{binding}({})", checker.locator().slice(obj)),
expr.range(),
)));
}
);
Ok(Fix::safe_edits(binding_edit, import_edit))
});
}
checker.diagnostics.push(diagnostic);
}

View File

@@ -107,7 +107,7 @@ pub(crate) fn unused_loop_control_variable(checker: &mut Checker, stmt_for: &ast
// Avoid fixing any variables that _may_ be used, but undetectably so.
let certainty =
Certainty::from(!helpers::uses_magic_variable_access(&stmt_for.body, |id| {
checker.semantic().is_builtin(id)
checker.semantic().has_builtin_binding(id)
}));
// Attempt to rename the variable by prepending an underscore, but avoid

View File

@@ -95,7 +95,7 @@ pub(crate) fn useless_expression(checker: &mut Checker, value: &Expr) {
}
// Ignore statements that have side effects.
if contains_effect(value, |id| checker.semantic().is_builtin(id)) {
if contains_effect(value, |id| checker.semantic().has_builtin_binding(id)) {
// Flag attributes as useless expressions, even if they're attached to calls or other
// expressions.
if value.is_attribute_expr() {

View File

@@ -17,7 +17,8 @@ use crate::fix::edits::add_argument;
/// iterable. This can lead to subtle bugs.
///
/// Use the `strict` parameter to raise a `ValueError` if the iterables are of
/// non-uniform length.
/// non-uniform length. If the iterables are intentionally different lengths, the
/// parameter should be explicitly set to `False`.
///
/// ## Example
/// ```python
@@ -52,18 +53,18 @@ impl AlwaysFixableViolation for ZipWithoutExplicitStrict {
/// B905
pub(crate) fn zip_without_explicit_strict(checker: &mut Checker, call: &ast::ExprCall) {
if let Expr::Name(ast::ExprName { id, .. }) = call.func.as_ref() {
if id == "zip"
&& checker.semantic().is_builtin("zip")
&& call.arguments.find_keyword("strict").is_none()
&& !call
.arguments
.args
.iter()
.any(|arg| is_infinite_iterator(arg, checker.semantic()))
{
let mut diagnostic = Diagnostic::new(ZipWithoutExplicitStrict, call.range());
diagnostic.set_fix(Fix::applicable_edit(
let semantic = checker.semantic();
if semantic.match_builtin_expr(&call.func, "zip")
&& call.arguments.find_keyword("strict").is_none()
&& !call
.arguments
.args
.iter()
.any(|arg| is_infinite_iterator(arg, semantic))
{
checker.diagnostics.push(
Diagnostic::new(ZipWithoutExplicitStrict, call.range()).with_fix(Fix::applicable_edit(
add_argument(
"strict=False",
&call.arguments,
@@ -81,9 +82,8 @@ pub(crate) fn zip_without_explicit_strict(checker: &mut Checker, call: &ast::Exp
} else {
Applicability::Safe
},
));
checker.diagnostics.push(diagnostic);
}
)),
);
}
}

View File

@@ -31,4 +31,58 @@ B004.py:5:8: B004 Using `hasattr(x, "__call__")` to test if x is callable is unr
|
= help: Replace with `callable()`
B004.py:12:8: B004 [*] Using `hasattr(x, "__call__")` to test if x is callable is unreliable. Use `callable(x)` for consistent results.
|
10 | import builtins
11 | o = object()
12 | if builtins.hasattr(o, "__call__"):
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ B004
13 | print("B U G")
14 | if builtins.getattr(o, "__call__", False):
|
= help: Replace with `callable()`
Safe fix
9 9 | def still_a_bug():
10 10 | import builtins
11 11 | o = object()
12 |- if builtins.hasattr(o, "__call__"):
12 |+ if callable(o):
13 13 | print("B U G")
14 14 | if builtins.getattr(o, "__call__", False):
15 15 | print("B U G")
B004.py:14:8: B004 Using `hasattr(x, "__call__")` to test if x is callable is unreliable. Use `callable(x)` for consistent results.
|
12 | if builtins.hasattr(o, "__call__"):
13 | print("B U G")
14 | if builtins.getattr(o, "__call__", False):
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ B004
15 | print("B U G")
|
= help: Replace with `callable()`
B004.py:24:8: B004 [*] Using `hasattr(x, "__call__")` to test if x is callable is unreliable. Use `callable(x)` for consistent results.
|
22 | return True
23 |
24 | if hasattr(o, "__call__"):
| ^^^^^^^^^^^^^^^^^^^^^^ B004
25 | print("STILL a bug!")
|
= help: Replace with `callable()`
Safe fix
1 |+import builtins
1 2 | def this_is_a_bug():
2 3 | o = object()
3 4 | if hasattr(o, "__call__"):
--------------------------------------------------------------------------------
21 22 | def callable(x):
22 23 | return True
23 24 |
24 |- if hasattr(o, "__call__"):
25 |+ if builtins.callable(o):
25 26 | print("STILL a bug!")
26 27 |
27 28 |

View File

@@ -0,0 +1,92 @@
---
source: crates/ruff_linter/src/rules/flake8_bugbear/mod.rs
---
B006_8.py:1:19: B006 [*] Do not use mutable data structures for argument defaults
|
1 | def foo(a: list = []):
| ^^ B006
2 | raise NotImplementedError("")
|
= help: Replace with `None`; initialize within function
Unsafe fix
1 |-def foo(a: list = []):
1 |+def foo(a: list = None):
2 2 | raise NotImplementedError("")
3 3 |
4 4 |
B006_8.py:5:19: B006 [*] Do not use mutable data structures for argument defaults
|
5 | def bar(a: dict = {}):
| ^^ B006
6 | """ This one also has a docstring"""
7 | raise NotImplementedError("and has some text in here")
|
= help: Replace with `None`; initialize within function
Unsafe fix
2 2 | raise NotImplementedError("")
3 3 |
4 4 |
5 |-def bar(a: dict = {}):
5 |+def bar(a: dict = None):
6 6 | """ This one also has a docstring"""
7 7 | raise NotImplementedError("and has some text in here")
8 8 |
B006_8.py:10:19: B006 [*] Do not use mutable data structures for argument defaults
|
10 | def baz(a: list = []):
| ^^ B006
11 | """This one raises a different exception"""
12 | raise IndexError()
|
= help: Replace with `None`; initialize within function
Unsafe fix
7 7 | raise NotImplementedError("and has some text in here")
8 8 |
9 9 |
10 |-def baz(a: list = []):
10 |+def baz(a: list = None):
11 11 | """This one raises a different exception"""
12 |+ if a is None:
13 |+ a = []
12 14 | raise IndexError()
13 15 |
14 16 |
B006_8.py:15:19: B006 [*] Do not use mutable data structures for argument defaults
|
15 | def qux(a: list = []):
| ^^ B006
16 | raise NotImplementedError
|
= help: Replace with `None`; initialize within function
Unsafe fix
12 12 | raise IndexError()
13 13 |
14 14 |
15 |-def qux(a: list = []):
15 |+def qux(a: list = None):
16 16 | raise NotImplementedError
17 17 |
18 18 |
B006_8.py:19:20: B006 [*] Do not use mutable data structures for argument defaults
|
19 | def quux(a: list = []):
| ^^ B006
20 | raise NotImplemented
|
= help: Replace with `None`; initialize within function
Unsafe fix
16 16 | raise NotImplementedError
17 17 |
18 18 |
19 |-def quux(a: list = []):
19 |+def quux(a: list = None):
20 20 | raise NotImplemented

View File

@@ -342,6 +342,8 @@ B009_B010.py:65:1: B009 [*] Do not call `getattr` with a constant attribute valu
65 | / getattr(self.
66 | | registration.registry, '__name__')
| |_____________________________________^ B009
67 |
68 | import builtins
|
= help: Replace `getattr` with attribute access
@@ -353,5 +355,21 @@ B009_B010.py:65:1: B009 [*] Do not call `getattr` with a constant attribute valu
66 |- registration.registry, '__name__')
65 |+(self.
66 |+ registration.registry).__name__
67 67 |
68 68 | import builtins
69 69 | builtins.getattr(foo, "bar")
B009_B010.py:69:1: B009 [*] Do not call `getattr` with a constant attribute value. It is not any safer than normal property access.
|
68 | import builtins
69 | builtins.getattr(foo, "bar")
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ B009
|
= help: Replace `getattr` with attribute access
Safe fix
66 66 | registration.registry, '__name__')
67 67 |
68 68 | import builtins
69 |-builtins.getattr(foo, "bar")
69 |+foo.bar

View File

@@ -162,6 +162,8 @@ B905.py:24:1: B905 [*] `zip()` without an explicit `strict=` parameter
24 |-zip([1, 2, 3], repeat(1, 1))
24 |+zip([1, 2, 3], repeat(1, 1), strict=False)
25 25 | zip([1, 2, 3], repeat(1, times=4))
26 26 |
27 27 | import builtins
B905.py:25:1: B905 [*] `zip()` without an explicit `strict=` parameter
|
@@ -169,6 +171,8 @@ B905.py:25:1: B905 [*] `zip()` without an explicit `strict=` parameter
24 | zip([1, 2, 3], repeat(1, 1))
25 | zip([1, 2, 3], repeat(1, times=4))
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ B905
26 |
27 | import builtins
|
= help: Add explicit `strict=False`
@@ -178,5 +182,22 @@ B905.py:25:1: B905 [*] `zip()` without an explicit `strict=` parameter
24 24 | zip([1, 2, 3], repeat(1, 1))
25 |-zip([1, 2, 3], repeat(1, times=4))
25 |+zip([1, 2, 3], repeat(1, times=4), strict=False)
26 26 |
27 27 | import builtins
28 28 | # Still an error even though it uses the qualified name
B905.py:29:1: B905 [*] `zip()` without an explicit `strict=` parameter
|
27 | import builtins
28 | # Still an error even though it uses the qualified name
29 | builtins.zip([1, 2, 3])
| ^^^^^^^^^^^^^^^^^^^^^^^ B905
|
= help: Add explicit `strict=False`
Safe fix
26 26 |
27 27 | import builtins
28 28 | # Still an error even though it uses the qualified name
29 |-builtins.zip([1, 2, 3])
29 |+builtins.zip([1, 2, 3], strict=False)

View File

@@ -8,4 +8,9 @@ B008_extended.py:24:51: B008 Do not perform function call `Depends` in argument
25 | ...
|
B008_extended.py:36:15: B008 Do not perform function call `OtherClass` in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable
|
36 | def error(obj=OtherClass()):
| ^^^^^^^^^^^^ B008
37 | ...
|

View File

@@ -60,40 +60,35 @@ impl AlwaysFixableViolation for UnnecessaryCallAroundSorted {
pub(crate) fn unnecessary_call_around_sorted(
checker: &mut Checker,
expr: &Expr,
func: &Expr,
outer_func: &Expr,
args: &[Expr],
) {
let Some(outer) = func.as_name_expr() else {
let Some(Expr::Call(ast::ExprCall {
func: inner_func, ..
})) = args.first()
else {
return;
};
if !matches!(outer.id.as_str(), "list" | "reversed") {
let semantic = checker.semantic();
let Some(outer_func_name) = semantic.resolve_builtin_symbol(outer_func) else {
return;
};
if !matches!(outer_func_name, "list" | "reversed") {
return;
}
let Some(arg) = args.first() else {
return;
};
let Expr::Call(ast::ExprCall { func, .. }) = arg else {
return;
};
let Some(inner) = func.as_name_expr() else {
return;
};
if inner.id != "sorted" {
return;
}
if !checker.semantic().is_builtin(&inner.id) || !checker.semantic().is_builtin(&outer.id) {
if !semantic.match_builtin_expr(inner_func, "sorted") {
return;
}
let mut diagnostic = Diagnostic::new(
UnnecessaryCallAroundSorted {
func: outer.id.to_string(),
func: outer_func_name.to_string(),
},
expr.range(),
);
diagnostic.try_set_fix(|| {
Ok(Fix::applicable_edit(
fixes::fix_unnecessary_call_around_sorted(expr, checker.locator(), checker.stylist())?,
if outer.id == "reversed" {
if outer_func_name == "reversed" {
Applicability::Unsafe
} else {
Applicability::Safe

View File

@@ -65,10 +65,10 @@ pub(crate) fn unnecessary_collection_call(
if !call.arguments.args.is_empty() {
return;
}
let Some(func) = call.func.as_name_expr() else {
let Some(builtin) = checker.semantic().resolve_builtin_symbol(&call.func) else {
return;
};
let collection = match func.id.as_str() {
let collection = match builtin {
"dict"
if call.arguments.keywords.is_empty()
|| (!settings.allow_dict_calls_with_keyword_arguments
@@ -87,13 +87,10 @@ pub(crate) fn unnecessary_collection_call(
}
_ => return,
};
if !checker.semantic().is_builtin(func.id.as_str()) {
return;
}
let mut diagnostic = Diagnostic::new(
UnnecessaryCollectionCall {
obj_type: func.id.to_string(),
obj_type: builtin.to_string(),
},
call.range(),
);

View File

@@ -60,7 +60,7 @@ fn add_diagnostic(checker: &mut Checker, expr: &Expr) {
Expr::DictComp(_) => "dict",
_ => return,
};
if !checker.semantic().is_builtin(id) {
if !checker.semantic().has_builtin_binding(id) {
return;
}
let mut diagnostic = Diagnostic::new(

View File

@@ -90,15 +90,6 @@ pub(crate) fn unnecessary_comprehension_in_call(
if !keywords.is_empty() {
return;
}
let Expr::Name(ast::ExprName { id, .. }) = func else {
return;
};
if !(matches!(id.as_str(), "any" | "all")
|| (checker.settings.preview.is_enabled() && matches!(id.as_str(), "sum" | "min" | "max")))
{
return;
}
let [arg] = args else {
return;
};
@@ -110,7 +101,13 @@ pub(crate) fn unnecessary_comprehension_in_call(
if contains_await(elt) {
return;
}
if !checker.semantic().is_builtin(id) {
let Some(builtin_function) = checker.semantic().resolve_builtin_symbol(func) else {
return;
};
if !(matches!(builtin_function, "any" | "all")
|| (checker.settings.preview.is_enabled()
&& matches!(builtin_function, "sum" | "min" | "max")))
{
return;
}

View File

@@ -70,24 +70,15 @@ impl AlwaysFixableViolation for UnnecessaryDoubleCastOrProcess {
pub(crate) fn unnecessary_double_cast_or_process(
checker: &mut Checker,
expr: &Expr,
func: &Expr,
outer_func: &Expr,
args: &[Expr],
outer_kw: &[Keyword],
) {
let Some(outer) = func.as_name_expr() else {
return;
};
if !matches!(
outer.id.as_str(),
"list" | "tuple" | "set" | "reversed" | "sorted"
) {
return;
}
let Some(arg) = args.first() else {
return;
};
let Expr::Call(ast::ExprCall {
func,
func: inner_func,
arguments: Arguments {
keywords: inner_kw, ..
},
@@ -96,16 +87,23 @@ pub(crate) fn unnecessary_double_cast_or_process(
else {
return;
};
let Some(inner) = func.as_name_expr() else {
let semantic = checker.semantic();
let Some(outer_func_name) = semantic.resolve_builtin_symbol(outer_func) else {
return;
};
if !checker.semantic().is_builtin(&inner.id) || !checker.semantic().is_builtin(&outer.id) {
if !matches!(
outer_func_name,
"list" | "tuple" | "set" | "reversed" | "sorted"
) {
return;
}
let Some(inner_func_name) = semantic.resolve_builtin_symbol(inner_func) else {
return;
};
// Avoid collapsing nested `sorted` calls with non-identical keyword arguments
// (i.e., `key`, `reverse`).
if inner.id == "sorted" && outer.id == "sorted" {
if inner_func_name == "sorted" && outer_func_name == "sorted" {
if inner_kw.len() != outer_kw.len() {
return;
}
@@ -122,15 +120,15 @@ pub(crate) fn unnecessary_double_cast_or_process(
// Ex) `list(tuple(...))`
// Ex) `set(set(...))`
if matches!(
(outer.id.as_str(), inner.id.as_str()),
(outer_func_name, inner_func_name),
("set" | "sorted", "list" | "tuple" | "reversed" | "sorted")
| ("set", "set")
| ("list" | "tuple", "list" | "tuple")
) {
let mut diagnostic = Diagnostic::new(
UnnecessaryDoubleCastOrProcess {
inner: inner.id.to_string(),
outer: outer.id.to_string(),
inner: inner_func_name.to_string(),
outer: outer_func_name.to_string(),
},
expr.range(),
);

View File

@@ -71,7 +71,7 @@ pub(crate) fn unnecessary_generator_list(checker: &mut Checker, call: &ast::Expr
) else {
return;
};
if !checker.semantic().is_builtin("list") {
if !checker.semantic().has_builtin_binding("list") {
return;
}

View File

@@ -72,7 +72,7 @@ pub(crate) fn unnecessary_generator_set(checker: &mut Checker, call: &ast::ExprC
) else {
return;
};
if !checker.semantic().is_builtin("set") {
if !checker.semantic().has_builtin_binding("set") {
return;
}

View File

@@ -53,7 +53,7 @@ pub(crate) fn unnecessary_list_call(
let Some(argument) = helpers::first_argument_with_matching_function("list", func, args) else {
return;
};
if !checker.semantic().is_builtin("list") {
if !checker.semantic().has_builtin_binding("list") {
return;
}
if !argument.is_list_comp_expr() {

View File

@@ -56,7 +56,7 @@ pub(crate) fn unnecessary_list_comprehension_dict(
else {
return;
};
if !checker.semantic().is_builtin("dict") {
if !checker.semantic().has_builtin_binding("dict") {
return;
}
let Expr::ListComp(ast::ExprListComp { elt, .. }) = argument else {

View File

@@ -52,7 +52,7 @@ pub(crate) fn unnecessary_list_comprehension_set(checker: &mut Checker, call: &a
) else {
return;
};
if !checker.semantic().is_builtin("set") {
if !checker.semantic().has_builtin_binding("set") {
return;
}
if argument.is_list_comp_expr() {

View File

@@ -63,7 +63,7 @@ pub(crate) fn unnecessary_literal_dict(
else {
return;
};
if !checker.semantic().is_builtin("dict") {
if !checker.semantic().has_builtin_binding("dict") {
return;
}
let (kind, elts) = match argument {

View File

@@ -60,7 +60,7 @@ pub(crate) fn unnecessary_literal_set(checker: &mut Checker, call: &ast::ExprCal
) else {
return;
};
if !checker.semantic().is_builtin("set") {
if !checker.semantic().has_builtin_binding("set") {
return;
}
let kind = match argument {

View File

@@ -75,7 +75,7 @@ pub(crate) fn unnecessary_literal_within_dict_call(checker: &mut Checker, call:
else {
return;
};
if !checker.semantic().is_builtin("dict") {
if !checker.semantic().has_builtin_binding("dict") {
return;
}
let argument_kind = match argument {

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