Compare commits

...

129 Commits

Author SHA1 Message Date
Charlie Marsh
7d4f0a8320 Bump Ruff version to 0.0.241 2023-02-03 19:25:12 -05:00
Charlie Marsh
4149bc7be8 Ignore direct source-children in implicit-namespace-package (#2560) 2023-02-03 19:20:27 -05:00
Pierre Sassoulas
e6316b185e [pylint] Rename 'too-many-args' to 'too-many-arguments'
The actual name
2023-02-03 18:58:32 -05:00
Pierre Sassoulas
a2183be96e [pylint] Rename constant-comparison to comparison-of-constant
The actual name
2023-02-03 18:58:32 -05:00
Pierre Sassoulas
df39a95925 [pylint] Rename use-sys-exit to consider-using-sys-exit
The actual name
2023-02-03 18:58:32 -05:00
Charlie Marsh
ff859ead85 Remove unused misplaced_comparison_constant.rs file 2023-02-03 17:10:48 -05:00
Charlie Marsh
b2be30cb07 Mark fixable issues in printer output (#2500) 2023-02-03 16:26:06 -05:00
Charlie Marsh
b9c1a3c5c1 Move benchmarking instructions to CONTRIBUTING.md (#2554) 2023-02-03 14:53:53 -05:00
Charlie Marsh
9751951d10 Allow F811 noqa declarations on containing import lines (#2553) 2023-02-03 14:51:06 -05:00
Charlie Marsh
64c79bde83 Mark sometimes-fixable rules as Availability::Sometimes (#2552) 2023-02-03 14:42:10 -05:00
Charlie Marsh
da0374f360 Update RustPython to pull in lexer optimizations (#2551) 2023-02-03 14:31:53 -05:00
Charlie Marsh
c26b58ba28 Hide globset logging even with --verbose 2023-02-03 13:31:04 -05:00
Charlie Marsh
56f935640a Avoid hang when detecting trailing comments (#2549) 2023-02-03 13:05:55 -05:00
Charlie Marsh
85ca6cde49 Fix a few nursery rule violations (#2548) 2023-02-03 11:59:29 -05:00
Aarni Koskela
38addbe50d Soft-deprecate update_check (#2530) 2023-02-03 11:33:38 -05:00
Charlie Marsh
924e35b1c3 Add print_stdout and print_stderr to Clippy enforcement (#2542) 2023-02-03 11:13:44 -05:00
Charlie Marsh
d05ea4dbac Exit upon showing files with --show-files (#2543) 2023-02-03 09:41:09 -05:00
Charlie Marsh
b5ac93d2ee Move Clippy configuration to config.toml (#2541) 2023-02-03 09:26:36 -05:00
Aarni Koskela
924e264156 Move flake8-{errmsg,print} violations (#2536) 2023-02-03 09:03:49 -05:00
Aarni Koskela
14c5000ad5 Move McCabe violations (#2534) 2023-02-03 08:41:11 -05:00
Aarni Koskela
d985473f4f Move pygrep-hooks violations (#2539) 2023-02-03 08:41:05 -05:00
Aarni Koskela
47e0b2521a Move flake8-2020 violations (#2537) 2023-02-03 08:40:56 -05:00
Aarni Koskela
a319980a7c Move pep8-naming violations (#2538) 2023-02-03 08:40:48 -05:00
Aarni Koskela
3336dd63f4 Move flake8-implicit-str-concat violations (#2535) 2023-02-03 08:24:06 -05:00
Aarni Koskela
ae20a721a1 Don't walk past project root when figuring out exclusion (#2471) 2023-02-03 08:23:51 -05:00
Aarni Koskela
a26b1f43e9 Move flake8-unused-arguments violations (#2533) 2023-02-03 08:19:18 -05:00
Chris Chan
139a6d8331 Minor fixes to PLR0915 logic (#2518) 2023-02-03 08:10:59 -05:00
Jacob Coffee
04ef674195 Add Jetbrains Webinar Event (Temporary) (#2516) 2023-02-03 08:08:27 -05:00
Aarni Koskela
db852a0b11 Move ruff violations (#2526) 2023-02-03 07:43:39 -05:00
Aarni Koskela
87c3b0e4e2 Move pydocstyle violations (#2524) 2023-02-03 07:42:52 -05:00
Aarni Koskela
82784a7607 Move flake8-debugger violation (#2522) 2023-02-03 07:40:53 -05:00
Aarni Koskela
f2da855048 Move flake8-datetimez violations (#2528) 2023-02-03 07:40:00 -05:00
Aarni Koskela
81b60cf9fe Move flake8-bandit violations (#2525) 2023-02-03 07:39:49 -05:00
Maksudul Haque
c96ba6dec4 [flake8-self] Fix False Negative Issue on Rule SLF001 (#2527) 2023-02-03 07:39:24 -05:00
Martin Fischer
0f8f250bea refactor: Simplify UpstreamCategory
There's no need to hardcode the prefix string since
it can be derived from the RuleCodePrefix.
2023-02-02 23:46:32 -05:00
Martin Fischer
a3ffaa5d9b refactor: Rename LinterCategory to UpstreamCategory
LinterCategory was somewhat misnamed since it's not actually
a category for linters but rather a category for upstream lints.
Since we want to introduce our own categories, naming the type
UpstreamCategory is more clear.
2023-02-02 23:46:32 -05:00
Martin Fischer
187ed874e9 refactor: Make Rule::from_code return Rule instead of &'static Rule 2023-02-02 23:46:32 -05:00
Charlie Marsh
a30c77e752 Mark --add-noqa as incompatible with --fix (#2513) 2023-02-02 23:43:05 -05:00
Florian Best
7e9b9cc7b3 feat: add autofix for PLR0402 (#2504) 2023-02-02 23:25:16 -05:00
Charlie Marsh
d4cef9305a Track overridden bindings within each scope (#2511) 2023-02-02 22:31:46 -05:00
Charlie Marsh
a074625121 Avoid renaming unused loop variables with deferred usages (#2509) 2023-02-02 20:59:47 -05:00
Charlie Marsh
9c55ab35df Change LogLevel comments to docs 2023-02-02 20:23:10 -05:00
Charlie Marsh
a95474f2b1 Use a copy-on-write to avoid extra contents clone (#2508) 2023-02-02 20:19:16 -05:00
Víctor
3e6fe46bc4 Add number of files processed in debug info (-v) (#2506) 2023-02-02 20:19:00 -05:00
Charlie Marsh
bc81cea4f4 Notify user if autofix introduces syntax error (#2507) 2023-02-02 20:02:09 -05:00
Charlie Marsh
cb0f226962 Always report parse errors back to the user (#2505) 2023-02-02 19:12:17 -05:00
Charlie Marsh
fa56fabed9 Remove a result wrapper from linter.rs (#2503) 2023-02-02 18:47:45 -05:00
Jonathan Plasse
bdcab87d2f Add markdownlint and dev Ruff to pre-commit (#2303) 2023-02-02 16:29:07 -05:00
Jonathan Plasse
ec8b827d26 Add known-standard-library for each Python version (#2491) 2023-02-02 16:22:47 -05:00
Jonathan Plasse
b232c43824 Fix an error in scripts/add_rule.py (#2497) 2023-02-02 15:58:11 -05:00
Charlie Marsh
ee01e666c5 Allow list() and tuple() calls in __all__ assignments (#2499) 2023-02-02 15:45:14 -05:00
Jonathan Plasse
2b0de8ccd9 Fix clippy error (#2498) 2023-02-02 15:38:18 -05:00
Aarni Koskela
739c57b31b Move flake8-annotations violations to rules file (#2496) 2023-02-02 15:17:54 -05:00
Aarni Koskela
c3e0137f22 Move flake8-return violations to rules module (#2492) 2023-02-02 15:13:49 -05:00
Aarni Koskela
77716108af Move flake8-simplify violations to rule modules (#2495) 2023-02-02 15:13:16 -05:00
Jonathan Plasse
335395adec Mirror CI clippy command for pre-commit hook (#2494) 2023-02-02 14:59:19 -05:00
Aarni Koskela
65f8f1a6f7 Move pylint violations to rule modules (#2489) 2023-02-02 14:47:58 -05:00
Aarni Koskela
858af8debb Move pyupgrade violations to rule modules (#2490) 2023-02-02 14:47:43 -05:00
Aarni Koskela
5f1bbf0b6b Move pycodestyle violations to rule modules (#2483) 2023-02-02 14:29:23 -05:00
Aarni Koskela
40cb905ae5 Move pyflakes violations to rule modules (#2488) 2023-02-02 14:00:59 -05:00
Jonathan Plasse
e89b4a5de5 Fix hardcoded url in transform_readme.py (#2487) 2023-02-02 13:59:22 -05:00
Charlie Marsh
651f6b6bce Bump Ruff version to 0.0.240 2023-02-02 12:45:23 -05:00
Charlie Marsh
d3c3198b24 Fix versions in BREAKING_CHANGES.md 2023-02-02 12:45:20 -05:00
Charlie Marsh
ec6054edce Treat if 0: and if False: as type-checking blocks (#2485) 2023-02-02 12:35:59 -05:00
Charlie Marsh
a0df78cb7d Visit NamedExpr values before targets (#2484) 2023-02-02 12:21:58 -05:00
Aarni Koskela
ac41c33d1f Move flake8-blind-except violation to rule module (#2479) 2023-02-02 12:21:25 -05:00
Aarni Koskela
b4b8782243 Move remaining flake8-pytest-style violations to rule modules (#2482) 2023-02-02 12:10:49 -05:00
Florian Best
8e53a4d1d3 fix: assertTrue()/assertFalse() fixer should not test for identity (#2476) 2023-02-02 11:24:35 -05:00
Charlie Marsh
668860cba3 Add more information to Pylint FAQ section 2023-02-02 11:08:17 -05:00
Aarni Koskela
038e8cfba0 Move flake8-quotes violations to rules module (#2475) 2023-02-02 10:08:12 -05:00
Aarni Koskela
ebfa55cea3 Move flake8-builtins violations to rules file (#2478) 2023-02-02 10:03:09 -05:00
Aarni Koskela
aa0fc0f9c2 Move flake8-bugbear violations to rule modules (#2474) 2023-02-02 09:34:26 -05:00
Aarni Koskela
aa85c81280 Move flake8-comprehensions violations to rule files (#2477) 2023-02-02 09:26:50 -05:00
Charlie Marsh
f5fd6f59ea Remove extraneous test file 2023-02-02 08:46:03 -05:00
Martin Fischer
540e31f5f4 Carry-over ignore to next config layer if select = [] (#2467)
Resolves #2461.
2023-02-02 08:45:07 -05:00
Chris Chan
8136cc9238 Implement pylint's too-many-statements rule (PLR0915) (#2445) 2023-02-02 08:18:37 -05:00
Charlie Marsh
2c71535016 Update snapshots 2023-02-02 08:15:33 -05:00
Aarni Koskela
cce8fb9882 isort: support forced_separate (#2268) 2023-02-02 08:08:02 -05:00
Maksudul Haque
9e59c99133 [flake8-self] Add Plugin and Rule SLF001 (#2470) 2023-02-02 07:58:14 -05:00
Colin Delahunty
b032f50775 [pyupgrade]: Remove outdated sys.version_info blocks (#2099) 2023-02-02 07:49:24 -05:00
Charlie Marsh
1c2fc38853 Use LibCST to reverse Yoda conditions (#2468)
Our existing solution was having trouble with parenthesized expressions. This actually may affect more than `SIM300`, but let's address them as they come up.

Closes #2466.
2023-02-02 00:07:43 -05:00
Charlie Marsh
f16f3a4a03 Avoid removing un-selected codes when applying --add-noqa edits (#2465)
The downside here is that we have to leave blank `# noqa` directives intact. Otherwise, we risk removing necessary `# noqa` coverage for rules that aren't selected.

Closes #2254.
2023-02-01 22:22:31 -05:00
Charlie Marsh
30a09ec211 Respect parent noqa in --add-noqa (#2464) 2023-02-01 21:58:01 -05:00
Reid Swan
ec7b25290b feat: Add isort option lines-after-imports (#2440)
Fixes https://github.com/charliermarsh/ruff/issues/2243

Adds support for the isort option [lines_after_imports](https://pycqa.github.io/isort/docs/configuration/options.html#lines-after-imports) to insert blank lines between imports and the follow up code.
2023-02-01 21:39:45 -05:00
Charlie Marsh
68422d4ff2 Allow non-ruff.toml-named files for --config (#2463)
Previously, if you passed in a file on the command-line via `--config`, it had to be named either `pyproject.toml` or `ruff.toml` -- otherwise, we errored. I think this is too strict. `pyproject.toml` is a special name in the ecosystem, so we should require _that_; but otherwise, let's just assume it's in `ruff.toml` format.

As an alternative, we could add a `--pyproject` argument for `pyproject.toml`, and assume anything passed to `--config` is in `ruff.toml` format. But that _would_ be a breaking change and is arguably more confusing. (This isn't a breaking change, since it only loosens the CLI.)

Closes #2462.
2023-02-01 21:35:42 -05:00
Charlie Marsh
2abaffd65b Improve consistency of backticks for plugin names (#2460) 2023-02-01 19:17:32 -05:00
Charlie Marsh
06cbf5a2ae Add some top-level links to the README (#2458) 2023-02-01 19:10:41 -05:00
Charlie Marsh
f432ce291a Add Fathom to docs 2023-02-01 18:41:24 -05:00
Charlie Marsh
1eb331143d Add Fathom to playground 2023-02-01 18:30:40 -05:00
Henry Schreiner
db1b1672b8 fix: minor spacing typo in message for PTH123 (#2453) 2023-02-01 14:39:50 -05:00
Charlie Marsh
6861e59103 Only avoid PEP604 rewrites for pre-Python 3.10 code (#2449)
I moved the `self.in_annotation` guard out of the version check in #1563. But, I think that was a mistake. It was done to resolve #1560, but the fix in that case _should've_ been to set a different Python version.

Closes #2447.
2023-02-01 13:03:51 -05:00
Charlie Marsh
778c644ee3 Trigger, but don't fix, SIM rules if comments are present (#2450) 2023-02-01 12:56:02 -05:00
Martin Fischer
e66a6b6d05 refactor: Define ruff_dev::ROOT_DIR 2023-02-01 09:17:53 -05:00
Martin Fischer
faea478ca5 fix: failing snapshot test on Windows 2023-02-01 09:17:53 -05:00
Martin Fischer
39b5fa0e24 refactor: Make test_path prefix the fixture path 2023-02-01 09:17:53 -05:00
Martin Fischer
df413d1ece refactor: Introduce test_resource_path helper 2023-02-01 09:17:53 -05:00
Martin Fischer
cfd0693ae5 refactor: Document internal test_path function 2023-02-01 09:17:53 -05:00
Martin Fischer
56ad160c05 refactor: Move test_path helper to new test module 2023-02-01 09:17:53 -05:00
Florian Best
9d8c6ba671 more builtin name checks when autofixing (#2430) 2023-02-01 08:16:47 -05:00
Charlie Marsh
1ea88ea56b Avoid iterating over body twice (#2439) 2023-02-01 08:12:36 -05:00
Florian Best
7f44ffb55c docs(CONTRIBUTING): add instructions how to update the test snapshots (#2412) 2023-02-01 07:44:20 -05:00
Charlie Marsh
dbd640d90f Remove unused Cargo.lock file (#2437) 2023-02-01 07:33:59 -05:00
Aarni Koskela
e5082c7d6c isort: split up package (#2434) 2023-02-01 07:17:31 -05:00
Charlie Marsh
841d176289 Move super-args and unnecessary-coding-comment into their own modules (#2432) 2023-01-31 22:26:56 -05:00
Charlie Marsh
c15595325c Bump version to 0.0.239 2023-01-31 19:06:22 -05:00
Florian Best
e97b1a4280 fix: ignore fix if "bool" is not builtin (#2429) 2023-01-31 19:03:46 -05:00
Florian Best
82ec884a61 feat: let SIM210 return expressions without bool() wrapping (#2410) (#2426) 2023-01-31 18:25:22 -05:00
Maksudul Haque
7c1a6bce7b [flake8-raise] Add Plugin and RSE102 Rule (#2354) 2023-01-31 18:09:40 -05:00
Charlie Marsh
84a8b628b8 Avoid implicit-namespace-package checks for .pyi files (#2420) 2023-01-31 17:35:30 -05:00
Charlie Marsh
142b627bb8 Avoid Bandit false-positives for empty-string-as-password (#2421) 2023-01-31 16:56:03 -05:00
Charlie Marsh
fbf231e1b8 Allow implicit multiline strings with internal quotes to use non-preferred quote (#2416)
As an example, if you have `single` as your preferred style, we'll now allow this:

```py
assert s.to_python(123) == (
    "123 info=SerializationInfo(include=None, exclude=None, mode='python', by_alias=True, exclude_unset=False, "
    "exclude_defaults=False, exclude_none=False, round_trip=False)"
)
```

Previously, the second line of the implicit string concatenation would be flagged as invalid, despite the _first_ line requiring double quotes. (Note that we'll accept either single or double quotes for that second line.)

Mechanically, this required that we process sequences of `Tok::String` rather than a single `Tok::String` at a time. Prior to iterating over the strings in the sequence, we check if any of them require the non-preferred quote style; if so, we let _any_ of them use it.

Closes #2400.
2023-01-31 16:27:15 -05:00
Florian Best
1dd9ccf7f6 feat: let SIM103 return expressions without bool() wrapping (#2410) 2023-01-31 16:11:44 -05:00
Charlie Marsh
d601abe01b Rename flake8-quotes snapshots and tests (#2415) 2023-01-31 16:08:00 -05:00
Charlie Marsh
15d4774b6b Avoid flagging same-condition cases in SIM103 (#2404) 2023-01-31 12:45:51 -05:00
Charlie Marsh
293c7e00d5 Include method name in B027 message (#2403) 2023-01-31 12:41:22 -05:00
Thomas M Kehrenberg
c3a3195922 Fix option name "max-args" in the documentation (#2401) 2023-01-31 12:30:05 -05:00
Martin Fischer
39d98d3488 Disable panic hook about reporting issues for debug builds
In order to avoid confusing new developers.  When a debug build panics
chances are that the panic is caused by local changes and should in
fact not be reported on GitHub.
2023-01-31 12:24:26 -05:00
Charlie Marsh
cd3d82213a Handle multi-byte lines in RUF100 (#2392) 2023-01-31 07:59:16 -05:00
Charlie Marsh
a9a0026f2f Don't panic for --statistics with no errors (#2391) 2023-01-31 07:53:29 -05:00
Hassan Kibirige
da4618d77b For neovim:null_ls use ruff builtin for formatting (#2386)
null_ls picked up the recommended snippet in README.md and ruff formatting now a builtin.

Ref:
1. 482990e391

2. 7b2b28e207/doc/BUILTINS.md (ruff-1)
2023-01-31 07:22:14 -05:00
Martin Fischer
1b0748d19d refactor: Simplify Linter::categories 2023-01-31 07:21:12 -05:00
Martin Fischer
0b7fa64481 refactor: Drop PartialOrd & Ord impls for RuleSelector
RuleSelector implemented PartialOrd & Ord because ruff::flake8_to_ruff
was using RuleSelector within a BTreeSet (which requires contained
elements to implement Ord). There however is no inherent order to
rule selectors, so PartialOrd & Ord should not be implemented.

This commit changes BTreeSet<RuleSelector> to HashSet<RuleSelector>
and adds an explicit sort calls based on the serialized strings,
letting us drop the PartialOrd & Ord impls in favor of a Hash impl.
2023-01-31 07:21:12 -05:00
Samuel Cormier-Iijima
09d593b124 [I001] fix isort check for files with tabs and no indented blocks (#2374)
This is a followup to #2361. The isort check still had an issue in a rather specific case: files with a multiline import, indented with tabs, and not containing any indented blocks.

The root cause is this: [`Stylist`'s indentation detection](ad8693e3de/src/source_code/stylist.rs (L163-L172)) works by finding `Indent` tokens to determine the type of indentation used by a file. This works for indented code blocks (loops/classes/functions/etc) but does not work for multiline values, so falls back to 4 spaces if the file doesn't contain code blocks.

I considered a few possible solutions:

1. Fix `detect_indentation` to avoid tokenizing and instead use some other heuristic to determine indentation. This would have the benefit of working in other places where this is potentially an issue, but would still fail if the file doesn't contain any indentation at all, and would need to fall back to option 2 anyways.
2. Add an option for specifying the default indentation in Ruff's config. I think this would confusing, since it wouldn't affect the detection behavior and only operate as a fallback, has no other current application and would probably end up being overloaded for other things.
3. Relax the isort check by comparing the expected and actual code's lexed tokens. This would require an additional lexing step.
4. Relax the isort check by comparing the expected and actual code modulo whitespace at the start of lines.

This PR does approach 4, which in addition to being the simplest option, has the (expected, although I didn't benchmark) added benefit of improved performance, since the check no longer needs to do two allocations for the two `dedent` calls. I also believe that the check is still correct enough for all practical purposes.
2023-01-31 07:18:54 -05:00
Erik Welch
adc134ced0 Fix typos: s/scripy/scipy/g (#2380) 2023-01-31 07:17:18 -05:00
Charlie Marsh
6051a0c1c8 Include per-file ignore matches in debug logging (#2376) 2023-01-30 23:11:56 -05:00
Charlie Marsh
00495e8620 Use human-readable types for documentation values (#2375) 2023-01-30 23:05:28 -05:00
Colin Delahunty
ad8693e3de [pyupgrade] Implement import-replacement rule (UP035) (#2049) 2023-01-30 19:58:28 -05:00
Charlie Marsh
69e20c4554 Minor improvements to the docs (#2371) 2023-01-30 19:06:05 -05:00
Charlie Marsh
b5816634b3 Add a link to MkDocs (#2370) 2023-01-30 19:00:57 -05:00
526 changed files with 19084 additions and 14305 deletions

View File

@@ -1,2 +1,28 @@
[alias]
dev = "run --package ruff_dev --bin ruff_dev"
[target.'cfg(all())']
rustflags = [
# CLIPPY LINT SETTINGS
# This is a workaround to configure lints for the entire workspace, pending the ability to configure this via TOML.
# See: `https://github.com/rust-lang/cargo/issues/5034`
# `https://github.com/EmbarkStudios/rust-ecosystem/issues/22#issuecomment-947011395`
"-Dunsafe_code",
"-Wclippy::pedantic",
# Allowed pedantic lints
"-Wclippy::char_lit_as_u8",
"-Aclippy::collapsible_else_if",
"-Aclippy::collapsible_if",
"-Aclippy::implicit_hasher",
"-Aclippy::match_same_arms",
"-Aclippy::missing_errors_doc",
"-Aclippy::missing_panics_doc",
"-Aclippy::module_name_repetitions",
"-Aclippy::must_use_candidate",
"-Aclippy::similar_names",
"-Aclippy::too_many_lines",
# Disallowed restriction lints
"-Wclippy::print_stdout",
"-Wclippy::print_stderr",
"-Wclippy::dbg_macro",
]

View File

@@ -3,8 +3,8 @@ Thank you for taking the time to report an issue! We're glad to have you involve
If you're filing a bug report, please consider including the following information:
- A minimal code snippet that reproduces the bug.
- The command you invoked (e.g., `ruff /path/to/file.py --fix`), ideally including the `--isolated` flag.
- The current Ruff settings (any relevant sections from your `pyproject.toml`).
- The current Ruff version (`ruff --version`).
* A minimal code snippet that reproduces the bug.
* The command you invoked (e.g., `ruff /path/to/file.py --fix`), ideally including the `--isolated` flag.
* The current Ruff settings (any relevant sections from your `pyproject.toml`).
* The current Ruff version (`ruff --version`).
-->

View File

@@ -50,13 +50,13 @@ jobs:
rustup component add clippy
rustup target add wasm32-unknown-unknown
- uses: Swatinem/rust-cache@v1
- run: cargo clippy --workspace --all-targets --all-features -- -D warnings -W clippy::pedantic
- run: cargo clippy -p ruff --target wasm32-unknown-unknown --all-features -- -D warnings -W clippy::pedantic
- run: cargo clippy --workspace --all-targets --all-features -- -D warnings
- run: cargo clippy -p ruff --target wasm32-unknown-unknown --all-features -- -D warnings
cargo-test:
strategy:
matrix:
os: [ ubuntu-latest, windows-latest ]
os: [ubuntu-latest, windows-latest]
runs-on: ${{ matrix.os }}
name: "cargo test | ${{ matrix.os }}"
steps:
@@ -95,8 +95,8 @@ jobs:
- run: ./scripts/add_rule.py --name DoTheThing --code PLC999 --linter pylint
- run: cargo check
- run: |
./scripts/add_plugin.py test --url https://pypi.org/project/-test/0.1.0/ --prefix TST
./scripts/add_rule.py --name FirstRule --code TST001 --linter test
./scripts/add_plugin.py test --url https://pypi.org/project/-test/0.1.0/ --prefix TST
./scripts/add_rule.py --name FirstRule --code TST001 --linter test
- run: cargo check
maturin-build:
@@ -118,7 +118,7 @@ jobs:
name: "spell check"
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: crate-ci/typos@master
with:
files: .
- uses: actions/checkout@v3
- uses: crate-ci/typos@master
with:
files: .

View File

@@ -6,10 +6,9 @@ on:
- README.md
- mkdocs.template.yml
- .github/workflows/docs.yaml
branches: [ main ]
branches: [main]
workflow_dispatch:
jobs:
mkdocs:
runs-on: ubuntu-latest

View File

@@ -15,7 +15,7 @@ jobs:
publish:
runs-on: ubuntu-latest
env:
CF_API_TOKEN_EXISTS: ${{ secrets.CF_API_TOKEN != '' }}
CF_API_TOKEN_EXISTS: ${{ secrets.CF_API_TOKEN != '' }}
steps:
- uses: actions/checkout@v3
- name: "Install Rust toolchain"

View File

@@ -138,7 +138,7 @@ jobs:
with:
target: ${{ matrix.target }}
manylinux: auto
args: --no-default-features --release --out dist
args: --release --out dist
- uses: uraimo/run-on-arch-action@v2.5.0
if: matrix.target != 'ppc64'
name: Install built wheel

1
.gitignore vendored
View File

@@ -3,6 +3,7 @@
resources/test/cpython
docs/
mkdocs.yml
.overrides
###
# Rust.gitignore

View File

@@ -1,16 +1,19 @@
repos:
- repo: https://github.com/charliermarsh/ruff-pre-commit
rev: v0.0.238
hooks:
- id: ruff
args: [--fix]
exclude: ^resources
- repo: https://github.com/abravalheri/validate-pyproject
rev: v0.10.1
hooks:
- id: validate-pyproject
- repo: https://github.com/igorshubovych/markdownlint-cli
rev: v0.33.0
hooks:
- id: markdownlint-fix
args:
- --disable
- MD013 # line-length
- MD033 # no-inline-html
- --
- repo: local
hooks:
- id: cargo-fmt
@@ -20,12 +23,22 @@ repos:
types: [rust]
- id: clippy
name: clippy
entry: cargo clippy --workspace --all-targets --all-features
entry: cargo clippy --workspace --all-targets --all-features -- -D warnings
language: rust
pass_filenames: false
- id: ruff
name: ruff
entry: cargo run -- --no-cache --fix
language: rust
types_or: [python, pyi]
require_serial: true
exclude: ^resources
- id: dev-generate-all
name: dev-generate-all
entry: cargo dev generate-all
language: rust
pass_filenames: false
exclude: target
ci:
skip: [cargo-fmt, clippy, dev-generate-all]

View File

@@ -37,22 +37,24 @@ will enable all `F` rules, including `F401`, as the command line's `--select` re
The `remove-six-compat` rule has been removed. This rule was only useful for one-time Python 2-to-3
upgrades.
## 0.0.238
## 0.0.237
### `--explain`, `--clean`, and `--generate-shell-completion` are now subcommands ([#2190](https://github.com/charliermarsh/ruff/pull/2190))
`--explain`, `--clean`, and `--generate-shell-completion` are now implemented as subcommands:
ruff . # Still works! And will always work.
ruff check . # New! Also works.
```console
ruff . # Still works! And will always work.
ruff check . # New! Also works.
ruff --explain E402 # Still works.
ruff rule E402 # New! Also works. (And preferred.)
ruff --explain E402 # Still works.
ruff rule E402 # New! Also works. (And preferred.)
# Oops! The command has to come first.
ruff --format json --explain E402 # No longer works.
ruff --explain E402 --format json # Still works!
ruff rule E402 --format json # Works! (And preferred.)
# Oops! The command has to come first.
ruff --format json --explain E402 # No longer works.
ruff --explain E402 --format json # Still works!
ruff rule E402 --format json # Works! (And preferred.)
```
This change is largely backwards compatible -- most users should experience
no change in behavior. However, please note the following exceptions:
@@ -60,7 +62,9 @@ no change in behavior. However, please note the following exceptions:
* Subcommands will now fail when invoked with unsupported arguments, instead
of silently ignoring them. For example, the following will now fail:
ruff --clean --respect-gitignore
```console
ruff --clean --respect-gitignore
```
(the `clean` command doesn't support `--respect-gitignore`.)

View File

@@ -106,7 +106,7 @@ Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within
@@ -115,14 +115,12 @@ the community.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.0, available at
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
version 2.0, available [here](https://www.contributor-covenant.org/version/2/0/code_of_conduct.html).
Community Impact Guidelines were inspired by [Mozilla's code of conduct
enforcement ladder](https://github.com/mozilla/diversity).
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see the FAQ at
https://www.contributor-covenant.org/faq. Translations are available at
https://www.contributor-covenant.org/translations.
For answers to common questions about this code of conduct, see the [FAQ](https://www.contributor-covenant.org/faq).
Translations are available [here](https://www.contributor-covenant.org/translations).

View File

@@ -4,10 +4,14 @@ Welcome! We're happy to have you here. Thank you in advance for your contributio
## The basics
Ruff welcomes contributions in the form of Pull Requests. For small changes (e.g., bug fixes), feel
free to submit a PR. For larger changes (e.g., new lint rules, new functionality, new configuration
options), consider submitting an [Issue](https://github.com/charliermarsh/ruff/issues) outlining
your proposed change.
Ruff welcomes contributions in the form of Pull Requests.
For small changes (e.g., bug fixes), feel free to submit a PR.
For larger changes (e.g., new lint rules, new functionality, new configuration options), consider
creating an [**issue**](https://github.com/charliermarsh/ruff/issues) outlining your proposed
change. You can also join us on [**Discord**](https://discord.gg/Z8KbeK24) to discuss your idea with
the community.
If you're looking for a place to start, we recommend implementing a new lint rule (see:
[_Adding a new lint rule_](#example-adding-a-new-lint-rule), which will allow you to learn from and
@@ -50,7 +54,14 @@ cargo test --all # Testing...
These checks will run on GitHub Actions when you open your Pull Request, but running them locally
will save you time and expedite the merge process.
If you have `pre-commit` [installed](https://pre-commit.com/#installation) then you can use it to
Note that many code changes also require updating the snapshot tests, which is done interactively
after running `cargo test` like so:
```shell
cargo insta review
```
If you have `pre-commit` [installed](https://pre-commit.com/#installation) then you can use it to
assist with formatting and linting. The following command will run the `pre-commit` hooks:
```shell
@@ -135,3 +146,126 @@ them to [PyPI](https://pypi.org/project/ruff/).
Ruff follows the [semver](https://semver.org/) versioning standard. However, as pre-1.0 software,
even patch releases may contain [non-backwards-compatible changes](https://semver.org/#spec-item-4).
## Benchmarks
First, clone [CPython](https://github.com/python/cpython). It's a large and diverse Python codebase,
which makes it a good target for benchmarking.
```shell
git clone --branch 3.10 https://github.com/python/cpython.git resources/test/cpython
```
To benchmark the release build:
```shell
cargo build --release && hyperfine --ignore-failure --warmup 10 \
"./target/release/ruff ./resources/test/cpython/ --no-cache" \
"./target/release/ruff ./resources/test/cpython/"
Benchmark 1: ./target/release/ruff ./resources/test/cpython/ --no-cache
Time (mean ± σ): 293.8 ms ± 3.2 ms [User: 2384.6 ms, System: 90.3 ms]
Range (min … max): 289.9 ms … 301.6 ms 10 runs
Warning: Ignoring non-zero exit code.
Benchmark 2: ./target/release/ruff ./resources/test/cpython/
Time (mean ± σ): 48.0 ms ± 3.1 ms [User: 65.2 ms, System: 124.7 ms]
Range (min … max): 45.0 ms … 66.7 ms 62 runs
Warning: Ignoring non-zero exit code.
Summary
'./target/release/ruff ./resources/test/cpython/' ran
6.12 ± 0.41 times faster than './target/release/ruff ./resources/test/cpython/ --no-cache'
```
To benchmark against the ecosystem's existing tools:
```shell
hyperfine --ignore-failure --warmup 5 \
"./target/release/ruff ./resources/test/cpython/ --no-cache" \
"pyflakes resources/test/cpython" \
"autoflake --recursive --expand-star-imports --remove-all-unused-imports --remove-unused-variables --remove-duplicate-keys resources/test/cpython" \
"pycodestyle resources/test/cpython" \
"flake8 resources/test/cpython"
Benchmark 1: ./target/release/ruff ./resources/test/cpython/ --no-cache
Time (mean ± σ): 294.3 ms ± 3.3 ms [User: 2467.5 ms, System: 89.6 ms]
Range (min … max): 291.1 ms … 302.8 ms 10 runs
Warning: Ignoring non-zero exit code.
Benchmark 2: pyflakes resources/test/cpython
Time (mean ± σ): 15.786 s ± 0.143 s [User: 15.560 s, System: 0.214 s]
Range (min … max): 15.640 s … 16.157 s 10 runs
Warning: Ignoring non-zero exit code.
Benchmark 3: autoflake --recursive --expand-star-imports --remove-all-unused-imports --remove-unused-variables --remove-duplicate-keys resources/test/cpython
Time (mean ± σ): 6.175 s ± 0.169 s [User: 54.102 s, System: 1.057 s]
Range (min … max): 5.950 s … 6.391 s 10 runs
Benchmark 4: pycodestyle resources/test/cpython
Time (mean ± σ): 46.921 s ± 0.508 s [User: 46.699 s, System: 0.202 s]
Range (min … max): 46.171 s … 47.863 s 10 runs
Warning: Ignoring non-zero exit code.
Benchmark 5: flake8 resources/test/cpython
Time (mean ± σ): 12.260 s ± 0.321 s [User: 102.934 s, System: 1.230 s]
Range (min … max): 11.848 s … 12.933 s 10 runs
Warning: Ignoring non-zero exit code.
Summary
'./target/release/ruff ./resources/test/cpython/ --no-cache' ran
20.98 ± 0.62 times faster than 'autoflake --recursive --expand-star-imports --remove-all-unused-imports --remove-unused-variables --remove-duplicate-keys resources/test/cpython'
41.66 ± 1.18 times faster than 'flake8 resources/test/cpython'
53.64 ± 0.77 times faster than 'pyflakes resources/test/cpython'
159.43 ± 2.48 times faster than 'pycodestyle resources/test/cpython'
```
You can run `poetry install` from `./scripts` to create a working environment for the above. All
reported benchmarks were computed using the versions specified by `./scripts/pyproject.toml`
on Python 3.11.
To benchmark Pylint, remove the following files from the CPython repository:
```shell
rm Lib/test/bad_coding.py \
Lib/test/bad_coding2.py \
Lib/test/bad_getattr.py \
Lib/test/bad_getattr2.py \
Lib/test/bad_getattr3.py \
Lib/test/badcert.pem \
Lib/test/badkey.pem \
Lib/test/badsyntax_3131.py \
Lib/test/badsyntax_future10.py \
Lib/test/badsyntax_future3.py \
Lib/test/badsyntax_future4.py \
Lib/test/badsyntax_future5.py \
Lib/test/badsyntax_future6.py \
Lib/test/badsyntax_future7.py \
Lib/test/badsyntax_future8.py \
Lib/test/badsyntax_future9.py \
Lib/test/badsyntax_pep3120.py \
Lib/test/test_asyncio/test_runners.py \
Lib/test/test_copy.py \
Lib/test/test_inspect.py \
Lib/test/test_typing.py
```
Then, from `resources/test/cpython`, run: `time pylint -j 0 -E $(git ls-files '*.py')`. This
will execute Pylint with maximum parallelism and only report errors.
To benchmark Pyupgrade, run the following from `resources/test/cpython`:
```shell
hyperfine --ignore-failure --warmup 5 --prepare "git reset --hard HEAD" \
"find . -type f -name \"*.py\" | xargs -P 0 pyupgrade --py311-plus"
Benchmark 1: find . -type f -name "*.py" | xargs -P 0 pyupgrade --py311-plus
Time (mean ± σ): 30.119 s ± 0.195 s [User: 28.638 s, System: 0.390 s]
Range (min … max): 29.813 s … 30.356 s 10 runs
```

84
Cargo.lock generated
View File

@@ -608,15 +608,6 @@ dependencies = [
"crypto-common",
]
[[package]]
name = "directories"
version = "4.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f51c5d4ddabd36886dd3e1438cb358cdcb0d7c499cb99cb4ac2e38e18b5cb210"
dependencies = [
"dirs-sys",
]
[[package]]
name = "dirs"
version = "4.0.0"
@@ -750,7 +741,7 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
[[package]]
name = "flake8-to-ruff"
version = "0.0.238"
version = "0.0.241"
dependencies = [
"anyhow",
"clap 4.1.4",
@@ -945,7 +936,7 @@ version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f92123bf2fe0d9f1b5df1964727b970ca3b2d0203d47cf97fb1f36d856b6398"
dependencies = [
"phf 0.11.1",
"phf",
"rust-stemmers",
]
@@ -1571,15 +1562,6 @@ dependencies = [
"indexmap",
]
[[package]]
name = "phf"
version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fabbf1ead8a5bcbc20f5f8b939ee3f5b0f6f281b6ad3468b84656b658b455259"
dependencies = [
"phf_shared 0.10.0",
]
[[package]]
name = "phf"
version = "0.11.1"
@@ -1589,36 +1571,16 @@ dependencies = [
"phf_shared 0.11.1",
]
[[package]]
name = "phf_codegen"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4fb1c3a8bc4dd4e5cfce29b44ffc14bedd2ee294559a294e2a4d4c9e9a6a13cd"
dependencies = [
"phf_generator 0.10.0",
"phf_shared 0.10.0",
]
[[package]]
name = "phf_codegen"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a56ac890c5e3ca598bbdeaa99964edb5b0258a583a9eb6ef4e89fc85d9224770"
dependencies = [
"phf_generator 0.11.1",
"phf_generator",
"phf_shared 0.11.1",
]
[[package]]
name = "phf_generator"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d5285893bb5eb82e6aaf5d59ee909a06a16737a8970984dd7746ba9283498d6"
dependencies = [
"phf_shared 0.10.0",
"rand",
]
[[package]]
name = "phf_generator"
version = "0.11.1"
@@ -1922,7 +1884,7 @@ dependencies = [
[[package]]
name = "ruff"
version = "0.0.238"
version = "0.0.241"
dependencies = [
"anyhow",
"bitflags",
@@ -1977,7 +1939,7 @@ dependencies = [
[[package]]
name = "ruff_cli"
version = "0.0.238"
version = "0.0.241"
dependencies = [
"annotate-snippets 0.9.1",
"anyhow",
@@ -2007,14 +1969,13 @@ dependencies = [
"similar",
"strum",
"textwrap",
"update-informer",
"ureq",
"walkdir",
]
[[package]]
name = "ruff_dev"
version = "0.0.238"
version = "0.0.241"
dependencies = [
"anyhow",
"clap 4.1.4",
@@ -2035,7 +1996,7 @@ dependencies = [
[[package]]
name = "ruff_macros"
version = "0.0.238"
version = "0.0.241"
dependencies = [
"once_cell",
"proc-macro2",
@@ -2089,7 +2050,7 @@ dependencies = [
[[package]]
name = "rustpython-ast"
version = "0.2.0"
source = "git+https://github.com/RustPython/RustPython.git?rev=4f38cb68e4a97aeea9eb19673803a0bd5f655383#4f38cb68e4a97aeea9eb19673803a0bd5f655383"
source = "git+https://github.com/RustPython/RustPython.git?rev=adc23253e4b58980b407ba2760dbe61681d752fc#adc23253e4b58980b407ba2760dbe61681d752fc"
dependencies = [
"num-bigint",
"rustpython-common",
@@ -2099,7 +2060,7 @@ dependencies = [
[[package]]
name = "rustpython-common"
version = "0.2.0"
source = "git+https://github.com/RustPython/RustPython.git?rev=4f38cb68e4a97aeea9eb19673803a0bd5f655383#4f38cb68e4a97aeea9eb19673803a0bd5f655383"
source = "git+https://github.com/RustPython/RustPython.git?rev=adc23253e4b58980b407ba2760dbe61681d752fc#adc23253e4b58980b407ba2760dbe61681d752fc"
dependencies = [
"ascii",
"bitflags",
@@ -2124,7 +2085,7 @@ dependencies = [
[[package]]
name = "rustpython-compiler-core"
version = "0.2.0"
source = "git+https://github.com/RustPython/RustPython.git?rev=4f38cb68e4a97aeea9eb19673803a0bd5f655383#4f38cb68e4a97aeea9eb19673803a0bd5f655383"
source = "git+https://github.com/RustPython/RustPython.git?rev=adc23253e4b58980b407ba2760dbe61681d752fc#adc23253e4b58980b407ba2760dbe61681d752fc"
dependencies = [
"bincode",
"bitflags",
@@ -2141,7 +2102,7 @@ dependencies = [
[[package]]
name = "rustpython-parser"
version = "0.2.0"
source = "git+https://github.com/RustPython/RustPython.git?rev=4f38cb68e4a97aeea9eb19673803a0bd5f655383#4f38cb68e4a97aeea9eb19673803a0bd5f655383"
source = "git+https://github.com/RustPython/RustPython.git?rev=adc23253e4b58980b407ba2760dbe61681d752fc#adc23253e4b58980b407ba2760dbe61681d752fc"
dependencies = [
"ahash",
"anyhow",
@@ -2151,8 +2112,8 @@ dependencies = [
"log",
"num-bigint",
"num-traits",
"phf 0.10.1",
"phf_codegen 0.10.0",
"phf",
"phf_codegen",
"rustc-hash",
"rustpython-ast",
"rustpython-compiler-core",
@@ -2455,8 +2416,8 @@ dependencies = [
"dirs",
"fnv",
"nom",
"phf 0.11.1",
"phf_codegen 0.11.1",
"phf",
"phf_codegen",
]
[[package]]
@@ -2758,19 +2719,6 @@ version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a"
[[package]]
name = "update-informer"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "152ff185ca29f7f487c51ca785b0f1d85970c4581f4cdd12ed499227890200f5"
dependencies = [
"directories",
"semver",
"serde",
"serde_json",
"ureq",
]
[[package]]
name = "ureq"
version = "2.6.2"
@@ -2782,8 +2730,6 @@ dependencies = [
"log",
"once_cell",
"rustls",
"serde",
"serde_json",
"url",
"webpki",
"webpki-roots",

View File

@@ -8,7 +8,7 @@ default-members = [".", "ruff_cli"]
[package]
name = "ruff"
version = "0.0.238"
version = "0.0.241"
authors = ["Charlie Marsh <charlie.r.marsh@gmail.com>"]
edition = "2021"
rust-version = "1.65.0"
@@ -46,11 +46,11 @@ num-traits = "0.2.15"
once_cell = { version = "1.16.0" }
path-absolutize = { version = "3.0.14", features = ["once_cell_cache", "use_unix_paths_on_wasm"] }
regex = { version = "1.6.0" }
ruff_macros = { version = "0.0.238", path = "ruff_macros" }
ruff_macros = { version = "0.0.241", path = "ruff_macros" }
rustc-hash = { version = "1.1.0" }
rustpython-ast = { features = ["unparse"], git = "https://github.com/RustPython/RustPython.git", rev = "4f38cb68e4a97aeea9eb19673803a0bd5f655383" }
rustpython-common = { git = "https://github.com/RustPython/RustPython.git", rev = "4f38cb68e4a97aeea9eb19673803a0bd5f655383" }
rustpython-parser = { features = ["lalrpop"], git = "https://github.com/RustPython/RustPython.git", rev = "4f38cb68e4a97aeea9eb19673803a0bd5f655383" }
rustpython-ast = { features = ["unparse"], git = "https://github.com/RustPython/RustPython.git", rev = "adc23253e4b58980b407ba2760dbe61681d752fc" }
rustpython-common = { git = "https://github.com/RustPython/RustPython.git", rev = "adc23253e4b58980b407ba2760dbe61681d752fc" }
rustpython-parser = { features = ["lalrpop"], git = "https://github.com/RustPython/RustPython.git", rev = "adc23253e4b58980b407ba2760dbe61681d752fc" }
schemars = { version = "0.8.11" }
semver = { version = "1.0.16" }
serde = { version = "1.0.147", features = ["derive"] }
@@ -77,7 +77,7 @@ wasm-bindgen = { version = "0.2.83" }
is_executable = "1.0.1"
[dev-dependencies]
insta = { version = "1.19.1", features = ["yaml", "redactions"] }
insta = { version = "1.19.0", features = ["yaml", "redactions"] }
test-case = { version = "2.2.2" }
wasm-bindgen-test = { version = "0.3.33" }

30
LICENSE
View File

@@ -1005,3 +1005,33 @@ are:
See the License for the specific language governing permissions and
limitations under the License.
"""
- flake8-raise, licensed as follows:
"""
MIT License
Copyright (c) 2020 Jon Dufresne
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.
"""
- flake8-self, licensed as follows:
"""
Freely Distributable
"""

773
README.md

File diff suppressed because it is too large Load Diff

2964
flake8_to_ruff/Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
[package]
name = "flake8-to-ruff"
version = "0.0.238"
version = "0.0.241"
edition = "2021"
[dependencies]

View File

@@ -1,18 +1,4 @@
//! Utility to generate Ruff's `pyproject.toml` section from a Flake8 INI file.
#![forbid(unsafe_code)]
#![warn(clippy::pedantic)]
#![allow(
clippy::collapsible_else_if,
clippy::collapsible_if,
clippy::implicit_hasher,
clippy::match_same_arms,
clippy::missing_errors_doc,
clippy::missing_panics_doc,
clippy::module_name_repetitions,
clippy::must_use_candidate,
clippy::similar_names,
clippy::too_many_lines
)]
use std::path::PathBuf;
@@ -61,7 +47,11 @@ fn main() -> Result<()> {
// Create Ruff's pyproject.toml section.
let pyproject = flake8_to_ruff::convert(&config, &external_config, args.plugin)?;
println!("{}", toml::to_string_pretty(&pyproject)?);
#[allow(clippy::print_stdout)]
{
println!("{}", toml::to_string_pretty(&pyproject)?);
}
Ok(())
}

View File

@@ -23,6 +23,7 @@ theme:
toggle:
icon: material/weather-night
name: Switch to light mode
custom_dir: .overrides
repo_url: https://github.com/charliermarsh/ruff
repo_name: ruff
site_author: charliermarsh

View File

@@ -4,10 +4,10 @@ In-browser playground for Ruff. Available [https://ruff.pages.dev/](https://ruff
## Getting started
- To build the WASM module, run `wasm-pack build --target web --out-dir playground/src/pkg` from the
* To build the WASM module, run `wasm-pack build --target web --out-dir playground/src/pkg` from the
root directory.
- Install TypeScript dependencies with: `npm install`.
- Start the development server with: `npm run dev`.
* Install TypeScript dependencies with: `npm install`.
* Start the development server with: `npm run dev`.
## Implementation

View File

@@ -1,4 +1,4 @@
<!DOCTYPE html>
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
@@ -14,6 +14,7 @@
href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>⚡</text></svg>"
/>
<link rel="stylesheet" href="https://rsms.me/inter/inter.css" />
<script src="https://cdn.usefathom.com/script.js" data-site="XWUDIXNB" defer></script>
</head>
<body>
<div id="root"></div>

View File

@@ -7,33 +7,36 @@ build-backend = "maturin"
[project]
name = "ruff"
version = "0.0.238"
version = "0.0.241"
description = "An extremely fast Python linter, written in Rust."
authors = [
{ name = "Charlie Marsh", email = "charlie.r.marsh@gmail.com" },
]
maintainers = [
{ name = "Charlie Marsh", email = "charlie.r.marsh@gmail.com" },
]
authors = [{ name = "Charlie Marsh", email = "charlie.r.marsh@gmail.com" }]
maintainers = [{ name = "Charlie Marsh", email = "charlie.r.marsh@gmail.com" }]
readme = "README.md"
requires-python = ">=3.7"
license = { file = "LICENSE" }
keywords = ["automation", "flake8", "pycodestyle", "pyflakes", "pylint", "clippy"]
keywords = [
"automation",
"flake8",
"pycodestyle",
"pyflakes",
"pylint",
"clippy",
]
classifiers = [
"Development Status :: 3 - Alpha",
"Environment :: Console",
"Intended Audience :: Developers",
"License :: OSI Approved :: MIT License",
"Operating System :: OS Independent",
"Programming Language :: Python",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3 :: Only",
"Topic :: Software Development :: Libraries :: Python Modules",
"Topic :: Software Development :: Quality Assurance",
"Development Status :: 3 - Alpha",
"Environment :: Console",
"Intended Audience :: Developers",
"License :: OSI Approved :: MIT License",
"Operating System :: OS Independent",
"Programming Language :: Python",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3 :: Only",
"Topic :: Software Development :: Libraries :: Python Modules",
"Topic :: Software Development :: Quality Assurance",
]
urls = { repository = "https://github.com/charliermarsh/ruff" }
@@ -42,3 +45,6 @@ bindings = "bin"
manifest-path = "ruff_cli/Cargo.toml"
python-source = "python"
strip = true
[tool.ruff.per-file-ignores]
"setup.py" = ["INP001"]

View File

@@ -1,7 +1,8 @@
import os
import sys
import sysconfig
from pathlib import Path
if __name__ == "__main__":
ruff = os.path.join(sysconfig.get_path("scripts"), "ruff")
ruff = Path(sysconfig.get_path("scripts")) / "ruff"
sys.exit(os.spawnv(os.P_WAIT, ruff, [ruff, *sys.argv[1:]]))

View File

@@ -4,6 +4,7 @@ d = {}
safe = "s3cr3t"
password = True
password = safe
password = ""
password is True
password == 1
d["safe"] = "s3cr3t"

View File

@@ -7,6 +7,7 @@ string = "Hello World"
# OK
func("s3cr3t")
func(1, password=string)
func(1, password="")
func(pos="s3cr3t", password=string)
# Error

View File

@@ -28,3 +28,7 @@ def ok_all(first, /, pos, default="posonly", *, kwonly="kwonly"):
def default_all(first, /, pos, secret="posonly", *, password="kwonly"):
pass
def ok_empty(first, password=""):
pass

View File

@@ -44,4 +44,39 @@ for foo, bar in [(1, 2)]:
print(FMT.format(**vars()))
for foo, bar in [(1, 2)]:
print(FMT.format(foo=foo, bar=eval('bar')))
print(FMT.format(foo=foo, bar=eval("bar")))
def f():
# Fixable.
for foo, bar, baz in (["1", "2", "3"],):
if foo or baz:
break
def f():
# Unfixable due to usage of `bar` outside of loop.
for foo, bar, baz in (["1", "2", "3"],):
if foo or baz:
break
print(bar)
def f():
# Fixable.
for foo, bar, baz in (["1", "2", "3"],):
if foo or baz:
break
bar = 1
def f():
# Fixable.
for foo, bar, baz in (["1", "2", "3"],):
if foo or baz:
break
bar = 1
print(bar)

View File

@@ -1,2 +1,4 @@
this_should_be_linted = "double quote string"
this_should_be_linted = u"double quote string"
this_should_be_linted = f"double quote string"
this_should_be_linted = f"double {'quote'} string"

View File

@@ -4,3 +4,8 @@ this_is_fine = '"This" is a \'string\''
this_is_fine = "This is a 'string'"
this_is_fine = "\"This\" is a 'string'"
this_is_fine = r'This is a \'string\''
this_is_fine = R'This is a \'string\''
this_should_raise = (
'This is a'
'\'string\''
)

View File

@@ -0,0 +1,27 @@
x = (
"This"
"is"
"not"
)
x = (
"This" \
"is" \
"not"
)
x = (
"This"
"is 'actually'"
"fine"
)
x = (
"This" \
"is 'actually'" \
"fine"
)
if True:
"This can use 'double' quotes"
"But this needs to be changed"

View File

@@ -1,2 +1,4 @@
this_should_be_linted = 'single quote string'
this_should_be_linted = u'double quote string'
this_should_be_linted = f'double quote string'
this_should_be_linted = f'double {"quote"} string'

View File

@@ -3,3 +3,8 @@ this_is_fine = "'This' is a \"string\""
this_is_fine = 'This is a "string"'
this_is_fine = '\'This\' is a "string"'
this_is_fine = r"This is a \"string\""
this_is_fine = R"This is a \"string\""
this_should_raise = (
"This is a"
"\"string\""
)

View File

@@ -0,0 +1,27 @@
x = (
'This'
'is'
'not'
)
x = (
'This' \
'is' \
'not'
)
x = (
'This'
'is "actually"'
'fine'
)
x = (
'This' \
'is "actually"' \
'fine'
)
if True:
'This can use "single" quotes'
'But this needs to be changed'

View File

@@ -0,0 +1,15 @@
try:
y = 6 + "7"
except TypeError:
raise ValueError() # RSE102
try:
x = 1 / 0
except ZeroDivisionError:
raise
raise TypeError() # RSE102
raise AssertionError
raise AttributeError("test message")

View File

@@ -0,0 +1,59 @@
class BazMeta(type):
_private_count = 1
def __new__(mcs, name, bases, attrs):
if mcs._private_count <= 5:
mcs.some_method()
return super().__new__(mcs, name, bases, attrs)
def some_method():
pass
class Bar:
_private = True
@classmethod
def is_private(cls):
return cls._private
class Foo(metaclass=BazMeta):
def __init__(self):
self.public_thing = "foo"
self._private_thing = "bar"
self.__really_private_thing = "baz"
self.bar = Bar()
def __str__(self):
return "foo"
def get_bar():
if self.bar._private: # SLF001
return None
return self.bar
def public_func(self):
pass
def _private_func(self):
pass
def __really_private_func(self, arg):
pass
foo = Foo()
print(foo.public_thing)
print(foo.public_func())
print(foo.__dict__)
print(foo.__str__())
print(foo._private_thing) # SLF001
print(foo.__really_private_thing) # SLF001
print(foo._private_func()) # SLF001
print(foo.__really_private_func(1)) # SLF001
print(foo.bar._private) # SLF001

View File

@@ -21,3 +21,10 @@ if isinstance(a, int) and isinstance(b, bool) or isinstance(a, float):
if isinstance(a, bool) or isinstance(b, str):
pass
def f():
# OK
def isinstance(a, b):
return False
if isinstance(a, int) or isinstance(a, float):
pass

View File

@@ -6,6 +6,14 @@ def f():
return False
def f():
# SIM103
if a == b:
return True
else:
return False
def f():
# SIM103
if a:
@@ -50,3 +58,29 @@ def f():
return False
else:
return True
def f():
# OK
if a:
return False
else:
return False
def f():
# OK
if a:
return True
else:
return True
def f():
# OK
def bool():
return False
if a:
return True
else:
return False

View File

@@ -54,7 +54,7 @@ else:
randbytes = _get_random_bytes
# OK (includes comments)
# SIM108 (without fix due to comments)
if x > 0:
# test test
abc = x
@@ -93,8 +93,20 @@ if True:
b = ddddddddddddddddddddddddddddddddddddd
# OK (trailing comments)
# SIM108 (without fix due to trailing comment)
if True:
exitcode = 0
else:
exitcode = 1 # Trailing comment
# SIM108
if True: x = 3 # Foo
else: x = 5
# SIM108
if True: # Foo
x = 3
else:
x = 5

View File

@@ -115,3 +115,43 @@ def f():
else:
return True
return False
def f():
def any(exp):
pass
for x in iterable:
if check(x):
return True
return False
def f():
def all(exp):
pass
for x in iterable:
if check(x):
return False
return True
def f():
x = 1
# SIM110
for x in iterable:
if check(x):
return True
return False
def f():
x = 1
# SIM111
for x in iterable:
if check(x):
return False
return True

View File

@@ -115,3 +115,43 @@ def f():
else:
return True
return False
def f():
def any(exp):
pass
for x in iterable:
if check(x):
return True
return False
def f():
def all(exp):
pass
for x in iterable:
if check(x):
return False
return True
def f():
x = 1
# SIM110
for x in iterable:
if check(x):
return True
return False
def f():
x = 1
# SIM111
for x in iterable:
if check(x):
return False
return True

View File

@@ -5,3 +5,10 @@ a = True if b != c else False # SIM210
a = True if b + c else False # SIM210
a = False if b else True # OK
def f():
# OK
def bool():
return False
a = True if b else False

View File

@@ -10,6 +10,7 @@ YODA == age # SIM300
YODA > age # SIM300
YODA >= age # SIM300
JediOrder.YODA == age # SIM300
0 < (number - 100) # SIM300
# OK
compare == "yoda"
@@ -24,3 +25,4 @@ age < YODA
age <= YODA
YODA == YODA
age == JediOrder.YODA
(number - 100) > 0

View File

@@ -4,15 +4,22 @@ if TYPE_CHECKING:
pass # TCH005
if False:
pass # TCH005
if 0:
pass # TCH005
def example():
if TYPE_CHECKING:
pass # TYP005
pass # TCH005
return
class Test:
if TYPE_CHECKING:
pass # TYP005
pass # TCH005
x = 2
@@ -23,3 +30,10 @@ if TYPE_CHECKING:
if TYPE_CHECKING:
x: List
if False:
x: List
if 0:
x: List

View File

@@ -0,0 +1,8 @@
# office_helper and tests are both first-party,
# but we want tests and experiments to be separated, in that order
from office_helper.core import CoreState
import tests.common.foo as tcf
from tests.common import async_mock_service
from experiments.starry import *
from experiments.weird import varieties
from office_helper.assistants import entity_registry as er

View File

@@ -0,0 +1,13 @@
from __future__ import annotations
from typing import Any
from requests import Session
from my_first_party import my_first_party_object
from . import my_local_folder_object
class Thing(object):
name: str
def __init__(self, name: str):
self.name = name

View File

@@ -0,0 +1,22 @@
from __future__ import annotations
from typing import Any
from requests import Session
from my_first_party import my_first_party_object
from . import my_local_folder_object
def main():
my_local_folder_object.get()

View File

@@ -0,0 +1,9 @@
from __future__ import annotations
from typing import Any
from requests import Session
from my_first_party import my_first_party_object
from . import my_local_folder_object

View File

@@ -0,0 +1,13 @@
from numpy import (
cos,
int8,
int16,
int32,
int64,
sin,
tan,
uint8,
uint16,
uint32,
uint64,
)

View File

@@ -1,2 +1,5 @@
[tool.ruff]
line-length = 88
[tool.ruff.isort]
lines-after-imports = 3

View File

@@ -0,0 +1,53 @@
"""Test: noqa directives."""
from typing_extensions import List, Sequence
# This should ignore both errors.
from typing import ( # noqa: F811
List,
Sequence,
)
# This should ignore both errors.
from typing import ( # noqa
List,
Sequence,
)
# This should ignore both errors.
from typing import (
List, # noqa: F811
Sequence, # noqa: F811
)
# This should ignore both errors.
from typing import (
List, # noqa
Sequence, # noqa
)
# This should ignore the first error.
from typing import (
List, # noqa: F811
Sequence,
)
# This should ignore both errors.
from typing import ( # noqa
List,
Sequence,
)
# This should ignore both errors.
from typing import List, Sequence # noqa: F811
# This should ignore both errors.
from typing import List, Sequence # noqa
def f():
# This should ignore both errors.
from typing import ( # noqa: F811
List,
Sequence,
)

View File

@@ -0,0 +1,3 @@
a = 1
__all__ = list(["a", "b"])

View File

@@ -86,3 +86,11 @@ def f():
open("") as ((this, that)),
):
print("hello")
def f():
exponential, base_multiplier = 1, 2
hash_map = {
(exponential := (exponential * base_multiplier) % 3): i + 1 for i in range(2)
}
return hash_map

View File

@@ -9,6 +9,7 @@ from collections import OrderedDict as o_dict
import os.path as path # [consider-using-from-import]
import os.path as p
import foo.bar.foobar as foobar # [consider-using-from-import]
import foo.bar.foobar as foobar, sys # [consider-using-from-import]
import os
import os as OS
from sys import version

View File

@@ -6,6 +6,10 @@ __all__ += {"world"} # [invalid-all-format]
__all__ = {"world"} + ["Hello"] # [invalid-all-format]
__all__ = {"world"} + list(["Hello"]) # [invalid-all-format]
__all__ = list(["Hello"]) + {"world"} # [invalid-all-format]
__all__ = (x for x in ["Hello", "world"]) # [invalid-all-format]
__all__ = {x for x in ["Hello", "world"]} # [invalid-all-format]
@@ -17,3 +21,11 @@ __all__ = ("Hello",)
__all__ = ["Hello"] + ("world",)
__all__ = [x for x in ["Hello", "world"]]
__all__ = list(["Hello", "world"])
__all__ = list({"Hello", "world"})
__all__ = list(["Hello"]) + list(["world"])
__all__ = tuple(["Hello"]) + ("world",)

View File

@@ -4,8 +4,12 @@ __all__ = (
Worm,
)
__all__ = list([None, "Fruit", "Worm"]) # [invalid-all-object]
class Fruit:
pass
class Worm:
pass

View File

@@ -0,0 +1,56 @@
def f(): # OK
return
async def f(): # Too many statements (52/50)
print()
print()
print()
print()
print()
print()
print()
print()
print()
print()
print()
print()
print()
print()
print()
print()
print()
print()
print()
print()
print()
print()
print()
print()
print()
print()
print()
print()
print()
print()
print()
print()
print()
print()
print()
print()
print()
print()
print()
print()
print()
print()
print()
print()
print()
print()
print()
print()
print()
print()
print()

View File

@@ -0,0 +1,8 @@
# Too may statements (2/1) for max_statements=1
def f(x):
pass
def f(x):
def g(x):
pass

View File

@@ -0,0 +1,50 @@
# UP035
from collections import Mapping
from collections import Mapping as MAP
from collections import Mapping, Sequence
from collections import Counter, Mapping
from collections import (Counter, Mapping)
from collections import (Counter,
Mapping)
from collections import Counter, \
Mapping
from collections import Counter, Mapping, Sequence
from collections import Mapping as mapping, Counter
if True:
from collections import Mapping, Counter
if True:
if True:
pass
from collections import Mapping, Counter
if True: from collections import Mapping
import os
from collections import Counter, Mapping
import sys
if True:
from collections import (
Mapping,
Callable,
Bad,
Good,
)
from typing import Callable, Match, Pattern, List
if True: from collections import (
Mapping, Counter)
# OK
from a import b

View File

@@ -0,0 +1,180 @@
import sys
if sys.version_info < (3,0):
print("py2")
else:
print("py3")
if sys.version_info < (3,0):
if True:
print("py2!")
else:
print("???")
else:
print("py3")
if sys.version_info < (3,0): print("PY2!")
else: print("PY3!")
if True:
if sys.version_info < (3,0):
print("PY2")
else:
print("PY3")
if sys.version_info < (3,0): print(1 if True else 3)
else:
print("py3")
if sys.version_info < (3,0):
def f():
print("py2")
else:
def f():
print("py3")
print("This the next")
if sys.version_info > (3,0):
print("py3")
else:
print("py2")
x = 1
if sys.version_info > (3,0):
print("py3")
else:
print("py2")
# ohai
x = 1
if sys.version_info > (3,0): print("py3")
else: print("py2")
if sys.version_info > (3,):
print("py3")
else:
print("py2")
if True:
if sys.version_info > (3,):
print("py3")
else:
print("py2")
if sys.version_info < (3,):
print("py2")
else:
print("py3")
def f():
if sys.version_info < (3,0):
try:
yield
finally:
pass
else:
yield
class C:
def g():
pass
if sys.version_info < (3,0):
def f(py2):
pass
else:
def f(py3):
pass
def h():
pass
if True:
if sys.version_info < (3,0):
2
else:
3
# comment
if sys.version_info < (3,0):
def f():
print("py2")
def g():
print("py2")
else:
def f():
print("py3")
def g():
print("py3")
if True:
if sys.version_info > (3,):
print(3)
# comment
print(2+3)
if True:
if sys.version_info > (3,): print(3)
if True:
if sys.version_info > (3,):
print(3)
if True:
if sys.version_info <= (3, 0):
expected_error = []
else:
expected_error = [
"<stdin>:1:5: Generator expression must be parenthesized",
"max(1 for i in range(10), key=lambda x: x+1)",
" ^",
]
if sys.version_info <= (3, 0):
expected_error = []
else:
expected_error = [
"<stdin>:1:5: Generator expression must be parenthesized",
"max(1 for i in range(10), key=lambda x: x+1)",
" ^",
]
if sys.version_info > (3,0):
"""this
is valid"""
"""the indentation on
this line is significant"""
"this is" \
"allowed too"
("so is"
"this for some reason")
if sys.version_info > (3, 0): expected_error = \
[]
if sys.version_info > (3, 0): expected_error = []
if sys.version_info > (3, 0): \
expected_error = []
if True:
if sys.version_info > (3, 0): expected_error = \
[]
if True:
if sys.version_info > (3, 0): expected_error = []
if True:
if sys.version_info > (3, 0): \
expected_error = []

View File

@@ -0,0 +1,76 @@
import sys
if sys.version_info == 2:
2
else:
3
if sys.version_info < (3,):
2
else:
3
if sys.version_info < (3,0):
2
else:
3
if sys.version_info == 3:
3
else:
2
if sys.version_info > (3,):
3
else:
2
if sys.version_info >= (3,):
3
else:
2
from sys import version_info
if version_info > (3,):
3
else:
2
if True:
print(1)
elif sys.version_info < (3,0):
print(2)
else:
print(3)
if True:
print(1)
elif sys.version_info > (3,):
print(3)
else:
print(2)
if True:
print(1)
elif sys.version_info > (3,):
print(3)
def f():
if True:
print(1)
elif sys.version_info > (3,):
print(3)
if True:
print(1)
elif sys.version_info < (3,0):
print(2)
else:
print(3)
def f():
if True:
print(1)
elif sys.version_info > (3,):
print(3)

View File

@@ -0,0 +1,62 @@
import sys
from sys import version_info
if sys.version_info > (3, 5):
3+6
else:
3-5
if version_info > (3, 5):
3+6
else:
3-5
if sys.version_info >= (3,6):
3+6
else:
3-5
if version_info >= (3,6):
3+6
else:
3-5
if sys.version_info < (3,6):
3-5
else:
3+6
if sys.version_info <= (3,5):
3-5
else:
3+6
if sys.version_info <= (3, 5):
3-5
else:
3+6
if sys.version_info >= (3, 5):
pass
if sys.version_info < (3,0):
pass
if True:
if sys.version_info < (3,0):
pass
if sys.version_info < (3,0):
pass
elif False:
pass
if sys.version_info > (3,):
pass
elif False:
pass
if sys.version_info[0] > "2":
3
else:
2

View File

@@ -0,0 +1,24 @@
import sys
if sys.version_info < (3,0):
print("py2")
for item in range(10):
print(f"PY2-{item}")
else :
print("py3")
for item in range(10):
print(f"PY3-{item}")
if False:
if sys.version_info < (3,0):
print("py2")
for item in range(10):
print(f"PY2-{item}")
else :
print("py3")
for item in range(10):
print(f"PY3-{item}")
if sys.version_info < (3,0): print("PY2!")
else : print("PY3!")

View File

@@ -0,0 +1,45 @@
import sys
if True:
if sys.version_info < (3, 3):
cmd = [sys.executable, "-m", "test.regrtest"]
if True:
if foo:
pass
elif sys.version_info < (3, 3):
cmd = [sys.executable, "-m", "test.regrtest"]
if True:
if foo:
pass
elif sys.version_info < (3, 3):
cmd = [sys.executable, "-m", "test.regrtest"]
elif foo:
cmd = [sys.executable, "-m", "test", "-j0"]
if foo:
pass
elif sys.version_info < (3, 3):
cmd = [sys.executable, "-m", "test.regrtest"]
if sys.version_info < (3, 3):
cmd = [sys.executable, "-m", "test.regrtest"]
if foo:
pass
elif sys.version_info < (3, 3):
cmd = [sys.executable, "-m", "test.regrtest"]
else:
cmd = [sys.executable, "-m", "test", "-j0"]
if sys.version_info < (3, 3):
cmd = [sys.executable, "-m", "test.regrtest"]
else:
cmd = [sys.executable, "-m", "test", "-j0"]
if sys.version_info < (3, 3):
cmd = [sys.executable, "-m", "test.regrtest"]
elif foo:
cmd = [sys.executable, "-m", "test", "-j0"]

View File

@@ -37,3 +37,9 @@ second = first + [
# touch
6,
]
[] + foo + [
]
[] + foo + [ # This will be preserved, but doesn't prevent the fix
]

View File

@@ -86,3 +86,5 @@ import shelve # noqa: RUF100
import sys # noqa: F401, RUF100
print(sys.path)
"shape: (6,)\nSeries: '' [duration[μs]]\n[\n\t0µs\n\t1µs\n\t2µs\n\t3µs\n\t4µs\n\t5µs\n]" # noqa: F401

View File

@@ -1,2 +1,7 @@
[tool.ruff]
src = ["."]
# This will make sure that `exclude` paths are rooted
# to where the configuration file was found; this file exists
# in a `resources/test` hierarchy.
exclude = ["resources"]

View File

@@ -0,0 +1,4 @@
# This file should be ignored, but it would otherwise trigger
# an unused import error:
import math

View File

@@ -7,7 +7,7 @@ behaviors.
Running from the repo root should pick up and enforce the appropriate settings for each package:
```
```console
∴ cargo run resources/test/project/
resources/test/project/examples/.dotfiles/script.py:1:1: I001 Import block is un-sorted or un-formatted
resources/test/project/examples/.dotfiles/script.py:1:8: F401 `numpy` imported but unused
@@ -22,7 +22,7 @@ Found 7 errors.
Running from the project directory itself should exhibit the same behavior:
```
```console
∴ (cd resources/test/project/ && cargo run .)
examples/.dotfiles/script.py:1:1: I001 Import block is un-sorted or un-formatted
examples/.dotfiles/script.py:1:8: F401 `numpy` imported but unused
@@ -38,7 +38,7 @@ Found 7 errors.
Running from the sub-package directory should exhibit the same behavior, but omit the top-level
files:
```
```console
∴ (cd resources/test/project/examples/docs && cargo run .)
docs/file.py:1:1: I001 Import block is un-sorted or un-formatted
docs/file.py:8:5: F841 Local variable `x` is assigned to but never used
@@ -49,7 +49,7 @@ Found 2 errors.
`--config` should force Ruff to use the specified `pyproject.toml` for all files, and resolve
file paths from the current working directory:
```
```console
∴ (cargo run -- --config=resources/test/project/pyproject.toml resources/test/project/)
resources/test/project/examples/.dotfiles/script.py:1:8: F401 `numpy` imported but unused
resources/test/project/examples/.dotfiles/script.py:2:17: F401 `app.app_file` imported but unused
@@ -67,7 +67,7 @@ Found 9 errors.
Running from a parent directory should "ignore" the `exclude` (hence, `concepts/file.py` gets
included in the output):
```
```console
∴ (cd resources/test/project/examples && cargo run -- --config=docs/ruff.toml .)
docs/docs/concepts/file.py:5:5: F841 Local variable `x` is assigned to but never used
docs/docs/file.py:1:1: I001 Import block is un-sorted or un-formatted
@@ -79,7 +79,7 @@ Found 4 errors.
Passing an excluded directory directly should report errors in the contained files:
```
```console
∴ cargo run resources/test/project/examples/excluded/
resources/test/project/examples/excluded/script.py:1:8: F401 `os` imported but unused
Found 1 error.
@@ -88,7 +88,7 @@ Found 1 error.
Unless we `--force-exclude`:
```
```console
∴ cargo run resources/test/project/examples/excluded/ --force-exclude
warning: No Python files found under the given path(s)
∴ cargo run resources/test/project/examples/excluded/script.py --force-exclude

View File

@@ -40,7 +40,7 @@
]
},
"exclude": {
"description": "A list of file patterns to exclude from linting.\n\nExclusions are based on globs, and can be either:\n\n- Single-path patterns, like `.mypy_cache` (to exclude any directory named `.mypy_cache` in the tree), `foo.py` (to exclude any file named `foo.py`), or `foo_*.py` (to exclude any file matching `foo_*.py` ). - Relative patterns, like `directory/foo.py` (to exclude that specific file) or `directory/*.py` (to exclude any Python files in `directory`). Note that these paths are relative to the project root (e.g., the directory containing your `pyproject.toml`).\n\nFor more information on the glob syntax, refer to the [`globset` documentation](https://docs.rs/globset/latest/globset/#syntax).\n\nNote that you'll typically want to use [`extend-exclude`](#extend-exclude) to modify the excluded paths.",
"description": "A list of file patterns to exclude from linting.\n\nExclusions are based on globs, and can be either:\n\n* Single-path patterns, like `.mypy_cache` (to exclude any directory named `.mypy_cache` in the tree), `foo.py` (to exclude any file named `foo.py`), or `foo_*.py` (to exclude any file matching `foo_*.py` ). * Relative patterns, like `directory/foo.py` (to exclude that specific file) or `directory/*.py` (to exclude any Python files in `directory`). Note that these paths are relative to the project root (e.g., the directory containing your `pyproject.toml`).\n\nFor more information on the glob syntax, refer to the [`globset` documentation](https://docs.rs/globset/latest/globset/#syntax).\n\nNote that you'll typically want to use [`extend-exclude`](#extend-exclude) to modify the excluded paths.",
"type": [
"array",
"null"
@@ -57,7 +57,7 @@
]
},
"extend-exclude": {
"description": "A list of file patterns to omit from linting, in addition to those specified by `exclude`.\n\nExclusions are based on globs, and can be either:\n\n- Single-path patterns, like `.mypy_cache` (to exclude any directory named `.mypy_cache` in the tree), `foo.py` (to exclude any file named `foo.py`), or `foo_*.py` (to exclude any file matching `foo_*.py` ). - Relative patterns, like `directory/foo.py` (to exclude that specific file) or `directory/*.py` (to exclude any Python files in `directory`). Note that these paths are relative to the project root (e.g., the directory containing your `pyproject.toml`).\n\nFor more information on the glob syntax, refer to the [`globset` documentation](https://docs.rs/globset/latest/globset/#syntax).",
"description": "A list of file patterns to omit from linting, in addition to those specified by `exclude`.\n\nExclusions are based on globs, and can be either:\n\n* Single-path patterns, like `.mypy_cache` (to exclude any directory named `.mypy_cache` in the tree), `foo.py` (to exclude any file named `foo.py`), or `foo_*.py` (to exclude any file matching `foo_*.py` ). * Relative patterns, like `directory/foo.py` (to exclude that specific file) or `directory/*.py` (to exclude any Python files in `directory`). Note that these paths are relative to the project root (e.g., the directory containing your `pyproject.toml`).\n\nFor more information on the glob syntax, refer to the [`globset` documentation](https://docs.rs/globset/latest/globset/#syntax).",
"type": [
"array",
"null"
@@ -556,7 +556,7 @@
]
},
"suppress-none-returning": {
"description": "Whether to suppress `ANN200`-level violations for functions that meet either of the following criteria:\n\n- Contain no `return` statement. - Explicit `return` statement(s) all return `None` (explicitly or implicitly).",
"description": "Whether to suppress `ANN200`-level violations for functions that meet either of the following criteria:\n\n* Contain no `return` statement. * Explicit `return` statement(s) all return `None` (explicitly or implicitly).",
"type": [
"boolean",
"null"
@@ -702,7 +702,7 @@
]
},
"parametrize-names-type": {
"description": "Expected type for multiple argument names in `@pytest.mark.parametrize`. The following values are supported: * `csv` — a comma-separated list, e.g. `@pytest.mark.parametrize('name1,name2', ...)` * `tuple` (default) — e.g. `@pytest.mark.parametrize(('name1', 'name2'), ...)` * `list` — e.g. `@pytest.mark.parametrize(['name1', 'name2'], ...)`",
"description": "Expected type for multiple argument names in `@pytest.mark.parametrize`. The following values are supported:\n\n* `csv` — a comma-separated list, e.g. `@pytest.mark.parametrize('name1,name2', ...)` * `tuple` (default) — e.g. `@pytest.mark.parametrize(('name1', 'name2'), ...)` * `list` — e.g. `@pytest.mark.parametrize(['name1', 'name2'], ...)`",
"anyOf": [
{
"$ref": "#/definitions/ParametrizeNameType"
@@ -713,7 +713,7 @@
]
},
"parametrize-values-row-type": {
"description": "Expected type for each row of values in `@pytest.mark.parametrize` in case of multiple parameters. The following values are supported: * `tuple` (default) — e.g. `@pytest.mark.parametrize(('name1', 'name2'), [(1, 2), (3, 4)])` * `list` — e.g. `@pytest.mark.parametrize(('name1', 'name2'), [[1, 2], [3, 4]])`",
"description": "Expected type for each row of values in `@pytest.mark.parametrize` in case of multiple parameters. The following values are supported:\n\n* `tuple` (default) — e.g. `@pytest.mark.parametrize(('name1', 'name2'), [(1, 2), (3, 4)])` * `list` — e.g. `@pytest.mark.parametrize(('name1', 'name2'), [[1, 2], [3, 4]])`",
"anyOf": [
{
"$ref": "#/definitions/ParametrizeValuesRowType"
@@ -724,7 +724,7 @@
]
},
"parametrize-values-type": {
"description": "Expected type for the list of values rows in `@pytest.mark.parametrize`. The following values are supported: * `tuple` — e.g. `@pytest.mark.parametrize('name', (1, 2, 3))` * `list` (default) — e.g. `@pytest.mark.parametrize('name', [1, 2, 3])`",
"description": "Expected type for the list of values rows in `@pytest.mark.parametrize`. The following values are supported:\n\n* `tuple` — e.g. `@pytest.mark.parametrize('name', (1, 2, 3))` * `list` (default) — e.g. `@pytest.mark.parametrize('name', [1, 2, 3])`",
"anyOf": [
{
"$ref": "#/definitions/ParametrizeValuesType"
@@ -768,7 +768,7 @@
]
},
"docstring-quotes": {
"description": "Quote style to prefer for docstrings (either \"single\" (`'`) or \"double\" (`\"`)).",
"description": "Quote style to prefer for docstrings (either \"single\" or \"double\").",
"anyOf": [
{
"$ref": "#/definitions/Quote"
@@ -779,7 +779,7 @@
]
},
"inline-quotes": {
"description": "Quote style to prefer for inline strings (either \"single\" (`'`) or \"double\" (`\"`)).",
"description": "Quote style to prefer for inline strings (either \"single\" or \"double\").",
"anyOf": [
{
"$ref": "#/definitions/Quote"
@@ -790,7 +790,7 @@
]
},
"multiline-quotes": {
"description": "Quote style to prefer for multiline strings (either \"single\" (`'`) or \"double\" (`\"`)).",
"description": "Quote style to prefer for multiline strings (either \"single\" or \"double\").",
"anyOf": [
{
"$ref": "#/definitions/Quote"
@@ -844,7 +844,7 @@
}
},
"strict": {
"description": "Enforce TC001, TC002, and TC003 rules even when valid runtime imports are present for the same module. See: https://github.com/snok/flake8-type-checking#strict.",
"description": "Enforce TC001, TC002, and TC003 rules even when valid runtime imports are present for the same module. See flake8-type-checking's [strict](https://github.com/snok/flake8-type-checking#strict) option.",
"type": [
"boolean",
"null"
@@ -937,6 +937,16 @@
"null"
]
},
"forced-separate": {
"description": "A list of modules to separate into auxiliary block(s) of imports, in the order specified.",
"type": [
"array",
"null"
],
"items": {
"type": "string"
}
},
"known-first-party": {
"description": "A list of modules to consider first-party, regardless of whether they can be identified as such via introspection of the local filesystem.",
"type": [
@@ -957,6 +967,14 @@
"type": "string"
}
},
"lines-after-imports": {
"description": "The number of blank lines to place after imports. -1 for automatic determination.",
"type": [
"integer",
"null"
],
"format": "int"
},
"no-lines-before": {
"description": "A list of sections that should _not_ be delineated from the previous section via empty lines.",
"type": [
@@ -1102,7 +1120,7 @@
"type": "object",
"properties": {
"keep-runtime-typing": {
"description": "Whether to avoid PEP 585 (`List[int]` -> `list[int]`) and PEP 604 (`Optional[str]` -> `str | None`) rewrites even if a file imports `from __future__ import annotations`. Note that this setting is only applicable when the target Python version is below 3.9 and 3.10 respectively.",
"description": "Whether to avoid PEP 585 (`List[int]` -> `list[int]`) and PEP 604 (`Optional[str]` -> `str | None`) rewrites even if a file imports `from __future__ import annotations`. Note that this setting is only applicable when the target Python version is below 3.9 and 3.10 respectively, and enabling it is equivalent to disabling `use-pep585-annotation` (`UP006`) and `use-pep604-annotation` (`UP007`) entirely.",
"type": [
"boolean",
"null"
@@ -1115,7 +1133,7 @@
"type": "object",
"properties": {
"ignore-overlong-task-comments": {
"description": "Whether or not line-length violations (`E501`) should be triggered for comments starting with `task-tags` (by default: [\"TODO\", \"FIXME\", and \"XXX\"]).",
"description": "Whether line-length violations (`E501`) should be triggered for comments starting with `task-tags` (by default: [\"TODO\", \"FIXME\", and \"XXX\"]).",
"type": [
"boolean",
"null"
@@ -1171,6 +1189,15 @@
],
"format": "uint",
"minimum": 0.0
},
"max-statements": {
"description": "Maximum number of statements allowed for a method or a statement (see: `PLR0915`).",
"type": [
"integer",
"null"
],
"format": "uint",
"minimum": 0.0
}
},
"additionalProperties": false
@@ -1188,14 +1215,14 @@
"Quote": {
"oneOf": [
{
"description": "Use single quotes (`'`).",
"description": "Use single quotes.",
"type": "string",
"enum": [
"single"
]
},
{
"description": "Use double quotes (`\"`).",
"description": "Use double quotes.",
"type": "string",
"enum": [
"double"
@@ -1643,6 +1670,7 @@
"PLR09",
"PLR091",
"PLR0913",
"PLR0915",
"PLR1",
"PLR17",
"PLR170",
@@ -1739,6 +1767,10 @@
"RET506",
"RET507",
"RET508",
"RSE",
"RSE1",
"RSE10",
"RSE102",
"RUF",
"RUF0",
"RUF00",
@@ -1816,6 +1848,10 @@
"SIM4",
"SIM40",
"SIM401",
"SLF",
"SLF0",
"SLF00",
"SLF001",
"T",
"T1",
"T10",
@@ -1892,6 +1928,8 @@
"UP032",
"UP033",
"UP034",
"UP035",
"UP036",
"W",
"W2",
"W29",

View File

@@ -1,6 +1,6 @@
[package]
name = "ruff_cli"
version = "0.0.238"
version = "0.0.241"
authors = ["Charlie Marsh <charlie.r.marsh@gmail.com>"]
edition = "2021"
rust-version = "1.65.0"
@@ -51,7 +51,6 @@ serde = { version = "1.0.147", features = ["derive"] }
serde_json = { version = "1.0.87" }
similar = { version = "2.2.1" }
textwrap = { version = "0.16.0" }
update-informer = { version = "0.6.0", default-features = false, features = ["pypi"], optional = true }
walkdir = { version = "2.3.2" }
strum = "0.24.1"
@@ -60,10 +59,6 @@ assert_cmd = { version = "2.0.4" }
strum = { version = "0.24.1" }
ureq = { version = "2.5.0", features = [] }
[features]
default = ["update-informer"]
update-informer = ["dep:update-informer"]
[package.metadata.maturin]
name = "ruff"
# Setting the name here is necessary for maturin to include the package in its builds.

View File

@@ -1,3 +1,5 @@
#![allow(dead_code)]
use std::path::PathBuf;
use clap::{command, Parser};
@@ -36,7 +38,7 @@ pub enum Command {
#[clap(alias = "--explain")]
Rule {
#[arg(value_parser=Rule::from_code)]
rule: &'static Rule,
rule: Rule,
/// Output format
#[arg(long, value_enum, default_value = "text")]
@@ -209,11 +211,12 @@ pub struct CheckArgs {
/// Exit with status code "0", even upon detecting lint violations.
#[arg(short, long, help_heading = "Miscellaneous")]
pub exit_zero: bool,
/// Enable or disable automatic update checks.
/// Does nothing and will be removed in the future.
#[arg(
long,
overrides_with("no_update_check"),
help_heading = "Miscellaneous"
help_heading = "Miscellaneous",
hide = true
)]
update_check: bool,
#[clap(long, overrides_with("update_check"), hide = true)]
@@ -237,6 +240,7 @@ pub struct CheckArgs {
conflicts_with = "statistics",
conflicts_with = "stdin_filename",
conflicts_with = "watch",
conflicts_with = "fix",
)]
pub add_noqa: bool,
/// See the files Ruff will be run against with the current settings.
@@ -309,13 +313,13 @@ pub struct LogLevelArgs {
impl From<&LogLevelArgs> for LogLevel {
fn from(args: &LogLevelArgs) -> Self {
if args.silent {
LogLevel::Silent
Self::Silent
} else if args.quiet {
LogLevel::Quiet
Self::Quiet
} else if args.verbose {
LogLevel::Verbose
Self::Verbose
} else {
LogLevel::Default
Self::Default
}
}
}

View File

@@ -85,6 +85,10 @@ fn read_sync(cache_dir: &Path, key: u64) -> Result<Vec<u8>, std::io::Error> {
fs::read(cache_dir.join(content_dir()).join(format!("{key:x}")))
}
fn del_sync(cache_dir: &Path, key: u64) -> Result<(), std::io::Error> {
fs::remove_file(cache_dir.join(content_dir()).join(format!("{key:x}")))
}
/// Get a value from the cache.
pub fn get<P: AsRef<Path>>(
path: P,
@@ -137,3 +141,16 @@ pub fn set<P: AsRef<Path>>(
error!("Failed to write to cache: {e:?}");
}
}
/// Delete a value from the cache.
pub fn del<P: AsRef<Path>>(
path: P,
package: Option<&P>,
settings: &AllSettings,
autofix: flags::Autofix,
) {
drop(del_sync(
&settings.cli.cache_dir,
cache_key(path, package, &settings.lib, autofix),
));
}

View File

@@ -1,5 +1,6 @@
use std::fs::remove_dir_all;
use std::io::{self, Read};
use std::io::Write;
use std::io::{self, BufWriter, Read};
use std::path::{Path, PathBuf};
use std::time::Instant;
@@ -11,6 +12,9 @@ use log::{debug, error};
use path_absolutize::path_dedot;
#[cfg(not(target_family = "wasm"))]
use rayon::prelude::*;
use serde::Serialize;
use walkdir::WalkDir;
use ruff::cache::CACHE_DIR_NAME;
use ruff::linter::add_noqa_to_path;
use ruff::logging::LogLevel;
@@ -19,8 +23,6 @@ use ruff::registry::{Linter, Rule, RuleNamespace};
use ruff::resolver::PyprojectDiscovery;
use ruff::settings::flags;
use ruff::{fix, fs, packaging, resolver, warn_user_once, AutofixAvailability, IOError};
use serde::Serialize;
use walkdir::WalkDir;
use crate::args::{HelpFormat, Overrides};
use crate::cache;
@@ -136,7 +138,7 @@ pub fn run(
diagnostics.messages.sort_unstable();
let duration = start.elapsed();
debug!("Checked files in: {:?}", duration);
debug!("Checked {:?} files in: {:?}", paths.len(), duration);
Ok(diagnostics)
}
@@ -230,8 +232,10 @@ pub fn show_settings(
};
let path = entry.path();
let settings = resolver.resolve(path, pyproject_strategy);
println!("Resolved settings for: {path:?}");
println!("{settings:#?}");
let mut stdout = BufWriter::new(io::stdout().lock());
write!(stdout, "Resolved settings for: {path:?}")?;
write!(stdout, "{settings:#?}")?;
Ok(())
}
@@ -251,12 +255,13 @@ pub fn show_files(
}
// Print the list of files.
let mut stdout = BufWriter::new(io::stdout().lock());
for entry in paths
.iter()
.flatten()
.sorted_by(|a, b| a.path().cmp(b.path()))
{
println!("{}", entry.path().to_string_lossy());
writeln!(stdout, "{}", entry.path().to_string_lossy())?;
}
Ok(())
@@ -272,34 +277,39 @@ struct Explanation<'a> {
/// Explain a `Rule` to the user.
pub fn rule(rule: &Rule, format: HelpFormat) -> Result<()> {
let (linter, _) = Linter::parse_code(rule.code()).unwrap();
let mut stdout = BufWriter::new(io::stdout().lock());
match format {
HelpFormat::Text => {
println!("{}\n", rule.as_ref());
println!("Code: {} ({})\n", rule.code(), linter.name());
writeln!(stdout, "{}\n", rule.as_ref())?;
writeln!(stdout, "Code: {} ({})\n", rule.code(), linter.name())?;
if let Some(autofix) = rule.autofixable() {
println!(
writeln!(
stdout,
"{}",
match autofix.available {
AutofixAvailability::Sometimes => "Autofix is sometimes available.\n",
AutofixAvailability::Always => "Autofix is always available.\n",
}
);
)?;
}
println!("Message formats:\n");
writeln!(stdout, "Message formats:\n")?;
for format in rule.message_formats() {
println!("* {format}");
writeln!(stdout, "* {format}")?;
}
}
HelpFormat::Json => {
println!(
writeln!(
stdout,
"{}",
serde_json::to_string_pretty(&Explanation {
code: rule.code(),
linter: linter.name(),
summary: rule.message_formats()[0],
})?
);
)?;
}
};
Ok(())
@@ -307,6 +317,7 @@ pub fn rule(rule: &Rule, format: HelpFormat) -> Result<()> {
/// Clear any caches in the current directory or any subdirectories.
pub fn clean(level: LogLevel) -> Result<()> {
let mut stderr = BufWriter::new(io::stderr().lock());
for entry in WalkDir::new(&*path_dedot::CWD)
.into_iter()
.filter_map(Result::ok)
@@ -315,7 +326,11 @@ pub fn clean(level: LogLevel) -> Result<()> {
let cache = entry.path().join(CACHE_DIR_NAME);
if cache.is_dir() {
if level >= LogLevel::Default {
eprintln!("Removing cache at: {}", fs::relativize_path(&cache).bold());
writeln!(
stderr,
"Removing cache at: {}",
fs::relativize_path(&cache).bold()
)?;
}
remove_dir_all(&cache)?;
}

View File

@@ -2,7 +2,7 @@ use itertools::Itertools;
use serde::Serialize;
use strum::IntoEnumIterator;
use ruff::registry::{Linter, LinterCategory, RuleNamespace};
use ruff::registry::{Linter, RuleNamespace, UpstreamCategory};
use crate::args::HelpFormat;
@@ -12,14 +12,18 @@ pub fn linter(format: HelpFormat) {
for linter in Linter::iter() {
let prefix = match linter.common_prefix() {
"" => linter
.categories()
.upstream_categories()
.unwrap()
.iter()
.map(|LinterCategory(prefix, ..)| prefix)
.map(|UpstreamCategory(prefix, ..)| prefix.as_ref())
.join("/"),
prefix => prefix.to_string(),
};
println!("{:>4} {}", prefix, linter.name());
#[allow(clippy::print_stdout)]
{
println!("{:>4} {}", prefix, linter.name());
}
}
}
@@ -28,10 +32,10 @@ pub fn linter(format: HelpFormat) {
.map(|linter_info| LinterInfo {
prefix: linter_info.common_prefix(),
name: linter_info.name(),
categories: linter_info.categories().map(|cats| {
categories: linter_info.upstream_categories().map(|cats| {
cats.iter()
.map(|LinterCategory(prefix, name, ..)| LinterCategoryInfo {
prefix,
.map(|UpstreamCategory(prefix, name, ..)| LinterCategoryInfo {
prefix: prefix.as_ref(),
name,
})
.collect()
@@ -39,7 +43,10 @@ pub fn linter(format: HelpFormat) {
})
.collect();
println!("{}", serde_json::to_string_pretty(&linters).unwrap());
#[allow(clippy::print_stdout)]
{
println!("{}", serde_json::to_string_pretty(&linters).unwrap());
}
}
}
}

View File

@@ -6,8 +6,9 @@ use std::ops::AddAssign;
use std::path::Path;
use anyhow::Result;
use colored::Colorize;
use log::debug;
use ruff::linter::{lint_fix, lint_only};
use ruff::linter::{lint_fix, lint_only, LinterResult};
use ruff::message::Message;
use ruff::settings::{flags, AllSettings, Settings};
use ruff::{fix, fs};
@@ -67,38 +68,69 @@ pub fn lint_path(
let contents = fs::read_file(path)?;
// Lint the file.
let (messages, fixed) = if matches!(autofix, fix::FixMode::Apply | fix::FixMode::Diff) {
let (transformed, fixed, messages) = lint_fix(&contents, path, package, &settings.lib)?;
if fixed > 0 {
if matches!(autofix, fix::FixMode::Apply) {
write(path, transformed)?;
} else if matches!(autofix, fix::FixMode::Diff) {
let mut stdout = io::stdout().lock();
TextDiff::from_lines(&contents, &transformed)
.unified_diff()
.header(&fs::relativize_path(path), &fs::relativize_path(path))
.to_writer(&mut stdout)?;
stdout.write_all(b"\n")?;
stdout.flush()?;
let (
LinterResult {
data: messages,
error: parse_error,
},
fixed,
) = if matches!(autofix, fix::FixMode::Apply | fix::FixMode::Diff) {
if let Ok((result, transformed, fixed)) = lint_fix(&contents, path, package, &settings.lib)
{
if fixed > 0 {
if matches!(autofix, fix::FixMode::Apply) {
write(path, transformed.as_bytes())?;
} else if matches!(autofix, fix::FixMode::Diff) {
let mut stdout = io::stdout().lock();
TextDiff::from_lines(contents.as_str(), &transformed)
.unified_diff()
.header(&fs::relativize_path(path), &fs::relativize_path(path))
.to_writer(&mut stdout)?;
stdout.write_all(b"\n")?;
stdout.flush()?;
}
}
(result, fixed)
} else {
// If we fail to autofix, lint the original source code.
let result = lint_only(&contents, path, package, &settings.lib, autofix.into());
let fixed = 0;
(result, fixed)
}
(messages, fixed)
} else {
let messages = lint_only(&contents, path, package, &settings.lib, autofix.into())?;
let result = lint_only(&contents, path, package, &settings.lib, autofix.into());
let fixed = 0;
(messages, fixed)
(result, fixed)
};
// Re-populate the cache.
if let Some(metadata) = metadata {
cache::set(
path,
package.as_ref(),
&metadata,
settings,
autofix.into(),
&messages,
);
if let Some(err) = parse_error {
// Notify the user of any parse errors.
#[allow(clippy::print_stderr)]
{
eprintln!(
"{}{} {}{}{} {err}",
"error".red().bold(),
":".bold(),
"Failed to parse ".bold(),
fs::relativize_path(path).bold(),
":".bold()
);
}
// Purge the cache.
cache::del(path, package.as_ref(), settings, autofix.into());
} else {
// Re-populate the cache.
if let Some(metadata) = metadata {
cache::set(
path,
package.as_ref(),
&metadata,
settings,
autofix.into(),
&messages,
);
}
}
Ok(Diagnostics { messages, fixed })
@@ -114,45 +146,80 @@ pub fn lint_stdin(
autofix: fix::FixMode,
) -> Result<Diagnostics> {
// Lint the inputs.
let (messages, fixed) = if matches!(autofix, fix::FixMode::Apply | fix::FixMode::Diff) {
let (transformed, fixed, messages) = lint_fix(
let (
LinterResult {
data: messages,
error: parse_error,
},
fixed,
) = if matches!(autofix, fix::FixMode::Apply | fix::FixMode::Diff) {
if let Ok((result, transformed, fixed)) = lint_fix(
contents,
path.unwrap_or_else(|| Path::new("-")),
package,
settings,
)?;
) {
if matches!(autofix, fix::FixMode::Apply) {
// Write the contents to stdout, regardless of whether any errors were fixed.
io::stdout().write_all(transformed.as_bytes())?;
} else if matches!(autofix, fix::FixMode::Diff) {
// But only write a diff if it's non-empty.
if fixed > 0 {
let text_diff = TextDiff::from_lines(contents, &transformed);
let mut unified_diff = text_diff.unified_diff();
if let Some(path) = path {
unified_diff.header(&fs::relativize_path(path), &fs::relativize_path(path));
}
if matches!(autofix, fix::FixMode::Apply) {
// Write the contents to stdout, regardless of whether any errors were fixed.
io::stdout().write_all(transformed.as_bytes())?;
} else if matches!(autofix, fix::FixMode::Diff) {
// But only write a diff if it's non-empty.
if fixed > 0 {
let text_diff = TextDiff::from_lines(contents, &transformed);
let mut unified_diff = text_diff.unified_diff();
if let Some(path) = path {
unified_diff.header(&fs::relativize_path(path), &fs::relativize_path(path));
let mut stdout = io::stdout().lock();
unified_diff.to_writer(&mut stdout)?;
stdout.write_all(b"\n")?;
stdout.flush()?;
}
let mut stdout = io::stdout().lock();
unified_diff.to_writer(&mut stdout)?;
stdout.write_all(b"\n")?;
stdout.flush()?;
}
}
(messages, fixed)
(result, fixed)
} else {
// If we fail to autofix, lint the original source code.
let result = lint_only(
contents,
path.unwrap_or_else(|| Path::new("-")),
package,
settings,
autofix.into(),
);
let fixed = 0;
// Write the contents to stdout anyway.
if matches!(autofix, fix::FixMode::Apply) {
io::stdout().write_all(contents.as_bytes())?;
}
(result, fixed)
}
} else {
let messages = lint_only(
let result = lint_only(
contents,
path.unwrap_or_else(|| Path::new("-")),
package,
settings,
autofix.into(),
)?;
);
let fixed = 0;
(messages, fixed)
(result, fixed)
};
if let Some(err) = parse_error {
#[allow(clippy::print_stderr)]
{
eprintln!(
"{}{} Failed to parse {}: {err}",
"error".red().bold(),
":".bold(),
path.map_or_else(|| "-".into(), fs::relativize_path).bold()
);
}
}
Ok(Diagnostics { messages, fixed })
}

View File

@@ -2,9 +2,6 @@
//! to automatically update the `ruff help` output in the `README.md`.
//!
//! For the actual Ruff library, see [`ruff`].
#![forbid(unsafe_code)]
#![warn(clippy::pedantic)]
#![allow(clippy::must_use_candidate, dead_code)]
mod args;

View File

@@ -1,28 +1,20 @@
#![forbid(unsafe_code)]
#![warn(clippy::pedantic)]
#![allow(
clippy::match_same_arms,
clippy::missing_errors_doc,
clippy::module_name_repetitions,
clippy::too_many_lines
)]
use std::io::{self};
use std::path::PathBuf;
use std::process::ExitCode;
use std::sync::mpsc::channel;
use ::ruff::logging::{set_up_logging, LogLevel};
use ::ruff::resolver::PyprojectDiscovery;
use ::ruff::settings::types::SerializationFormat;
use ::ruff::{fix, fs, warn_user_once};
use anyhow::Result;
use args::{Args, CheckArgs, Command};
use clap::{CommandFactory, Parser, Subcommand};
use colored::Colorize;
use notify::{recommended_watcher, RecursiveMode, Watcher};
use ::ruff::logging::{set_up_logging, LogLevel};
use ::ruff::resolver::PyprojectDiscovery;
use ::ruff::settings::types::SerializationFormat;
use ::ruff::settings::CliSettings;
use ::ruff::{fix, fs, warn_user_once};
use args::{Args, CheckArgs, Command};
use printer::{Printer, Violations};
use ruff::settings::CliSettings;
pub(crate) mod args;
mod cache;
@@ -31,8 +23,6 @@ mod diagnostics;
mod iterators;
mod printer;
mod resolve;
#[cfg(all(feature = "update-informer"))]
pub mod updates;
fn inner_main() -> Result<ExitCode> {
let mut args: Vec<_> = std::env::args_os().collect();
@@ -58,26 +48,32 @@ fn inner_main() -> Result<ExitCode> {
log_level_args,
} = Args::parse_from(args);
let default_panic_hook = std::panic::take_hook();
std::panic::set_hook(Box::new(move |info| {
eprintln!(
r#"
#[cfg(not(debug_assertions))]
{
let default_panic_hook = std::panic::take_hook();
std::panic::set_hook(Box::new(move |info| {
#[allow(clippy::print_stderr)]
{
eprintln!(
r#"
{}: `ruff` crashed. This indicates a bug in `ruff`. If you could open an issue at:
https://github.com/charliermarsh/ruff/issues/new?title=%5BPanic%5D
quoting the executed command, along with the relevant file contents and `pyproject.toml` settings, we'd be very appreciative!
"#,
"error".red().bold(),
);
default_panic_hook(info);
}));
"error".red().bold(),
);
}
default_panic_hook(info);
}));
}
let log_level: LogLevel = (&log_level_args).into();
set_up_logging(&log_level)?;
match command {
Command::Rule { rule, format } => commands::rule(rule, format)?,
Command::Rule { rule, format } => commands::rule(&rule, format)?,
Command::Linter { format } => commands::linter::linter(format),
Command::Clean => commands::clean(log_level)?,
Command::GenerateShellCompletion { shell } => {
@@ -108,6 +104,7 @@ fn check(args: CheckArgs, log_level: LogLevel) -> Result<ExitCode> {
}
if cli.show_files {
commands::show_files(&cli.files, &pyproject_strategy, &overrides)?;
return Ok(ExitCode::SUCCESS);
}
// Extract options that are included in `Settings`, but only apply at the top
@@ -157,9 +154,15 @@ fn check(args: CheckArgs, log_level: LogLevel) -> Result<ExitCode> {
}
if cli.add_noqa {
if !matches!(autofix, fix::FixMode::None) {
warn_user_once!("--fix is incompatible with --add-noqa.");
}
let modifications = commands::add_noqa(&cli.files, &pyproject_strategy, &overrides)?;
if modifications > 0 && log_level >= LogLevel::Default {
println!("Added {modifications} noqa directives.");
#[allow(clippy::print_stderr)]
{
eprintln!("Added {modifications} noqa directives.");
}
}
return Ok(ExitCode::SUCCESS);
}
@@ -168,7 +171,7 @@ fn check(args: CheckArgs, log_level: LogLevel) -> Result<ExitCode> {
if cli.watch {
if !matches!(autofix, fix::FixMode::None) {
warn_user_once!("--fix is not enabled in watch mode.");
warn_user_once!("--fix is unsupported in watch mode.");
}
if format != SerializationFormat::Text {
warn_user_once!("--format 'text' is used in watch mode.");
@@ -252,14 +255,10 @@ fn check(args: CheckArgs, log_level: LogLevel) -> Result<ExitCode> {
}
}
// Check for updates if we're in a non-silent log level.
#[cfg(feature = "update-informer")]
if update_check
&& !is_stdin
&& log_level >= LogLevel::Default
&& atty::is(atty::Stream::Stdout)
{
drop(updates::check_for_updates());
if update_check {
warn_user_once!(
"update-check has been removed; setting it will cause an error in a future version."
);
}
if !cli.exit_zero {
@@ -289,7 +288,10 @@ pub fn main() -> ExitCode {
match inner_main() {
Ok(code) => code,
Err(err) => {
eprintln!("{}{} {err:?}", "error".red().bold(), ":".bold());
#[allow(clippy::print_stderr)]
{
eprintln!("{}{} {err:?}", "error".red().bold(), ":".bold());
}
ExitCode::FAILURE
}
}

View File

@@ -7,7 +7,7 @@ use annotate_snippets::display_list::{DisplayList, FormatOptions};
use annotate_snippets::snippet::{Annotation, AnnotationType, Slice, Snippet, SourceAnnotation};
use anyhow::Result;
use colored::Colorize;
use itertools::iterate;
use itertools::{iterate, Itertools};
use ruff::fs::relativize_path;
use ruff::logging::LogLevel;
use ruff::message::{Location, Message};
@@ -75,7 +75,7 @@ pub struct Printer<'a> {
}
impl<'a> Printer<'a> {
pub fn new(
pub const fn new(
format: &'a SerializationFormat,
log_level: &'a LogLevel,
autofix: &'a fix::FixMode,
@@ -122,7 +122,7 @@ impl<'a> Printer<'a> {
if num_fixable > 0 {
writeln!(
stdout,
"{num_fixable} potentially fixable with the --fix option."
"[*] {num_fixable} potentially fixable with the --fix option."
)?;
}
}
@@ -344,13 +344,16 @@ impl<'a> Printer<'a> {
}
pub fn write_statistics(&self, diagnostics: &Diagnostics) -> Result<()> {
let mut violations = diagnostics
let violations = diagnostics
.messages
.iter()
.map(|message| message.kind.rule())
.sorted()
.dedup()
.collect::<Vec<_>>();
violations.sort();
violations.dedup();
if violations.is_empty() {
return Ok(());
}
let statistics = violations
.iter()
@@ -472,17 +475,31 @@ fn num_digits(n: usize) -> usize {
/// Print a single `Message` with full details.
fn print_message<T: Write>(stdout: &mut T, message: &Message) -> Result<()> {
let label = format!(
"{}{}{}{}{}{} {} {}",
relativize_path(Path::new(&message.filename)).bold(),
":".cyan(),
message.location.row(),
":".cyan(),
message.location.column(),
":".cyan(),
message.kind.rule().code().red().bold(),
message.kind.body(),
);
let label = if message.kind.fixable() {
format!(
"{}{}{}{}{}{} {} [*] {}",
relativize_path(Path::new(&message.filename)).bold(),
":".cyan(),
message.location.row(),
":".cyan(),
message.location.column(),
":".cyan(),
message.kind.rule().code().red().bold(),
message.kind.body(),
)
} else {
format!(
"{}{}{}{}{}{} {} {}",
relativize_path(Path::new(&message.filename)).bold(),
":".cyan(),
message.location.row(),
":".cyan(),
message.location.column(),
":".cyan(),
message.kind.rule().code().red().bold(),
message.kind.body(),
)
};
writeln!(stdout, "{label}")?;
if let Some(source) = &message.source {
let commit = message.kind.commit();
@@ -537,16 +554,29 @@ fn print_grouped_message<T: Write>(
row_length: usize,
column_length: usize,
) -> Result<()> {
let label = format!(
" {}{}{}{}{} {} {}",
" ".repeat(row_length - num_digits(message.location.row())),
message.location.row(),
":".cyan(),
message.location.column(),
" ".repeat(column_length - num_digits(message.location.column())),
message.kind.rule().code().red().bold(),
message.kind.body(),
);
let label = if message.kind.fixable() {
format!(
" {}{}{}{}{} {} [*] {}",
" ".repeat(row_length - num_digits(message.location.row())),
message.location.row(),
":".cyan(),
message.location.column(),
" ".repeat(column_length - num_digits(message.location.column())),
message.kind.rule().code().red().bold(),
message.kind.body(),
)
} else {
format!(
" {}{}{}{}{} {} {}",
" ".repeat(row_length - num_digits(message.location.row())),
message.location.row(),
":".cyan(),
message.location.column(),
" ".repeat(column_length - num_digits(message.location.column())),
message.kind.rule().code().red().bold(),
message.kind.body(),
)
};
writeln!(stdout, "{label}")?;
if let Some(source) = &message.source {
let commit = message.kind.commit();

View File

@@ -1,75 +0,0 @@
use std::fs::{create_dir_all, read_to_string, File};
use std::io::Write;
use std::path::{Path, PathBuf};
use anyhow::Result;
use colored::Colorize;
const CARGO_PKG_NAME: &str = env!("CARGO_PKG_NAME");
const CARGO_PKG_VERSION: &str = env!("CARGO_PKG_VERSION");
fn cache_dir() -> &'static str {
"./.ruff_cache"
}
fn file_path() -> PathBuf {
Path::new(cache_dir()).join(".update-informer")
}
/// Get the "latest" version for which the user has been informed.
fn get_latest() -> Result<Option<String>> {
let path = file_path();
if path.exists() {
Ok(Some(read_to_string(path)?.trim().to_string()))
} else {
Ok(None)
}
}
/// Set the "latest" version for which the user has been informed.
fn set_latest(version: &str) -> Result<()> {
create_dir_all(cache_dir())?;
let path = file_path();
let mut file = File::create(path)?;
file.write_all(version.trim().as_bytes())?;
Ok(())
}
/// Update the user if a newer version is available.
pub fn check_for_updates() -> Result<()> {
use update_informer::{registry, Check};
let informer = update_informer::new(registry::PyPI, CARGO_PKG_NAME, CARGO_PKG_VERSION);
if let Some(new_version) = informer
.check_version()
.ok()
.flatten()
.map(|version| version.to_string())
{
// If we've already notified the user about this version, return early.
if let Some(latest_version) = get_latest()? {
if latest_version == new_version {
return Ok(());
}
}
set_latest(&new_version)?;
let msg = format!(
"A new version of {pkg_name} is available: v{pkg_version} -> {new_version}",
pkg_name = CARGO_PKG_NAME.italic().cyan(),
pkg_version = CARGO_PKG_VERSION,
new_version = new_version.green()
);
let cmd = format!(
"Run to update: {cmd} {pkg_name}",
cmd = "pip3 install --upgrade".green(),
pkg_name = CARGO_PKG_NAME.green()
);
println!("\n{msg}\n{cmd}");
}
Ok(())
}

View File

@@ -31,8 +31,10 @@ fn test_stdin_error() -> Result<()> {
.failure();
assert_eq!(
str::from_utf8(&output.get_output().stdout)?,
"-:1:8: F401 `os` imported but unused\nFound 1 error.\n1 potentially fixable with the \
--fix option.\n"
r#"-:1:8: F401 [*] `os` imported but unused
Found 1 error.
[*] 1 potentially fixable with the --fix option.
"#
);
Ok(())
}
@@ -54,8 +56,10 @@ fn test_stdin_filename() -> Result<()> {
.failure();
assert_eq!(
str::from_utf8(&output.get_output().stdout)?,
"F401.py:1:8: F401 `os` imported but unused\nFound 1 error.\n1 potentially fixable with \
the --fix option.\n"
r#"F401.py:1:8: F401 [*] `os` imported but unused
Found 1 error.
[*] 1 potentially fixable with the --fix option.
"#
);
Ok(())
}

View File

@@ -1,6 +1,6 @@
[package]
name = "ruff_dev"
version = "0.0.238"
version = "0.0.241"
edition = "2021"
[dependencies]
@@ -11,9 +11,9 @@ libcst = { git = "https://github.com/charliermarsh/LibCST", rev = "f2f0b7a487a87
once_cell = { version = "1.16.0" }
ruff = { path = ".." }
ruff_cli = { path = "../ruff_cli" }
rustpython-ast = { features = ["unparse"], git = "https://github.com/RustPython/RustPython.git", rev = "4f38cb68e4a97aeea9eb19673803a0bd5f655383" }
rustpython-common = { git = "https://github.com/RustPython/RustPython.git", rev = "4f38cb68e4a97aeea9eb19673803a0bd5f655383" }
rustpython-parser = { features = ["lalrpop"], git = "https://github.com/RustPython/RustPython.git", rev = "4f38cb68e4a97aeea9eb19673803a0bd5f655383" }
rustpython-ast = { features = ["unparse"], git = "https://github.com/RustPython/RustPython.git", rev = "adc23253e4b58980b407ba2760dbe61681d752fc" }
rustpython-common = { git = "https://github.com/RustPython/RustPython.git", rev = "adc23253e4b58980b407ba2760dbe61681d752fc" }
rustpython-parser = { features = ["lalrpop"], git = "https://github.com/RustPython/RustPython.git", rev = "adc23253e4b58980b407ba2760dbe61681d752fc" }
schemars = { version = "0.8.11" }
serde_json = { version = "1.0.91" }
strum = { version = "0.24.1", features = ["strum_macros"] }

View File

@@ -1,13 +1,14 @@
//! Generate CLI help.
#![allow(clippy::print_stdout, clippy::print_stderr)]
use crate::utils::replace_readme_section;
use anyhow::Result;
use std::str;
const COMMAND_HELP_BEGIN_PRAGMA: &str = "<!-- Begin auto-generated command help. -->";
const COMMAND_HELP_BEGIN_PRAGMA: &str = "<!-- Begin auto-generated command help. -->\n";
const COMMAND_HELP_END_PRAGMA: &str = "<!-- End auto-generated command help. -->";
const SUBCOMMAND_HELP_BEGIN_PRAGMA: &str = "<!-- Begin auto-generated subcommand help. -->";
const SUBCOMMAND_HELP_BEGIN_PRAGMA: &str = "<!-- Begin auto-generated subcommand help. -->\n";
const SUBCOMMAND_HELP_END_PRAGMA: &str = "<!-- End auto-generated subcommand help. -->";
#[derive(clap::Args)]
@@ -33,12 +34,12 @@ pub fn main(args: &Args) -> Result<()> {
print!("{subcommand_help}");
} else {
replace_readme_section(
&format!("```\n{command_help}\n```\n"),
&format!("```text\n{command_help}\n```\n\n"),
COMMAND_HELP_BEGIN_PRAGMA,
COMMAND_HELP_END_PRAGMA,
)?;
replace_readme_section(
&format!("```\n{subcommand_help}\n```\n"),
&format!("```text\n{subcommand_help}\n```\n\n"),
SUBCOMMAND_HELP_BEGIN_PRAGMA,
SUBCOMMAND_HELP_END_PRAGMA,
)?;

View File

@@ -1,3 +1,5 @@
#![allow(clippy::print_stdout, clippy::print_stderr)]
use std::fs;
use std::path::PathBuf;
@@ -5,6 +7,8 @@ use anyhow::Result;
use ruff::settings::options::Options;
use schemars::schema_for;
use crate::ROOT_DIR;
#[derive(clap::Args)]
pub struct Args {
/// Write the generated table to stdout (rather than to `ruff.schema.json`).
@@ -19,10 +23,7 @@ pub fn main(args: &Args) -> Result<()> {
if args.dry_run {
println!("{schema_string}");
} else {
let file = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.parent()
.expect("Failed to find root directory")
.join("ruff.schema.json");
let file = PathBuf::from(ROOT_DIR).join("ruff.schema.json");
fs::write(file, schema_string.as_bytes())?;
}
Ok(())

View File

@@ -1,4 +1,5 @@
//! Generate a Markdown-compatible listing of configuration options.
#![allow(clippy::print_stdout, clippy::print_stderr)]
use anyhow::Result;
use itertools::Itertools;
@@ -7,7 +8,7 @@ use ruff::settings::options_base::{ConfigurationOptions, OptionEntry, OptionFiel
use crate::utils::replace_readme_section;
const BEGIN_PRAGMA: &str = "<!-- Begin auto-generated options sections. -->";
const BEGIN_PRAGMA: &str = "<!-- Begin auto-generated options sections. -->\n";
const END_PRAGMA: &str = "<!-- End auto-generated options sections. -->";
#[derive(clap::Args)]

View File

@@ -1,13 +1,14 @@
//! Generate a Markdown-compatible table of supported lint rules.
#![allow(clippy::print_stdout, clippy::print_stderr)]
use anyhow::Result;
use itertools::Itertools;
use ruff::registry::{Linter, LinterCategory, Rule, RuleNamespace};
use ruff::registry::{Linter, Rule, RuleNamespace, UpstreamCategory};
use strum::IntoEnumIterator;
use crate::utils::replace_readme_section;
const TABLE_BEGIN_PRAGMA: &str = "<!-- Begin auto-generated sections. -->";
const TABLE_BEGIN_PRAGMA: &str = "<!-- Begin auto-generated sections. -->\n";
const TABLE_END_PRAGMA: &str = "<!-- End auto-generated sections. -->";
const TOC_BEGIN_PRAGMA: &str = "<!-- Begin auto-generated table of contents. -->";
@@ -50,10 +51,10 @@ pub fn main(args: &Args) -> Result<()> {
for linter in Linter::iter() {
let codes_csv: String = match linter.common_prefix() {
"" => linter
.categories()
.upstream_categories()
.unwrap()
.iter()
.map(|LinterCategory(prefix, ..)| prefix)
.map(|UpstreamCategory(prefix, ..)| prefix.as_ref())
.join(", "),
prefix => prefix.to_string(),
};
@@ -93,11 +94,12 @@ pub fn main(args: &Args) -> Result<()> {
table_out.push('\n');
}
if let Some(categories) = linter.categories() {
for LinterCategory(prefix, name, selector) in categories {
table_out.push_str(&format!("#### {name} ({prefix})"));
if let Some(categories) = linter.upstream_categories() {
for UpstreamCategory(prefix, name) in categories {
table_out.push_str(&format!("#### {name} ({})", prefix.as_ref()));
table_out.push('\n');
generate_table(&mut table_out, selector);
table_out.push('\n');
generate_table(&mut table_out, prefix);
}
} else {
generate_table(&mut table_out, &linter);

View File

@@ -1,20 +1,6 @@
//! This crate implements an internal CLI for developers of Ruff.
//!
//! Within the ruff repository you can run it with `cargo dev`.
#![forbid(unsafe_code)]
#![warn(clippy::pedantic)]
#![allow(
clippy::collapsible_else_if,
clippy::collapsible_if,
clippy::implicit_hasher,
clippy::match_same_arms,
clippy::missing_errors_doc,
clippy::missing_panics_doc,
clippy::module_name_repetitions,
clippy::must_use_candidate,
clippy::similar_names,
clippy::too_many_lines
)]
mod generate_all;
mod generate_cli_help;
@@ -30,6 +16,8 @@ mod utils;
use anyhow::Result;
use clap::{Parser, Subcommand};
const ROOT_DIR: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/../");
#[derive(Parser)]
#[command(author, version, about, long_about = None)]
#[command(propagate_version = true)]

View File

@@ -1,4 +1,5 @@
//! Print the AST for a given Python file.
#![allow(clippy::print_stdout, clippy::print_stderr)]
use std::fs;
use std::path::PathBuf;

View File

@@ -1,4 +1,5 @@
//! Print the `LibCST` CST for a given Python file.
#![allow(clippy::print_stdout, clippy::print_stderr)]
use std::fs;
use std::path::PathBuf;

View File

@@ -1,4 +1,5 @@
//! Print the token stream for a given Python file.
#![allow(clippy::print_stdout, clippy::print_stderr)]
use std::fs;
use std::path::PathBuf;

View File

@@ -1,4 +1,5 @@
//! Run round-trip source code generation on a given Python file.
#![allow(clippy::print_stdout, clippy::print_stderr)]
use std::fs;
use std::path::PathBuf;

View File

@@ -5,12 +5,11 @@ use std::path::PathBuf;
use anyhow::Result;
use crate::ROOT_DIR;
pub fn replace_readme_section(content: &str, begin_pragma: &str, end_pragma: &str) -> Result<()> {
// Read the existing file.
let file = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.parent()
.expect("Failed to find root directory")
.join("README.md");
let file = PathBuf::from(ROOT_DIR).join("README.md");
let existing = fs::read_to_string(&file)?;
// Extract the prefix.

View File

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

View File

@@ -176,7 +176,7 @@ impl Parse for FieldAttributes {
input.parse::<Comma>()?;
}
Ok(FieldAttributes {
Ok(Self {
default,
value_type,
example: textwrap::dedent(&example).trim_matches('\n').to_string(),

View File

@@ -29,7 +29,7 @@ pub fn define_rule_mapping(mapping: &Mapping) -> proc_macro2::TokenStream {
.extend(quote! {Self::#name => <#path as Violation>::message_formats(),});
rule_autofixable_match_arms.extend(quote! {Self::#name => <#path as Violation>::AUTOFIX,});
rule_code_match_arms.extend(quote! {Self::#name => #code_str,});
rule_from_code_match_arms.extend(quote! {#code_str => Ok(&Rule::#name), });
rule_from_code_match_arms.extend(quote! {#code_str => Ok(Rule::#name), });
diagkind_code_match_arms.extend(quote! {Self::#name(..) => &Rule::#name, });
diagkind_body_match_arms.extend(quote! {Self::#name(x) => Violation::message(x), });
diagkind_fixable_match_arms
@@ -96,7 +96,7 @@ pub fn define_rule_mapping(mapping: &Mapping) -> proc_macro2::TokenStream {
match self { #rule_code_match_arms }
}
pub fn from_code(code: &str) -> Result<&'static Self, FromCodeError> {
pub fn from_code(code: &str) -> Result<Self, FromCodeError> {
match code {
#rule_from_code_match_arms
_ => Err(FromCodeError::Unknown),
@@ -148,6 +148,6 @@ impl Parse for Mapping {
let _: Token![,] = input.parse()?;
entries.push((code, path, name));
}
Ok(Mapping { entries })
Ok(Self { entries })
}
}

View File

@@ -1,18 +1,4 @@
//! This crate implements internal macros for the `ruff` library.
#![forbid(unsafe_code)]
#![warn(clippy::pedantic)]
#![allow(
clippy::collapsible_else_if,
clippy::collapsible_if,
clippy::implicit_hasher,
clippy::match_same_arms,
clippy::missing_errors_doc,
clippy::missing_panics_doc,
clippy::module_name_repetitions,
clippy::must_use_candidate,
clippy::similar_names,
clippy::too_many_lines
)]
use proc_macro::TokenStream;
use syn::{parse_macro_input, DeriveInput, ItemFn};

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