Compare commits

..

155 Commits

Author SHA1 Message Date
Zanie
d65addffa0 Use https instead of ssh for schemastore authentication 2023-11-09 08:42:10 -06:00
Dhruv Manilawala
9d167a1f5c Slice source code instead of generating it for EM fixes (#7746)
## Summary

This PR fixes the bug where the generated fix for `EM*` rules would
replace a
triple-quoted (f-)string with a single-quoted (f-)string. This changes
the
semantic of the string in case it contains a single-quoted string
literal. This
is especially evident with f-strings where the expression could contain
another
string within it. For example,

```python
f"""normal {"another"} normal"""
```

## Test Plan

Add test case for triple-quoted string and update the snapshots.

fixes: #6988
fixes: #7736
2023-11-09 05:22:15 +00:00
Charlie Marsh
9e184a9067 Revert "Avoid inserting trailing commas within f-strings" (#8576)
Reverts astral-sh/ruff#8574. This caused a bunch of ecosystem changes --
needs more work.
2023-11-09 05:02:04 +00:00
Charlie Marsh
9d1027c239 Fix permalink to convention setting (#8575) 2023-11-09 04:50:32 +00:00
Charlie Marsh
f499f0ca60 Avoid inserting trailing commas within f-strings (#8574)
Closes https://github.com/astral-sh/ruff/issues/8556.
2023-11-08 23:25:23 -05:00
Charlie Marsh
722687ad72 Detect runtime-evaluated base classes defined in the current file (#8572)
Closes https://github.com/astral-sh/ruff/issues/8250.

Closes https://github.com/astral-sh/ruff/issues/5486.
2023-11-08 22:38:06 -05:00
Dhruv Manilawala
4760af3dcb Avoid FURB113 autofix if comments are present (#8494)
This PR avoids creating the fix for `FURB113` if there are comments in
between the `append` calls.

fixes: #8105
2023-11-09 03:10:11 +00:00
doolio
4fdf97a95c Apply consistent code block labels (#8563)
This ensures the python label is used for all python code blocks for
consistency.

## Test Plan

Visual inspection of all changes via git client ensuring no other
changes were made in error.
2023-11-09 01:49:24 +00:00
doolio
0ea1076f85 Add missing config tabs (#8558) 2023-11-09 01:49:06 +00:00
Zanie Blue
3956f38999 Prepare release 0.1.5 (#8570)
[Rendered
CHANGELOG](https://github.com/astral-sh/ruff/blob/release/015/CHANGELOG.md#015)
2023-11-08 16:00:57 -06:00
Zanie Blue
fe9727ac38 Add rooster release management configuration and instructions (#8567)
I'd rather not be the only one who can easily generate our changelog
entries so I invested some time to get Rooster a bit further along and
add instructions.
2023-11-08 13:08:19 -06:00
Dosenpfand
3ebaca5246 Doc: Fix link to isort known-first-party (#8562) 2023-11-08 11:12:11 -05:00
Felix Williams
7391f74cbc Add hidden --extension to override inference of source type from file extension (#8373)
## Summary

This PR addresses the incompatibility with `jupyterlab-lsp` +
`python-lsp-ruff` arising from the inference of source type from file
extension, raised in #6847.

In particular it follows the suggestion in
https://github.com/astral-sh/ruff/issues/6847#issuecomment-1765724679 to
specify a mapping from file extension to source type.

The source types are

- python
- pyi
- ipynb

Usage:

```sh
ruff check --no-cache --stdin-filename Untitled.ipynb --extension ipynb:python
```

Unlike the original suggestion, `:` instead of `=` is used to associate
file extensions to language since that is what is used with
`--per-file-ignores` which is an existing option that accepts a mapping.

## Test Plan

2 tests added to `integration_test.rs` to ensure the override works as
expected

---------

Co-authored-by: Charlie Marsh <charlie.r.marsh@gmail.com>
2023-11-08 08:02:40 +05:30
Charlie Marsh
71e93a9fa4 Only flag flake8-trio rule when trio is present (#8550)
## Summary

Hoping to avoid some false positives by narrowing the scope of
https://github.com/astral-sh/ruff/pull/8534.
2023-11-07 22:27:58 +00:00
Kar Petrosyan
e2c7b1ece6 [TRIO] Add TRIO109 rule (#8534)
## Summary

Adds TRIO109 from the [flake8-trio
plugin](https://github.com/Zac-HD/flake8-trio).
Relates to: https://github.com/astral-sh/ruff/issues/8451
2023-11-07 17:13:01 -05:00
Charlie Marsh
621e98f452 Improve detail link contrast in dark mode (#8548)
Use our light-mode styling for links in that context.

<img width="627" alt="Screen Shot 2023-11-07 at 4 34 48 PM"
src="https://github.com/astral-sh/ruff/assets/1309177/1e30c3ac-18e2-4663-876c-75c6f8b67d53">

Closes https://github.com/astral-sh/ruff/issues/8519.
2023-11-07 21:41:34 +00:00
Kar Petrosyan
0126f74c29 Add TRIO110 rule (#8537)
## Summary

Adds TRIO110 from the [flake8-trio
plugin](https://github.com/Zac-HD/flake8-trio).
Relates to: https://github.com/astral-sh/ruff/issues/8451
2023-11-07 21:27:19 +00:00
Chaojie
fce9f63418 [flake8-bandit] Implement mako-templates (S702) (#8533)
See: https://github.com/astral-sh/ruff/issues/1646.
2023-11-07 20:58:43 +00:00
Charlie Marsh
ce549e75bc Update pre-commit documentation (#8545)
I got some feedback on Mastodon that it wasn't clear how to use the
linter and formatter together in pre-commit (mostly in the pre-commit
repo's documentation, which is even less clear, but the two should be
consistent).
2023-11-07 18:40:13 +00:00
Lukasz Piatkowski
03303a9edd Account for selector specificity when merging extend_unsafe_fixes and override extend_safe_fixes (#8444)
## Summary

Prior to this change `extend_unsafe_fixes` took precedence over
`extend_safe_fixes` selectors, so any conflicts were resolved in favour
of `extend_unsafe_fixes`. Thanks to that ruff were conservatively
assuming that if configs conlict the fix corresponding to selected rule
will be treated as unsafe.

After this change we take into account Specificity of the selectors. For
conflicts between selectors of the same Specificity we will treat the
corresponding fixes as unsafe. But if the conflicting selectors are of
different specificity the more specific one will win.

## Test Plan

Tests were added for the `FixSafetyTable` struct. The
`check_extend_unsafe_fixes_conflict_with_extend_safe_fixes_by_specificity`
integration test was added to test conflicting rules of different
specificity.

Fixes #8404

---------

Co-authored-by: Zanie <contact@zanie.dev>
2023-11-07 10:33:40 -06:00
Zanie Blue
7873ca38e5 Update applicability messages for clarity in tests (#8541)
These names are only ever displayed internally right now and we could be
clearer in our test snapshots.

The diff is kind of scary because all of the tests fixtures are updated.
2023-11-07 16:11:43 +00:00
Aarni Koskela
7dabc4598b Allow RUFF_NO_CACHE environment variable (like RUFF_CACHE_DIR) (#8538)
## Summary

Being able to set `--no-cache` without touching the command line makes
comparing formatter speed with e.g. Hyperfine a lot easier; Black allows
one to set `BLACK_CACHE_DIR=/dev/null`, but setting
`RUFF_CACHE_DIR=/dev/null` has Ruff choke:

```
error: Failed to initialize cache at /dev/null: Not a directory (os error 20)
error: Failed to initialize cache at /dev/null: Not a directory (os error 20)
warning: Failed to open cache file '/dev/null/0.1.4/18160934645386409287': Not a directory (os error 20)
```

Alternately, we could make a `/dev/null` (or `nul` on Windows) cache
directory imply `--no-cache`?

## Test Plan

None yet.
2023-11-07 08:35:28 -06:00
Andrew Gallant
6a1fa4778f Reject more syntactically invalid Python programs (#8524)
## Summary

This commit adds some additional error checking to the parser such that
assignments that are invalid syntax are rejected. This covers the
obvious cases like `5 = 3` and some not so obvious cases like `x + y =
42`.

This does add an additional recursive call to the parser for the cases
handling assignments. I had initially been concerned about doing this,
but `set_context` is already doing recursion during assignments, so I
didn't feel as though this was changing any fundamental performance
characteristics of the parser. (Also, in practice, I would expect any
such recursion here to be quite shallow since the recursion is done on
the target of an assignment. Such things are rarely nested much in
practice.)

Fixes #6895

## Test Plan

I've added unit tests covering every case that is detected as invalid on
an `Expr`.
2023-11-07 07:16:06 -05:00
Charlie Marsh
c3d6d5d006 Add singleton escape hatch to B008 documentation (#8501)
## Summary:

Closes: https://github.com/astral-sh/ruff/issues/8378.
2023-11-07 04:53:45 +00:00
qdegraaf
9a8400a287 Avoid raising TRIO115 violations for trio.sleep(...) calls with non-number values (#8532)
## Summary

Fixes bug in `TRIO115` where it would not `return` for values that were
not a `NumberLiteral` so
```python
x = "bla"
trio.sleep(x)
```
would set off a false positive

## Test Plan

Added test case to fixture
2023-11-06 16:49:12 -06:00
Juan Orduz
d71c65d0c8 Add PyMC Marketing to Users (#8529)
Add [PyMC-Marketing](https://github.com/pymc-labs/pymc-marketing) to
users. See https://github.com/pymc-labs/pymc-marketing/pull/424
2023-11-06 16:21:49 -06:00
doolio
7f92bfbc4a docs: Add missing toml config tabs (#8512) 2023-11-06 21:12:38 +00:00
Charlie Marsh
37301375c8 Make SIM118 fix as safe when the expression is a known dictionary (#8525)
## Summary

Given `key in obj.keys()`, `obj` _could_ be a dictionary, or it could be
another type that defines
a `.keys()` method. In the latter case, removing the `.keys()` attribute
could lead to a runtime error.

Previously, we marked all `SIM118` fixes as unsafe for this reason;
however, in preview, we now mark them as safe if we can
infer that the expression is a dictionary.

## Test Plan

Added a preview fixture.
2023-11-06 21:06:33 +00:00
Aarni Koskela
c07947bfac Add Pillow to Ruff users (#8523)
## Summary

See https://github.com/python-pillow/Pillow/pull/6966 :)

## Test Plan

Looked at the Markdown preview!
2023-11-06 12:59:06 -06:00
T-256
72964529a5 Skip ecosystem check when no changes detected (#8520)
<!--
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

For example, https://github.com/astral-sh/ruff/pull/8512 doesn't need
ecosystem check
<!-- What's the purpose of the change? What does it do, and why? -->

## Test Plan

<!-- How was it tested? -->
2023-11-06 12:18:20 -06:00
Charlie Marsh
eab8ca4d7e Add dedicated method to find typed binding (#8517)
## Summary

We have this pattern in a bunch of places, where we find the _only_
binding to a name (and return `None`) if it's bound multiple times. This
PR DRYs it up into a method on `SemanticModel`.
2023-11-06 11:25:32 -05:00
Zanie Blue
5b3e922050 Upgrade pre-commit dependencies (#8518) 2023-11-06 10:08:22 -06:00
Zanie Blue
311a7751f9 Ensure ecosystem project errors are properly fenced (#8516)
Fixes bug where errors could be unfenced resulting in hidden remaining
content

e.g. https://github.com/astral-sh/ruff/pull/8508#issuecomment-1794960132
2023-11-06 09:35:07 -06:00
Charlie Marsh
5e2bb8ca07 Add a Fix constructor that takes Applicability as an argument (#8514)
## Summary

If you want to create an edit with dynamic applicability, you have to
branch and repeat the edit entirely between the two branches. If you
further need the edit itself to be dynamic (e.g., perhaps you have a
single edit in one case, vs. multiple in another), you suddenly have
four branches. This PR just adds an alternate constructor that takes
applicability as an argument, as an escape hatch.
2023-11-06 09:45:10 -05:00
konsti
3c8d9d45fb Recommend project.requires-python over target-version (#8513)
**Summary** Recommend the standardized, shared `project.requires-python`
over ruff's custom `target-version`. See
https://mastodon.social/deck/@davidism@mas.to/111347072204727710

**Test Plan** Docs only change
2023-11-06 14:35:32 +00:00
dependabot[bot]
82c3c513d2 Bump codspeed-criterion-compat from 2.3.0 to 2.3.1 (#8508) 2023-11-06 14:32:40 +00:00
dependabot[bot]
f2dc01e3aa Bump bitflags from 2.4.0 to 2.4.1 (#8511) 2023-11-06 09:20:39 -05:00
dependabot[bot]
5349143fca Bump serde_json from 1.0.107 to 1.0.108 (#8510) 2023-11-06 09:20:30 -05:00
dependabot[bot]
b6f23d57aa Bump syn from 2.0.38 to 2.0.39 (#8509) 2023-11-06 09:19:47 -05:00
dependabot[bot]
b7b6e0136e Bump serde-wasm-bindgen from 0.6.0 to 0.6.1 (#8507) 2023-11-06 09:19:30 -05:00
Ofek Lev
218f517487 Fix typo in example (#8506) 2023-11-06 12:52:14 +05:30
Dhruv Manilawala
75c669a007 Fix tab configuration docs (#8502)
Otherwise it doesn't render as expected.
2023-11-06 03:02:45 +00:00
Shantanu
2d5ce4532a Flag all comparisons against builtin types in E721 (#8491)
See #8483. Generalised fix on top of #8485

Based on the output of `print("\n".join(k for k, v in
builtins.__dict__.items() if isinstance(v, type)))`
2023-11-05 21:28:47 -05:00
qdegraaf
f3e2d12609 [TRIO] Add TRIO115: TrioZeroSleepCall (#8486)
## Summary

Adds `TRIO115` from the [flake8-trio
plugin](https://github.com/Zac-HD/flake8-trio).

## Test Plan

Added a new fixture, based on [the one from upstream
plugin](https://github.com/Zac-HD/flake8-trio/blob/main/tests/eval_files/trio115.py)

## Issue link

Relates to: https://github.com/astral-sh/ruff/issues/8451
2023-11-06 01:19:46 +00:00
Tom Kuson
de2d7e97b1 [refurb] Implement type-none-comparison (FURB169) (#8487)
## Summary

Implement
[`no-is-type-none`](https://github.com/dosisod/refurb/blob/master/refurb/checks/builtin/no_is_type_none.py)
as `type-none-comparison` (`FURB169`).

Auto-fixes comparisons that use `type` to compare the type of an object
to `type(None)` to a `None` identity check. For example,

```python
type(foo) is type(None)
```

becomes

```python
foo is None
```

Related to #1348.

## Test Plan

`cargo test`
2023-11-06 00:56:20 +00:00
Charlie Marsh
bcb737dd80 Add notes on fix safety to a few rules (#8500) 2023-11-06 00:48:57 +00:00
Charlie Marsh
8c146bbf11 Allow collapsed-ellipsis bodies in other statements (#8499)
## Summary

Black and Ruff's preview styles now collapse statements like:

```python
from contextlib import nullcontext

ctx = nullcontext()
with ctx: ...
```

Historically, we made an exception here for classes
(https://github.com/astral-sh/ruff/pull/2837). This PR extends it to
other statement kinds for consistency with the formatter.

Closes https://github.com/astral-sh/ruff/issues/8496.
2023-11-05 19:42:34 -05:00
qdegraaf
4170ef0508 [TRIO] Add TRIO105: SyncTrioCall (#8490)
## Summary

Adds `TRIO105` from the [flake8-trio
plugin](https://github.com/Zac-HD/flake8-trio). The `MethodName` logic
mirrors that of `TRIO100` to stay consistent within the plugin.

It is at 95% parity with the exception of upstream also checking for a
slightly more complex scenario where a call to `start()` on a
`trio.Nursery` context should also be immediately awaited. Upstream
plugin appears to just check for anything named `nursery` judging from
[the relevant issue](https://github.com/Zac-HD/flake8-trio/issues/56).

Unsure if we want to do so something similar or, alternatively, if there
is some capability in ruff to check for calls made on this context some
other way

## Test Plan

Added a new fixture, based on [the one from upstream
plugin](https://github.com/Zac-HD/flake8-trio/blob/main/tests/eval_files/trio105.py)

## Issue link

Refers: https://github.com/astral-sh/ruff/issues/8451
2023-11-05 19:56:10 +00:00
Chris Rose
72ebde8d38 Add instructions for configuration of Emacs (#8488)
## Summary

Add editor integration docs for `ruff format` in Emacs by way of the
Apheleia formatter library

Depends on:  https://github.com/radian-software/apheleia/issues/233
2023-11-05 17:15:59 +00:00
trag1c
1672a3d3b7 Added tabs for configuration files in the documentation (#8480)
## Summary

Closes #8384.

## Test Plan

Checked whether it renders properly on the `mkdocs serve` preview.
2023-11-05 17:10:29 +00:00
Tom Kuson
8c0d65c98e Fix F841 false negative on assignment to multiple variables (#8489)
## Summary

Closes #8441 behind preview feature flag.

## Test Plan

`cargo test`
2023-11-05 12:01:10 -05:00
Dhruv Manilawala
b3c2935fa5 Avoid D301 autofix for u prefixed strings (#8495)
This PR avoids creating the fix for `D301` if the string is prefixed
with `u` i.e., it's a unicode string. The reason being that `u` and `r`
cannot be used together as it's a syntax error.

Refer:
https://github.com/astral-sh/ruff/issues/8402#issuecomment-1788783287
2023-11-05 09:45:49 -05:00
Micha Reiser
e57bccd500 Fix multiline lambda expression statement formating (#8466)
## Summary

This PR fixes a bug in our formatter where a multiline lambda expression
statement was formatted over multiple lines without adding parentheses.

The PR "fixes" the problem by not splitting the lambda parameters if it
is not parenthesized

## Test Plan

Added test
2023-11-05 09:35:23 -05:00
qdegraaf
75c9be099f [E721] Flag comparisons to memoryview (#8485)
## Summary

Adds `memoryview` to the list of typeclasses that `fn is_type()` uses
for type comparison checks so that it raises a violation if `is`, `is
not` or `isinstance()` are not used.

## Test Plan

Added examples to existing fixture

## Issue Link

Closes: https://github.com/astral-sh/ruff/issues/8483
2023-11-04 13:41:58 +00:00
Charlie Marsh
c4889196e7 Add missing pyupgrade entry to changelog (#8479)
This got merged after the changelog was generated, but is part of the
release.
2023-11-03 20:57:19 +00:00
Charlie Marsh
6e635e99f4 Add changelog for v0.1.4 (#8478) 2023-11-03 20:11:21 +00:00
Charlie Marsh
260ea41975 Bump version to v0.1.4 (#8477) 2023-11-03 14:52:56 -04:00
Hugo van Kemenade
65effc6666 Add pyupgrade UP041 to replace TimeoutError aliases (#8476)
## Summary

Add UP041 to replace `TimeoutError` aliases:

* Python 3.10+: `socket.timeout`
* Python 3.11+: `asyncio.TimeoutError`

Re:

* https://github.com/asottile/pyupgrade#timeouterror-aliases
*
https://docs.python.org/3/library/asyncio-exceptions.html#asyncio.TimeoutError
* https://docs.python.org/3/library/socket.html#socket.timeout

Based on `os_error_alias.rs`.

## Test Plan

<!-- How was it tested? -->

By running:

```
cargo clippy --workspace --all-targets --all-features -- -D warnings  # Rust linting
RUFF_UPDATE_SCHEMA=1 cargo test  # Rust testing and updating ruff.schema.json
pre-commit run --all-files --show-diff-on-failure  # Rust and Python formatting, Markdown and Python linting, etc.
cargo insta review
```

And also running with different `--target-version` values:

```sh
cargo run -p ruff_cli -- check crates/ruff_linter/resources/test/fixtures/pyupgrade/UP041.py --no-cache --select UP041 --target-version py37 --diff
cargo run -p ruff_cli -- check crates/ruff_linter/resources/test/fixtures/pyupgrade/UP041.py --no-cache --select UP041 --target-version py310 --diff
cargo run -p ruff_cli -- check crates/ruff_linter/resources/test/fixtures/pyupgrade/UP041.py --no-cache --select UP041 --target-version py311 --diff
```
2023-11-03 17:24:47 +00:00
T-256
4982694b54 D300: prevent autofix when both triples are in body (#8462)
## Summary
Addresses
https://github.com/astral-sh/ruff/issues/8402#issuecomment-1788782750

## Test Plan

Added associated test
2023-11-03 12:49:50 -04:00
Charlie Marsh
536ac550ed Remove trailing periods from NumPy 2.0 code actions (#8475)
Very minor consistency thing with other rules. For code actions, we tend
to say `Replace with {X}` rathern than `Use {X} instead.`
2023-11-03 16:28:06 +00:00
Charlie Marsh
f2335fe692 Make Unicode-to-Unicode confusables a preview change (#8473) 2023-11-03 12:17:28 -04:00
Charlie Marsh
b0f9a14d9a Mark byte_bounds as a non-backwards-compatible NumPy 2.0 change (#8474)
This is the one refactor in the NumPy 2.0 upgrade rule that isn't
compatible with earlier versions of NumPy, so I'm marking it as unsafe
and adding a dedicated message.
2023-11-03 12:14:57 -04:00
Deepyaman Datta
f56bc1983b Place 'r' prefix before 'f' for raw format strings (#8464)
## Summary

Currently, `UP032` applied to raw strings results in format strings with
the prefix 'fr'. This gets changed to 'rf' by Ruff format (or Black). In
order to avoid that, this PR uses the prefix 'rf' to begin with.

## Test Plan

Updated the expectation on an existing test.
2023-11-03 10:56:21 -04:00
Charlie Marsh
7c12eaf322 Use characters instead of u32 in confusable map (#8463) 2023-11-03 09:57:47 -04:00
Dhruv Manilawala
41e538a748 Provide example for exclusive linting or formatting Notebooks (#8461)
Reference screenshot: https://github.com/astral-sh/ruff/assets/67177269/eef5ab79-77e9-4ced-be7b-a61b7bb20ecd
2023-11-03 16:56:20 +05:30
Micha Reiser
dd2d8cb579 Avoid parenthesizing unsplittable because of comments (#8431) 2023-11-03 05:12:59 +00:00
Dhruv Manilawala
a08c5b7fa7 Upgrade PyYAML to 6.0.1 to avoid build error (#8460)
Refer: https://github.com/yaml/pyyaml/pull/702
2023-11-03 10:41:30 +05:30
Christopher Covington
9f30ccc1f4 Autoformat confusable units (#4430)
I've seen errors crop up from using the different micro and mu
characters. Follow matching recommendations on which character to prefer
for micro, ohm, and angstrom. References:
* Section 22.2 Letterlike Symbols, subsection Unit Symbols, page 877 of
[The Unicode Standard, Version 15.0

](https://www.unicode.org/versions/Unicode15.0.0/UnicodeStandard-15.0.pdf)
* Section 2.5 Duplicated Characters of [Unicode Technical Report
25](https://www.unicode.org/reports/tr25/)
* [SI
brochure](https://www.bipm.org/documents/20126/41483022/SI-Brochure-9-EN.pdf)
*
https://github.com/unicode-org/icu/blob/main/icu4c/source/data/unidata/confusables.txt
2023-11-03 04:58:43 +00:00
Charlie Marsh
31286e1c95 Re-run scripts/update_ambiguous_characters.py (#8459)
These weren't formatted consistently, and when I re-ran, the formatting
changed a bit, so I'm editing the script to keep that file constant.
2023-11-03 04:50:10 +00:00
Charlie Marsh
b9994dc495 Use fixedOverflowWidgets for playground popover (#8458)
After some Googling...

<img width="656" alt="Screen Shot 2023-11-03 at 12 23 09 AM"
src="https://github.com/astral-sh/ruff/assets/1309177/be6aaa3d-0068-4bad-a27f-01785179567d">

Closes https://github.com/astral-sh/ruff/issues/8442.
2023-11-03 04:29:37 +00:00
Micha Reiser
f16505d885 Formatter: Remove unnecessary group (#8455) 2023-11-03 04:14:29 +00:00
Mateusz Sokół
d04d964ace Implement NumPy 2.0 migration rule (#7702)
## Summary

<!-- What's the purpose of the change? What does it do, and why? -->

Hi! Currently NumPy Python API is undergoing a cleanup process that will
be delivered in NumPy 2.0 (release is planned for the end of the year).
Most changes are rather simple (renaming, removing or moving a member of
the main namespace to a new place), and they could be flagged/fixed by
an additional ruff rule for numpy (e.g. changing occurrences of
`np.float_` to `np.float64`).

Would you accept such rule?  

I named it `NPY201` in the existing group, so people will receive a
heads-up for changes arriving in 2.0 before actually migrating to it.

~~This is still a draft PR.~~ I'm not an expert in rust so if any part
of code can be done better please share!

NumPy 2.0 migration guide:
https://numpy.org/devdocs/numpy_2_0_migration_guide.html
NEP 52: https://numpy.org/neps/nep-0052-python-api-cleanup.html
NumPy cleanup tracking issue:
https://github.com/numpy/numpy/issues/23999


## Test Plan

A unit test is provided that checks all rule's fix cases.
2023-11-03 03:47:01 +00:00
Charlie Marsh
f64c389654 Detect and ignore Jupyter automagics (#8398)
## Summary

LangChain is attempting to use Ruff over their Jupyter notebooks
(https://github.com/langchain-ai/langchain/pull/12677/files), but
running into a bunch of syntax errors, the majority of which come from
our inability to recognize automagic.

If you run this in a cell:

```jupyter
pip install requests
```

Jupyter will automatically treat that as:

```jupyter
%pip install requests
```

We need to ignore cells that use these automagics, since the parser
doesn't understand them. (I guess we could support it in the parser, but
that seems much harder?). The good news is that AFAICT Jupyter doesn't
let you mix automagics with code, so by skipping these cells, we don't
miss out on analyzing any Python code.

## Test Plan

1. `cargo test`
2. Ran over LangChain and verified that there are no more errors
relating to `pip install` automagics.
2023-11-03 01:14:10 +00:00
Kar Petrosyan
2ff1afb15c Add initial flake8-trio rule (#8439)
## Summary

This pull request adds
[flake8-trio](https://github.com/Zac-HD/flake8-trio) support to ruff,
which is a very useful plugin for trio users to avoid very common
mistakes.

Part of https://github.com/astral-sh/ruff/issues/8451.

## Test Plan

Traditional rule testing, as [described in the
documentation](https://docs.astral.sh/ruff/contributing/#rule-testing-fixtures-and-snapshots).
2023-11-03 01:05:12 +00:00
Zanie Blue
7fa6ac976a Fix documentation for RuleTable (#8448) 2023-11-02 11:10:07 -05:00
Zanie Blue
7dd5137913 Fix ecosystem check bug where comment is no longer updated (#8446)
Instead, a second is posted
2023-11-02 10:49:57 -05:00
Zanie Blue
0d93fbb4a2 Only show ecosystem command used if options are non-default (#8435)
To save that precious character count
2023-11-02 08:53:33 -05:00
Dhruv Manilawala
d350ede992 Remove unicode flag from comparable (#8440)
## Summary

This PR removes the `unicode` flag from the string literal in
`ComparableExpr`. This flag isn't required as all strings are unicode in
Python 3 so `"foo" == u"foo"`.
2023-11-02 13:21:45 +05:30
Zanie Blue
a8a72306f0 Fix bug where PLE1307 was raised when formatting %c with characters (#8407)
Closes https://github.com/astral-sh/ruff/issues/8406

---------

Co-authored-by: Dhruv Manilawala <dhruvmanila@gmail.com>
2023-11-02 04:36:52 +00:00
Charlie Marsh
c8122563a6 Avoid triggering NamedTuple rewrite with starred annotation (#8434)
See:
https://github.com/astral-sh/ruff/issues/8402#issuecomment-1788787357
2023-11-02 03:30:52 +00:00
Charlie Marsh
f8f507cfc8 Avoid triggering single-element test for starred expressions (#8433)
See:
https://github.com/astral-sh/ruff/issues/8402#issuecomment-1788784721
2023-11-02 03:29:37 +00:00
Charlie Marsh
ab6bf50a2d Add caveat around action comments within docstrings (#8432)
Closes https://github.com/astral-sh/ruff/issues/8417.
2023-11-02 03:22:34 +00:00
Zanie Blue
df4dc040de Run both stable and preview ecosystem checks (#8422)
Closes https://github.com/astral-sh/ruff/issues/8076
Follow-up to #8358 

Doubles the amount of ecosystem checks we do, adding separate groups for
the stable sections.

We're likely to run into GitHub comment length restrictions if there are
significant deviations. However, it should not be common for changes in
stable and preview to occur at the same time, nor should it be common
for linter and formatter changes to occur at the same time.
2023-11-01 20:51:21 -05:00
Zanie Blue
3a889f4686 Add --line-length option to format command (#8363)
Restores the `--line-length` option removed in
https://github.com/astral-sh/ruff/pull/8131

Closes #8362
Closes #8352
2023-11-01 20:39:52 -05:00
Zanie Blue
edc75dc5d6 Pull updates for refs in cached repos in ecosystem checks (#8420)
Otherwise, the cache can end up not testing the latest changes to the
ref.
2023-11-02 01:30:35 +00:00
Zanie Blue
ebad36da06 Add support for ruff-ecosystem format comparisons with black (#8419)
Extends https://github.com/astral-sh/ruff/pull/8416 activating the
`black-and-ruff` and `black-then-ruff` formatter comparison modes for
ecosystem checks allowing us to compare changes to Black across the
ecosystem.
2023-11-02 01:29:25 +00:00
Zanie Blue
2f7e2a8de3 Add new ecosystem comparison modes for the formatter (#8416)
Previously, the ecosystem checks formatted with the baseline then
formatted again with `--diff` to get the changed files.

Now, the ecosystem checks support a new mode where we:
- Format with the baseline
- Commit the changes
- Reset to the target ref
- Format again
- Check the diff from the baseline commit

This effectively tests Ruff changes on unformatted code rather than
changes in previously formatted code (unless, of course, the project is
already using Ruff).

While this mode is the new default, I've retained the old one for local
checks. The mode can be toggled with `--format-comparison <type>`.

Includes some more aggressive resetting of the GitHub repositories when
cached.

Here, I've also stubbed comparison modes in which `black` is used as the
baseline. While these do nothing here, #8419 adds support.

I tested this with the commit from #8216 and ecosystem changes appear
https://gist.github.com/zanieb/a982ec8c392939043613267474471a6e
2023-11-02 01:20:52 +00:00
Zanie Blue
4d23c1fc83 Change default format for ecosystem checks to markdown (#8412)
To facilitate easier local runs
2023-11-01 16:51:33 -05:00
Zanie Blue
29573daef5 Use production builds of Ruff for pre-commit (#8410)
Requiring `cargo build` per commit is way too slow. Instead, we use the
production Ruff version. Additionally, Black is replaced with the Ruff
formatter.
2023-11-01 16:33:09 -05:00
Zanie Blue
9558bac64a Update the contributing guide with basic ruff-ecosystem instructions (#8413) 2023-11-01 16:29:15 -05:00
konsti
d5abe55b03 Include rust-toolchain in source distribution (#8414)
**Summary** Simplify CI by ensuring that the source distribution is
always built with the rust version that has been explicitly tested. See
discussion in #8389

Closes #8389

---------

Co-authored-by: Zanie <contact@zanie.dev>
Co-authored-by: Stijn de Gooijer <stijndegooijer@gmail.com>
2023-11-01 13:51:14 -05:00
Zanie Blue
3fc920cd12 Run ecosystem checks with preview mode enabled (#8358)
Until https://github.com/astral-sh/ruff/issues/8076 is ready, it seems
beneficial to get feedback on preview mode changes.

Tested locally, updated logs to output the flags passed to `ruff` and
verified `--preview` is used.
2023-11-01 12:12:02 -05:00
Dhruv Manilawala
e9acb99f7d Add PEP reference to D212, D213 docs (#8399) 2023-11-01 05:06:46 +00:00
Charlie Marsh
1642f4dbd9 Respect --force-exclude for lint.exclude and format.exclude (#8393)
## Summary

We typically avoid enforcing exclusions if a file was passed to Ruff
directly on the CLI. However, we also allow `--force-exclude`, which
ignores excluded files _even_ if they're passed to Ruff directly. This
is really important for pre-commit, which always passes changed files --
we need to exclude files passed by pre-commit if they're in the
`exclude` lists.

Turns out the new `lint.exclude` and `format.exclude` settings weren't
respecting `--force-exclude`.

Closes https://github.com/astral-sh/ruff/issues/8391.
2023-10-31 17:45:48 -04:00
doolio
38358980f1 Update docs related to file level error suppression (#8366)
Fixes: #8364

---------

Co-authored-by: Charlie Marsh <charlie.r.marsh@gmail.com>
2023-10-31 13:55:43 -05:00
Niranjan Kurhade
43691f97d0 Editor integrations link fixed in README (#8386) 2023-10-31 18:37:15 +00:00
konsti
3076d76b0a No newline after function docstrings (#8375)
Fixup for #8216 to not apply to function docstrings.

Main before #8216:

| project | similarity index | total files | changed files |

|----------------|------------------:|------------------:|------------------:|
| cpython | 0.75804 | 1799 | 1648 |
| django | 0.99984 | 2772 | 33 |
| home-assistant | 0.99963 | 10596 | 148 |
| poetry | 0.99925 | 317 | 12 |
| transformers | 0.99967 | 2657 | 328 |
| twine | 1.00000 | 33 | 0 |
| typeshed | 0.99980 | 3669 | 18 |
| warehouse | 0.99977 | 654 | 13 |
| zulip | 0.99970 | 1459 | 22 |

main now:

| project | similarity index | total files | changed files |

|----------------|------------------:|------------------:|------------------:|
| cpython | 0.75804 | 1799 | 1648 |
| django | 0.99984 | 2772 | 48 |
| home-assistant | 0.99963 | 10596 | 181 |
| poetry | 0.99925 | 317 | 12 |
| transformers | 0.99967 | 2657 | 339 |
| twine | 1.00000 | 33 | 0 |
| typeshed | 0.99980 | 3669 | 18 |
| warehouse | 0.99977 | 654 | 13 |
| zulip | 0.99970 | 1459 | 23 |

PR:

| project | similarity index | total files | changed files |

|----------------|------------------:|------------------:|------------------:|
| cpython | 0.75804 | 1799 | 1648 |
| django | 0.99984 | 2772 | 33 |
| home-assistant | 0.99963 | 10596 | 148 |
| poetry | 0.99925 | 317 | 12 |
| transformers | 0.99967 | 2657 | 328 |
| twine | 1.00000 | 33 | 0 |
| typeshed | 0.99980 | 3669 | 18 |
| warehouse | 0.99977 | 654 | 13 |
| zulip | 0.99970 | 1459 | 22 |
2023-10-31 14:32:15 -04:00
Charlie Marsh
23ed4e9616 Avoid un-setting bracket flag in logical lines (#8380)
## Summary

By using `set`, we were setting the bracket flag to `false` if another
operator was visited.

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

## Test Plan

`cargo test`
2023-10-31 10:30:15 -04:00
Dhruv Manilawala
97ae617fac Introduce LiteralExpressionRef for all literals (#8339)
## Summary

This PR adds a new `LiteralExpressionRef` which wraps all of the literal
expression nodes in a single enum. This allows for a narrow type when
working exclusively with a literal node. Additionally, it also
implements a `Expr::as_literal_expr` method to return the new enum if
the expression is indeed a literal one.

A few rules have been updated to account for the new enum:
1. `redundant_literal_union`
2. `if_else_block_instead_of_dict_lookup`
3. `magic_value_comparison`

To account for the change in (2), a new `ComparableLiteral` has been
added which can be constructed from the new enum
(`ComparableLiteral::from(<LiteralExpressionRef>)`).

### Open Questions

1. The new `ComparableLiteral` can be exclusively used via the
`LiteralExpressionRef` enum. Should we remove all of the literal
variants from `ComparableExpr` and instead have a single
`ComparableExpr::Literal(ComparableLiteral)` variant instead?

## Test Plan

`cargo test`
2023-10-31 12:56:11 +00:00
Dhruv Manilawala
a8d04cbd88 Update plugins for Neovim integration docs (#8371)
This PR updates the editor integration section of the documentation for
Neovim.
* Removes the now archived `null-ls` plugin
* Add `nvim-lint` (for linters) and `conform.nvim` (for formatter)
plugins

Screenshot ref: https://github.com/astral-sh/ruff/assets/67177269/b7032228-57b1-4141-ae17-e186c4428b61
2023-10-31 18:18:07 +05:30
Micha Reiser
230c93459f Delete redundant branch in NeedsParentheses (#8377) 2023-10-31 12:06:17 +00:00
Dhruv Manilawala
8977b6ae11 Inline AST helpers for new literal nodes (#8374)
A small refactor to inline the `is_const_none` now that there's a
dedicated `ExprNoneLiteral` node.
2023-10-31 11:06:54 +00:00
Zanie Blue
982ae6ff08 Ensure that ecosystem check job fails if the tooling encounters an unexpected error (#8365)
Previously, `| tee` would hide bad exit codes from `ruff-ecosystem ...`

See poc failure at
https://github.com/astral-sh/ruff/actions/runs/6698487019/job/18200852648?pr=8365
2023-10-30 19:48:38 -05:00
Charlie Marsh
c674db6e51 Fix invalid E231 error with f-strings (#8369)
## Summary

We were considering the `{` within an f-string to be a left brace, which
caused the "space-after-colon" rule to trigger incorrectly.

Closes https://github.com/astral-sh/ruff/issues/8299.
2023-10-30 19:38:13 -04:00
Charlie Marsh
7323c12eee Avoid duplicating linter-formatter compatibility warnings (#8292)
## Summary

Uses `warn_user_once!` instead of `warn!` to ensure that every warning
is shown exactly once, regardless of whether there are duplicates in the
list, or warnings that are raised by multiple configuration files.

Closes #8271.
2023-10-30 19:32:55 -04:00
Charlie Marsh
161c093c06 Avoid including literal shell=True for truthy, non-True diagnostics (#8359)
## Summary

If the value of `shell` wasn't literally `True`, we now show a message
describing it as truthy, rather than the (misleading) `shell=True`
literal in the diagnostic.

Closes https://github.com/astral-sh/ruff/issues/8310.
2023-10-30 15:44:38 +00:00
konsti
daea870c3c Fix panic with 8 in octal escape (#8356)
**Summary** The digits for an octal escape are 0 to 7, not 0 to 8,
fixing the panic in #8355

**Test plan** Regression test parser fixture
2023-10-30 14:42:15 +01:00
konsti
b6c4074836 Insert newline between docstring and following own line comment (#8216)
**Summary** Previously, own line comment following after a docstring
followed by newline(s) before the first content statement were treated
as trailing on the docstring and we didn't insert a newline after the
docstring as black would.

Before:
```python
class ModuleBrowser:
    """Browse module classes and functions in IDLE."""
    # This class is also the base class for pathbrowser.PathBrowser.

    def __init__(self, master, path, *, _htest=False, _utest=False):
        pass
```
After:
```python
class ModuleBrowser:
    """Browse module classes and functions in IDLE."""

    # This class is also the base class for pathbrowser.PathBrowser.

    def __init__(self, master, path, *, _htest=False, _utest=False):
        pass
```

I'm not entirely happy about hijacking
`handle_own_line_comment_between_statements`, but i don't know a better
spot to put it.

Fixes #7948

**Test Plan** Fixtures
2023-10-30 13:18:54 +00:00
konsti
cf74debf42 Update pyproject-toml to 0.8 (#8351)
`build-system` is now also optional upstream.

Closes #8343
2023-10-30 10:05:37 +00:00
konsti
f483ed4240 Byte strings aren't docstrings (#8350)
We previously incorrectly treated byte strings in docstring position as
docstrings because black does so
(https://github.com/astral-sh/ruff/pull/8283#discussion_r1375682931,
https://github.com/psf/black/issues/4002), even CPython doesn't
recognize them:

```console
$ python3.12
Python 3.12.0 (main, Oct  6 2023, 17:57:44) [GCC 11.4.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> def f():
...     b""" a"""
...
>>> print(str(f.__doc__))
None
```

<!--
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?
-->
2023-10-30 10:58:33 +01:00
dependabot[bot]
98b3d716c6 Bump clap from 4.4.6 to 4.4.7 (#8342)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-30 09:09:09 +00:00
dependabot[bot]
03df6fa105 Bump cloudflare/wrangler-action from 3.3.1 to 3.3.2 (#8348)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-30 09:08:55 +00:00
Micha Reiser
8cc97f70b4 Dedicated cache directory per ruff version (#8333) 2023-10-30 09:08:30 +00:00
dependabot[bot]
951c59c6ad Bump actions/setup-node from 3 to 4 (#8349)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-30 09:08:01 +00:00
dependabot[bot]
d177df226d Bump tempfile from 3.8.0 to 3.8.1 (#8345)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-30 09:06:48 +00:00
dependabot[bot]
703e2a9da3 Bump serde from 1.0.188 to 1.0.190 (#8346)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-30 09:06:09 +00:00
dependabot[bot]
bdad5e9a5f Bump tj-actions/changed-files from 39 to 40 (#8347)
Co-authored-by: dependabot[bot] <!-- raw HTML omitted --> (<a
Co-authored-by: jackton1 <a
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-30 09:05:45 +00:00
dependabot[bot]
b21eb1f689 Bump uuid from 1.4.1 to 1.5.0 (#8344)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-30 09:04:49 +00:00
Dhruv Manilawala
b0dc5a86a1 Impl Default for (String|Bytes|Boolean|None|Ellipsis)Literal (#8341)
## Summary

This PR adds `Default` for the following literal nodes:
* `StringLiteral`
* `BytesLiteral`
* `BooleanLiteral`
* `NoneLiteral`
* `EllipsisLiteral`

The implementation creates the zero value of the respective literal
nodes in terms of the Python language.

## Test Plan

`cargo test`
2023-10-30 08:47:44 +00:00
Dhruv Manilawala
b5a4a9a356 Inline ExprNumberLiteral formatting logic (#8340)
## Summary

This PR inlines the formatting logic for `ExprNumberLiteral` and removes
the need of having dedicated `Format*` struct for each number type.

## Test Plan

`cargo test`
2023-10-30 14:09:38 +05:30
Dhruv Manilawala
230c9ce236 Split Constant to individual literal nodes (#8064)
## Summary

This PR splits the `Constant` enum as individual literal nodes. It
introduces the following new nodes for each variant:
* `ExprStringLiteral`
* `ExprBytesLiteral`
* `ExprNumberLiteral`
* `ExprBooleanLiteral`
* `ExprNoneLiteral`
* `ExprEllipsisLiteral`

The main motivation behind this refactor is to introduce the new AST
node for implicit string concatenation in the coming PR. The elements of
that node will be either a string literal, bytes literal or a f-string
which can be implemented using an enum. This means that a string or
bytes literal cannot be represented by `Constant::Str` /
`Constant::Bytes` which creates an inconsistency.

This PR avoids that inconsistency by splitting the constant nodes into
it's own literal nodes, literal being the more appropriate naming
convention from a static analysis tool perspective.

This also makes working with literals in the linter and formatter much
more ergonomic like, for example, if one would want to check if this is
a string literal, it can be done easily using
`Expr::is_string_literal_expr` or matching against `Expr::StringLiteral`
as oppose to matching against the `ExprConstant` and enum `Constant`. A
few AST helper methods can be simplified as well which will be done in a
follow-up PR.

This introduces a new `Expr::is_literal_expr` method which is the same
as `Expr::is_constant_expr`. There are also intermediary changes related
to implicit string concatenation which are quiet less. This is done so
as to avoid having a huge PR which this already is.

## Test Plan

1. Verify and update all of the existing snapshots (parser, visitor)
2. Verify that the ecosystem check output remains **unchanged** for both
the linter and formatter

### Formatter ecosystem check

#### `main`

| project | similarity index | total files | changed files |

|----------------|------------------:|------------------:|------------------:|
| cpython | 0.75803 | 1799 | 1647 |
| django | 0.99983 | 2772 | 34 |
| home-assistant | 0.99953 | 10596 | 186 |
| poetry | 0.99891 | 317 | 17 |
| transformers | 0.99966 | 2657 | 330 |
| twine | 1.00000 | 33 | 0 |
| typeshed | 0.99978 | 3669 | 20 |
| warehouse | 0.99977 | 654 | 13 |
| zulip | 0.99970 | 1459 | 22 |

#### `dhruv/constant-to-literal`

| project | similarity index | total files | changed files |

|----------------|------------------:|------------------:|------------------:|
| cpython | 0.75803 | 1799 | 1647 |
| django | 0.99983 | 2772 | 34 |
| home-assistant | 0.99953 | 10596 | 186 |
| poetry | 0.99891 | 317 | 17 |
| transformers | 0.99966 | 2657 | 330 |
| twine | 1.00000 | 33 | 0 |
| typeshed | 0.99978 | 3669 | 20 |
| warehouse | 0.99977 | 654 | 13 |
| zulip | 0.99970 | 1459 | 22 |
2023-10-30 12:13:23 +05:30
Dhruv Manilawala
78bbf6d403 New Singleton enum for PatternMatchSingleton node (#8063)
## Summary

This PR adds a new `Singleton` enum for the `PatternMatchSingleton`
node.

Earlier the node was using the `Constant` enum but the value for this
pattern can only be either `None`, `True` or `False`. With the coming PR
to remove the `Constant`, this node required a new type to fill in.

This also has the benefit of narrowing the type down to only the
possible values for the node as evident by the removal of `unreachable`.

## Test Plan

Update the AST snapshots and run `cargo test`.
2023-10-30 05:48:53 +00:00
Charlie Marsh
ee7d445ef5 Use associated methods for MemberKey and ModuleKey (#8337) 2023-10-30 04:48:28 +00:00
bluthej
5776ec1079 Sort imports by cached key (#7963)
## Summary

Refactor for isort implementation. Closes #7738.

I introduced a `NatOrdString` and a `NatOrdStr` type to have a naturally
ordered `String` and `&str`, and I pretty much went back to the original
implementation based on `module_key`, `member_key` and
`sorted_by_cached_key` from itertools. I tried my best to avoid
unnecessary allocations but it may have been clumsy in some places, so
feedback is appreciated! I also renamed the `Prefix` enum to
`MemberType` (and made some related adjustments) because I think this
fits more what it is, and it's closer to the wording found in the isort
documentation.

I think the result is nicer to work with, and it should make
implementing #1567 and the like easier :)

Of course, I am very much open to any and all remarks on what I did!

## Test Plan

I didn't add any test, I am relying on the existing tests since this is
just a refactor.
2023-10-30 04:37:33 +00:00
Micha Reiser
1f2d4f3ee1 File exclusion: Reduce code duplication (#8336) 2023-10-30 03:15:08 +00:00
Charlie Marsh
221f7cd932 Respect --extend-per-file-ignores on the CLI (#8329)
## Summary

This field was being dropped from the CLI.

Closes https://github.com/astral-sh/ruff/issues/8328.
2023-10-29 20:44:24 -04:00
Micha Reiser
c7aa816f17 Split tuples in return positions by comma first (#8280) 2023-10-30 00:25:44 +00:00
Micha Reiser
3ccca332bd Preserve trailing semicolons when using fmt: off (#8275) 2023-10-30 00:22:34 +00:00
Micha Reiser
2c84f911c4 Preserve trailing statement semicolons when using fmt: skip (#8273) 2023-10-30 00:07:14 +00:00
Joshua Bronson
e799f90782 Fix typo (s/adding then/adding them). (#8327)
Noticed this typo and figured I'd submit a drive-by fix.
2023-10-29 22:08:15 +00:00
Andrew Shannon Brown
9b89bf7d8a Implement pylint import-outside-toplevel rule (C0415) (#5180)
## Summary

Implements pylint C0415 (import-outside-toplevel) — imports should be at
the top level of a file.

The great debate I had on this implementation is whether "top-level" is
one word or two (`toplevel` or `top_level`). I opted for 2 because that
seemed to be how it is used in the codebase but the rule string itself
uses one-word "toplevel." 🤷 I'd be happy to change it as desired.

I suppose this could be auto-fixed by moving the import to the
top-level, but it seems likely that the author's intent was to actually
import this dynamically, so I view the main point of this rule is to
force some sort of explanation, and auto-fixing might be annoying.

For reference, this is what "pylint" reports:
```
> pylint crates/ruff/resources/test/fixtures/pylint/import_outside_top_level.py
************* Module import_outside_top_level
...
crates/ruff/resources/test/fixtures/pylint/import_outside_top_level.py:4:4: C0415: Import outside toplevel (string) (import-outside-toplevel)
```

ruff would now report:

```
import_outside_top_level.py:4:5: PLC0415 `import` should be used only at the top level of a file
  |
3 | def import_outside_top_level():
4 |     import string # [import-outside-toplevel]
  |     ^^^^^^^^^^^^^ PLC0415
  |
```

Contributes to https://github.com/astral-sh/ruff/issues/970.

## Test Plan

Snapshot test.
2023-10-29 16:40:26 +00:00
T-256
d7b966d6cd Docs: add .lint on generating options examples. (#8324) 2023-10-29 12:39:54 -04:00
Harutaka Kawamura
44e21cfada [pylint] Implement useless-with-lock (#8321) 2023-10-29 16:24:52 +00:00
Charlie Marsh
cda1c5dd35 Consistently link more settings in the documentation (#8325) 2023-10-29 16:14:50 +00:00
Charlie Marsh
86cdaea743 Allow selective caching for --fix and --diff (#8316)
## Summary

If a file has no diagnostics, then we can read and write that
information from and to the cache, even if the fix mode is `--fix` or
`--diff`. (Typically, we can't read or write such results from or to the
cache, because `--fix` and `--diff` have side effects that take place
during diagnostic analysis (writing to disk or outputting the diff).)
This greatly improves performance when running `--fix` on a codebase in
the common case (few diagnostics).

Closes #8311.
Closes https://github.com/astral-sh/ruff/issues/8315.
2023-10-29 16:06:35 +00:00
Zanie Blue
af4cb34ce2 Update old ecosystem checks for formatter for clarity (#8285)
Changes the title and adds some notes re the old formatter ecosystem
checks in light of #8223

Does not remove it as I'm not sure where else we test for instabilities.
2023-10-29 00:13:28 -05:00
Dhruv Manilawala
4afff436ff Update playground format title (#8320)
Rename `Format (alpha)` to `Format (beta)`
2023-10-29 04:18:25 +00:00
Charlie Marsh
f2f2e759c7 Add a note on line-too-long to the formatter docs (#8314)
Suggested here:
https://github.com/astral-sh/ruff/discussions/7310#discussioncomment-7410638.
2023-10-28 22:25:38 -04:00
Mathieu Kniewallner
317b6e8682 Use tool.ruff.lint in more places (#8317)
## Summary

As a follow-up of https://github.com/astral-sh/ruff/pull/7732, use
`tool.ruff.lint` in more places in documentations, tests and internal
usages.
2023-10-28 18:39:38 -05:00
Carter Snook
2f5734d1ac perf(parser): use faster string parser methods (#8227)
## Summary

This makes use of memchr and other methods to parse the strings
(hopefully) faster. It might also be worth converting the
`parse_fstring_middle` helper to use similar techniques, but I did not
implement it in this PR.

## Test Plan

This was tested using the existing tests and passed all of them.
2023-10-28 18:50:54 -04:00
T-256
c39ea6ef05 Docs: Avoid mention deprecated extend-ignore settings (#8305)
## Summary

Closes #8243

I'm not sure about #8222. formatter conflicts warning should include
deprecations happened before warning implementation?
2023-10-28 22:50:33 +00:00
Tom Kuson
10a50bf1e2 [refurb] Implement isinstance-type-none (FURB168) (#8308)
## Summary

Implement
[`no-isinstance-type-none`](https://github.com/dosisod/refurb/blob/master/refurb/checks/builtin/no_isinstance_type_none.py)
as `isinstance-type-none` (`FURB168`).

Auto-fixes calls to `isinstance` to check if an object is `None` to a
`None` identity check. For example,

```python
isinstance(foo, type(None))
```

becomes

```python
foo is None
```

Related to #1348.

## Test Plan

`cargo test`

---------

Co-authored-by: Charlie Marsh <charlie.r.marsh@gmail.com>
2023-10-28 22:37:02 +00:00
Harutaka Kawamura
a151e50ad3 [pylint] Implement bad-open-mode (W1501) (#8294) 2023-10-28 22:30:31 +00:00
Mathieu Kniewallner
854f5d09fa docs(configuration): replace extend-exclude with exclude (#8306)
## Summary

Similarly to https://github.com/astral-sh/ruff/pull/8302, the
configuration documentation mentions `extend-exclude` for tool specific
configuration, although neither `format` nor `lint` supports it, since
they only support `exclude`.
2023-10-28 17:08:00 -04:00
Mathieu Kniewallner
c2f6c79b3d chore: update packaging metadata (#8307)
## Summary

Tiny updates on packaging metadata:
- mention code formatting in description
- link to changelog instead of releases, now that a changelog file
exists and updated on each release
2023-10-28 23:02:07 +02:00
Farookh Zaheer Siddiqui
87772c2884 Fix typo (#8309)
<!--
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

<!-- What's the purpose of the change? What does it do, and why? -->

## Test Plan

<!-- How was it tested? -->
2023-10-28 12:36:39 -05:00
Harutaka Kawamura
aa90a425e0 Improve B015 message (#8295) 2023-10-28 07:18:02 -04:00
Lukas Burgholzer
81a2e74fe2 Extend bad-dunder-method-name to permit __index__ (#8300)
## Summary

Fixes #8282

## Test Plan

`cargo test`
2023-10-28 07:15:30 -04:00
Giulio Mazzanti
3af890f32f Remove exclude suggestion to use extend-exclude in tool.ruff.format config docs (#8302)
## Summary

Remove wrong note on `tool.ruff.format` `exclude` option from
documentation which is referencing `extend-exclude` even if it's not
relevant for the formatter options (`exclude` is additive). See #8301

## Test Plan

N/A (Docs change)
2023-10-28 07:15:14 -04:00
Harmon
223873c8c7 Correct typo in rules documentation (#8303)
## Summary

Add missing "is":
```diff
- The 🧪 emoji indicates that a rule in "preview".
+ The 🧪 emoji indicates that a rule is in "preview".
```

## Test Plan

N/A
2023-10-28 07:14:13 -04:00
Aarni Koskela
7b4b004506 Harmonize help commands' --format to --output-format with deprecation warnings (#8203)
## Summary

Since `--format` was changed to `--output-format` for `check`, it feels
like it makes sense for the same to work for the auxiliary commands.

This 

* adds the same deprecation warning that used to be a thing in #7514
(and un-became a thing in #7984)

Fixes #7990.

## Test Plan

* `cargo run --bin=ruff -- rule --all --output-format=json` works
* `cargo run --bin=ruff -- rule --format=json` works with warnings
2023-10-28 03:30:46 +00:00
Zanie Blue
9f5102d536 Improve calculation of max display per rule in ecosystem checks (#8291)
Fixes bug where `total_affected_rules` is empty, a division by zero
error can occur if there are only errors and no rule changes. Calculates
the maximum display per rule with the calculated project maximum as the
upper bound instead of 50, this should show more rule variety when
project maximums are lower.

This commit was meant to be in #8223 but I missed it.
2023-10-27 22:04:52 -05:00
konsti
af95cbaeef Add newline after module docstrings in preview style (#8283)
Change
```python
"""Test docstring"""
a = 1
```
to
```python
"""Test docstring"""

a = 1
```
in preview style, but don't touch the docstring otherwise.

Do we want to ask black to also format the content of module level
docstrings? Seems inconsistent to me that we change function and class
docstring indentation/contents but not module docstrings.

Fixes https://github.com/astral-sh/ruff/issues/7995
2023-10-28 01:16:50 +00:00
Zanie Blue
fc94857a20 Rewrite ecosystem checks and add ruff format reports (#8223)
Closes #7239 

- Refactors `scripts/check_ecosystem.py` into a new Python project at
`python/ruff-ecosystem`
- Includes
[documentation](https://github.com/astral-sh/ruff/blob/zanie/ecosystem-format/python/ruff-ecosystem/README.md)
now
    - Provides a `ruff-ecosystem` CLI
- Fixes bug where `ruff check` report included "fixable" summary line
- Adds truncation to `ruff check` reports
    - Otherwise we often won't see the `ruff format` reports
- The truncation uses some very simple heuristics and could be improved
in the future
- Identifies diagnostic changes that occur just because a violation's
fix available changes
- We still show the diff for the line because it's could matter _where_
this changes, but we could improve this
- Similarly, we could improve detection of diagnostic changes where just
the message changes
- Adds support for JSON ecosystem check output
    - I added this primarily for development purposes
- If there are no changes, only errors while processing projects, we
display a different summary message
- When caching repositories, we now checkout the requested ref
- Adds `ruff format` reports, which format with the baseline then the
use `format --diff` to generate a report
- Runs all CI jobs when the CI workflow is changed

## Known problems

- Since we must format the project to get a baseline, the permalink line
numbers do not exactly correspond to the correct range
- This looks... hard. I tried using `git diff` and some wonky hunk
matching to recover the original line numbers but it doesn't seem worth
it. I think we should probably commit the formatted changes to a fork or
something if we want great results here. Consequently, I've just used
the start line instead of a range for now.
- I don't love the comment structure — it'd be nice, perhaps, to have
separate headings for the linter and formatter.
- However, the `pr-comment` workflow is an absolute pain to change
because it runs _separately_ from this pull request so I if I want to
make edits to it I can only test it via manual workflow dispatch.
- Lines are not printed "as we go" which means they're all held in
memory, presumably this would be a problem for large-scale ecosystem
checks
- We are encountering a hard limit with the maximum comment length
supported by GitHub. We will need to move the bulk of the report
elsewhere.

## Future work

- Update `ruff-ecosystem` to support non-default projects and
`check_ecosystem_all.py` behavior
- Remove existing ecosystem check scripts
- Add preview mode toggle (#8076)
- Add a toggle for truncation
- Add hints for quick reproduction of runs locally
- Consider parsing JSON output of Ruff instead of using regex to parse
the text output
- Links to project repositories should use the commit hash we checked
against
- When caching repositories, we should pull the latest changes for the
ref
- Sort check diffs by path and rule code only (changes in messages
should not change order)
- Update check diffs to distinguish between new violations and changes
in messages
- Add "fix" diffs
- Remove existing formatter similarity reports
- On release pull request, compare to the previous tag instead

---------

Co-authored-by: konsti <konstin@mailbox.org>
2023-10-27 17:28:01 -05:00
Zanie Blue
5f26411577 Update ecosystem pull request comment to post when unrelated jobs fail (#8289)
While we ran the `pr-comment` workflow on `completed` workflows (i.e.
instead of `success`, it can have failed) we used the default artifact
download behavior which required `success` workflows
(https://github.com/dawidd6/action-download-artifact) which meant that
if any jobs failed the ecosystem checks would not be reported.

For example, a successful ecosystem run at
https://github.com/astral-sh/ruff/actions/runs/6672033727/job/18135290880
was not posted at
https://github.com/astral-sh/ruff/actions/runs/6672153598/job/18135534008
because no artifacts met the success criterion.

You can see this is "valid" with a manual dispatch at
https://github.com/astral-sh/ruff/actions/runs/6672278055/job/18135883849
but it pulls from the latest commit rather than the one with the failed
one mentioned above so you can't see verification it'll work for failed
jobs. Another manual dispatch at
https://github.com/astral-sh/ruff/actions/runs/6672349316/job/18136082917
shows it works great for successful jobs still.
2023-10-27 16:20:39 -05:00
1186 changed files with 25347 additions and 13983 deletions

29
.github/release.yml vendored
View File

@@ -1,29 +0,0 @@
# https://docs.github.com/en/repositories/releasing-projects-on-github/automatically-generated-release-notes#configuring-automatically-generated-release-notes
changelog:
exclude:
labels:
- internal
- documentation
categories:
- title: Breaking Changes
labels:
- breaking
- title: Rules
labels:
- rule
- title: Settings
labels:
- configuration
- cli
- title: Bug Fixes
labels:
- bug
- title: Formatter
labels:
- formatter
- title: Preview
labels:
- preview
- title: Other Changes
labels:
- "*"

View File

@@ -30,7 +30,7 @@ jobs:
with:
fetch-depth: 0
- uses: tj-actions/changed-files@v39
- uses: tj-actions/changed-files@v40
id: changed
with:
files_yaml: |
@@ -132,7 +132,7 @@ jobs:
- uses: actions/checkout@v4
- name: "Install Rust toolchain"
run: rustup target add wasm32-unknown-unknown
- uses: actions/setup-node@v3
- uses: actions/setup-node@v4
with:
node-version: 18
cache: "npm"
@@ -184,7 +184,11 @@ jobs:
- cargo-test-linux
- determine_changes
# Only runs on pull requests, since that is the only we way we can find the base version for comparison.
if: github.event_name == 'pull_request'
# Ecosystem check needs linter and/or formatter changes.
if: github.event_name == 'pull_request' && ${{
needs.determine_changes.outputs.linter == 'true' ||
needs.determine_changes.outputs.formatter == 'true'
}}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v4
@@ -209,28 +213,69 @@ jobs:
run: |
pip install ./python/ruff-ecosystem
- name: Run `ruff check` ecosystem check
- name: Run `ruff check` stable ecosystem check
if: ${{ needs.determine_changes.outputs.linter == 'true' }}
run: |
# Make executable, since artifact download doesn't preserve this
chmod +x ./ruff ${{ steps.ruff-target.outputs.download-path }}/ruff
ruff-ecosystem check ./ruff ${{ steps.ruff-target.outputs.download-path }}/ruff --cache ./checkouts --output-format markdown | tee ecosystem-result-check
# Set pipefail to avoid hiding errors with tee
set -eo pipefail
cat ecosystem-result-check > $GITHUB_STEP_SUMMARY
cat ecosystem-result-check > ecosystem-result
ruff-ecosystem check ./ruff ${{ steps.ruff-target.outputs.download-path }}/ruff --cache ./checkouts --output-format markdown | tee ecosystem-result-check-stable
cat ecosystem-result-check-stable > $GITHUB_STEP_SUMMARY
echo "### Linter (stable)" > ecosystem-result
cat ecosystem-result-check-stable >> ecosystem-result
echo "" >> ecosystem-result
- name: Run `ruff format` ecosystem check
- name: Run `ruff check` preview ecosystem check
if: ${{ needs.determine_changes.outputs.linter == 'true' }}
run: |
# Make executable, since artifact download doesn't preserve this
chmod +x ./ruff ${{ steps.ruff-target.outputs.download-path }}/ruff
# Set pipefail to avoid hiding errors with tee
set -eo pipefail
ruff-ecosystem check ./ruff ${{ steps.ruff-target.outputs.download-path }}/ruff --cache ./checkouts --output-format markdown --force-preview | tee ecosystem-result-check-preview
cat ecosystem-result-check-preview > $GITHUB_STEP_SUMMARY
echo "### Linter (preview)" >> ecosystem-result
cat ecosystem-result-check-preview >> ecosystem-result
echo "" >> ecosystem-result
- name: Run `ruff format` stable ecosystem check
if: ${{ needs.determine_changes.outputs.formatter == 'true' }}
run: |
# Make executable, since artifact download doesn't preserve this
chmod +x ./ruff ${{ steps.ruff-target.outputs.download-path }}/ruff
ruff-ecosystem format ./ruff ${{ steps.ruff-target.outputs.download-path }}/ruff --cache ./checkouts --output-format markdown | tee ecosystem-result-format
# Set pipefail to avoid hiding errors with tee
set -eo pipefail
cat ecosystem-result-format > $GITHUB_STEP_SUMMARY
cat ecosystem-result-format >> ecosystem-result
ruff-ecosystem format ./ruff ${{ steps.ruff-target.outputs.download-path }}/ruff --cache ./checkouts --output-format markdown | tee ecosystem-result-format-stable
cat ecosystem-result-format-stable > $GITHUB_STEP_SUMMARY
echo "### Formatter (stable)" >> ecosystem-result
cat ecosystem-result-format-stable >> ecosystem-result
echo "" >> ecosystem-result
- name: Run `ruff format` preview ecosystem check
if: ${{ needs.determine_changes.outputs.formatter == 'true' }}
run: |
# Make executable, since artifact download doesn't preserve this
chmod +x ./ruff ${{ steps.ruff-target.outputs.download-path }}/ruff
# Set pipefail to avoid hiding errors with tee
set -eo pipefail
ruff-ecosystem format ./ruff ${{ steps.ruff-target.outputs.download-path }}/ruff --cache ./checkouts --output-format markdown --force-preview | tee ecosystem-result-format-preview
cat ecosystem-result-format-preview > $GITHUB_STEP_SUMMARY
echo "### Formatter (preview)" >> ecosystem-result
cat ecosystem-result-format-preview >> ecosystem-result
echo "" >> ecosystem-result
- name: Export pull request number
run: |
@@ -349,8 +394,8 @@ jobs:
if: ${{ env.MKDOCS_INSIDERS_SSH_KEY_EXISTS != 'true' }}
run: mkdocs build --strict -f mkdocs.generated.yml
check-formatter-ecosystem:
name: "Formatter ecosystem and progress checks"
check-formatter-instability-and-black-similarity:
name: "formatter instabilities and black similarity"
runs-on: ubuntu-latest
needs: determine_changes
if: needs.determine_changes.outputs.formatter == 'true' || github.ref == 'refs/heads/main'

View File

@@ -47,7 +47,7 @@ jobs:
run: mkdocs build --strict -f mkdocs.generated.yml
- name: "Deploy to Cloudflare Pages"
if: ${{ env.CF_API_TOKEN_EXISTS == 'true' }}
uses: cloudflare/wrangler-action@v3.3.1
uses: cloudflare/wrangler-action@v3.3.2
with:
apiToken: ${{ secrets.CF_API_TOKEN }}
accountId: ${{ secrets.CF_ACCOUNT_ID }}

View File

@@ -20,7 +20,7 @@ jobs:
- uses: actions/checkout@v4
- name: "Install Rust toolchain"
run: rustup target add wasm32-unknown-unknown
- uses: actions/setup-node@v3
- uses: actions/setup-node@v4
with:
node-version: 18
cache: "npm"
@@ -40,7 +40,7 @@ jobs:
working-directory: playground
- name: "Deploy to Cloudflare Pages"
if: ${{ env.CF_API_TOKEN_EXISTS == 'true' }}
uses: cloudflare/wrangler-action@v3.3.1
uses: cloudflare/wrangler-action@v3.3.2
with:
apiToken: ${{ secrets.CF_API_TOKEN }}
accountId: ${{ secrets.CF_ACCOUNT_ID }}

View File

@@ -1,4 +1,4 @@
name: PR Check Comment
name: Ecosystem check comment
on:
workflow_run:
@@ -18,13 +18,13 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: dawidd6/action-download-artifact@v2
name: Download PR Number
name: Download pull request number
with:
name: pr-number
run_id: ${{ github.event.workflow_run.id || github.event.inputs.workflow_run_id }}
if_no_artifact_found: ignore
- name: Extract PR Number
- name: Parse pull request number
id: pr-number
run: |
if [[ -f pr-number ]]
@@ -33,7 +33,7 @@ jobs:
fi
- uses: dawidd6/action-download-artifact@v2
name: "Download Ecosystem Result"
name: "Download ecosystem results"
id: download-ecosystem-result
if: steps.pr-number.outputs.pr-number
with:
@@ -41,15 +41,18 @@ jobs:
workflow: ci.yaml
pr: ${{ steps.pr-number.outputs.pr-number }}
path: pr/ecosystem
workflow_conclusion: completed
if_no_artifact_found: ignore
- name: Generate Comment
- name: Generate comment content
id: generate-comment
if: steps.download-ecosystem-result.outputs.found_artifact == 'true'
run: |
echo '## PR Check Results' >> comment.txt
# Note this identifier is used to find the comment to update on
# subsequent runs
echo '<!-- generated-comment ecosystem -->' >> comment.txt
echo "### Ecosystem" >> comment.txt
echo '## `ruff-ecosystem` results' >> comment.txt
cat pr/ecosystem/ecosystem-result >> comment.txt
echo "" >> comment.txt
@@ -57,14 +60,14 @@ jobs:
cat comment.txt >> $GITHUB_OUTPUT
echo 'EOF' >> $GITHUB_OUTPUT
- name: Find Comment
- name: Find existing comment
uses: peter-evans/find-comment@v2
if: steps.generate-comment.outcome == 'success'
id: find-comment
with:
issue-number: ${{ steps.pr-number.outputs.pr-number }}
comment-author: "github-actions[bot]"
body-includes: PR Check Results
body-includes: "<!-- generated-comment ecosystem -->"
- name: Create or update comment
if: steps.find-comment.outcome == 'success'

View File

@@ -48,7 +48,6 @@ jobs:
args: --out dist
- name: "Test sdist"
run: |
rustup default $(cat rust-toolchain)
pip install dist/${{ env.PACKAGE_NAME }}-*.tar.gz --force-reinstall
ruff --help
python -m ruff --help

View File

@@ -13,12 +13,12 @@ exclude: |
repos:
- repo: https://github.com/abravalheri/validate-pyproject
rev: v0.12.1
rev: v0.15
hooks:
- id: validate-pyproject
- repo: https://github.com/executablebooks/mdformat
rev: 0.7.16
rev: 0.7.17
hooks:
- id: mdformat
additional_dependencies:
@@ -26,16 +26,22 @@ repos:
- mdformat-admon
exclude: |
(?x)^(
docs/formatter/black.md
docs/formatter/black\.md
| docs/\w+\.md
)$
- repo: https://github.com/igorshubovych/markdownlint-cli
rev: v0.33.0
rev: v0.37.0
hooks:
- id: markdownlint-fix
exclude: |
(?x)^(
docs/formatter/black\.md
| docs/\w+\.md
)$
- repo: https://github.com/crate-ci/typos
rev: v1.14.12
rev: v1.16.22
hooks:
- id: typos
@@ -47,10 +53,13 @@ repos:
language: system
types: [rust]
pass_filenames: false # This makes it a lot faster
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.1.4
hooks:
- id: ruff-format
- id: ruff
name: ruff
entry: cargo run --bin ruff -- check --no-cache --force-exclude --fix --exit-non-zero-on-fix
language: system
args: [--fix, --exit-non-zero-on-fix]
types_or: [python, pyi]
require_serial: true
exclude: |
@@ -59,15 +68,9 @@ repos:
crates/ruff_python_formatter/resources/.*
)$
# Black
- repo: https://github.com/psf/black
rev: 23.1.0
hooks:
- id: black
# Prettier
- repo: https://github.com/pre-commit/mirrors-prettier
rev: v3.0.0
rev: v3.0.3
hooks:
- id: prettier
types: [yaml]

View File

@@ -1,5 +1,115 @@
# Changelog
## 0.1.5
### Preview features
- \[`flake8-bandit`\] Implement `mako-templates` (`S702`) ([#8533](https://github.com/astral-sh/ruff/pull/8533))
- \[`flake8-trio`\] Implement `TRIO105` ([#8490](https://github.com/astral-sh/ruff/pull/8490))
- \[`flake8-trio`\] Implement `TRIO109` ([#8534](https://github.com/astral-sh/ruff/pull/8534))
- \[`flake8-trio`\] Implement `TRIO110` ([#8537](https://github.com/astral-sh/ruff/pull/8537))
- \[`flake8-trio`\] Implement `TRIO115` ([#8486](https://github.com/astral-sh/ruff/pull/8486))
- \[`refurb`\] Implement `type-none-comparison` (`FURB169`) ([#8487](https://github.com/astral-sh/ruff/pull/8487))
- Flag all comparisons against builtin types in `E721` ([#8491](https://github.com/astral-sh/ruff/pull/8491))
- Make `SIM118` fix as safe when the expression is a known dictionary ([#8525](https://github.com/astral-sh/ruff/pull/8525))
### Formatter
- Fix multiline lambda expression statement formatting ([#8466](https://github.com/astral-sh/ruff/pull/8466))
### CLI
- Add hidden `--extension` to override inference of source type from file extension ([#8373](https://github.com/astral-sh/ruff/pull/8373))
### Configuration
- Account for selector specificity when merging `extend_unsafe_fixes` and `override extend_safe_fixes` ([#8444](https://github.com/astral-sh/ruff/pull/8444))
- Add support for disabling cache with `RUFF_NO_CACHE` environment variable ([#8538](https://github.com/astral-sh/ruff/pull/8538))
### Bug fixes
- \[`E721`\] Flag comparisons to `memoryview` ([#8485](https://github.com/astral-sh/ruff/pull/8485))
- Allow collapsed-ellipsis bodies in other statements ([#8499](https://github.com/astral-sh/ruff/pull/8499))
- Avoid `D301` autofix for `u` prefixed strings ([#8495](https://github.com/astral-sh/ruff/pull/8495))
- Only flag `flake8-trio` rules when `trio` import is present ([#8550](https://github.com/astral-sh/ruff/pull/8550))
- Reject more syntactically invalid Python programs ([#8524](https://github.com/astral-sh/ruff/pull/8524))
- Avoid raising `TRIO115` violations for `trio.sleep(...)` calls with non-number values ([#8532](https://github.com/astral-sh/ruff/pull/8532))
- Fix `F841` false negative on assignment to multiple variables ([#8489](https://github.com/astral-sh/ruff/pull/8489))
### Documentation
- Fix link to isort `known-first-party` ([#8562](https://github.com/astral-sh/ruff/pull/8562))
- Add notes on fix safety to a few rules ([#8500](https://github.com/astral-sh/ruff/pull/8500))
- Add missing toml config tabs ([#8512](https://github.com/astral-sh/ruff/pull/8512))
- Add instructions for configuration of Emacs ([#8488](https://github.com/astral-sh/ruff/pull/8488))
- Improve detail link contrast in dark mode ([#8548](https://github.com/astral-sh/ruff/pull/8548))
- Fix typo in example ([#8506](https://github.com/astral-sh/ruff/pull/8506))
- Added tabs for configuration files in the documentation ([#8480](https://github.com/astral-sh/ruff/pull/8480))
- Recommend `project.requires-python` over `target-version` ([#8513](https://github.com/astral-sh/ruff/pull/8513))
- Add singleton escape hatch to `B008` documentation ([#8501](https://github.com/astral-sh/ruff/pull/8501))
- Fix tab configuration docs ([#8502](https://github.com/astral-sh/ruff/pull/8502))
## 0.1.4
### Preview features
- \[`flake8-trio`\] Implement `timeout-without-await` (`TRIO001`) ([#8439](https://github.com/astral-sh/ruff/pull/8439))
- \[`numpy`\] Implement NumPy 2.0 migration rule (`NPY200`) ([#7702](https://github.com/astral-sh/ruff/pull/7702))
- \[`pylint`\] Implement `bad-open-mode` (`W1501`) ([#8294](https://github.com/astral-sh/ruff/pull/8294))
- \[`pylint`\] Implement `import-outside-toplevel` (`C0415`) rule ([#5180](https://github.com/astral-sh/ruff/pull/5180))
- \[`pylint`\] Implement `useless-with-lock` (`W2101`) ([#8321](https://github.com/astral-sh/ruff/pull/8321))
- \[`pyupgrade`\] Implement `timeout-error-alias` (`UP041`) ([#8476](https://github.com/astral-sh/ruff/pull/8476))
- \[`refurb`\] Implement `isinstance-type-none` (`FURB168`) ([#8308](https://github.com/astral-sh/ruff/pull/8308))
- Detect confusable Unicode-to-Unicode units in `RUF001`, `RUF002`, and `RUF003` ([#4430](https://github.com/astral-sh/ruff/pull/4430))
- Add newline after module docstrings in preview style ([#8283](https://github.com/astral-sh/ruff/pull/8283))
### Formatter
- Add a note on line-too-long to the formatter docs ([#8314](https://github.com/astral-sh/ruff/pull/8314))
- Preserve trailing statement semicolons when using `fmt: skip` ([#8273](https://github.com/astral-sh/ruff/pull/8273))
- Preserve trailing semicolons when using `fmt: off` ([#8275](https://github.com/astral-sh/ruff/pull/8275))
- Avoid duplicating linter-formatter compatibility warnings ([#8292](https://github.com/astral-sh/ruff/pull/8292))
- Avoid inserting a newline after function docstrings ([#8375](https://github.com/astral-sh/ruff/pull/8375))
- Insert newline between docstring and following own line comment ([#8216](https://github.com/astral-sh/ruff/pull/8216))
- Split tuples in return positions by comma first ([#8280](https://github.com/astral-sh/ruff/pull/8280))
- Avoid treating byte strings as docstrings ([#8350](https://github.com/astral-sh/ruff/pull/8350))
- Add `--line-length` option to `format` command ([#8363](https://github.com/astral-sh/ruff/pull/8363))
- Avoid parenthesizing unsplittable because of comments ([#8431](https://github.com/astral-sh/ruff/pull/8431))
### CLI
- Add `--output-format` to `ruff rule` and `ruff linter` ([#8203](https://github.com/astral-sh/ruff/pull/8203))
### Bug fixes
- Respect `--force-exclude` in `lint.exclude` and `format.exclude` ([#8393](https://github.com/astral-sh/ruff/pull/8393))
- Respect `--extend-per-file-ignores` on the CLI ([#8329](https://github.com/astral-sh/ruff/pull/8329))
- Extend `bad-dunder-method-name` to permit `__index__` ([#8300](https://github.com/astral-sh/ruff/pull/8300))
- Fix panic with 8 in octal escape ([#8356](https://github.com/astral-sh/ruff/pull/8356))
- Avoid raising `D300` when both triple quote styles are present ([#8462](https://github.com/astral-sh/ruff/pull/8462))
- Consider unterminated f-strings in `FStringRanges` ([#8154](https://github.com/astral-sh/ruff/pull/8154))
- Avoid including literal `shell=True` for truthy, non-`True` diagnostics ([#8359](https://github.com/astral-sh/ruff/pull/8359))
- Avoid triggering single-element test for starred expressions ([#8433](https://github.com/astral-sh/ruff/pull/8433))
- Detect and ignore Jupyter automagics ([#8398](https://github.com/astral-sh/ruff/pull/8398))
- Fix invalid E231 error with f-strings ([#8369](https://github.com/astral-sh/ruff/pull/8369))
- Avoid triggering `NamedTuple` rewrite with starred annotation ([#8434](https://github.com/astral-sh/ruff/pull/8434))
- Avoid un-setting bracket flag in logical lines ([#8380](https://github.com/astral-sh/ruff/pull/8380))
- Place 'r' prefix before 'f' for raw format strings ([#8464](https://github.com/astral-sh/ruff/pull/8464))
- Remove trailing periods from NumPy 2.0 code actions ([#8475](https://github.com/astral-sh/ruff/pull/8475))
- Fix bug where `PLE1307` was raised when formatting `%c` with characters ([#8407](https://github.com/astral-sh/ruff/pull/8407))
- Remove unicode flag from comparable ([#8440](https://github.com/astral-sh/ruff/pull/8440))
- Improve B015 message ([#8295](https://github.com/astral-sh/ruff/pull/8295))
- Use `fixedOverflowWidgets` for playground popover ([#8458](https://github.com/astral-sh/ruff/pull/8458))
- Mark `byte_bounds` as a non-backwards-compatible NumPy 2.0 change ([#8474](https://github.com/astral-sh/ruff/pull/8474))
### Internals
- Add a dedicated cache directory per Ruff version ([#8333](https://github.com/astral-sh/ruff/pull/8333))
- Allow selective caching for `--fix` and `--diff` ([#8316](https://github.com/astral-sh/ruff/pull/8316))
- Improve performance of comment parsing ([#8193](https://github.com/astral-sh/ruff/pull/8193))
- Improve performance of string parsing ([#8227](https://github.com/astral-sh/ruff/pull/8227))
- Use a dedicated sort key for isort import sorting ([#7963](https://github.com/astral-sh/ruff/pull/7963))
## 0.1.3
This release includes a variety of improvements to the Ruff formatter, removing several known and

View File

@@ -72,7 +72,7 @@ representative at an online or offline event.
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
charlie.r.marsh@gmail.com.
<charlie.r.marsh@gmail.com>.
All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the

View File

@@ -114,7 +114,7 @@ such that all crates are contained in a flat `crates` directory.
The vast majority of the code, including all lint rules, lives in the `ruff` crate (located at
`crates/ruff_linter`). As a contributor, that's the crate that'll be most relevant to you.
At time of writing, the repository includes the following crates:
At the time of writing, the repository includes the following crates:
- `crates/ruff_linter`: library crate containing all lint rules and the core logic for running them.
If you're working on a rule, this is the crate for you.
@@ -315,9 +315,18 @@ even patch releases may contain [non-backwards-compatible changes](https://semve
### Creating a new release
1. Update the version with `rg 0.0.269 --files-with-matches | xargs sed -i 's/0.0.269/0.0.270/g'`
1. Update `BREAKING_CHANGES.md`
1. Create a PR with the version and `BREAKING_CHANGES.md` updated
We use an experimental in-house tool for managing releases.
1. Install `rooster`: `pip install git+https://github.com/zanieb/rooster@main`
1. Run `rooster release`; this command will:
- Generate a changelog entry in `CHANGELOG.md`
- Update versions in `pyproject.toml` and `Cargo.toml`
- Update references to versions in the `README.md` and documentation
1. The changelog should then be editorialized for consistency
- Often labels will be missing from pull requests they will need to be manually organized into the proper section
- Changes should be edited to be user-facing descriptions, avoiding internal details
1. Highlight any breaking changes in `BREAKING_CHANGES.md`
1. Create a pull request with the changelog and version updates
1. Merge the PR
1. Run the release workflow with the version number (without starting `v`) as input. Make sure
main has your merged PR as last commit
@@ -330,23 +339,26 @@ even patch releases may contain [non-backwards-compatible changes](https://semve
1. Attach artifacts to draft GitHub release
1. Trigger downstream repositories. This can fail non-catastrophically, as we can run any
downstream jobs manually if needed.
1. Create release notes in GitHub UI and promote from draft.
1. Publish the GitHub release
1. Open the draft release in the GitHub release section
1. Copy the changelog for the release into the GitHub release
- See previous releases for formatting of section headers
1. Generate the contributor list with `rooster contributors` and add to the release notes
1. If needed, [update the schemastore](https://github.com/charliermarsh/ruff/blob/main/scripts/update_schemastore.py)
1. If needed, update the `ruff-lsp` and `ruff-vscode` repositories.
## Ecosystem CI
GitHub Actions will run your changes against a number of real-world projects from GitHub and
report on any diagnostic differences. You can also run those checks locally via:
report on any linter or formatter differences. You can also run those checks locally via:
```shell
python scripts/check_ecosystem.py path/to/your/ruff path/to/older/ruff
pip install -e ./python/ruff-ecosystem
ruff-ecosystem check ruff "./target/debug/ruff"
ruff-ecosystem format ruff "./target/debug/ruff"
```
You can also run the Ecosystem CI check in a Docker container across a larger set of projects by
downloading the [`known-github-tomls.json`](https://github.com/akx/ruff-usage-aggregate/blob/master/data/known-github-tomls.jsonl)
as `github_search.jsonl` and following the instructions in [scripts/Dockerfile.ecosystem](https://github.com/astral-sh/ruff/blob/main/scripts/Dockerfile.ecosystem).
Note that this check will take a while to run.
See the [ruff-ecosystem package](https://github.com/astral-sh/ruff/tree/main/python/ruff-ecosystem) for more details.
## Benchmarking and Profiling
@@ -877,5 +889,5 @@ By default, `src` is set to the project root. In the above example, we'd want to
`src = ["./src"]` to ensure that we locate `./my_project/src/foo` and thus categorize `import foo`
as first-party in `baz.py`. In practice, for this limited example, setting `src = ["./src"]` is
unnecessary, as all imports within `./my_project/src/foo` would be categorized as first-party via
the same-package heuristic; but your project contains multiple packages, you'll want to set `src`
the same-package heuristic; but if your project contains multiple packages, you'll want to set `src`
explicitly.

146
Cargo.lock generated
View File

@@ -210,9 +210,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bitflags"
version = "2.4.0"
version = "2.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635"
checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07"
[[package]]
name = "bstr"
@@ -313,9 +313,9 @@ dependencies = [
[[package]]
name = "clap"
version = "4.4.6"
version = "4.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d04704f56c2cde07f43e8e2c154b43f216dc5c92fc98ada720177362f953b956"
checksum = "ac495e00dcec98c83465d5ad66c5c4fabd652fd6686e7c6269b117e729a6f17b"
dependencies = [
"clap_builder",
"clap_derive",
@@ -323,9 +323,9 @@ dependencies = [
[[package]]
name = "clap_builder"
version = "4.4.6"
version = "4.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e231faeaca65ebd1ea3c737966bf858971cd38c3849107aa3ea7de90a804e45"
checksum = "c77ed9a32a62e6ca27175d00d29d05ca32e396ea1eb5fb01d8256b669cec7663"
dependencies = [
"anstream",
"anstyle",
@@ -376,21 +376,21 @@ dependencies = [
[[package]]
name = "clap_derive"
version = "4.4.2"
version = "4.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0862016ff20d69b84ef8247369fabf5c008a7417002411897d40ee1f4532b873"
checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442"
dependencies = [
"heck",
"proc-macro2",
"quote",
"syn 2.0.38",
"syn 2.0.39",
]
[[package]]
name = "clap_lex"
version = "0.5.1"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd7cc57abe963c6d3b9d8be5b06ba7c8957a930305ca90304f24ef040aa6f961"
checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1"
[[package]]
name = "clearscreen"
@@ -407,9 +407,9 @@ dependencies = [
[[package]]
name = "codspeed"
version = "2.3.0"
version = "2.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d680ccd1eedd2dd7c7a3649a78c7d06e0f16b191b30d81cc58e7bc906488d344"
checksum = "918b13a0f1a32460ab3bd5debd56b5a27a7071fa5ff5dfeb3a5cf291a85b174b"
dependencies = [
"colored",
"libc",
@@ -418,9 +418,9 @@ dependencies = [
[[package]]
name = "codspeed-criterion-compat"
version = "2.3.0"
version = "2.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "58b48b6c8e890d7d4ad0ed85e9ab4949bf7023198c006000ef6338ba84cf5b71"
checksum = "c683c7fef2b873fbbdf4062782914c652309951244bf0bd362fe608b7d6f901c"
dependencies = [
"codspeed",
"colored",
@@ -608,7 +608,7 @@ dependencies = [
"proc-macro2",
"quote",
"strsim",
"syn 2.0.38",
"syn 2.0.39",
]
[[package]]
@@ -619,7 +619,7 @@ checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5"
dependencies = [
"darling_core",
"quote",
"syn 2.0.38",
"syn 2.0.39",
]
[[package]]
@@ -810,7 +810,7 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
[[package]]
name = "flake8-to-ruff"
version = "0.1.3"
version = "0.1.5"
dependencies = [
"anyhow",
"clap",
@@ -1129,7 +1129,7 @@ dependencies = [
"pmutil 0.6.1",
"proc-macro2",
"quote",
"syn 2.0.38",
"syn 2.0.39",
]
[[package]]
@@ -1262,9 +1262,9 @@ dependencies = [
[[package]]
name = "libc"
version = "0.2.147"
version = "0.2.149"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3"
checksum = "a08173bc88b7955d1b3145aa561539096c421ac8debde8cbc3612ec635fee29b"
[[package]]
name = "libcst"
@@ -1309,9 +1309,9 @@ checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f"
[[package]]
name = "linux-raw-sys"
version = "0.4.5"
version = "0.4.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57bcfdad1b858c2db7c38303a6d2ad4dfaf5eb53dfeb0910128b2c26d6158503"
checksum = "da2479e8c062e40bf0066ffa0bc823de0a9368974af99c9f6df941d2c231e03f"
[[package]]
name = "lock_api"
@@ -1440,7 +1440,7 @@ version = "6.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6205bd8bb1e454ad2e27422015fb5e4f2bcc7e08fa8f27058670d208324a4d2d"
dependencies = [
"bitflags 2.4.0",
"bitflags 2.4.1",
"crossbeam-channel",
"filetime",
"fsevent-sys",
@@ -1707,7 +1707,7 @@ checksum = "52a40bc70c2c58040d2d8b167ba9a5ff59fc9dab7ad44771cfde3dcfde7a09c6"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.38",
"syn 2.0.39",
]
[[package]]
@@ -1801,9 +1801,9 @@ dependencies = [
[[package]]
name = "pyproject-toml"
version = "0.7.0"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "569e259cd132eb8cec5df8b672d187c5260f82ad352156b5da9549d4472e64b0"
checksum = "0774c13ff0b8b7ebb4791c050c497aefcfe3f6a222c0829c7017161ed38391ff"
dependencies = [
"indexmap",
"pep440_rs",
@@ -1912,6 +1912,15 @@ dependencies = [
"bitflags 1.3.2",
]
[[package]]
name = "redox_syscall"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa"
dependencies = [
"bitflags 1.3.2",
]
[[package]]
name = "redox_users"
version = "0.4.3"
@@ -2051,14 +2060,14 @@ dependencies = [
[[package]]
name = "ruff_cli"
version = "0.1.3"
version = "0.1.5"
dependencies = [
"annotate-snippets 0.9.1",
"anyhow",
"argfile",
"assert_cmd",
"bincode",
"bitflags 2.4.0",
"bitflags 2.4.1",
"cachedir",
"chrono",
"clap",
@@ -2072,7 +2081,6 @@ dependencies = [
"insta-cmd",
"is-macro",
"itertools 0.11.0",
"itoa",
"log",
"mimalloc",
"notify",
@@ -2188,12 +2196,12 @@ dependencies = [
[[package]]
name = "ruff_linter"
version = "0.1.3"
version = "0.1.5"
dependencies = [
"aho-corasick",
"annotate-snippets 0.9.1",
"anyhow",
"bitflags 2.4.0",
"bitflags 2.4.1",
"chrono",
"clap",
"colored",
@@ -2259,7 +2267,7 @@ dependencies = [
"proc-macro2",
"quote",
"ruff_python_trivia",
"syn 2.0.38",
"syn 2.0.39",
]
[[package]]
@@ -2285,7 +2293,7 @@ dependencies = [
name = "ruff_python_ast"
version = "0.0.0"
dependencies = [
"bitflags 2.4.0",
"bitflags 2.4.1",
"insta",
"is-macro",
"itertools 0.11.0",
@@ -2317,7 +2325,7 @@ name = "ruff_python_formatter"
version = "0.0.0"
dependencies = [
"anyhow",
"bitflags 2.4.0",
"bitflags 2.4.1",
"clap",
"countme",
"insta",
@@ -2361,7 +2369,7 @@ dependencies = [
name = "ruff_python_literal"
version = "0.0.0"
dependencies = [
"bitflags 2.4.0",
"bitflags 2.4.1",
"hexf-parse",
"is-macro",
"itertools 0.11.0",
@@ -2375,7 +2383,7 @@ name = "ruff_python_parser"
version = "0.0.0"
dependencies = [
"anyhow",
"bitflags 2.4.0",
"bitflags 2.4.1",
"insta",
"is-macro",
"itertools 0.11.0",
@@ -2405,7 +2413,7 @@ dependencies = [
name = "ruff_python_semantic"
version = "0.0.0"
dependencies = [
"bitflags 2.4.0",
"bitflags 2.4.1",
"is-macro",
"ruff_index",
"ruff_python_ast",
@@ -2439,7 +2447,7 @@ dependencies = [
[[package]]
name = "ruff_shrinking"
version = "0.1.3"
version = "0.1.5"
dependencies = [
"anyhow",
"clap",
@@ -2551,11 +2559,11 @@ checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
[[package]]
name = "rustix"
version = "0.38.10"
version = "0.38.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed6248e1caa625eb708e266e06159f135e8c26f2bb7ceb72dc4b2766d0340964"
checksum = "2b426b0506e5d50a7d8dafcf2e81471400deb602392c7dd110815afb4eaf02a3"
dependencies = [
"bitflags 2.4.0",
"bitflags 2.4.1",
"errno",
"libc",
"linux-raw-sys",
@@ -2665,18 +2673,18 @@ checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090"
[[package]]
name = "serde"
version = "1.0.188"
version = "1.0.190"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e"
checksum = "91d3c334ca1ee894a2c6f6ad698fe8c435b76d504b13d436f0685d648d6d96f7"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde-wasm-bindgen"
version = "0.6.0"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30c9933e5689bd420dc6c87b7a1835701810cbc10cd86a26e4da45b73e6b1d78"
checksum = "17ba92964781421b6cef36bf0d7da26d201e96d84e1b10e7ae6ed416e516906d"
dependencies = [
"js-sys",
"serde",
@@ -2685,13 +2693,13 @@ dependencies = [
[[package]]
name = "serde_derive"
version = "1.0.188"
version = "1.0.190"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2"
checksum = "67c5609f394e5c2bd7fc51efda478004ea80ef42fee983d5c67a65e34f32c0e3"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.38",
"syn 2.0.39",
]
[[package]]
@@ -2707,9 +2715,9 @@ dependencies = [
[[package]]
name = "serde_json"
version = "1.0.107"
version = "1.0.108"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6b420ce6e3d8bd882e9b243c6eed35dbc9a6110c9769e74b584e0d68d1f20c65"
checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b"
dependencies = [
"itoa",
"ryu",
@@ -2753,7 +2761,7 @@ dependencies = [
"darling",
"proc-macro2",
"quote",
"syn 2.0.38",
"syn 2.0.39",
]
[[package]]
@@ -2848,7 +2856,7 @@ dependencies = [
"proc-macro2",
"quote",
"rustversion",
"syn 2.0.38",
"syn 2.0.39",
]
[[package]]
@@ -2864,9 +2872,9 @@ dependencies = [
[[package]]
name = "syn"
version = "2.0.38"
version = "2.0.39"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e96b79aaa137db8f61e26363a0c9b47d8b4ec75da28b7d1d614c2303e232408b"
checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a"
dependencies = [
"proc-macro2",
"quote",
@@ -2884,13 +2892,13 @@ dependencies = [
[[package]]
name = "tempfile"
version = "3.8.0"
version = "3.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb94d2f3cc536af71caac6b6fcebf65860b347e7ce0cc9ebe8f70d3e521054ef"
checksum = "7ef1adac450ad7f4b3c28589471ade84f25f731a7a0fe30d71dfa9f60fd808e5"
dependencies = [
"cfg-if",
"fastrand",
"redox_syscall 0.3.5",
"redox_syscall 0.4.1",
"rustix",
"windows-sys 0.48.0",
]
@@ -2953,7 +2961,7 @@ dependencies = [
"proc-macro-error",
"proc-macro2",
"quote",
"syn 2.0.38",
"syn 2.0.39",
]
[[package]]
@@ -2965,7 +2973,7 @@ dependencies = [
"proc-macro-error",
"proc-macro2",
"quote",
"syn 2.0.38",
"syn 2.0.39",
"test-case-core",
]
@@ -2986,7 +2994,7 @@ checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.38",
"syn 2.0.39",
]
[[package]]
@@ -3123,7 +3131,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.38",
"syn 2.0.39",
]
[[package]]
@@ -3323,9 +3331,9 @@ checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
[[package]]
name = "uuid"
version = "1.4.1"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "79daa5ed5740825c40b389c5e50312b9c86df53fccd33f281df655642b43869d"
checksum = "88ad59a7560b41a70d191093a945f0b87bc1deeda46fb237479708a1d6b6cdfc"
dependencies = [
"getrandom",
"rand",
@@ -3335,13 +3343,13 @@ dependencies = [
[[package]]
name = "uuid-macro-internal"
version = "1.4.1"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7e1ba1f333bd65ce3c9f27de592fcbc256dafe3af2717f56d7c87761fbaccf4"
checksum = "3d8c6bba9b149ee82950daefc9623b32bb1dacbfb1890e352f6b887bd582adaf"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.38",
"syn 2.0.39",
]
[[package]]
@@ -3435,7 +3443,7 @@ dependencies = [
"once_cell",
"proc-macro2",
"quote",
"syn 2.0.38",
"syn 2.0.39",
"wasm-bindgen-shared",
]
@@ -3469,7 +3477,7 @@ checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.38",
"syn 2.0.39",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]

View File

@@ -13,9 +13,9 @@ license = "MIT"
[workspace.dependencies]
anyhow = { version = "1.0.69" }
bitflags = { version = "2.3.1" }
bitflags = { version = "2.4.1" }
chrono = { version = "0.4.31", default-features = false, features = ["clock"] }
clap = { version = "4.4.6", features = ["derive"] }
clap = { version = "4.4.7", features = ["derive"] }
colored = { version = "2.0.0" }
filetime = { version = "0.2.20" }
glob = { version = "0.3.1" }
@@ -34,15 +34,15 @@ quote = { version = "1.0.23" }
regex = { version = "1.10.2" }
rustc-hash = { version = "1.1.0" }
schemars = { version = "0.8.15" }
serde = { version = "1.0.152", features = ["derive"] }
serde_json = { version = "1.0.107" }
serde = { version = "1.0.190", features = ["derive"] }
serde_json = { version = "1.0.108" }
shellexpand = { version = "3.0.0" }
similar = { version = "2.3.0", features = ["inline"] }
smallvec = { version = "1.11.1" }
static_assertions = "1.1.0"
strum = { version = "0.25.0", features = ["strum_macros"] }
strum_macros = { version = "0.25.3" }
syn = { version = "2.0.38" }
syn = { version = "2.0.39" }
test-case = { version = "3.2.1" }
thiserror = { version = "1.0.50" }
toml = { version = "0.7.8" }
@@ -52,7 +52,7 @@ tracing-subscriber = { version = "0.3.17", features = ["env-filter"] }
unicode-ident = { version = "1.0.12" }
unicode_names2 = { version = "1.2.0" }
unicode-width = { version = "0.1.11" }
uuid = { version = "1.4.1", features = ["v4", "fast-rng", "macro-diagnostics", "js"] }
uuid = { version = "1.5.0", features = ["v4", "fast-rng", "macro-diagnostics", "js"] }
wsl = { version = "0.1.0" }
[profile.release]

25
LICENSE
View File

@@ -1269,6 +1269,31 @@ are:
SOFTWARE.
"""
- flake8-trio, licensed as follows:
"""
MIT License
Copyright (c) 2022 Zac Hatfield-Dodds
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"""
- Pyright, licensed as follows:
"""
MIT License

View File

@@ -33,7 +33,7 @@ An extremely fast Python linter and code formatter, written in Rust.
- 🔧 Fix support, for automatic error correction (e.g., automatically remove unused imports)
- 📏 Over [700 built-in rules](https://docs.astral.sh/ruff/rules/), with native re-implementations
of popular Flake8 plugins, like flake8-bugbear
- ⌨️ First-party [editor integrations](https://docs.astral.sh/ruff/editor-integrations/) for
- ⌨️ First-party [editor integrations](https://docs.astral.sh/ruff/integrations/) for
[VS Code](https://github.com/astral-sh/ruff-vscode) and [more](https://github.com/astral-sh/ruff-lsp)
- 🌎 Monorepo-friendly, with [hierarchical and cascading configuration](https://docs.astral.sh/ruff/configuration/#pyprojecttoml-discovery)
@@ -148,14 +148,14 @@ ruff format @arguments.txt # Format using an input file, treating its
Ruff can also be used as a [pre-commit](https://pre-commit.com/) hook via [`ruff-pre-commit`](https://github.com/astral-sh/ruff-pre-commit):
```yaml
# Run the Ruff linter.
- repo: https://github.com/astral-sh/ruff-pre-commit
# Ruff version.
rev: v0.1.3
rev: v0.1.5
hooks:
# Run the Ruff linter.
# Run the linter.
- id: ruff
# Run the Ruff formatter.
args: [ --fix ]
# Run the formatter.
- id: ruff-format
```
@@ -314,6 +314,7 @@ quality tools, including:
- [flake8-super](https://pypi.org/project/flake8-super/)
- [flake8-tidy-imports](https://pypi.org/project/flake8-tidy-imports/)
- [flake8-todos](https://pypi.org/project/flake8-todos/)
- [flake8-trio](https://pypi.org/project/flake8-trio/)
- [flake8-type-checking](https://pypi.org/project/flake8-type-checking/)
- [flake8-use-pathlib](https://pypi.org/project/flake8-use-pathlib/)
- [flynt](https://pypi.org/project/flynt/) ([#2102](https://github.com/astral-sh/ruff/issues/2102))
@@ -415,6 +416,7 @@ Ruff is used by a number of major open-source projects and companies, including:
- [PDM](https://github.com/pdm-project/pdm)
- [PaddlePaddle](https://github.com/PaddlePaddle/Paddle)
- [Pandas](https://github.com/pandas-dev/pandas)
- [Pillow](https://github.com/python-pillow/Pillow)
- [Poetry](https://github.com/python-poetry/poetry)
- [Polars](https://github.com/pola-rs/polars)
- [PostHog](https://github.com/PostHog/posthog)
@@ -423,6 +425,7 @@ Ruff is used by a number of major open-source projects and companies, including:
- [PyTorch](https://github.com/pytorch/pytorch)
- [Pydantic](https://github.com/pydantic/pydantic)
- [Pylint](https://github.com/PyCQA/pylint)
- [PyMC-Marketing](https://github.com/pymc-labs/pymc-marketing)
- [Reflex](https://github.com/reflex-dev/reflex)
- [Rippling](https://rippling.com)
- [Robyn](https://github.com/sansyrox/robyn)

View File

@@ -1,5 +1,6 @@
[files]
extend-exclude = ["resources", "snapshots"]
# https://github.com/crate-ci/typos/issues/868
extend-exclude = ["**/resources/**/*", "**/snapshots/**/*"]
[default.extend-words]
hel = "hel"

View File

@@ -1,6 +1,6 @@
[package]
name = "flake8-to-ruff"
version = "0.1.3"
version = "0.1.5"
description = """
Convert Flake8 configuration files to Ruff configuration files.
"""

View File

@@ -37,7 +37,7 @@ serde_json.workspace = true
url = "2.3.1"
ureq = "2.8.0"
criterion = { version = "0.5.1", default-features = false }
codspeed-criterion-compat = { version="2.3.0", default-features = false, optional = true}
codspeed-criterion-compat = { version="2.3.1", default-features = false, optional = true}
[dev-dependencies]
ruff_linter.path = "../ruff_linter"

View File

@@ -1,6 +1,6 @@
[package]
name = "ruff_cli"
version = "0.1.3"
version = "0.1.5"
publish = false
authors = { workspace = true }
edition = { workspace = true }
@@ -44,7 +44,6 @@ glob = { workspace = true }
ignore = { workspace = true }
is-macro = { workspace = true }
itertools = { workspace = true }
itoa = { version = "1.0.6" }
log = { workspace = true }
notify = { version = "6.1.1" }
path-absolutize = { workspace = true, features = ["once_cell_cache"] }
@@ -68,7 +67,7 @@ assert_cmd = { version = "2.0.8" }
colored = { workspace = true, features = ["no-color"]}
insta = { workspace = true, features = ["filters", "json"] }
insta-cmd = { version = "0.4.0" }
tempfile = "3.6.0"
tempfile = "3.8.1"
test-case = { workspace = true }
ureq = { version = "2.8.0", features = [] }

View File

@@ -8,8 +8,8 @@ use ruff_linter::line_width::LineLength;
use ruff_linter::logging::LogLevel;
use ruff_linter::registry::Rule;
use ruff_linter::settings::types::{
FilePattern, PatternPrefixPair, PerFileIgnore, PreviewMode, PythonVersion, SerializationFormat,
UnsafeFixes,
ExtensionPair, FilePattern, PatternPrefixPair, PerFileIgnore, PreviewMode, PythonVersion,
SerializationFormat, UnsafeFixes,
};
use ruff_linter::{RuleParser, RuleSelector, RuleSelectorParser};
use ruff_workspace::configuration::{Configuration, RuleSelection};
@@ -50,7 +50,11 @@ pub enum Command {
/// Output format
#[arg(long, value_enum, default_value = "text")]
format: HelpFormat,
output_format: HelpFormat,
/// Output format (Deprecated: Use `--output-format` instead).
#[arg(long, value_enum, conflicts_with = "output_format", hide = true)]
format: Option<HelpFormat>,
},
/// List or describe the available configuration options.
Config { option: Option<String> },
@@ -58,7 +62,11 @@ pub enum Command {
Linter {
/// Output format
#[arg(long, value_enum, default_value = "text")]
format: HelpFormat,
output_format: HelpFormat,
/// Output format (Deprecated: Use `--output-format` instead).
#[arg(long, value_enum, conflicts_with = "output_format", hide = true)]
format: Option<HelpFormat>,
},
/// Clear any caches in the current directory and any subdirectories.
#[clap(alias = "--clean")]
@@ -270,7 +278,7 @@ pub struct CheckCommand {
#[arg(long, help_heading = "Rule configuration", hide = true)]
pub dummy_variable_rgx: Option<Regex>,
/// Disable cache reads.
#[arg(short, long, help_heading = "Miscellaneous")]
#[arg(short, long, env = "RUFF_NO_CACHE", help_heading = "Miscellaneous")]
pub no_cache: bool,
/// Ignore all configuration files.
#[arg(long, conflicts_with = "config", help_heading = "Miscellaneous")]
@@ -343,6 +351,9 @@ pub struct CheckCommand {
conflicts_with = "watch",
)]
pub show_settings: bool,
/// List of mappings from file extension to language (one of ["python", "ipynb", "pyi"]).
#[arg(long, value_delimiter = ',', hide = true)]
pub extension: Option<Vec<ExtensionPair>>,
/// Dev-only argument to show fixes
#[arg(long, hide = true)]
pub ecosystem_ci: bool,
@@ -366,7 +377,7 @@ pub struct FormatCommand {
pub config: Option<PathBuf>,
/// Disable cache reads.
#[arg(short, long, help_heading = "Miscellaneous")]
#[arg(short, long, env = "RUFF_NO_CACHE", help_heading = "Miscellaneous")]
pub no_cache: bool,
/// Path to the cache directory.
#[arg(long, env = "RUFF_CACHE_DIR", help_heading = "Miscellaneous")]
@@ -401,6 +412,9 @@ pub struct FormatCommand {
force_exclude: bool,
#[clap(long, overrides_with("force_exclude"), hide = true)]
no_force_exclude: bool,
/// Set the line-length.
#[arg(long, help_heading = "Format configuration")]
pub line_length: Option<LineLength>,
/// Ignore all configuration files.
#[arg(long, conflicts_with = "config", help_heading = "Miscellaneous")]
pub isolated: bool,
@@ -499,6 +513,7 @@ impl CheckCommand {
extend_exclude: self.extend_exclude,
extend_fixable: self.extend_fixable,
extend_ignore: self.extend_ignore,
extend_per_file_ignores: self.extend_per_file_ignores,
extend_select: self.extend_select,
extend_unfixable: self.extend_unfixable,
fixable: self.fixable,
@@ -523,6 +538,7 @@ impl CheckCommand {
force_exclude: resolve_bool_arg(self.force_exclude, self.no_force_exclude),
output_format: self.output_format,
show_fixes: resolve_bool_arg(self.show_fixes, self.no_show_fixes),
extension: self.extension,
},
)
}
@@ -543,6 +559,7 @@ impl FormatCommand {
stdin_filename: self.stdin_filename,
},
CliOverrides {
line_length: self.line_length,
respect_gitignore: resolve_bool_arg(
self.respect_gitignore,
self.no_respect_gitignore,
@@ -619,6 +636,7 @@ pub struct CliOverrides {
pub ignore: Option<Vec<RuleSelector>>,
pub line_length: Option<LineLength>,
pub per_file_ignores: Option<Vec<PatternPrefixPair>>,
pub extend_per_file_ignores: Option<Vec<PatternPrefixPair>>,
pub preview: Option<PreviewMode>,
pub respect_gitignore: Option<bool>,
pub select: Option<Vec<RuleSelector>>,
@@ -633,6 +651,7 @@ pub struct CliOverrides {
pub force_exclude: Option<bool>,
pub output_format: Option<SerializationFormat>,
pub show_fixes: Option<bool>,
pub extension: Option<Vec<ExtensionPair>>,
}
impl ConfigurationTransformer for CliOverrides {
@@ -649,6 +668,12 @@ impl ConfigurationTransformer for CliOverrides {
if let Some(extend_exclude) = &self.extend_exclude {
config.extend_exclude.extend(extend_exclude.clone());
}
if let Some(extend_per_file_ignores) = &self.extend_per_file_ignores {
config
.lint
.extend_per_file_ignores
.extend(collect_per_file_ignores(extend_per_file_ignores.clone()));
}
if let Some(fix) = &self.fix {
config.fix = Some(*fix);
}
@@ -711,6 +736,9 @@ impl ConfigurationTransformer for CliOverrides {
if let Some(target_version) = &self.target_version {
config.target_version = Some(*target_version);
}
if let Some(extension) = &self.extension {
config.lint.extension = Some(extension.clone().into_iter().collect());
}
config
}

View File

@@ -1,4 +1,3 @@
use std::collections::HashMap;
use std::fmt::Debug;
use std::fs::{self, File};
use std::hash::Hasher;
@@ -20,7 +19,7 @@ use serde::{Deserialize, Serialize};
use ruff_cache::{CacheKey, CacheKeyHasher};
use ruff_diagnostics::{DiagnosticKind, Fix};
use ruff_linter::message::Message;
use ruff_linter::warn_user;
use ruff_linter::{warn_user, VERSION};
use ruff_macros::CacheKey;
use ruff_notebook::NotebookIndex;
use ruff_python_ast::imports::ImportMap;
@@ -102,9 +101,8 @@ impl Cache {
pub(crate) fn open(package_root: PathBuf, settings: &Settings) -> Self {
debug_assert!(package_root.is_absolute(), "package root not canonicalized");
let mut buf = itoa::Buffer::new();
let key = Path::new(buf.format(cache_key(&package_root, settings)));
let path = PathBuf::from_iter([&settings.cache_dir, Path::new("content"), key]);
let key = format!("{}", cache_key(&package_root, settings));
let path = PathBuf::from_iter([&settings.cache_dir, Path::new(VERSION), Path::new(&key)]);
let file = match File::open(&path) {
Ok(file) => file,
@@ -142,7 +140,7 @@ impl Cache {
fn empty(path: PathBuf, package_root: PathBuf) -> Self {
let package = PackageCache {
package_root,
files: HashMap::new(),
files: FxHashMap::default(),
};
Cache::new(path, package)
}
@@ -294,7 +292,7 @@ struct PackageCache {
/// single file "packages", e.g. scripts.
package_root: PathBuf,
/// Mapping of source file path to it's cached data.
files: HashMap<RelativePathBuf, FileCache>,
files: FxHashMap<RelativePathBuf, FileCache>,
}
/// On disk representation of the cache per source file.
@@ -350,16 +348,16 @@ struct FileCacheData {
/// version.
fn cache_key(package_root: &Path, settings: &Settings) -> u64 {
let mut hasher = CacheKeyHasher::new();
env!("CARGO_PKG_VERSION").cache_key(&mut hasher);
package_root.cache_key(&mut hasher);
settings.cache_key(&mut hasher);
hasher.finish()
}
/// Initialize the cache at the specified `Path`.
pub(crate) fn init(path: &Path) -> Result<()> {
// Create the cache directories.
fs::create_dir_all(path.join("content"))?;
fs::create_dir_all(path.join(VERSION))?;
// Add the CACHEDIR.TAG.
if !cachedir::is_tagged(path)? {

View File

@@ -30,6 +30,7 @@ use crate::diagnostics::Diagnostics;
use crate::panic::catch_unwind;
/// Run the linter over a collection of files.
#[allow(clippy::too_many_arguments)]
pub(crate) fn check(
files: &[PathBuf],
pyproject_config: &PyprojectConfig,
@@ -82,7 +83,7 @@ pub(crate) fn check(
let settings = resolver.resolve(path, pyproject_config);
if !resolved_file.is_root()
if (settings.file_resolver.force_exclude || !resolved_file.is_root())
&& match_exclusion(
resolved_file.path(),
resolved_file.file_name(),
@@ -184,6 +185,7 @@ pub(crate) fn check(
/// Wraps [`lint_path`](crate::diagnostics::lint_path) in a [`catch_unwind`](std::panic::catch_unwind) and emits
/// a diagnostic if the linting the file panics.
#[allow(clippy::too_many_arguments)]
fn lint_path(
path: &Path,
package: Option<&Path>,

View File

@@ -18,17 +18,19 @@ pub(crate) fn check_stdin(
noqa: flags::Noqa,
fix_mode: flags::FixMode,
) -> Result<Diagnostics> {
if let Some(filename) = filename {
if !python_file_at_path(filename, pyproject_config, overrides)? {
return Ok(Diagnostics::default());
}
if pyproject_config.settings.file_resolver.force_exclude {
if let Some(filename) = filename {
if !python_file_at_path(filename, pyproject_config, overrides)? {
return Ok(Diagnostics::default());
}
let lint_settings = &pyproject_config.settings.linter;
if filename
.file_name()
.is_some_and(|name| match_exclusion(filename, name, &lint_settings.exclude))
{
return Ok(Diagnostics::default());
let lint_settings = &pyproject_config.settings.linter;
if filename
.file_name()
.is_some_and(|name| match_exclusion(filename, name, &lint_settings.exclude))
{
return Ok(Diagnostics::default());
}
}
}
let package_root = filename.and_then(Path::parent).and_then(|path| {

View File

@@ -11,6 +11,7 @@ use itertools::Itertools;
use log::{error, warn};
use rayon::iter::Either::{Left, Right};
use rayon::iter::{IntoParallelRefIterator, ParallelIterator};
use rustc_hash::FxHashSet;
use thiserror::Error;
use tracing::debug;
@@ -116,14 +117,14 @@ pub(crate) fn format(
return None;
};
let resolved_settings = resolver.resolve(path, &pyproject_config);
let settings = resolver.resolve(path, &pyproject_config);
// Ignore files that are excluded from formatting
if !resolved_file.is_root()
if (settings.file_resolver.force_exclude || !resolved_file.is_root())
&& match_exclusion(
path,
resolved_file.file_name(),
&resolved_settings.formatter.exclude,
&settings.formatter.exclude,
)
{
return None;
@@ -138,13 +139,7 @@ pub(crate) fn format(
Some(
match catch_unwind(|| {
format_path(
path,
&resolved_settings.formatter,
source_type,
mode,
cache,
)
format_path(path, &settings.formatter, source_type, mode, cache)
}) {
Ok(inner) => inner.map(|result| FormatPathResult {
path: resolved_file.path().to_path_buf(),
@@ -695,11 +690,11 @@ pub(super) fn warn_incompatible_formatter_settings(
pyproject_config: &PyprojectConfig,
resolver: Option<&Resolver>,
) {
// First, collect all rules that are incompatible regardless of the linter-specific settings.
let mut incompatible_rules = FxHashSet::default();
for setting in std::iter::once(&pyproject_config.settings)
.chain(resolver.iter().flat_map(|resolver| resolver.settings()))
{
let mut incompatible_rules = Vec::new();
for rule in [
// The formatter might collapse implicit string concatenation on a single line.
Rule::SingleLineImplicitStringConcatenation,
@@ -713,41 +708,48 @@ pub(super) fn warn_incompatible_formatter_settings(
Rule::MissingTrailingComma,
] {
if setting.linter.rules.enabled(rule) {
incompatible_rules.push(rule);
incompatible_rules.insert(rule);
}
}
}
// Rules asserting for space indentation
if setting.formatter.indent_style.is_tab() {
for rule in [Rule::TabIndentation, Rule::IndentWithSpaces] {
if setting.linter.rules.enabled(rule) {
incompatible_rules.push(rule);
}
}
if !incompatible_rules.is_empty() {
let mut rule_names: Vec<_> = incompatible_rules
.into_iter()
.map(|rule| format!("`{}`", rule.noqa_code()))
.collect();
rule_names.sort();
warn_user_once!("The following rules may cause conflicts when used with the formatter: {}. To avoid unexpected behavior, we recommend disabling these rules, either by removing them from the `select` or `extend-select` configuration, or adding them to the `ignore` configuration.", rule_names.join(", "));
}
// Next, validate settings-specific incompatibilities.
for setting in std::iter::once(&pyproject_config.settings)
.chain(resolver.iter().flat_map(|resolver| resolver.settings()))
{
// Validate all rules that rely on tab styles.
if setting.linter.rules.enabled(Rule::TabIndentation)
&& setting.formatter.indent_style.is_tab()
{
warn_user_once!("The `format.indent-style=\"tab\"` option is incompatible with `W191`, which lints against all uses of tabs. We recommend disabling these rules when using the formatter, which enforces a consistent indentation style. Alternatively, set the `format.indent-style` option to `\"space\"`.");
}
// Rules asserting for indent-width=4
if setting.formatter.indent_width.value() != 4 {
for rule in [
Rule::IndentationWithInvalidMultiple,
Rule::IndentationWithInvalidMultipleComment,
] {
if setting.linter.rules.enabled(rule) {
incompatible_rules.push(rule);
}
}
// Validate all rules that rely on tab styles.
if setting.linter.rules.enabled(Rule::IndentWithSpaces)
&& setting.formatter.indent_style.is_tab()
{
warn_user_once!("The `format.indent-style=\"tab\"` option is incompatible with `D206`, with requires space-based indentation. We recommend disabling these rules when using the formatter, which enforces a consistent indentation style. Alternatively, set the `format.indent-style` option to `\"space\"`.");
}
if !incompatible_rules.is_empty() {
let mut rule_names: Vec<_> = incompatible_rules
.into_iter()
.map(|rule| format!("`{}`", rule.noqa_code()))
.collect();
rule_names.sort();
warn!("The following rules may cause conflicts when used with the formatter: {}. To avoid unexpected behavior, we recommend disabling these rules, either by removing them from the `select` or `extend-select` configuration, or adding then to the `ignore` configuration.", rule_names.join(", "));
// Validate all rules that rely on custom indent widths.
if setting.linter.rules.any_enabled(&[
Rule::IndentationWithInvalidMultiple,
Rule::IndentationWithInvalidMultipleComment,
]) && setting.formatter.indent_width.value() != 4
{
warn_user_once!("The `format.indent-width` option with a value other than 4 is incompatible with `E111` and `E114`. We recommend disabling these rules when using the formatter, which enforces a consistent indentation width. Alternatively, set the `format.indent-width` option to `4`.");
}
// Rules with different quote styles.
// Validate all rules that rely on quote styles.
if setting
.linter
.rules
@@ -758,10 +760,10 @@ pub(super) fn warn_incompatible_formatter_settings(
setting.formatter.quote_style,
) {
(Quote::Double, QuoteStyle::Single) => {
warn!("The `flake8-quotes.inline-quotes=\"double\"` option is incompatible with the formatter's `format.quote-style=\"single\"`. We recommend disabling `Q000` and `Q003` when using the formatter, which enforces a consistent quote style. Alternatively, set both options to either `\"single\"` or `\"double\"`.");
warn_user_once!("The `flake8-quotes.inline-quotes=\"double\"` option is incompatible with the formatter's `format.quote-style=\"single\"`. We recommend disabling `Q000` and `Q003` when using the formatter, which enforces a consistent quote style. Alternatively, set both options to either `\"single\"` or `\"double\"`.");
}
(Quote::Single, QuoteStyle::Double) => {
warn!("The `flake8-quotes.inline-quotes=\"single\"` option is incompatible with the formatter's `format.quote-style=\"double\"`. We recommend disabling `Q000` and `Q003` when using the formatter, which enforces a consistent quote style. Alternatively, set both options to either `\"single\"` or `\"double\"`.");
warn_user_once!("The `flake8-quotes.inline-quotes=\"single\"` option is incompatible with the formatter's `format.quote-style=\"double\"`. We recommend disabling `Q000` and `Q003` when using the formatter, which enforces a consistent quote style. Alternatively, set both options to either `\"single\"` or `\"double\"`.");
}
_ => {}
}
@@ -770,25 +772,26 @@ pub(super) fn warn_incompatible_formatter_settings(
if setting.linter.rules.enabled(Rule::BadQuotesMultilineString)
&& setting.linter.flake8_quotes.multiline_quotes == Quote::Single
{
warn!("The `flake8-quotes.multiline-quotes=\"single\"` option is incompatible with the formatter. We recommend disabling `Q001` when using the formatter, which enforces double quotes for multiline strings. Alternatively, set the `flake8-quotes.multiline-quotes` option to `\"double\"`.`");
warn_user_once!("The `flake8-quotes.multiline-quotes=\"single\"` option is incompatible with the formatter. We recommend disabling `Q001` when using the formatter, which enforces double quotes for multiline strings. Alternatively, set the `flake8-quotes.multiline-quotes` option to `\"double\"`.`");
}
if setting.linter.rules.enabled(Rule::BadQuotesDocstring)
&& setting.linter.flake8_quotes.docstring_quotes == Quote::Single
{
warn!("The `flake8-quotes.multiline-quotes=\"single\"` option is incompatible with the formatter. We recommend disabling `Q002` when using the formatter, which enforces double quotes for docstrings. Alternatively, set the `flake8-quotes.docstring-quotes` option to `\"double\"`.`");
warn_user_once!("The `flake8-quotes.multiline-quotes=\"single\"` option is incompatible with the formatter. We recommend disabling `Q002` when using the formatter, which enforces double quotes for docstrings. Alternatively, set the `flake8-quotes.docstring-quotes` option to `\"double\"`.`");
}
// Validate all isort settings.
if setting.linter.rules.enabled(Rule::UnsortedImports) {
// The formatter removes empty lines if the value is larger than 2 but always inserts a empty line after imports.
// Two empty lines are okay because `isort` only uses this setting for top-level imports (not in nested blocks).
if !matches!(setting.linter.isort.lines_after_imports, 1 | 2 | -1) {
warn!("The isort option `isort.lines-after-imports` with a value other than `-1`, `1` or `2` is incompatible with the formatter. To avoid unexpected behavior, we recommend setting the option to one of: `2`, `1`, or `-1` (default).");
warn_user_once!("The isort option `isort.lines-after-imports` with a value other than `-1`, `1` or `2` is incompatible with the formatter. To avoid unexpected behavior, we recommend setting the option to one of: `2`, `1`, or `-1` (default).");
}
// Values larger than two get reduced to one line by the formatter if the import is in a nested block.
if setting.linter.isort.lines_between_types > 1 {
warn!("The isort option `isort.lines-between-types` with a value greater than 1 is incompatible with the formatter. To avoid unexpected behavior, we recommend setting the option to one of: `1` or `0` (default).");
warn_user_once!("The isort option `isort.lines-between-types` with a value greater than 1 is incompatible with the formatter. To avoid unexpected behavior, we recommend setting the option to one of: `1` or `0` (default).");
}
// isort inserts a trailing comma which the formatter preserves, but only if `skip-magic-trailing-comma` isn't false.
@@ -797,11 +800,11 @@ pub(super) fn warn_incompatible_formatter_settings(
&& !setting.linter.isort.force_single_line
{
if setting.linter.isort.force_wrap_aliases {
warn!("The isort option `isort.force-wrap-aliases` is incompatible with the formatter `format.skip-magic-trailing-comma=true` option. To avoid unexpected behavior, we recommend either setting `isort.force-wrap-aliases=false` or `format.skip-magic-trailing-comma=false`.");
warn_user_once!("The isort option `isort.force-wrap-aliases` is incompatible with the formatter `format.skip-magic-trailing-comma=true` option. To avoid unexpected behavior, we recommend either setting `isort.force-wrap-aliases=false` or `format.skip-magic-trailing-comma=false`.");
}
if setting.linter.isort.split_on_trailing_comma {
warn!("The isort option `isort.split-on-trailing-comma` is incompatible with the formatter `format.skip-magic-trailing-comma=true` option. To avoid unexpected behavior, we recommend either setting `isort.split-on-trailing-comma=false` or `format.skip-magic-trailing-comma=false`.");
warn_user_once!("The isort option `isort.split-on-trailing-comma` is incompatible with the formatter `format.skip-magic-trailing-comma=true` option. To avoid unexpected behavior, we recommend either setting `isort.split-on-trailing-comma=false` or `format.skip-magic-trailing-comma=false`.");
}
}
}

View File

@@ -31,17 +31,19 @@ pub(crate) fn format_stdin(cli: &FormatArguments, overrides: &CliOverrides) -> R
let mode = FormatMode::from_cli(cli);
if let Some(filename) = cli.stdin_filename.as_deref() {
if !python_file_at_path(filename, &pyproject_config, overrides)? {
return Ok(ExitStatus::Success);
}
if pyproject_config.settings.file_resolver.force_exclude {
if let Some(filename) = cli.stdin_filename.as_deref() {
if !python_file_at_path(filename, &pyproject_config, overrides)? {
return Ok(ExitStatus::Success);
}
let format_settings = &pyproject_config.settings.formatter;
if filename
.file_name()
.is_some_and(|name| match_exclusion(filename, name, &format_settings.exclude))
{
return Ok(ExitStatus::Success);
let format_settings = &pyproject_config.settings.formatter;
if filename
.file_name()
.is_some_and(|name| match_exclusion(filename, name, &format_settings.exclude))
{
return Ok(ExitStatus::Success);
}
}
}

View File

@@ -17,13 +17,13 @@ use ruff_linter::logging::DisplayParseError;
use ruff_linter::message::Message;
use ruff_linter::pyproject_toml::lint_pyproject_toml;
use ruff_linter::registry::AsRule;
use ruff_linter::settings::types::UnsafeFixes;
use ruff_linter::settings::types::{ExtensionMapping, UnsafeFixes};
use ruff_linter::settings::{flags, LinterSettings};
use ruff_linter::source_kind::{SourceError, SourceKind};
use ruff_linter::{fs, IOError, SyntaxError};
use ruff_notebook::{Notebook, NotebookError, NotebookIndex};
use ruff_python_ast::imports::ImportMap;
use ruff_python_ast::{SourceType, TomlSourceType};
use ruff_python_ast::{PySourceType, SourceType, TomlSourceType};
use ruff_source_file::{LineIndex, SourceCode, SourceFileBuilder};
use ruff_text_size::{TextRange, TextSize};
use ruff_workspace::Settings;
@@ -177,6 +177,11 @@ impl AddAssign for FixMap {
}
}
fn override_source_type(path: Option<&Path>, extension: &ExtensionMapping) -> Option<PySourceType> {
let ext = path?.extension()?.to_str()?;
extension.get(ext).map(PySourceType::from)
}
/// Lint the source code at the given `Path`.
pub(crate) fn lint_path(
path: &Path,
@@ -188,13 +193,8 @@ pub(crate) fn lint_path(
unsafe_fixes: UnsafeFixes,
) -> Result<Diagnostics> {
// Check the cache.
// TODO(charlie): `fixer::Mode::Apply` and `fixer::Mode::Diff` both have
// side-effects that aren't captured in the cache. (In practice, it's fine
// to cache `fixer::Mode::Apply`, since a file either has no fixes, or we'll
// write the fixes to disk, thus invalidating the cache. But it's a bit hard
// to reason about. We need to come up with a better solution here.)
let caching = match cache {
Some(cache) if noqa.into() && fix_mode.is_generate() => {
Some(cache) if noqa.into() => {
let relative_path = cache
.relative_path(path)
.expect("wrong package cache for file");
@@ -204,7 +204,17 @@ pub(crate) fn lint_path(
.get(relative_path, &cache_key)
.and_then(|entry| entry.to_diagnostics(path));
if let Some(diagnostics) = cached_diagnostics {
return Ok(diagnostics);
// `FixMode::Generate` and `FixMode::Diff` rely on side-effects (writing to disk,
// and writing the diff to stdout, respectively). If a file has diagnostics, we
// need to avoid reading from and writing to the cache in these modes.
if match fix_mode {
flags::FixMode::Generate => true,
flags::FixMode::Apply | flags::FixMode::Diff => {
diagnostics.messages.is_empty() && diagnostics.fixed.is_empty()
}
} {
return Ok(diagnostics);
}
}
// Stash the file metadata for later so when we update the cache it reflects the prerun
@@ -216,31 +226,35 @@ pub(crate) fn lint_path(
debug!("Checking: {}", path.display());
let source_type = match SourceType::from(path) {
SourceType::Toml(TomlSourceType::Pyproject) => {
let messages = if settings
.rules
.iter_enabled()
.any(|rule_code| rule_code.lint_source().is_pyproject_toml())
{
let contents = match std::fs::read_to_string(path).map_err(SourceError::from) {
Ok(contents) => contents,
Err(err) => {
return Ok(Diagnostics::from_source_error(&err, Some(path), settings));
}
let source_type = match override_source_type(Some(path), &settings.extension) {
Some(source_type) => source_type,
None => match SourceType::from(path) {
SourceType::Toml(TomlSourceType::Pyproject) => {
let messages = if settings
.rules
.iter_enabled()
.any(|rule_code| rule_code.lint_source().is_pyproject_toml())
{
let contents = match std::fs::read_to_string(path).map_err(SourceError::from) {
Ok(contents) => contents,
Err(err) => {
return Ok(Diagnostics::from_source_error(&err, Some(path), settings));
}
};
let source_file =
SourceFileBuilder::new(path.to_string_lossy(), contents).finish();
lint_pyproject_toml(source_file, settings)
} else {
vec![]
};
let source_file = SourceFileBuilder::new(path.to_string_lossy(), contents).finish();
lint_pyproject_toml(source_file, settings)
} else {
vec![]
};
return Ok(Diagnostics {
messages,
..Diagnostics::default()
});
}
SourceType::Toml(_) => return Ok(Diagnostics::default()),
SourceType::Python(source_type) => source_type,
return Ok(Diagnostics {
messages,
..Diagnostics::default()
});
}
SourceType::Toml(_) => return Ok(Diagnostics::default()),
SourceType::Python(source_type) => source_type,
},
};
// Extract the sources from the file.
@@ -304,15 +318,25 @@ pub(crate) fn lint_path(
if let Some((cache, relative_path, key)) = caching {
// We don't cache parsing errors.
if parse_error.is_none() {
cache.update_lint(
relative_path.to_owned(),
&key,
LintCacheData::from_messages(
&messages,
imports.clone(),
source_kind.as_ipy_notebook().map(Notebook::index).cloned(),
),
);
// `FixMode::Generate` and `FixMode::Diff` rely on side-effects (writing to disk,
// and writing the diff to stdout, respectively). If a file has diagnostics, we
// need to avoid reading from and writing to the cache in these modes.
if match fix_mode {
flags::FixMode::Generate => true,
flags::FixMode::Apply | flags::FixMode::Diff => {
messages.is_empty() && fixed.is_empty()
}
} {
cache.update_lint(
relative_path.to_owned(),
&key,
LintCacheData::from_messages(
&messages,
imports.clone(),
source_kind.as_ipy_notebook().map(Notebook::index).cloned(),
),
);
}
}
}
@@ -355,8 +379,15 @@ pub(crate) fn lint_stdin(
fix_mode: flags::FixMode,
) -> Result<Diagnostics> {
// TODO(charlie): Support `pyproject.toml`.
let SourceType::Python(source_type) = path.map(SourceType::from).unwrap_or_default() else {
return Ok(Diagnostics::default());
let source_type = if let Some(source_type) =
override_source_type(path, &settings.linter.extension)
{
source_type
} else {
let SourceType::Python(source_type) = path.map(SourceType::from).unwrap_or_default() else {
return Ok(Diagnostics::default());
};
source_type
};
// Extract the sources from the file.

View File

@@ -18,7 +18,7 @@ use ruff_linter::settings::types::SerializationFormat;
use ruff_linter::{fs, warn_user, warn_user_once};
use ruff_workspace::Settings;
use crate::args::{Args, CheckCommand, Command, FormatCommand};
use crate::args::{Args, CheckCommand, Command, FormatCommand, HelpFormat};
use crate::printer::{Flags as PrinterFlags, Printer};
pub mod args;
@@ -101,6 +101,15 @@ fn is_stdin(files: &[PathBuf], stdin_filename: Option<&Path>) -> bool {
file == Path::new("-")
}
/// Get the actual value of the `format` desired from either `output_format`
/// or `format`, and warn the user if they're using the deprecated form.
fn resolve_help_output_format(output_format: HelpFormat, format: Option<HelpFormat>) -> HelpFormat {
if format.is_some() {
warn_user!("The `--format` argument is deprecated. Use `--output-format` instead.");
}
format.unwrap_or(output_format)
}
pub fn run(
Args {
command,
@@ -141,12 +150,18 @@ pub fn run(
commands::version::version(output_format)?;
Ok(ExitStatus::Success)
}
Command::Rule { rule, all, format } => {
Command::Rule {
rule,
all,
format,
mut output_format,
} => {
output_format = resolve_help_output_format(output_format, format);
if all {
commands::rule::rules(format)?;
commands::rule::rules(output_format)?;
}
if let Some(rule) = rule {
commands::rule::rule(rule, format)?;
commands::rule::rule(rule, output_format)?;
}
Ok(ExitStatus::Success)
}
@@ -154,8 +169,12 @@ pub fn run(
commands::config::config(option.as_deref())?;
Ok(ExitStatus::Success)
}
Command::Linter { format } => {
commands::linter::linter(format)?;
Command::Linter {
format,
mut output_format,
} => {
output_format = resolve_help_output_format(output_format, format);
commands::linter::linter(output_format)?;
Ok(ExitStatus::Success)
}
Command::Clean => {

View File

@@ -188,6 +188,73 @@ OTHER = "OTHER"
Ok(())
}
#[test]
fn force_exclude() -> Result<()> {
let tempdir = TempDir::new()?;
let ruff_toml = tempdir.path().join("ruff.toml");
fs::write(
&ruff_toml,
r#"
extend-exclude = ["out"]
[format]
exclude = ["test.py", "generated.py"]
"#,
)?;
fs::write(
tempdir.path().join("main.py"),
r#"
from test import say_hy
if __name__ == "__main__":
say_hy("dear Ruff contributor")
"#,
)?;
// Excluded file but passed to the CLI directly, should be formatted
let test_path = tempdir.path().join("test.py");
fs::write(
&test_path,
r#"
def say_hy(name: str):
print(f"Hy {name}")"#,
)?;
fs::write(
tempdir.path().join("generated.py"),
r#"NUMBERS = [
0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
10, 11, 12, 13, 14, 15, 16, 17, 18, 19
]
OTHER = "OTHER"
"#,
)?;
let out_dir = tempdir.path().join("out");
fs::create_dir(&out_dir)?;
fs::write(out_dir.join("a.py"), "a = a")?;
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
.current_dir(tempdir.path())
.args(["format", "--no-cache", "--force-exclude", "--check", "--config"])
.arg(ruff_toml.file_name().unwrap())
// Explicitly pass test.py, should be respect the `format.exclude` when `--force-exclude` is present
.arg(test_path.file_name().unwrap())
// Format all other files in the directory, should respect the `exclude` and `format.exclude` options
.arg("."), @r###"
success: false
exit_code: 1
----- stdout -----
Would reformat: main.py
1 file would be reformatted
----- stderr -----
"###);
Ok(())
}
#[test]
fn exclude_stdin() -> Result<()> {
let tempdir = TempDir::new()?;
@@ -209,6 +276,43 @@ exclude = ["generated.py"]
.pass_stdin(r#"
from test import say_hy
if __name__ == '__main__':
say_hy("dear Ruff contributor")
"#), @r###"
success: true
exit_code: 0
----- stdout -----
from test import say_hy
if __name__ == "__main__":
say_hy("dear Ruff contributor")
----- stderr -----
"###);
Ok(())
}
#[test]
fn force_exclude_stdin() -> Result<()> {
let tempdir = TempDir::new()?;
let ruff_toml = tempdir.path().join("ruff.toml");
fs::write(
&ruff_toml,
r#"
extend-select = ["B", "Q"]
ignore = ["Q000", "Q001", "Q002", "Q003"]
[format]
exclude = ["generated.py"]
"#,
)?;
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
.current_dir(tempdir.path())
.args(["format", "--config", &ruff_toml.file_name().unwrap().to_string_lossy(), "--stdin-filename", "generated.py", "--force-exclude", "-"])
.pass_stdin(r#"
from test import say_hy
if __name__ == '__main__':
say_hy("dear Ruff contributor")
"#), @r###"
@@ -275,7 +379,7 @@ if condition:
print('Should change quotes')
----- stderr -----
warning: The following rules may cause conflicts when used with the formatter: `COM812`. To avoid unexpected behavior, we recommend disabling these rules, either by removing them from the `select` or `extend-select` configuration, or adding then to the `ignore` configuration.
warning: The following rules may cause conflicts when used with the formatter: `COM812`. To avoid unexpected behavior, we recommend disabling these rules, either by removing them from the `select` or `extend-select` configuration, or adding them to the `ignore` configuration.
"###);
Ok(())
}
@@ -403,7 +507,9 @@ def say_hy(name: str):
1 file reformatted
----- stderr -----
warning: The following rules may cause conflicts when used with the formatter: `COM812`, `D206`, `ISC001`, `W191`. To avoid unexpected behavior, we recommend disabling these rules, either by removing them from the `select` or `extend-select` configuration, or adding then to the `ignore` configuration.
warning: The following rules may cause conflicts when used with the formatter: `COM812`, `ISC001`. To avoid unexpected behavior, we recommend disabling these rules, either by removing them from the `select` or `extend-select` configuration, or adding them to the `ignore` configuration.
warning: The `format.indent-style="tab"` option is incompatible with `W191`, which lints against all uses of tabs. We recommend disabling these rules when using the formatter, which enforces a consistent indentation style. Alternatively, set the `format.indent-style` option to `"space"`.
warning: The `format.indent-style="tab"` option is incompatible with `D206`, with requires space-based indentation. We recommend disabling these rules when using the formatter, which enforces a consistent indentation style. Alternatively, set the `format.indent-style` option to `"space"`.
warning: The `flake8-quotes.inline-quotes="single"` option is incompatible with the formatter's `format.quote-style="double"`. We recommend disabling `Q000` and `Q003` when using the formatter, which enforces a consistent quote style. Alternatively, set both options to either `"single"` or `"double"`.
warning: The `flake8-quotes.multiline-quotes="single"` option is incompatible with the formatter. We recommend disabling `Q001` when using the formatter, which enforces double quotes for multiline strings. Alternatively, set the `flake8-quotes.multiline-quotes` option to `"double"`.`
warning: The `flake8-quotes.multiline-quotes="single"` option is incompatible with the formatter. We recommend disabling `Q002` when using the formatter, which enforces double quotes for docstrings. Alternatively, set the `flake8-quotes.docstring-quotes` option to `"double"`.`
@@ -460,7 +566,9 @@ def say_hy(name: str):
print(f"Hy {name}")
----- stderr -----
warning: The following rules may cause conflicts when used with the formatter: `COM812`, `D206`, `ISC001`, `W191`. To avoid unexpected behavior, we recommend disabling these rules, either by removing them from the `select` or `extend-select` configuration, or adding then to the `ignore` configuration.
warning: The following rules may cause conflicts when used with the formatter: `COM812`, `ISC001`. To avoid unexpected behavior, we recommend disabling these rules, either by removing them from the `select` or `extend-select` configuration, or adding them to the `ignore` configuration.
warning: The `format.indent-style="tab"` option is incompatible with `W191`, which lints against all uses of tabs. We recommend disabling these rules when using the formatter, which enforces a consistent indentation style. Alternatively, set the `format.indent-style` option to `"space"`.
warning: The `format.indent-style="tab"` option is incompatible with `D206`, with requires space-based indentation. We recommend disabling these rules when using the formatter, which enforces a consistent indentation style. Alternatively, set the `format.indent-style` option to `"space"`.
warning: The `flake8-quotes.inline-quotes="single"` option is incompatible with the formatter's `format.quote-style="double"`. We recommend disabling `Q000` and `Q003` when using the formatter, which enforces a consistent quote style. Alternatively, set both options to either `"single"` or `"double"`.
warning: The `flake8-quotes.multiline-quotes="single"` option is incompatible with the formatter. We recommend disabling `Q001` when using the formatter, which enforces double quotes for multiline strings. Alternatively, set the `flake8-quotes.multiline-quotes` option to `"double"`.`
warning: The `flake8-quotes.multiline-quotes="single"` option is incompatible with the formatter. We recommend disabling `Q002` when using the formatter, which enforces double quotes for docstrings. Alternatively, set the `flake8-quotes.docstring-quotes` option to `"double"`.`
@@ -556,7 +664,7 @@ def say_hy(name: str):
----- stderr -----
warning: `one-blank-line-before-class` (D203) and `no-blank-line-before-class` (D211) are incompatible. Ignoring `one-blank-line-before-class`.
warning: `multi-line-summary-first-line` (D212) and `multi-line-summary-second-line` (D213) are incompatible. Ignoring `multi-line-summary-second-line`.
warning: The following rules may cause conflicts when used with the formatter: `COM812`, `ISC001`. To avoid unexpected behavior, we recommend disabling these rules, either by removing them from the `select` or `extend-select` configuration, or adding then to the `ignore` configuration.
warning: The following rules may cause conflicts when used with the formatter: `COM812`, `ISC001`. To avoid unexpected behavior, we recommend disabling these rules, either by removing them from the `select` or `extend-select` configuration, or adding them to the `ignore` configuration.
"###);
Ok(())
}

View File

@@ -320,6 +320,119 @@ fn stdin_fix_jupyter() {
Found 2 errors (2 fixed, 0 remaining).
"###);
}
#[test]
fn stdin_override_parser_ipynb() {
let args = ["--extension", "py:ipynb", "--stdin-filename", "Jupyter.py"];
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
.args(STDIN_BASE_OPTIONS)
.args(args)
.pass_stdin(r#"{
"cells": [
{
"cell_type": "code",
"execution_count": 1,
"id": "dccc687c-96e2-4604-b957-a8a89b5bec06",
"metadata": {},
"outputs": [],
"source": [
"import os"
]
},
{
"cell_type": "markdown",
"id": "19e1b029-f516-4662-a9b9-623b93edac1a",
"metadata": {},
"source": [
"Foo"
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "cdce7b92-b0fb-4c02-86f6-e233b26fa84f",
"metadata": {},
"outputs": [],
"source": [
"import sys"
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "e40b33d2-7fe4-46c5-bdf0-8802f3052565",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"1\n"
]
}
],
"source": [
"print(1)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "a1899bc8-d46f-4ec0-b1d1-e1ca0f04bf60",
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.11.2"
}
},
"nbformat": 4,
"nbformat_minor": 5
}"#), @r###"
success: false
exit_code: 1
----- stdout -----
Jupyter.py:cell 1:1:8: F401 [*] `os` imported but unused
Jupyter.py:cell 3:1:8: F401 [*] `sys` imported but unused
Found 2 errors.
[*] 2 fixable with the `--fix` option.
----- stderr -----
"###);
}
#[test]
fn stdin_override_parser_py() {
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
.args(STDIN_BASE_OPTIONS)
.args(["--extension", "ipynb:python", "--stdin-filename", "F401.ipynb"])
.pass_stdin("import os\n"), @r###"
success: false
exit_code: 1
----- stdout -----
F401.ipynb:1:8: F401 [*] `os` imported but unused
Found 1 error.
[*] 1 fixable with the `--fix` option.
----- stderr -----
"###);
}
#[test]
fn stdin_fix_when_not_fixable_should_still_print_contents() {
@@ -1407,3 +1520,47 @@ extend-safe-fixes = ["UP034"]
Ok(())
}
#[test]
fn check_extend_unsafe_fixes_conflict_with_extend_safe_fixes_by_specificity() -> Result<()> {
// Adding a rule to one option with a more specific selector should override the other option
let tempdir = TempDir::new()?;
let ruff_toml = tempdir.path().join("ruff.toml");
fs::write(
&ruff_toml,
r#"
target-version = "py310"
[lint]
extend-unsafe-fixes = ["UP", "UP034"]
extend-safe-fixes = ["UP03"]
"#,
)?;
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
.args(["check", "--config"])
.arg(&ruff_toml)
.arg("-")
.args([
"--output-format",
"text",
"--no-cache",
"--select",
"F601,UP018,UP034,UP038",
])
.pass_stdin("x = {'a': 1, 'a': 1}\nprint(('foo'))\nprint(str('foo'))\nisinstance(x, (int, str))\n"),
@r###"
success: false
exit_code: 1
----- stdout -----
-:1:14: F601 Dictionary key literal `'a'` repeated
-:2:7: UP034 Avoid extraneous parentheses
-:3:7: UP018 Unnecessary `str` call (rewrite as a literal)
-:4:1: UP038 [*] Use `X | Y` in `isinstance` call instead of `(X, Y)`
Found 4 errors.
[*] 1 fixable with the `--fix` option (3 hidden fixes can be enabled with the `--unsafe-fixes` option).
----- stderr -----
"###);
Ok(())
}

View File

@@ -262,9 +262,13 @@ from test import say_hy
if __name__ == "__main__":
say_hy("dear Ruff contributor")
"#), @r###"
success: true
exit_code: 0
success: false
exit_code: 1
----- stdout -----
generated.py:4:16: Q000 [*] Double quotes found but single quotes preferred
generated.py:5:12: Q000 [*] Double quotes found but single quotes preferred
Found 2 errors.
[*] 2 fixable with the `--fix` option.
----- stderr -----
"###);
@@ -308,3 +312,87 @@ _ = "---------------------------------------------------------------------------
"###);
Ok(())
}
#[test]
fn per_file_ignores_stdin() -> Result<()> {
let tempdir = TempDir::new()?;
let ruff_toml = tempdir.path().join("ruff.toml");
fs::write(
&ruff_toml,
r#"
extend-select = ["B", "Q"]
[lint.flake8-quotes]
inline-quotes = "single"
"#,
)?;
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
.current_dir(tempdir.path())
.arg("check")
.args(STDIN_BASE_OPTIONS)
.args(["--config", &ruff_toml.file_name().unwrap().to_string_lossy()])
.args(["--stdin-filename", "generated.py"])
.args(["--per-file-ignores", "generated.py:Q"])
.arg("-")
.pass_stdin(r#"
import os
from test import say_hy
if __name__ == "__main__":
say_hy("dear Ruff contributor")
"#), @r###"
success: false
exit_code: 1
----- stdout -----
generated.py:2:8: F401 [*] `os` imported but unused
Found 1 error.
[*] 1 fixable with the `--fix` option.
----- stderr -----
"###);
Ok(())
}
#[test]
fn extend_per_file_ignores_stdin() -> Result<()> {
let tempdir = TempDir::new()?;
let ruff_toml = tempdir.path().join("ruff.toml");
fs::write(
&ruff_toml,
r#"
extend-select = ["B", "Q"]
[lint.flake8-quotes]
inline-quotes = "single"
"#,
)?;
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
.current_dir(tempdir.path())
.arg("check")
.args(STDIN_BASE_OPTIONS)
.args(["--config", &ruff_toml.file_name().unwrap().to_string_lossy()])
.args(["--stdin-filename", "generated.py"])
.args(["--extend-per-file-ignores", "generated.py:Q"])
.arg("-")
.pass_stdin(r#"
import os
from test import say_hy
if __name__ == "__main__":
say_hy("dear Ruff contributor")
"#), @r###"
success: false
exit_code: 1
----- stdout -----
generated.py:2:8: F401 [*] `os` imported but unused
Found 1 error.
[*] 1 fixable with the `--fix` option.
----- stderr -----
"###);
Ok(())
}

View File

@@ -41,7 +41,7 @@ serde_json = { workspace = true }
similar = { workspace = true }
strum = { workspace = true }
strum_macros = { workspace = true }
tempfile = "3.6.0"
tempfile = "3.8.1"
toml = { workspace = true, features = ["parse"] }
tracing = { workspace = true }
tracing-indicatif = { workspace = true }

View File

@@ -3,6 +3,7 @@
//! Used for <https://docs.astral.sh/ruff/settings/>.
use std::fmt::Write;
use ruff_python_trivia::textwrap;
use ruff_workspace::options::Options;
use ruff_workspace::options_base::{OptionField, OptionSet, OptionsMetadata, Visit};
@@ -125,18 +126,57 @@ fn emit_field(output: &mut String, name: &str, field: &OptionField, parent_set:
output.push('\n');
output.push_str(&format!("**Type**: `{}`\n", field.value_type));
output.push('\n');
output.push_str(&format!(
"**Example usage**:\n\n```toml\n[tool.ruff{}]\n{}\n```\n",
if let Some(set_name) = parent_set.name() {
format!(".{set_name}")
} else {
String::new()
},
field.example
output.push_str("**Example usage**:\n\n");
output.push_str(&format_tab(
"pyproject.toml",
&format_header(parent_set, ConfigurationFile::PyprojectToml),
field.example,
));
output.push_str(&format_tab(
"ruff.toml",
&format_header(parent_set, ConfigurationFile::RuffToml),
field.example,
));
output.push('\n');
}
fn format_tab(tab_name: &str, header: &str, content: &str) -> String {
format!(
"=== \"{}\"\n\n ```toml\n {}\n{}\n ```\n",
tab_name,
header,
textwrap::indent(content, " ")
)
}
fn format_header(parent_set: &Set, configuration: ConfigurationFile) -> String {
let fmt = if let Some(set_name) = parent_set.name() {
if set_name == "format" {
String::from(".format")
} else {
format!(".lint.{set_name}")
}
} else {
String::new()
};
match configuration {
ConfigurationFile::PyprojectToml => format!("[tool.ruff{fmt}]"),
ConfigurationFile::RuffToml => {
if fmt.is_empty() {
String::new()
} else {
format!("[{}]", fmt.strip_prefix('.').unwrap())
}
}
}
}
#[derive(Debug, Copy, Clone)]
enum ConfigurationFile {
PyprojectToml,
RuffToml,
}
#[derive(Default)]
struct CollectOptionsVisitor {
groups: Vec<(String, OptionSet)>,

View File

@@ -64,7 +64,7 @@ pub(crate) fn generate() -> String {
table_out.push('\n');
table_out.push_str(&format!(
"The {PREVIEW_SYMBOL} emoji indicates that a rule in [\"preview\"](faq.md#what-is-preview)."
"The {PREVIEW_SYMBOL} emoji indicates that a rule is in [\"preview\"](faq.md#what-is-preview)."
));
table_out.push('\n');
table_out.push('\n');

View File

@@ -12,7 +12,7 @@ use crate::edit::Edit;
pub enum Applicability {
/// The fix is unsafe and should only be displayed for manual application by the user.
/// The fix is likely to be incorrect or the resulting code may have invalid syntax.
Display,
DisplayOnly,
/// The fix is unsafe and should only be applied with user opt-in.
/// The fix may be what the user intended, but it is uncertain; the resulting code will have valid syntax.
@@ -87,22 +87,46 @@ impl Fix {
}
}
/// Create a new [`Fix`] that should only [display](Applicability::Display) and not apply from an [`Edit`] element .
pub fn display_edit(edit: Edit) -> Self {
/// Create a new [`Fix`] that should only [display](Applicability::DisplayOnly) and not apply from an [`Edit`] element .
pub fn display_only_edit(edit: Edit) -> Self {
Self {
edits: vec![edit],
applicability: Applicability::Display,
applicability: Applicability::DisplayOnly,
isolation_level: IsolationLevel::default(),
}
}
/// Create a new [`Fix`] that should only [display](Applicability::Display) and not apply from multiple [`Edit`] elements.
pub fn display_edits(edit: Edit, rest: impl IntoIterator<Item = Edit>) -> Self {
/// Create a new [`Fix`] that should only [display](Applicability::DisplayOnly) and not apply from multiple [`Edit`] elements.
pub fn display_only_edits(edit: Edit, rest: impl IntoIterator<Item = Edit>) -> Self {
let mut edits: Vec<Edit> = std::iter::once(edit).chain(rest).collect();
edits.sort_by_key(|edit| (edit.start(), edit.end()));
Self {
edits,
applicability: Applicability::Display,
applicability: Applicability::DisplayOnly,
isolation_level: IsolationLevel::default(),
}
}
/// Create a new [`Fix`] with the specified [`Applicability`] to apply an [`Edit`] element.
pub fn applicable_edit(edit: Edit, applicability: Applicability) -> Self {
Self {
edits: vec![edit],
applicability,
isolation_level: IsolationLevel::default(),
}
}
/// Create a new [`Fix`] with the specified [`Applicability`] to apply multiple [`Edit`] elements.
pub fn applicable_edits(
edit: Edit,
rest: impl IntoIterator<Item = Edit>,
applicability: Applicability,
) -> Self {
let mut edits: Vec<Edit> = std::iter::once(edit).chain(rest).collect();
edits.sort_by_key(|edit| (edit.start(), edit.end()));
Self {
edits,
applicability,
isolation_level: IsolationLevel::default(),
}
}

View File

@@ -1,6 +1,6 @@
[package]
name = "ruff_linter"
version = "0.1.3"
version = "0.1.5"
publish = false
authors = { workspace = true }
edition = { workspace = true }
@@ -53,7 +53,7 @@ path-absolutize = { workspace = true, features = [
] }
pathdiff = { version = "0.2.1" }
pep440_rs = { version = "0.3.12", features = ["serde"] }
pyproject-toml = { version = "0.7.0" }
pyproject-toml = { version = "0.8.0" }
quick-junit = { version = "0.3.2" }
regex = { workspace = true }
result-like = { version = "0.4.6" }
@@ -79,7 +79,7 @@ pretty_assertions = "1.3.0"
test-case = { workspace = true }
# Disable colored output in tests
colored = { workspace = true, features = ["no-color"] }
tempfile = "3.6.0"
tempfile = "3.8.1"
[features]
default = []

View File

@@ -0,0 +1,9 @@
from mako.template import Template
from mako import template
import mako
Template("hello")
mako.template.Template("hern")
template.Template("hern")

View File

@@ -58,3 +58,33 @@ def f_fix_indentation_check(foo):
# Report these, but don't fix them
if foo: raise RuntimeError("This is an example exception")
if foo: x = 1; raise RuntimeError("This is an example exception")
def f_triple_quoted_string():
raise RuntimeError(f"""This is an {"example"} exception""")
def f_multi_line_string():
raise RuntimeError(
"first"
"second"
)
def f_multi_line_string2():
raise RuntimeError(
"This is an {example} exception".format(
example="example"
)
)
def f_multi_line_string2():
raise RuntimeError(
(
"This is an "
"{example} exception"
).format(
example="example"
)
)

View File

@@ -1,3 +1,5 @@
obj = {}
key in obj.keys() # SIM118
key not in obj.keys() # SIM118

View File

@@ -0,0 +1,18 @@
import trio
async def foo():
with trio.fail_after():
...
async def foo():
with trio.fail_at():
await ...
async def foo():
with trio.move_on_after():
...
async def foo():
with trio.move_at():
await ...

View File

@@ -0,0 +1,64 @@
import trio
async def func() -> None:
trio.run(foo) # OK, not async
# OK
await trio.aclose_forcefully(foo)
await trio.open_file(foo)
await trio.open_ssl_over_tcp_listeners(foo, foo)
await trio.open_ssl_over_tcp_stream(foo, foo)
await trio.open_tcp_listeners(foo)
await trio.open_tcp_stream(foo, foo)
await trio.open_unix_socket(foo)
await trio.run_process(foo)
await trio.sleep(5)
await trio.sleep_until(5)
await trio.lowlevel.cancel_shielded_checkpoint()
await trio.lowlevel.checkpoint()
await trio.lowlevel.checkpoint_if_cancelled()
await trio.lowlevel.open_process(foo)
await trio.lowlevel.permanently_detach_coroutine_object(foo)
await trio.lowlevel.reattach_detached_coroutine_object(foo, foo)
await trio.lowlevel.temporarily_detach_coroutine_object(foo)
await trio.lowlevel.wait_readable(foo)
await trio.lowlevel.wait_task_rescheduled(foo)
await trio.lowlevel.wait_writable(foo)
# TRIO105
trio.aclose_forcefully(foo)
trio.open_file(foo)
trio.open_ssl_over_tcp_listeners(foo, foo)
trio.open_ssl_over_tcp_stream(foo, foo)
trio.open_tcp_listeners(foo)
trio.open_tcp_stream(foo, foo)
trio.open_unix_socket(foo)
trio.run_process(foo)
trio.serve_listeners(foo, foo)
trio.serve_ssl_over_tcp(foo, foo, foo)
trio.serve_tcp(foo, foo)
trio.sleep(foo)
trio.sleep_forever()
trio.sleep_until(foo)
trio.lowlevel.cancel_shielded_checkpoint()
trio.lowlevel.checkpoint()
trio.lowlevel.checkpoint_if_cancelled()
trio.lowlevel.open_process()
trio.lowlevel.permanently_detach_coroutine_object(foo)
trio.lowlevel.reattach_detached_coroutine_object(foo, foo)
trio.lowlevel.temporarily_detach_coroutine_object(foo)
trio.lowlevel.wait_readable(foo)
trio.lowlevel.wait_task_rescheduled(foo)
trio.lowlevel.wait_writable(foo)
async with await trio.open_file(foo): # Ok
pass
async with trio.open_file(foo): # TRIO105
pass
def func() -> None:
# TRIO105 (without fix)
trio.open_file(foo)

View File

@@ -0,0 +1,13 @@
import trio
async def func():
...
async def func(timeout):
...
async def func(timeout=10):
...

View File

@@ -0,0 +1,16 @@
import trio
async def func():
while True:
await trio.sleep(10)
async def func():
while True:
await trio.sleep_until(10)
async def func():
while True:
trio.sleep(10)

View File

@@ -0,0 +1,28 @@
import trio
from trio import sleep
async def func():
await trio.sleep(0) # TRIO115
await trio.sleep(1) # OK
await trio.sleep(0, 1) # OK
await trio.sleep(...) # OK
await trio.sleep() # OK
trio.sleep(0) # TRIO115
foo = 0
trio.sleep(foo) # TRIO115
trio.sleep(1) # OK
time.sleep(0) # OK
sleep(0) # TRIO115
bar = "bar"
trio.sleep(bar)
trio.sleep(0) # TRIO115
def func():
trio.run(trio.sleep(0)) # TRIO115

View File

@@ -0,0 +1,11 @@
from __future__ import annotations
from collections.abc import Sequence
class MyBaseClass:
pass
class Foo(MyBaseClass):
foo: Sequence

View File

@@ -0,0 +1,9 @@
from __future__ import annotations
from collections.abc import Sequence
from module.direct import MyBaseClass
class Foo(MyBaseClass):
foo: Sequence

View File

@@ -0,0 +1,7 @@
from __future__ import annotations
from collections.abc import Sequence
class Foo(MyBaseClass):
foo: Sequence

View File

@@ -0,0 +1,11 @@
from __future__ import annotations
from collections.abc import Sequence # TCH003
class MyBaseClass:
pass
class Foo(MyBaseClass):
foo: Sequence

View File

@@ -1,7 +1,7 @@
[tool.ruff]
line-length = 88
[tool.ruff.isort]
[tool.ruff.lint.isort]
lines-after-imports = 3
lines-between-types = 2
known-local-folder = ["ruff"]

View File

@@ -0,0 +1,106 @@
def func():
import numpy as np
np.add_docstring
np.add_newdoc
np.add_newdoc_ufunc
np.asfarray([1,2,3])
np.byte_bounds(np.array([1,2,3]))
np.cast
np.cfloat(12+34j)
np.clongfloat(12+34j)
np.compat
np.complex_(12+34j)
np.DataSource
np.deprecate
np.deprecate_with_doc
np.disp(10)
np.fastCopyAndTranspose
np.find_common_type
np.get_array_wrap
np.float_
np.geterrobj
np.Inf
np.Infinity
np.infty
np.issctype
np.issubclass_(np.int32, np.integer)
np.issubsctype
np.mat
np.maximum_sctype
np.NaN
np.nbytes[np.int64]
np.NINF
np.NZERO
np.longcomplex(12+34j)
np.longfloat(12+34j)
np.lookfor
np.obj2sctype(int)
np.PINF
np.PZERO
np.recfromcsv
np.recfromtxt
np.round_(12.34)
np.safe_eval
np.sctype2char
np.sctypes
np.seterrobj
np.set_numeric_ops
np.set_string_function
np.singlecomplex(12+1j)
np.string_("asdf")
np.source
np.tracemalloc_domain
np.unicode_("asf")
np.who()

View File

@@ -12,3 +12,19 @@ dict['key'] = list[index]
# This is not prohibited by PEP8, but avoid it.
class Foo (Bar, Baz):
pass
def fetch_name () -> Union[str, None]:
"""Fetch name from --person-name in sys.argv.
Returns:
name of the person if available, otherwise None
"""
test = len(5)
Logger.info(test)
# test commented code
# Logger.info("test code")
for i in range (0, len (sys.argv)) :
if sys.argv[i] == "--name" :
return sys.argv[i + 1]
return None

View File

@@ -40,5 +40,11 @@ f"{(a:=1)}"
f"{(lambda x:x)}"
f"normal{f"{a:.3f}"}normal"
#: Okay
snapshot.file_uri[len(f's3://{self.s3_bucket_name}/'):]
#: E231
{len(f's3://{self.s3_bucket_name}/'):1}
#: Okay
a = (1,

View File

@@ -69,3 +69,5 @@ while 1:
#: E701:2:3
a = \
5;
#:
with x(y) as z: ...

View File

@@ -4,6 +4,9 @@ if type(res) == type(42):
#: E721
if type(res) != type(""):
pass
#: E721
if type(res) == memoryview:
pass
#: Okay
import types
@@ -47,6 +50,14 @@ if isinstance(res, str):
pass
if isinstance(res, types.MethodType):
pass
if isinstance(res, memoryview):
pass
#: Okay
if type(res) is type:
pass
#: E721
if type(res) == type:
pass
#: Okay
def func_histype(a, b, c):
pass

View File

@@ -8,3 +8,19 @@ def ends_in_quote():
def contains_quote():
'Sum"\\mary.'
# OK
def contains_triples(t):
"""('''|\""")"""
# OK
def contains_triples(t):
'''(\'''|""")'''
# TODO: here should raise D300 for using dobule quotes instead,
# because escaped double quote does allow us.
def contains_triples(t):
'''(\""")'''

View File

@@ -31,3 +31,7 @@ def make_unique_pod_id(pod_id: str) -> str | None:
:param pod_id: requested pod name
:return: ``str`` valid Pod name of appropriate length
"""
def shouldnt_add_raw_here2():
u"Sum\\mary."

View File

@@ -1,5 +1,5 @@
def f(tup):
x, y = tup # this does NOT trigger F841
x, y = tup
def f():
@@ -7,17 +7,17 @@ def f():
def f():
(x, y) = coords = 1, 2 # this does NOT trigger F841
(x, y) = coords = 1, 2
if x > 1:
print(coords)
def f():
(x, y) = coords = 1, 2 # this triggers F841 on coords
(x, y) = coords = 1, 2
def f():
coords = (x, y) = 1, 2 # this triggers F841 on coords
coords = (x, y) = 1, 2
def f():

View File

@@ -0,0 +1,32 @@
"""Test fix for issue #8441.
Ref: https://github.com/astral-sh/ruff/issues/8441
"""
def foo():
...
def bar():
a = foo()
b, c = foo()
def baz():
d, _e = foo()
print(d)
def qux():
f, _ = foo()
print(f)
def quux():
g, h = foo()
print(g, h)
def quuz():
_i, _j = foo()

View File

@@ -70,6 +70,10 @@ class Apples:
def __html__(self):
pass
# Allow Python's __index__
def __index__(self):
pass
# Allow _missing_, used by enum.Enum.
@classmethod
def _missing_(cls, value):

View File

@@ -0,0 +1,34 @@
import pathlib
NAME = "foo.bar"
open(NAME, "wb")
open(NAME, "w", encoding="utf-8")
open(NAME, "rb")
open(NAME, "x", encoding="utf-8")
open(NAME, "br")
open(NAME, "+r", encoding="utf-8")
open(NAME, "xb")
open(NAME, "rwx") # [bad-open-mode]
open(NAME, mode="rwx") # [bad-open-mode]
open(NAME, "rwx", encoding="utf-8") # [bad-open-mode]
open(NAME, "rr", encoding="utf-8") # [bad-open-mode]
open(NAME, "+", encoding="utf-8") # [bad-open-mode]
open(NAME, "xw", encoding="utf-8") # [bad-open-mode]
open(NAME, "ab+")
open(NAME, "a+b")
open(NAME, "+ab")
open(NAME, "+rUb")
open(NAME, "x+b")
open(NAME, "Ua", encoding="utf-8") # [bad-open-mode]
open(NAME, "Ur++", encoding="utf-8") # [bad-open-mode]
open(NAME, "Ut", encoding="utf-8")
open(NAME, "Ubr")
mode = "rw"
open(NAME, mode)
pathlib.Path(NAME).open("wb")
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]

View File

@@ -57,3 +57,9 @@ r'\%03o' % (ord(c),)
'(%r, %r, %r, %r)' % (hostname, address, username, '$PASSWORD')
'%r' % ({'server_school_roles': server_school_roles, 'is_school_multiserver_domain': is_school_multiserver_domain}, )
"%d" % (1 if x > 0 else 2)
# Special cases for %c allowing single character strings
# https://github.com/astral-sh/ruff/issues/8406
"%c" % ("x",)
"%c" % "x"
"%c" % "œ"

View File

@@ -0,0 +1,22 @@
from typing import TYPE_CHECKING
# Verify that statements nested in conditionals (such as top-level type-checking blocks)
# are still considered top-level
if TYPE_CHECKING:
import string
def import_in_function():
import symtable # [import-outside-toplevel]
import os, sys # [import-outside-toplevel]
import time as thyme # [import-outside-toplevel]
import random as rand, socket as sock # [import-outside-toplevel]
from collections import defaultdict # [import-outside-toplevel]
from math import sin as sign, cos as cosplay # [import-outside-toplevel]
class ClassWithImports:
import tokenize # [import-outside-toplevel]
def __init__(self):
import trace # [import-outside-toplevel]

View File

@@ -0,0 +1,56 @@
import threading
from threading import Lock, RLock, Condition, Semaphore, BoundedSemaphore
with threading.Lock(): # [useless-with-lock]
...
with Lock(): # [useless-with-lock]
...
with threading.Lock() as this_shouldnt_matter: # [useless-with-lock]
...
with threading.RLock(): # [useless-with-lock]
...
with RLock(): # [useless-with-lock]
...
with threading.Condition(): # [useless-with-lock]
...
with Condition(): # [useless-with-lock]
...
with threading.Semaphore(): # [useless-with-lock]
...
with Semaphore(): # [useless-with-lock]
...
with threading.BoundedSemaphore(): # [useless-with-lock]
...
with BoundedSemaphore(): # [useless-with-lock]
...
lock = threading.Lock()
with lock: # this is ok
...
rlock = threading.RLock()
with rlock: # this is ok
...
cond = threading.Condition()
with cond: # this is ok
...
sem = threading.Semaphore()
with sem: # this is ok
...
b_sem = threading.BoundedSemaphore()
with b_sem: # this is ok
...

View File

@@ -5,4 +5,6 @@ extend-exclude = [
"migrations",
"with_excluded_file/other_excluded_file.py",
]
[tool.ruff.lint]
per-file-ignores = { "__init__.py" = ["F401"] }

View File

@@ -23,3 +23,11 @@ MyType = typing.NamedTuple("MyType", a=int, b=tuple[str, ...])
MyType = typing.NamedTuple("MyType", [("a", int)], [("b", str)])
MyType = typing.NamedTuple("MyType", [("a", int)], b=str)
MyType = typing.NamedTuple(typename="MyType", a=int, b=str)
# Regression test for: https://github.com/astral-sh/ruff/issues/8402#issuecomment-1788787357
S3File = NamedTuple(
"S3File",
[
("dataHPK",* str),
],
)

View File

@@ -0,0 +1,68 @@
import asyncio, socket
# These should be fixed
try:
pass
except asyncio.TimeoutError:
pass
try:
pass
except socket.timeout:
pass
# Should NOT be in parentheses when replaced
try:
pass
except (asyncio.TimeoutError,):
pass
try:
pass
except (socket.timeout,):
pass
try:
pass
except (asyncio.TimeoutError, socket.timeout,):
pass
# Should be kept in parentheses (because multiple)
try:
pass
except (asyncio.TimeoutError, socket.timeout, KeyError, TimeoutError):
pass
# First should change, second should not
from .mmap import error
try:
pass
except (asyncio.TimeoutError, error):
pass
# These should not change
from foo import error
try:
pass
except (TimeoutError, error):
pass
try:
pass
except:
pass
try:
pass
except TimeoutError:
pass
try:
pass
except (TimeoutError, KeyError):
pass

View File

@@ -123,6 +123,15 @@ def yes_six(x: list):
x.append(2)
if True:
# FURB113
nums.append(1)
# comment
nums.append(2)
# comment
nums.append(3)
# Non-errors.
nums.append(1)

View File

@@ -43,6 +43,12 @@ def yes_four(x: Dict[int, str]):
del x[:]
def yes_five(x: Dict[int, str]):
# FURB131
del x[:]
x = 1
# these should not
del names["key"]

View File

@@ -0,0 +1,51 @@
foo: object
# Errors.
if isinstance(foo, type(None)):
pass
if isinstance(foo, (type(None))):
pass
if isinstance(foo, (type(None), type(None), type(None))):
pass
if isinstance(foo, None | None):
pass
if isinstance(foo, (None | None)):
pass
if isinstance(foo, None | type(None)):
pass
if isinstance(foo, (None | type(None))):
pass
# A bit contrived, but is both technically valid and equivalent to the above.
if isinstance(foo, (type(None) | ((((type(None))))) | ((None | type(None))))):
pass
# Okay.
if isinstance(foo, int):
pass
if isinstance(foo, (int)):
pass
if isinstance(foo, (int, str)):
pass
if isinstance(foo, (int, type(None), str)):
pass
# This is a TypeError, which the rule ignores.
if isinstance(foo, None):
pass
# This is also a TypeError, which the rule ignores.
if isinstance(foo, (None,)):
pass

View File

@@ -0,0 +1,67 @@
foo = None
# Error.
type(foo) is type(None)
type(None) is type(foo)
type(None) is type(None)
type(foo) is not type(None)
type(None) is not type(foo)
type(None) is not type(None)
type(foo) == type(None)
type(None) == type(foo)
type(None) == type(None)
type(foo) != type(None)
type(None) != type(foo)
type(None) != type(None)
# Ok.
foo is None
foo is not None
None is foo
None is not foo
None is None
None is not None
foo is type(None)
type(foo) is None
type(None) is None
foo is not type(None)
type(foo) is not None
type(None) is not None
foo == type(None)
type(foo) == None
type(None) == None
foo != type(None)
type(foo) != None
type(None) != None
type(foo) > type(None)

View File

@@ -43,3 +43,6 @@ if 1 is {1}:
if "a" == "a":
pass
if 1 in {*[1]}:
pass

View File

@@ -45,3 +45,11 @@ x = f"string { # And here's a comment with an unusual parenthesis:
# And here's a comment with a greek alpha:
foo # And here's a comment with an unusual punctuation mark:
}"
# At runtime the attribute will be stored as Greek small letter mu instead of
# micro sign because of PEP 3131's NFKC normalization
class Labware:
µL = 1.5
assert getattr(Labware(), "µL") == 1.5

View File

@@ -1,5 +1,7 @@
[tool.ruff]
src = [".", "python_modules/*"]
exclude = ["examples/excluded"]
[tool.ruff.lint]
extend-select = ["I001"]
extend-ignore = ["F841"]

View File

@@ -170,7 +170,7 @@ pub(crate) fn definitions(checker: &mut Checker) {
expr.start(),
));
if pydocstyle::helpers::should_ignore_docstring(expr) {
if expr.implicit_concatenated {
#[allow(deprecated)]
let location = checker.locator.compute_source_location(expr.start());
warn_user!(

View File

@@ -1,4 +1,4 @@
use ruff_python_ast::{self as ast, Arguments, Constant, Expr, ExprContext, Operator};
use ruff_python_ast::{self as ast, Arguments, Expr, ExprContext, Operator};
use ruff_python_literal::cformat::{CFormatError, CFormatErrorType};
use ruff_diagnostics::Diagnostic;
@@ -15,8 +15,8 @@ use crate::rules::{
flake8_comprehensions, flake8_datetimez, flake8_debugger, flake8_django,
flake8_future_annotations, flake8_gettext, flake8_implicit_str_concat, flake8_logging,
flake8_logging_format, flake8_pie, flake8_print, flake8_pyi, flake8_pytest_style, flake8_self,
flake8_simplify, flake8_tidy_imports, flake8_use_pathlib, flynt, numpy, pandas_vet,
pep8_naming, pycodestyle, pyflakes, pygrep_hooks, pylint, pyupgrade, refurb, ruff,
flake8_simplify, flake8_tidy_imports, flake8_trio, flake8_use_pathlib, flynt, numpy,
pandas_vet, pep8_naming, pycodestyle, pyflakes, pygrep_hooks, pylint, pyupgrade, refurb, ruff,
};
use crate::settings::types::PythonVersion;
@@ -158,6 +158,9 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
if checker.enabled(Rule::NumpyDeprecatedFunction) {
numpy::rules::deprecated_function(checker, expr);
}
if checker.enabled(Rule::Numpy2Deprecation) {
numpy::rules::numpy_2_0_deprecation(checker, expr);
}
if checker.enabled(Rule::CollectionsNamedTuple) {
flake8_pyi::rules::collections_named_tuple(checker, expr);
}
@@ -314,6 +317,9 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
if checker.enabled(Rule::NumpyDeprecatedFunction) {
numpy::rules::deprecated_function(checker, expr);
}
if checker.enabled(Rule::Numpy2Deprecation) {
numpy::rules::numpy_2_0_deprecation(checker, expr);
}
if checker.enabled(Rule::DeprecatedMockImport) {
pyupgrade::rules::deprecated_mock_attribute(checker, expr);
}
@@ -363,20 +369,18 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
]) {
if let Expr::Attribute(ast::ExprAttribute { value, attr, .. }) = func.as_ref() {
let attr = attr.as_str();
if let Expr::Constant(ast::ExprConstant {
value: Constant::Str(val),
..
}) = value.as_ref()
if let Expr::StringLiteral(ast::ExprStringLiteral { value: string, .. }) =
value.as_ref()
{
if attr == "join" {
// "...".join(...) call
if checker.enabled(Rule::StaticJoinToFString) {
flynt::rules::static_join_to_fstring(checker, expr, val);
flynt::rules::static_join_to_fstring(checker, expr, string);
}
} else if attr == "format" {
// "...".format(...) call
let location = expr.range();
match pyflakes::format::FormatSummary::try_from(val.as_ref()) {
match pyflakes::format::FormatSummary::try_from(string.as_ref()) {
Err(e) => {
if checker.enabled(Rule::StringDotFormatInvalidFormat) {
checker.diagnostics.push(Diagnostic::new(
@@ -421,7 +425,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
if checker.enabled(Rule::BadStringFormatCharacter) {
pylint::rules::bad_string_format_character::call(
checker, val, location,
checker, string, location,
);
}
}
@@ -462,6 +466,11 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
if checker.enabled(Rule::OSErrorAlias) {
pyupgrade::rules::os_error_alias_call(checker, func);
}
if checker.enabled(Rule::TimeoutErrorAlias) {
if checker.settings.target_version >= PythonVersion::Py310 {
pyupgrade::rules::timeout_error_alias_call(checker, func);
}
}
if checker.enabled(Rule::NonPEP604Isinstance) {
if checker.settings.target_version >= PythonVersion::Py310 {
pyupgrade::rules::use_pep604_isinstance(checker, expr, func, args);
@@ -562,6 +571,9 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
if checker.enabled(Rule::Jinja2AutoescapeFalse) {
flake8_bandit::rules::jinja2_autoescape_false(checker, call);
}
if checker.enabled(Rule::MakoTemplates) {
flake8_bandit::rules::mako_templates(checker, call);
}
if checker.enabled(Rule::HardcodedPasswordFuncArg) {
flake8_bandit::rules::hardcoded_password_func_arg(checker, keywords);
}
@@ -749,6 +761,9 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
if checker.enabled(Rule::SysExitAlias) {
pylint::rules::sys_exit_alias(checker, func);
}
if checker.enabled(Rule::BadOpenMode) {
pylint::rules::bad_open_mode(checker, call);
}
if checker.enabled(Rule::BadStrStripCall) {
pylint::rules::bad_str_strip_call(checker, func, args);
}
@@ -908,9 +923,18 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
if checker.enabled(Rule::ExceptionWithoutExcInfo) {
flake8_logging::rules::exception_without_exc_info(checker, call);
}
if checker.enabled(Rule::IsinstanceTypeNone) {
refurb::rules::isinstance_type_none(checker, call);
}
if checker.enabled(Rule::ImplicitCwd) {
refurb::rules::no_implicit_cwd(checker, call);
}
if checker.enabled(Rule::TrioSyncCall) {
flake8_trio::rules::sync_call(checker, call);
}
if checker.enabled(Rule::TrioZeroSleepCall) {
flake8_trio::rules::zero_sleep_call(checker, call);
}
}
Expr::Dict(
dict @ ast::ExprDict {
@@ -987,11 +1011,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
right,
range: _,
}) => {
if let Expr::Constant(ast::ExprConstant {
value: Constant::Str(ast::StringConstant { value, .. }),
..
}) = left.as_ref()
{
if let Expr::StringLiteral(ast::ExprStringLiteral { value, .. }) = left.as_ref() {
if checker.any_enabled(&[
Rule::PercentFormatInvalidFormat,
Rule::PercentFormatExpectedMapping,
@@ -1224,42 +1244,36 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
comparators,
);
}
if checker.enabled(Rule::TypeNoneComparison) {
refurb::rules::type_none_comparison(checker, compare);
}
if checker.enabled(Rule::SingleItemMembershipTest) {
refurb::rules::single_item_membership_test(checker, expr, left, ops, comparators);
}
}
Expr::Constant(ast::ExprConstant {
value: Constant::Int(_) | Constant::Float(_) | Constant::Complex { .. },
range: _,
}) => {
Expr::NumberLiteral(_) => {
if checker.source_type.is_stub() && checker.enabled(Rule::NumericLiteralTooLong) {
flake8_pyi::rules::numeric_literal_too_long(checker, expr);
}
}
Expr::Constant(ast::ExprConstant {
value: Constant::Bytes(_),
range: _,
}) => {
Expr::BytesLiteral(_) => {
if checker.source_type.is_stub() && checker.enabled(Rule::StringOrBytesTooLong) {
flake8_pyi::rules::string_or_bytes_too_long(checker, expr);
}
}
Expr::Constant(ast::ExprConstant {
value: Constant::Str(value),
range: _,
}) => {
Expr::StringLiteral(string) => {
if checker.enabled(Rule::HardcodedBindAllInterfaces) {
if let Some(diagnostic) =
flake8_bandit::rules::hardcoded_bind_all_interfaces(value, expr.range())
flake8_bandit::rules::hardcoded_bind_all_interfaces(string)
{
checker.diagnostics.push(diagnostic);
}
}
if checker.enabled(Rule::HardcodedTempFile) {
flake8_bandit::rules::hardcoded_tmp_directory(checker, expr, value);
flake8_bandit::rules::hardcoded_tmp_directory(checker, string);
}
if checker.enabled(Rule::UnicodeKindPrefix) {
pyupgrade::rules::unicode_kind_prefix(checker, expr, value.unicode);
pyupgrade::rules::unicode_kind_prefix(checker, string);
}
if checker.source_type.is_stub() {
if checker.enabled(Rule::StringOrBytesTooLong) {

View File

@@ -12,8 +12,8 @@ use crate::rules::{
airflow, flake8_bandit, flake8_boolean_trap, flake8_bugbear, flake8_builtins, flake8_debugger,
flake8_django, flake8_errmsg, flake8_import_conventions, flake8_pie, flake8_pyi,
flake8_pytest_style, flake8_raise, flake8_return, flake8_simplify, flake8_slots,
flake8_tidy_imports, flake8_type_checking, mccabe, pandas_vet, pep8_naming, perflint,
pycodestyle, pyflakes, pygrep_hooks, pylint, pyupgrade, refurb, ruff, tryceratops,
flake8_tidy_imports, flake8_trio, flake8_type_checking, mccabe, pandas_vet, pep8_naming,
perflint, pycodestyle, pyflakes, pygrep_hooks, pylint, pyupgrade, refurb, ruff, tryceratops,
};
use crate::settings::types::PythonVersion;
@@ -356,6 +356,9 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
flake8_builtins::rules::builtin_variable_shadowing(checker, name, name.range());
}
}
if checker.enabled(Rule::TrioAsyncFunctionWithTimeout) {
flake8_trio::rules::async_function_with_timeout(checker, function_def);
}
#[cfg(feature = "unreachable-code")]
if checker.enabled(Rule::UnreachableCode) {
checker
@@ -530,6 +533,9 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
if checker.enabled(Rule::ModuleImportNotAtTopOfFile) {
pycodestyle::rules::module_import_not_at_top_of_file(checker, stmt);
}
if checker.enabled(Rule::ImportOutsideTopLevel) {
pylint::rules::import_outside_top_level(checker, stmt);
}
if checker.enabled(Rule::GlobalStatement) {
for name in names {
if let Some(asname) = name.asname.as_ref() {
@@ -706,6 +712,9 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
if checker.enabled(Rule::ModuleImportNotAtTopOfFile) {
pycodestyle::rules::module_import_not_at_top_of_file(checker, stmt);
}
if checker.enabled(Rule::ImportOutsideTopLevel) {
pylint::rules::import_outside_top_level(checker, stmt);
}
if checker.enabled(Rule::GlobalStatement) {
for name in names {
if let Some(asname) = name.asname.as_ref() {
@@ -1000,6 +1009,13 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
pyupgrade::rules::os_error_alias_raise(checker, item);
}
}
if checker.enabled(Rule::TimeoutErrorAlias) {
if checker.settings.target_version >= PythonVersion::Py310 {
if let Some(item) = exc {
pyupgrade::rules::timeout_error_alias_raise(checker, item);
}
}
}
if checker.enabled(Rule::RaiseVanillaClass) {
if let Some(expr) = exc {
tryceratops::rules::raise_vanilla_class(checker, expr);
@@ -1186,8 +1202,14 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
if checker.enabled(Rule::ReadWholeFile) {
refurb::rules::read_whole_file(checker, with_stmt);
}
if checker.enabled(Rule::UselessWithLock) {
pylint::rules::useless_with_lock(checker, with_stmt);
}
if checker.enabled(Rule::TrioTimeoutWithoutAwait) {
flake8_trio::rules::timeout_without_await(checker, with_stmt, items);
}
}
Stmt::While(ast::StmtWhile { body, orelse, .. }) => {
Stmt::While(while_stmt @ ast::StmtWhile { body, orelse, .. }) => {
if checker.enabled(Rule::FunctionUsesLoopVariable) {
flake8_bugbear::rules::function_uses_loop_variable(checker, &Node::Stmt(stmt));
}
@@ -1197,6 +1219,9 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
if checker.enabled(Rule::TryExceptInLoop) {
perflint::rules::try_except_in_loop(checker, body);
}
if checker.enabled(Rule::TrioUnneededSleep) {
flake8_trio::rules::unneeded_sleep(checker, while_stmt);
}
}
Stmt::For(
for_stmt @ ast::StmtFor {
@@ -1292,6 +1317,11 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
if checker.enabled(Rule::OSErrorAlias) {
pyupgrade::rules::os_error_alias_handlers(checker, handlers);
}
if checker.enabled(Rule::TimeoutErrorAlias) {
if checker.settings.target_version >= PythonVersion::Py310 {
pyupgrade::rules::timeout_error_alias_handlers(checker, handlers);
}
}
if checker.enabled(Rule::PytestAssertInExcept) {
flake8_pytest_style::rules::assert_in_exception_handler(checker, handlers);
}

View File

@@ -31,9 +31,8 @@ use std::path::Path;
use itertools::Itertools;
use log::debug;
use ruff_python_ast::{
self as ast, Arguments, Comprehension, Constant, ElifElseClause, ExceptHandler, Expr,
ExprContext, Keyword, MatchCase, Parameter, ParameterWithDefault, Parameters, Pattern, Stmt,
Suite, UnaryOp,
self as ast, Arguments, Comprehension, ElifElseClause, ExceptHandler, Expr, ExprContext,
Keyword, MatchCase, Parameter, ParameterWithDefault, Parameters, Pattern, Stmt, Suite, UnaryOp,
};
use ruff_text_size::{Ranged, TextRange, TextSize};
@@ -787,11 +786,7 @@ where
&& self.semantic.in_type_definition()
&& self.semantic.future_annotations()
{
if let Expr::Constant(ast::ExprConstant {
value: Constant::Str(value),
..
}) = expr
{
if let Expr::StringLiteral(ast::ExprStringLiteral { value, .. }) = expr {
self.deferred.string_type_definitions.push((
expr.range(),
value,
@@ -1186,10 +1181,7 @@ where
}
}
}
Expr::Constant(ast::ExprConstant {
value: Constant::Str(value),
range: _,
}) => {
Expr::StringLiteral(ast::ExprStringLiteral { value, .. }) => {
if self.semantic.in_type_definition()
&& !self.semantic.in_literal()
&& !self.semantic.in_f_string()
@@ -1423,7 +1415,7 @@ impl<'a> Checker<'a> {
// subsequent nodes are evaluated in the inner scope.
//
// For example, given:
// ```py
// ```python
// class A:
// T = range(10)
//
@@ -1431,7 +1423,7 @@ impl<'a> Checker<'a> {
// ```
//
// Conceptually, this is compiled as:
// ```py
// ```python
// class A:
// T = range(10)
//

View File

@@ -211,6 +211,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
(Pylint, "C0205") => (RuleGroup::Stable, rules::pylint::rules::SingleStringSlots),
(Pylint, "C0208") => (RuleGroup::Stable, rules::pylint::rules::IterationOverSet),
(Pylint, "C0414") => (RuleGroup::Stable, rules::pylint::rules::UselessImportAlias),
(Pylint, "C0415") => (RuleGroup::Preview, rules::pylint::rules::ImportOutsideTopLevel),
(Pylint, "C2401") => (RuleGroup::Preview, rules::pylint::rules::NonAsciiName),
(Pylint, "C2403") => (RuleGroup::Preview, rules::pylint::rules::NonAsciiImportName),
#[allow(deprecated)]
@@ -270,12 +271,14 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
(Pylint, "W0604") => (RuleGroup::Preview, rules::pylint::rules::GlobalAtModuleLevel),
(Pylint, "W0603") => (RuleGroup::Stable, rules::pylint::rules::GlobalStatement),
(Pylint, "W0711") => (RuleGroup::Stable, rules::pylint::rules::BinaryOpException),
(Pylint, "W1501") => (RuleGroup::Preview, rules::pylint::rules::BadOpenMode),
(Pylint, "W1508") => (RuleGroup::Stable, rules::pylint::rules::InvalidEnvvarDefault),
(Pylint, "W1509") => (RuleGroup::Stable, rules::pylint::rules::SubprocessPopenPreexecFn),
(Pylint, "W1510") => (RuleGroup::Stable, rules::pylint::rules::SubprocessRunWithoutCheck),
(Pylint, "W1514") => (RuleGroup::Preview, rules::pylint::rules::UnspecifiedEncoding),
#[allow(deprecated)]
(Pylint, "W1641") => (RuleGroup::Nursery, rules::pylint::rules::EqWithoutHash),
(Pylint, "W2101") => (RuleGroup::Preview, rules::pylint::rules::UselessWithLock),
(Pylint, "R0904") => (RuleGroup::Preview, rules::pylint::rules::TooManyPublicMethods),
(Pylint, "W2901") => (RuleGroup::Stable, rules::pylint::rules::RedefinedLoopName),
#[allow(deprecated)]
@@ -287,6 +290,13 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
(Flake8Async, "101") => (RuleGroup::Stable, rules::flake8_async::rules::OpenSleepOrSubprocessInAsyncFunction),
(Flake8Async, "102") => (RuleGroup::Stable, rules::flake8_async::rules::BlockingOsCallInAsyncFunction),
// flake8-trio
(Flake8Trio, "100") => (RuleGroup::Preview, rules::flake8_trio::rules::TrioTimeoutWithoutAwait),
(Flake8Trio, "105") => (RuleGroup::Preview, rules::flake8_trio::rules::TrioSyncCall),
(Flake8Trio, "109") => (RuleGroup::Preview, rules::flake8_trio::rules::TrioAsyncFunctionWithTimeout),
(Flake8Trio, "110") => (RuleGroup::Preview, rules::flake8_trio::rules::TrioUnneededSleep),
(Flake8Trio, "115") => (RuleGroup::Preview, rules::flake8_trio::rules::TrioZeroSleepCall),
// flake8-builtins
(Flake8Builtins, "001") => (RuleGroup::Stable, rules::flake8_builtins::rules::BuiltinVariableShadowing),
(Flake8Builtins, "002") => (RuleGroup::Stable, rules::flake8_builtins::rules::BuiltinArgumentShadowing),
@@ -494,6 +504,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
(Pyupgrade, "038") => (RuleGroup::Stable, rules::pyupgrade::rules::NonPEP604Isinstance),
(Pyupgrade, "039") => (RuleGroup::Stable, rules::pyupgrade::rules::UnnecessaryClassParentheses),
(Pyupgrade, "040") => (RuleGroup::Stable, rules::pyupgrade::rules::NonPEP695TypeAlias),
(Pyupgrade, "041") => (RuleGroup::Preview, rules::pyupgrade::rules::TimeoutErrorAlias),
// pydocstyle
(Pydocstyle, "100") => (RuleGroup::Stable, rules::pydocstyle::rules::UndocumentedPublicModule),
@@ -620,6 +631,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
(Flake8Bandit, "609") => (RuleGroup::Stable, rules::flake8_bandit::rules::UnixCommandWildcardInjection),
(Flake8Bandit, "612") => (RuleGroup::Stable, rules::flake8_bandit::rules::LoggingConfigInsecureListen),
(Flake8Bandit, "701") => (RuleGroup::Stable, rules::flake8_bandit::rules::Jinja2AutoescapeFalse),
(Flake8Bandit, "702") => (RuleGroup::Preview, rules::flake8_bandit::rules::MakoTemplates),
// flake8-boolean-trap
(Flake8BooleanTrap, "001") => (RuleGroup::Stable, rules::flake8_boolean_trap::rules::BooleanTypeHintPositionalArgument),
@@ -853,6 +865,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
(Numpy, "001") => (RuleGroup::Stable, rules::numpy::rules::NumpyDeprecatedTypeAlias),
(Numpy, "002") => (RuleGroup::Stable, rules::numpy::rules::NumpyLegacyRandom),
(Numpy, "003") => (RuleGroup::Stable, rules::numpy::rules::NumpyDeprecatedFunction),
(Numpy, "201") => (RuleGroup::Preview, rules::numpy::rules::Numpy2Deprecation),
// ruff
(Ruff, "001") => (RuleGroup::Stable, rules::ruff::rules::AmbiguousUnicodeCharacterString),
@@ -935,6 +948,8 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
(Refurb, "140") => (RuleGroup::Preview, rules::refurb::rules::ReimplementedStarmap),
(Refurb, "145") => (RuleGroup::Preview, rules::refurb::rules::SliceCopy),
(Refurb, "148") => (RuleGroup::Preview, rules::refurb::rules::UnnecessaryEnumerate),
(Refurb, "168") => (RuleGroup::Preview, rules::refurb::rules::IsinstanceTypeNone),
(Refurb, "169") => (RuleGroup::Preview, rules::refurb::rules::TypeNoneComparison),
(Refurb, "171") => (RuleGroup::Preview, rules::refurb::rules::SingleItemMembershipTest),
(Refurb, "177") => (RuleGroup::Preview, rules::refurb::rules::ImplicitCwd),

View File

@@ -3,7 +3,7 @@
use std::iter::FusedIterator;
use ruff_python_ast::{self as ast, Constant, Expr, Stmt, Suite};
use ruff_python_ast::{self as ast, Stmt, Suite};
use ruff_python_parser::lexer::LexResult;
use ruff_python_parser::Tok;
use ruff_text_size::{Ranged, TextSize};
@@ -69,15 +69,15 @@ struct StringLinesVisitor<'a> {
impl StatementVisitor<'_> for StringLinesVisitor<'_> {
fn visit_stmt(&mut self, stmt: &Stmt) {
if let Stmt::Expr(ast::StmtExpr { value, range: _ }) = stmt {
if let Expr::Constant(ast::ExprConstant {
value: Constant::Str(..),
..
}) = value.as_ref()
{
if let Stmt::Expr(ast::StmtExpr {
value: expr,
range: _,
}) = stmt
{
if expr.is_string_literal_expr() {
for line in UniversalNewlineIterator::with_offset(
self.locator.slice(value.as_ref()),
value.start(),
self.locator.slice(expr.as_ref()),
expr.start(),
) {
self.string_lines.push(line.start());
}

View File

@@ -1,30 +1,23 @@
//! Extract docstrings from an AST.
use ruff_python_ast::{self as ast, Constant, Expr, Stmt};
use ruff_python_ast::{self as ast, Stmt};
use ruff_python_semantic::{Definition, DefinitionId, Definitions, Member, MemberKind};
/// Extract a docstring from a function or class body.
pub(crate) fn docstring_from(suite: &[Stmt]) -> Option<&Expr> {
pub(crate) fn docstring_from(suite: &[Stmt]) -> Option<&ast::ExprStringLiteral> {
let stmt = suite.first()?;
// Require the docstring to be a standalone expression.
let Stmt::Expr(ast::StmtExpr { value, range: _ }) = stmt else {
return None;
};
// Only match strings.
if !matches!(
value.as_ref(),
Expr::Constant(ast::ExprConstant {
value: Constant::Str(_),
..
})
) {
return None;
}
Some(value)
value.as_string_literal_expr()
}
/// Extract a docstring from a `Definition`.
pub(crate) fn extract_docstring<'a>(definition: &'a Definition<'a>) -> Option<&'a Expr> {
pub(crate) fn extract_docstring<'a>(
definition: &'a Definition<'a>,
) -> Option<&'a ast::ExprStringLiteral> {
match definition {
Definition::Module(module) => docstring_from(module.python_ast),
Definition::Member(member) => docstring_from(member.body()),

View File

@@ -1,7 +1,7 @@
use std::fmt::{Debug, Formatter};
use std::ops::Deref;
use ruff_python_ast::Expr;
use ruff_python_ast::ExprStringLiteral;
use ruff_python_semantic::Definition;
use ruff_text_size::{Ranged, TextRange};
@@ -14,7 +14,8 @@ pub(crate) mod styles;
#[derive(Debug)]
pub(crate) struct Docstring<'a> {
pub(crate) definition: &'a Definition<'a>,
pub(crate) expr: &'a Expr,
/// The literal AST node representing the docstring.
pub(crate) expr: &'a ExprStringLiteral,
/// The content of the docstring, including the leading and trailing quotes.
pub(crate) contents: &'a str,
/// The range of the docstring body (without the quotes). The range is relative to [`Self::contents`].

View File

@@ -189,7 +189,7 @@ impl<'a> Insertion<'a> {
Tok::NonLogicalNewline => {}
Tok::Indent => {
// This is like:
// ```py
// ```python
// if True:
// pass
// ```

View File

@@ -209,7 +209,7 @@ impl<'a> Importer<'a> {
// We also add a no-op edit to force conflicts with any other fixes that might try to
// remove the import. Consider:
//
// ```py
// ```python
// import sys
//
// quit()

View File

@@ -8,7 +8,7 @@ use itertools::Itertools;
use log::error;
use rustc_hash::FxHashMap;
use ruff_diagnostics::{Applicability, Diagnostic};
use ruff_diagnostics::Diagnostic;
use ruff_python_ast::imports::ImportMap;
use ruff_python_ast::PySourceType;
use ruff_python_codegen::Stylist;
@@ -268,24 +268,13 @@ pub fn check_path(
}
// Update fix applicability to account for overrides
if !settings.extend_safe_fixes.is_empty() || !settings.extend_unsafe_fixes.is_empty() {
if !settings.fix_safety.is_empty() {
for diagnostic in &mut diagnostics {
if let Some(fix) = diagnostic.fix.take() {
// Enforce demotions over promotions so if someone puts a rule in both we are conservative
if fix.applicability().is_safe()
&& settings
.extend_unsafe_fixes
.contains(diagnostic.kind.rule())
{
diagnostic.set_fix(fix.with_applicability(Applicability::Unsafe));
} else if fix.applicability().is_unsafe()
&& settings.extend_safe_fixes.contains(diagnostic.kind.rule())
{
diagnostic.set_fix(fix.with_applicability(Applicability::Safe));
} else {
// Retain the existing fix (will be dropped from `.take()` otherwise)
diagnostic.set_fix(fix);
}
let fixed_applicability = settings
.fix_safety
.resolve_applicability(diagnostic.kind.rule(), fix.applicability());
diagnostic.set_fix(fix.with_applicability(fixed_applicability));
}
}
}

View File

@@ -54,9 +54,9 @@ impl Display for Diff<'_> {
let message = match self.fix.applicability() {
// TODO(zanieb): Adjust this messaging once it's user-facing
Applicability::Safe => "Fix",
Applicability::Unsafe => "Suggested fix",
Applicability::Display => "Possible fix",
Applicability::Safe => "Safe fix",
Applicability::Unsafe => "Unsafe fix",
Applicability::DisplayOnly => "Display-only fix",
};
writeln!(f, " {}", message.blue())?;

View File

@@ -1,8 +1,7 @@
use colored::Colorize;
use log::warn;
use pyproject_toml::{BuildSystem, Project};
use pyproject_toml::PyProjectToml;
use ruff_text_size::{TextRange, TextSize};
use serde::{Deserialize, Serialize};
use ruff_diagnostics::Diagnostic;
use ruff_source_file::SourceFile;
@@ -13,16 +12,6 @@ use crate::rules::ruff::rules::InvalidPyprojectToml;
use crate::settings::LinterSettings;
use crate::IOError;
/// Unlike [`pyproject_toml::PyProjectToml`], in our case `build_system` is also optional
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
#[serde(rename_all = "kebab-case")]
struct PyProjectToml {
/// Build-related data
build_system: Option<BuildSystem>,
/// Project metadata
project: Option<Project>,
}
pub fn lint_pyproject_toml(source_file: SourceFile, settings: &LinterSettings) -> Vec<Message> {
let Some(err) = toml::from_str::<PyProjectToml>(source_file.source_text()).err() else {
return Vec::default();

View File

@@ -64,6 +64,9 @@ pub enum Linter {
/// [flake8-async](https://pypi.org/project/flake8-async/)
#[prefix = "ASYNC"]
Flake8Async,
/// [flake8-trio](https://pypi.org/project/flake8-trio/)
#[prefix = "TRIO"]
Flake8Trio,
/// [flake8-bandit](https://pypi.org/project/flake8-bandit/)
#[prefix = "S"]
Flake8Bandit,

View File

@@ -1,7 +1,6 @@
use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast as ast;
use ruff_python_ast::Constant;
use ruff_python_ast::Expr;
use ruff_text_size::Ranged;
@@ -79,13 +78,7 @@ pub(crate) fn variable_name_task_id(
let keyword = arguments.find_keyword("task_id")?;
// If the keyword argument is not a string, we can't do anything.
let task_id = match &keyword.value {
Expr::Constant(constant) => match &constant.value {
Constant::Str(ast::StringConstant { value, .. }) => value,
_ => return None,
},
_ => return None,
};
let ast::ExprStringLiteral { value: task_id, .. } = keyword.value.as_string_literal_expr()?;
// If the target name is the same as the task_id, no violation.
if id == task_id {

View File

@@ -163,11 +163,15 @@ mod tests {
"# ( user_content_type , _ )= TimelineEvent.objects.using(db_alias).get_or_create(",
&[]
));
assert!(comment_contains_code(
assert!(comment_contains_code("# )", &[]));
// This used to return true, but our parser has gotten a bit better
// at rejecting invalid Python syntax. And indeed, this is not valid
// Python code.
assert!(!comment_contains_code(
"# app_label=\"core\", model=\"user\"",
&[]
));
assert!(comment_contains_code("# )", &[]));
// TODO(charlie): This should be `true` under aggressive mode.
assert!(!comment_contains_code("#def foo():", &[]));

View File

@@ -66,7 +66,7 @@ pub(crate) fn commented_out_code(
if is_standalone_comment(line) && comment_contains_code(line, &settings.task_tags[..]) {
let mut diagnostic = Diagnostic::new(CommentedOutCode, *range);
diagnostic.set_fix(Fix::display_edit(Edit::range_deletion(
diagnostic.set_fix(Fix::display_only_edit(Edit::range_deletion(
locator.full_lines_range(*range),
)));
diagnostics.push(diagnostic);

View File

@@ -10,7 +10,7 @@ ERA001.py:1:1: ERA001 Found commented-out code
|
= help: Remove commented-out code
Possible fix
Display-only fix
1 |-#import os
2 1 | # from foo import junk
3 2 | #a = 3
@@ -26,7 +26,7 @@ ERA001.py:2:1: ERA001 Found commented-out code
|
= help: Remove commented-out code
Possible fix
Display-only fix
1 1 | #import os
2 |-# from foo import junk
3 2 | #a = 3
@@ -44,7 +44,7 @@ ERA001.py:3:1: ERA001 Found commented-out code
|
= help: Remove commented-out code
Possible fix
Display-only fix
1 1 | #import os
2 2 | # from foo import junk
3 |-#a = 3
@@ -63,7 +63,7 @@ ERA001.py:5:1: ERA001 Found commented-out code
|
= help: Remove commented-out code
Possible fix
Display-only fix
2 2 | # from foo import junk
3 3 | #a = 3
4 4 | a = 4
@@ -82,7 +82,7 @@ ERA001.py:13:5: ERA001 Found commented-out code
|
= help: Remove commented-out code
Possible fix
Display-only fix
10 10 |
11 11 | # This is a real comment.
12 12 | # # This is a (nested) comment.
@@ -100,7 +100,7 @@ ERA001.py:21:5: ERA001 Found commented-out code
|
= help: Remove commented-out code
Possible fix
Display-only fix
18 18 |
19 19 | class A():
20 20 | pass
@@ -120,7 +120,7 @@ ERA001.py:26:5: ERA001 Found commented-out code
|
= help: Remove commented-out code
Possible fix
Display-only fix
23 23 |
24 24 | dictionary = {
25 25 | # "key1": 123, # noqa: ERA001
@@ -139,7 +139,7 @@ ERA001.py:27:5: ERA001 Found commented-out code
|
= help: Remove commented-out code
Possible fix
Display-only fix
24 24 | dictionary = {
25 25 | # "key1": 123, # noqa: ERA001
26 26 | # "key2": 456,

View File

@@ -1,6 +1,6 @@
use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::{self as ast, CmpOp, Constant, Expr};
use ruff_python_ast::{self as ast, CmpOp, Expr};
use ruff_text_size::Ranged;
use crate::checkers::ast::Checker;
@@ -230,16 +230,16 @@ pub(crate) fn compare(checker: &mut Checker, left: &Expr, ops: &[CmpOp], compara
Expr::Subscript(ast::ExprSubscript { value, slice, .. })
if is_sys(value, "version_info", checker.semantic()) =>
{
if let Expr::Constant(ast::ExprConstant {
value: Constant::Int(i),
if let Expr::NumberLiteral(ast::ExprNumberLiteral {
value: ast::Number::Int(i),
..
}) = slice.as_ref()
{
if *i == 0 {
if let (
[CmpOp::Eq | CmpOp::NotEq],
[Expr::Constant(ast::ExprConstant {
value: Constant::Int(n),
[Expr::NumberLiteral(ast::ExprNumberLiteral {
value: ast::Number::Int(n),
..
})],
) = (ops, comparators)
@@ -253,8 +253,8 @@ pub(crate) fn compare(checker: &mut Checker, left: &Expr, ops: &[CmpOp], compara
} else if *i == 1 {
if let (
[CmpOp::Lt | CmpOp::LtE | CmpOp::Gt | CmpOp::GtE],
[Expr::Constant(ast::ExprConstant {
value: Constant::Int(_),
[Expr::NumberLiteral(ast::ExprNumberLiteral {
value: ast::Number::Int(_),
..
})],
) = (ops, comparators)
@@ -274,8 +274,8 @@ pub(crate) fn compare(checker: &mut Checker, left: &Expr, ops: &[CmpOp], compara
{
if let (
[CmpOp::Lt | CmpOp::LtE | CmpOp::Gt | CmpOp::GtE],
[Expr::Constant(ast::ExprConstant {
value: Constant::Int(_),
[Expr::NumberLiteral(ast::ExprNumberLiteral {
value: ast::Number::Int(_),
..
})],
) = (ops, comparators)
@@ -294,13 +294,10 @@ pub(crate) fn compare(checker: &mut Checker, left: &Expr, ops: &[CmpOp], compara
if is_sys(left, "version", checker.semantic()) {
if let (
[CmpOp::Lt | CmpOp::LtE | CmpOp::Gt | CmpOp::GtE],
[Expr::Constant(ast::ExprConstant {
value: Constant::Str(s),
..
})],
[Expr::StringLiteral(ast::ExprStringLiteral { value, .. })],
) = (ops, comparators)
{
if s.len() == 1 {
if value.len() == 1 {
if checker.enabled(Rule::SysVersionCmpStr10) {
checker
.diagnostics

View File

@@ -1,6 +1,6 @@
use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::{self as ast, Constant, Expr};
use ruff_python_ast::{self as ast, Expr};
use ruff_text_size::Ranged;
use crate::checkers::ast::Checker;
@@ -177,8 +177,8 @@ pub(crate) fn subscript(checker: &mut Checker, value: &Expr, slice: &Expr) {
step: None,
range: _,
}) => {
if let Expr::Constant(ast::ExprConstant {
value: Constant::Int(i),
if let Expr::NumberLiteral(ast::ExprNumberLiteral {
value: ast::Number::Int(i),
..
}) = upper.as_ref()
{
@@ -194,8 +194,8 @@ pub(crate) fn subscript(checker: &mut Checker, value: &Expr, slice: &Expr) {
}
}
Expr::Constant(ast::ExprConstant {
value: Constant::Int(i),
Expr::NumberLiteral(ast::ExprNumberLiteral {
value: ast::Number::Int(i),
..
}) => {
if *i == 2 && checker.enabled(Rule::SysVersion2) {

View File

@@ -3,7 +3,7 @@ use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::helpers::ReturnStatementVisitor;
use ruff_python_ast::identifier::Identifier;
use ruff_python_ast::statement_visitor::StatementVisitor;
use ruff_python_ast::{self as ast, Constant, Expr, ParameterWithDefault, Stmt};
use ruff_python_ast::{self as ast, Expr, ParameterWithDefault, Stmt};
use ruff_python_parser::typing::parse_type_annotation;
use ruff_python_semantic::analyze::visibility;
use ruff_python_semantic::Definition;
@@ -265,7 +265,7 @@ impl Violation for MissingReturnTypePrivateFunction {
/// or `ruff.toml` file:
///
/// ```toml
/// [tool.ruff.flake8-annotations]
/// [tool.ruff.lint.flake8-annotations]
/// mypy-init-return = true
/// ```
///
@@ -431,10 +431,7 @@ fn is_none_returning(body: &[Stmt]) -> bool {
visitor.visit_body(body);
for stmt in visitor.returns {
if let Some(value) = stmt.value.as_deref() {
if !matches!(
value,
Expr::Constant(constant) if constant.value.is_none()
) {
if !value.is_none_literal_expr() {
return false;
}
}
@@ -451,9 +448,10 @@ fn check_dynamically_typed<F>(
) where
F: FnOnce() -> String,
{
if let Expr::Constant(ast::ExprConstant {
if let Expr::StringLiteral(ast::ExprStringLiteral {
range,
value: Constant::Str(string),
value: string,
..
}) = annotation
{
// Quoted annotations

View File

@@ -252,7 +252,7 @@ annotation_presence.py:159:9: ANN204 [*] Missing return type annotation for spec
|
= help: Add `None` return type
Suggested fix
Unsafe fix
156 156 |
157 157 | class Foo:
158 158 | @decorator()
@@ -272,7 +272,7 @@ annotation_presence.py:165:9: ANN204 [*] Missing return type annotation for spec
|
= help: Add `None` return type
Suggested fix
Unsafe fix
162 162 |
163 163 | # Regression test for: https://github.com/astral-sh/ruff/issues/7711
164 164 | class Class:

View File

@@ -11,7 +11,7 @@ mypy_init_return.py:5:9: ANN204 [*] Missing return type annotation for special m
|
= help: Add `None` return type
Suggested fix
Unsafe fix
2 2 |
3 3 | # Error
4 4 | class Foo:
@@ -31,7 +31,7 @@ mypy_init_return.py:11:9: ANN204 [*] Missing return type annotation for special
|
= help: Add `None` return type
Suggested fix
Unsafe fix
8 8 |
9 9 | # Error
10 10 | class Foo:
@@ -59,7 +59,7 @@ mypy_init_return.py:47:9: ANN204 [*] Missing return type annotation for special
|
= help: Add `None` return type
Suggested fix
Unsafe fix
44 44 | # Error used to be ok for a moment since the mere presence
45 45 | # of a vararg falsely indicated that the function has a typed argument.
46 46 | class Foo:

View File

@@ -10,7 +10,7 @@ simple_magic_methods.py:2:9: ANN204 [*] Missing return type annotation for speci
|
= help: Add `None` return type
Suggested fix
Unsafe fix
1 1 | class Foo:
2 |- def __str__(self):
2 |+ def __str__(self) -> str:
@@ -28,7 +28,7 @@ simple_magic_methods.py:5:9: ANN204 [*] Missing return type annotation for speci
|
= help: Add `None` return type
Suggested fix
Unsafe fix
2 2 | def __str__(self):
3 3 | ...
4 4 |
@@ -48,7 +48,7 @@ simple_magic_methods.py:8:9: ANN204 [*] Missing return type annotation for speci
|
= help: Add `None` return type
Suggested fix
Unsafe fix
5 5 | def __repr__(self):
6 6 | ...
7 7 |
@@ -68,7 +68,7 @@ simple_magic_methods.py:11:9: ANN204 [*] Missing return type annotation for spec
|
= help: Add `None` return type
Suggested fix
Unsafe fix
8 8 | def __len__(self):
9 9 | ...
10 10 |
@@ -88,7 +88,7 @@ simple_magic_methods.py:14:9: ANN204 [*] Missing return type annotation for spec
|
= help: Add `None` return type
Suggested fix
Unsafe fix
11 11 | def __length_hint__(self):
12 12 | ...
13 13 |
@@ -108,7 +108,7 @@ simple_magic_methods.py:17:9: ANN204 [*] Missing return type annotation for spec
|
= help: Add `None` return type
Suggested fix
Unsafe fix
14 14 | def __init__(self):
15 15 | ...
16 16 |
@@ -128,7 +128,7 @@ simple_magic_methods.py:20:9: ANN204 [*] Missing return type annotation for spec
|
= help: Add `None` return type
Suggested fix
Unsafe fix
17 17 | def __del__(self):
18 18 | ...
19 19 |
@@ -148,7 +148,7 @@ simple_magic_methods.py:23:9: ANN204 [*] Missing return type annotation for spec
|
= help: Add `None` return type
Suggested fix
Unsafe fix
20 20 | def __bool__(self):
21 21 | ...
22 22 |
@@ -168,7 +168,7 @@ simple_magic_methods.py:26:9: ANN204 [*] Missing return type annotation for spec
|
= help: Add `None` return type
Suggested fix
Unsafe fix
23 23 | def __bytes__(self):
24 24 | ...
25 25 |
@@ -188,7 +188,7 @@ simple_magic_methods.py:29:9: ANN204 [*] Missing return type annotation for spec
|
= help: Add `None` return type
Suggested fix
Unsafe fix
26 26 | def __format__(self, format_spec):
27 27 | ...
28 28 |
@@ -208,7 +208,7 @@ simple_magic_methods.py:32:9: ANN204 [*] Missing return type annotation for spec
|
= help: Add `None` return type
Suggested fix
Unsafe fix
29 29 | def __contains__(self, item):
30 30 | ...
31 31 |
@@ -228,7 +228,7 @@ simple_magic_methods.py:35:9: ANN204 [*] Missing return type annotation for spec
|
= help: Add `None` return type
Suggested fix
Unsafe fix
32 32 | def __complex__(self):
33 33 | ...
34 34 |
@@ -248,7 +248,7 @@ simple_magic_methods.py:38:9: ANN204 [*] Missing return type annotation for spec
|
= help: Add `None` return type
Suggested fix
Unsafe fix
35 35 | def __int__(self):
36 36 | ...
37 37 |
@@ -268,7 +268,7 @@ simple_magic_methods.py:41:9: ANN204 [*] Missing return type annotation for spec
|
= help: Add `None` return type
Suggested fix
Unsafe fix
38 38 | def __float__(self):
39 39 | ...
40 40 |

View File

@@ -1,6 +1,6 @@
use once_cell::sync::Lazy;
use regex::Regex;
use ruff_python_ast::{self as ast, Constant, Expr};
use ruff_python_ast::{self as ast, Expr};
use ruff_python_semantic::SemanticModel;
@@ -10,10 +10,7 @@ static PASSWORD_CANDIDATE_REGEX: Lazy<Regex> = Lazy::new(|| {
pub(super) fn string_literal(expr: &Expr) -> Option<&str> {
match expr {
Expr::Constant(ast::ExprConstant {
value: Constant::Str(string),
..
}) => Some(string),
Expr::StringLiteral(ast::ExprStringLiteral { value, .. }) => Some(value),
_ => None,
}
}

View File

@@ -28,6 +28,7 @@ mod tests {
#[test_case(Rule::HardcodedTempFile, Path::new("S108.py"))]
#[test_case(Rule::HashlibInsecureHashFunction, Path::new("S324.py"))]
#[test_case(Rule::Jinja2AutoescapeFalse, Path::new("S701.py"))]
#[test_case(Rule::MakoTemplates, Path::new("S702.py"))]
#[test_case(Rule::LoggingConfigInsecureListen, Path::new("S612.py"))]
#[test_case(Rule::ParamikoCall, Path::new("S601.py"))]
#[test_case(Rule::RequestWithNoCertValidation, Path::new("S501.py"))]

View File

@@ -3,7 +3,7 @@ use anyhow::Result;
use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::call_path::CallPath;
use ruff_python_ast::{self as ast, Constant, Expr, Operator};
use ruff_python_ast::{self as ast, Expr, Operator};
use ruff_python_semantic::SemanticModel;
use ruff_text_size::Ranged;
@@ -144,8 +144,8 @@ fn py_stat(call_path: &CallPath) -> Option<u16> {
/// an integer value, but that value is out of range.
fn parse_mask(expr: &Expr, semantic: &SemanticModel) -> Result<Option<u16>> {
match expr {
Expr::Constant(ast::ExprConstant {
value: Constant::Int(int),
Expr::NumberLiteral(ast::ExprNumberLiteral {
value: ast::Number::Int(int),
..
}) => match int.as_u16() {
Some(value) => Ok(Some(value)),

View File

@@ -1,7 +1,6 @@
use ruff_text_size::TextRange;
use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::ExprStringLiteral;
/// ## What it does
/// Checks for hardcoded bindings to all network interfaces (`0.0.0.0`).
@@ -35,9 +34,9 @@ impl Violation for HardcodedBindAllInterfaces {
}
/// S104
pub(crate) fn hardcoded_bind_all_interfaces(value: &str, range: TextRange) -> Option<Diagnostic> {
if value == "0.0.0.0" {
Some(Diagnostic::new(HardcodedBindAllInterfaces, range))
pub(crate) fn hardcoded_bind_all_interfaces(string: &ExprStringLiteral) -> Option<Diagnostic> {
if string.value == "0.0.0.0" {
Some(Diagnostic::new(HardcodedBindAllInterfaces, string.range))
} else {
None
}

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