Compare commits

...

97 Commits

Author SHA1 Message Date
Charlie Marsh
cf56955ba6 Bump version to 0.0.227 2023-01-19 23:24:52 -05:00
Charlie Marsh
8a8939afd8 Avoid checking row types for single-name @parametrize decorators (#2013)
Closes #2008.
2023-01-19 22:13:17 -05:00
Martin Fischer
6acf2accc6 Improve --explain output
Previous output for `ruff --explain E711`:

    E711 (pycodestyle): Comparison to `None` should be `cond is None`

New output:

    none-comparison

    Code: E711 (pycodestyle)

    Autofix is always available.

    Message formats:

    * Comparison to `None` should be `cond is None`
    * Comparison to `None` should be `cond is not None`
2023-01-19 22:08:00 -05:00
Charlie Marsh
ec0c7647ab Avoid SIM401 in elif blocks (#2012)
For now, we're just gonna avoid flagging this for `elif` blocks, following the same reasoning as for ternaries. We can handle all of these cases, but we'll knock out the TODOs as a pair, and this avoids broken code.

Closes #2007.
2023-01-19 21:57:18 -05:00
Charlie Marsh
045229630e Upgrade RustPython (#2011)
This lets us revert the "manual" fix introduced in #1944.
2023-01-19 21:49:12 -05:00
Martin Fischer
c600991905 Change AsRef<str> impl for Rule to kebab-case
As we surface rule names more to users we want
them to be easier to type than PascalCase.

Prior art:

Pylint and ESLint also use kebab-case for their rule names.
Clippy uses snake_case but only for syntactical reasons
(so that the argument to e.g. #![allow(clippy::some_lint)]
can be parsed as a path[1]).

[1]: https://doc.rust-lang.org/reference/paths.html
2023-01-19 21:37:11 -05:00
Charlie Marsh
f6a93a4c3d Enable autofix for FitsOnOneLine (D200) (#2006)
Closes #1965.
2023-01-19 19:24:50 -05:00
Aarni Koskela
de54ff114e Add RUF005 "unpack instead of concatenating" check (#1957)
This PR adds a new check that turns expressions such as `[1, 2, 3] + foo` into `[1, 2, 3, *foo]`, since the latter is easier to read and faster:

```
~ $ python3.11 -m timeit -s 'b = [6, 5, 4]' '[1, 2, 3] + b'
5000000 loops, best of 5: 81.4 nsec per loop
~ $ python3.11 -m timeit -s 'b = [6, 5, 4]' '[1, 2, 3, *b]'
5000000 loops, best of 5: 66.2 nsec per loop
```

However there's a couple of gotchas:

* This felt like a `simplify` rule, so I borrowed an unused `SIM` code even if the upstream `flake8-simplify` doesn't do this transform. If it should be assigned some other code, let me know 😄 
* **More importantly** this transform could be unsafe if the other operand of the `+` operation has overridden `__add__` to do something else. What's the `ruff` policy around potentially unsafe operations? (I think some of the suggestions other ported rules give could be semantically different from the original code, but I'm not sure.)
* I'm not a very established Rustacean, so there's no doubt my code isn't quite idiomatic. (For instance, is there a neater way to write that four-way `match` statement?)

Thanks for `ruff`, by the way! :)
2023-01-19 17:38:17 -05:00
Charlie Marsh
64b398c72b Tweak some instructions in CONTRIBUTING.md 2023-01-19 17:17:39 -05:00
Aarni Koskela
c99bd3fa60 Split up pydocstyle rules (#2003)
As per @not-mAs per @not-my-profile's [comment](https://github.com/charliermarsh/ruff/pull/1999#discussion_r1081579337):

> we actually want to break up such rules.rs files into smaller files

this breaks up `pydocstyle/rules.rs` into a directory.y-profile's [comment](https://github.com/charliermarsh/ruff/pull/1999#discussion_r1081579337):

> we actually want to break up such rules.rs files into smaller files

this breaks up `pydocstyle/rules.rs` into a directory.
2023-01-19 13:17:25 -05:00
Martin Fischer
8ac930f886 Fix that --explain panics
This commit fixes a bug accidentally introduced in
6cf770a692,
which resulted every `ruff --explain <code>` invocation to fail with:

    thread 'main' panicked at 'Mismatch between definition and access of `explain`.
    Could not downcast to ruff::registry::Rule, need to downcast to &ruff::registry::Rule',
    ruff_cli/src/cli.rs:184:18

We also add an integration test for --explain to prevent such bugs from
going by unnoticed in the future.
2023-01-19 12:58:44 -05:00
Charlie Marsh
ad80fdc2cd Avoid SIM201 and SIM202 errors in __ne__ et al (#2001)
Closes #1986.
2023-01-19 11:27:27 -05:00
Aarni Koskela
a0ea8fe22f Apply #[derive(Default)] fixes suggested by Clippy (#2000)
These were bugging me every time I ran `clippy` 😁
2023-01-19 11:04:43 -05:00
Martin Fischer
3c3da8a88c derive-msg-formats 5/5: Remove placeholder implementations
# This commit has been generated via the following Python script:
# (followed by `cargo +nightly fmt` and `cargo dev generate-all`)
# For the reasoning see the previous commit(s).

import re
import sys

for path in (
    'src/violations.rs',
    'src/rules/flake8_tidy_imports/banned_api.rs',
    'src/rules/flake8_tidy_imports/relative_imports.rs',
):
    with open(path) as f:
        text = ''

        while line := next(f, None):

            if line.strip() != 'fn message(&self) -> String {':
                text += line
                continue

            text += '    #[derive_message_formats]\n' + line

            body = next(f)
            while (line := next(f)) != '    }\n':
                body += line

            # body = re.sub(r'(?<!code\| |\.push\()format!', 'format!', body)
            body = re.sub(
                r'("[^"]+")\s*\.to_string\(\)', r'format!(\1)', body, re.DOTALL
            )
            body = re.sub(
                r'(r#".+?"#)\s*\.to_string\(\)', r'format!(\1)', body, re.DOTALL
            )

            text += body + '    }\n'

            while (line := next(f)).strip() != 'fn placeholder() -> Self {':
                text += line
            while (line := next(f)) != '    }\n':
                pass

    with open(path, 'w') as f:
        f.write(text)
2023-01-19 11:03:32 -05:00
Martin Fischer
16e79c8db6 derive-msg-formats 4/5: Implement #[derive_message_formats]
The idea is nice and simple we replace:

    fn placeholder() -> Self;

with

    fn message_formats() -> &'static [&'static str];

So e.g. if a Violation implementation defines:

    fn message(&self) -> String {
        format!("Local variable `{name}` is assigned to but never used")
    }

it would also have to define:

   fn message_formats() -> &'static [&'static str] {
       &["Local variable `{name}` is assigned to but never used"]
   }

Since we however obviously do not want to duplicate all of our format
strings we simply introduce a new procedural macro attribute
#[derive_message_formats] that can be added to the message method
declaration in order to automatically derive the message_formats
implementation.

This commit implements the macro. The following and final commit
updates violations.rs to use the macro. (The changes have been separated
because the next commit is autogenerated via a Python script.)
2023-01-19 11:03:32 -05:00
Martin Fischer
8f6d8e215c derive-msg-formats 3/5: Introduce Violation::AUTOFIX associated constant
ruff_dev::generate_rules_table previously documented which rules are
autofixable via DiagnosticKind::fixable ... since the DiagnosticKind was
obtained via Rule::kind (and Violation::placeholder) which we both want
to get rid of we have to obtain the autofixability via another way.

This commit implements such another way by adding an AUTOFIX
associated constant to the Violation trait. The constant is of the type
Option<AutoFixkind>, AutofixKind is a new struct containing an
Availability enum { Sometimes, Always}, letting us additionally document
that some autofixes are only available sometimes (which previously
wasn't documented). We intentionally introduce this information in a
struct so that we can easily introduce further autofix metadata in the
future such as autofix applicability[1].

[1]: https://doc.rust-lang.org/stable/nightly-rustc/rustc_errors/enum.Applicability.html
2023-01-19 11:03:32 -05:00
Martin Fischer
8993baab01 derive-msg-formats 2/5: Remove DiagnosticKind::summary
While ruff displays the string returned by Violation::message in its
output for detected violations the messages displayed in the README
and in the `--explain <code>` output previously used the
DiagnosticKind::summary() function which for some verbose messages
provided shorter descriptions.

This commit removes DiagnosticKind::summary, and moves the more
extensive documentation into doc comments ... these are not displayed
yet to the user but doing that is very much planned.
2023-01-19 11:03:32 -05:00
Martin Fischer
2568627c4c derive-msg-formats 1/5: Remove unnecessary usages of Rule::kind
This commit series removes the following associated
function from the Violation trait:

    fn placeholder() -> Self;

ruff previously used this placeholder approach for the messages it
listed in the README and displayed when invoked with --explain <code>.

This approach is suboptimal for three reasons:

1. The placeholder implementations are completely boring code since they
   just initialize the struct with some dummy values.

2. Displaying concrete error messages with arbitrary interpolated values
   can be confusing for the user since they might not recognize that the
   values are interpolated.

3. Some violations have varying format strings depending on the
   violation which could not be documented with the previous approach
   (while we could have changed the signature to return Vec<Self> this
   would still very much suffer from the previous two points).

We therefore drop Violation::placeholder in favor of a new macro-based
approach, explained in commit 4/5.

Violation::placeholder is only invoked via Rule::kind, so we firstly
have to get rid of all Rule::kind invocations ... this commit starts
removing the trivial cases.
2023-01-19 11:03:32 -05:00
Martin Fischer
9603a024b3 refactor: Move a bunch of pandas-vet logic to rules::pandas_vet 2023-01-19 11:03:32 -05:00
Charlie Marsh
a122d95ef5 Preserve unmatched comparators in SIM109 (#1998)
Closes #1993.
2023-01-19 10:23:20 -05:00
Damien Allen
6ddfe50ac4 Added pylint formatter (#1995)
Fixes: #1953

@charliermarsh thank you for the tips in the issue.

I'm not very familiar with Rust, so please excuse if my string formatting syntax is messy.

In terms of testing, I compared output of `flake8 --format=pylint ` and `cargo run --format=pylint` on the same code and the output syntax seems to check out.
2023-01-19 08:01:27 -05:00
Martin Fischer
26901a78c9 Make define_rule_mapping! set rule code as doc comment of variants
Since the UI still relies on the rule codes this improves the developer
experience by letting developers view the code of a Rule enum variant by
hovering over it.
2023-01-19 07:37:16 -05:00
Martin Fischer
6649225167 rule 8/8: Automatically rewrite RuleCode to Rule
# This commit was automatically generated by running the following
# script (followed by `cargo +nightly fmt`):

import glob
import re
from typing import NamedTuple

class Rule(NamedTuple):
    code: str
    name: str
    path: str

def rules() -> list[Rule]:
    """Returns all the rules defined in `src/registry.rs`."""
    file = open('src/registry.rs')

    rules = []

    while next(file) != 'ruff_macros::define_rule_mapping!(\n':
        continue

    while (line := next(file)) != ');\n':
        line = line.strip().rstrip(',')
        if line.startswith('//'):
            continue
        code, path = line.split(' => ')
        name = path.rsplit('::')[-1]
        rules.append(Rule(code, name, path))

    return rules

code2name = {r.code: r.name for r in rules()}

for pattern in ('src/**/*.rs', 'ruff_cli/**/*.rs', 'ruff_dev/**/*.rs', 'scripts/add_*.py'):
    for name in glob.glob(pattern, recursive=True):
        with open(name) as f:
            text = f.read()

        text = re.sub('Rule(?:Code)?::([A-Z]\w+)', lambda m: 'Rule::' + code2name[m.group(1)], text)
        text = re.sub(r'(?<!"<FilePattern>:<)RuleCode\b', 'Rule', text)
        text = re.sub('(use crate::registry::{.*, Rule), Rule(.*)', r'\1\2', text) # fix duplicate import

        with open(name, 'w') as f:
            f.write(text)
2023-01-18 23:51:48 -05:00
Martin Fischer
9e3083aa2c rule 7/8: Change Rule enum definition 2023-01-18 23:51:48 -05:00
Martin Fischer
6d11ff3822 rule 6/8: Remove Serialize & Deserialize impls for Rule 2023-01-18 23:51:48 -05:00
Martin Fischer
6cf770a692 rule 5/8: Remove FromStr impl for Rule 2023-01-18 23:51:48 -05:00
Martin Fischer
3534e370e1 rule 4/8: Remove Display impl for Rule 2023-01-18 23:51:48 -05:00
Martin Fischer
dbcab5128c rule 3/8: Remove AsRef<str> impl for Rule 2023-01-18 23:51:48 -05:00
Martin Fischer
3810250bb6 rule 2/8: Rename DiagnosticKind::code to rule 2023-01-18 23:51:48 -05:00
Martin Fischer
3c1c1e1dd3 rule 1/8: Rename RuleCode to Rule (backwards-compatible for now)
This commit series refactors ruff to decouple "rules" from "rule codes",
in order to:

1. Make our code more readable by changing e.g.
   RuleCode::UP004 to Rule::UselessObjectInheritance.

2. Let us cleanly map multiple codes to one rule, for example:

   [UP004] in pyupgrade, [R0205] in pylint and [PIE792] in flake8-pie
   all refer to the rule UselessObjectInheritance but ruff currently
   only associates that rule with the UP004 code (since the
   implementation was initially modeled after pyupgrade).

3. Let us cleanly map one code to multiple rules, for example:

   [C0103] from pylint encompasses N801, N802 and N803 from pep8-naming.

The latter two steps are not yet implemented by this commit series
but this refactoring enables us to introduce such a mapping.  Such a
mapping would also let us expand flake8_to_ruff to support e.g. pylint.

After the next commit which just does some renaming the following four
commits remove all trait derivations from the Rule (previously RuleCode)
enum that depend on the variant names to guarantee that they are not
used anywhere anymore so that we can rename all of these variants in the
eigth and final commit without breaking anything.

While the plan very much is to also surface these human-friendly names
more in the user interface this is not yet done in this commit series,
which does not change anything about the UI: it's purely a refactor.

[UP004]: pyupgrade doesn't actually assign codes to its messages.
[R0205]: https://pylint.pycqa.org/en/latest/user_guide/messages/refactor/useless-object-inheritance.html
[PIE792]: https://github.com/sbdchd/flake8-pie#pie792-no-inherit-object
[C0103]: https://pylint.pycqa.org/en/latest/user_guide/messages/convention/invalid-name.html
2023-01-18 23:51:48 -05:00
Martin Fischer
5b7bd93b91 refactor: Use Self:: in match arms 2023-01-18 23:51:48 -05:00
Martin Fischer
9e096b4a4c refactor: Make define_rule_mapping! generate RuleCodePrefix directly 2023-01-18 23:51:48 -05:00
Charlie Marsh
d8645acd1f Bump version to 0.0.226 2023-01-18 20:54:38 -05:00
Charlie Marsh
92dd073191 Add Pylint settings to lib_wasm.rs 2023-01-18 20:54:03 -05:00
Charlie Marsh
ff96219e62 Exclude None, Bool, and Ellipsis from ConstantType (#1988)
These have no effect, so it's confusing that they're even settable.
2023-01-18 20:50:09 -05:00
Charlie Marsh
d33424ec9d Enable suppression of magic values by type (#1987)
Closes #1949.
2023-01-18 20:44:24 -05:00
Charlie Marsh
34412a0a01 Avoid removing side effects for boolean simplifications (#1984)
Closes #1978.
2023-01-18 19:08:14 -05:00
Charlie Marsh
ceb48d3a32 Use relative paths for INP001 (#1981) 2023-01-18 18:45:47 -05:00
Charlie Marsh
969a6f0d53 Replace misplaced-comparison-constant with SIM300 (#1980)
Closes: #1954.
2023-01-18 18:42:49 -05:00
Charlie Marsh
7628876ff2 Invert order of yoda-conditions message (#1979)
The suggestion was wrong!
2023-01-18 18:27:36 -05:00
Charlie Marsh
ef355e5c2c Remove artificial wraps from GitHub messages (#1977) 2023-01-18 18:20:56 -05:00
Charlie Marsh
97f55b8e97 Convert remaining call path sites to use SmallVec (#1972) 2023-01-18 14:50:33 -05:00
Aarni Koskela
ff2be35f51 Run cargo fmt in pre-commit (#1968)
Since `cargo fmt` is a required CI check, we could just as well run it in `pre-commit`.
2023-01-18 13:14:51 -05:00
Anders Kaseorg
1e803f7108 README: Link “Flake8” for consistency with the rest of the list (#1969)
Signed-off-by: Anders Kaseorg <andersk@mit.edu>
2023-01-18 13:07:21 -05:00
Charlie Marsh
1ab0273aa7 Strip whitespace when injecting D209 newline (#1967)
Closes #1963.
2023-01-18 12:09:17 -05:00
Charlie Marsh
5a7d8c25f4 Treat subscript accesses as unsafe effects for autofix (#1966)
See: #1809.
2023-01-18 11:46:12 -05:00
Charlie Marsh
26d6414558 Fix UP003 check from rebase 2023-01-18 11:39:39 -05:00
Charlie Marsh
dae95626ae Use smallvec for call path representation (#1960)
This provides a ~10% speed-up for large codebases with `--select ALL`:

![Screen Shot 2023-01-18 at 11 28 20 AM](https://user-images.githubusercontent.com/1309177/213236389-cff50840-6e55-47a3-9164-2e40cbc885f6.png)
2023-01-18 11:29:05 -05:00
Maksudul Haque
9a3e525930 [isort] Add no-lines-before Option (#1955)
Closes https://github.com/charliermarsh/ruff/issues/1916.
2023-01-18 11:09:47 -05:00
Anders Kaseorg
b9c6cfc0ab Autofix SIM117 (MultipleWithStatements) (#1961)
This is slightly buggy due to Instagram/LibCST#855; it will complain `[ERROR] Failed to fix nested with: Failed to extract CST from source` when trying to fix nested parenthesized `with` statements lacking trailing commas. But presumably people who write parenthesized `with` statements already knew that they don’t need to nest them.

Signed-off-by: Anders Kaseorg <andersk@mit.edu>
2023-01-18 11:06:04 -05:00
Charlie Marsh
b1f10c8339 Confine type-of-primitive checks to builtin type calls (#1962)
Closes #1958.
2023-01-18 10:53:50 -05:00
Anders Kaseorg
83346de6e0 Autofix SIM102 (NestedIfStatements)
Co-authored-by: Charlie Marsh <charlie.r.marsh@gmail.com>
Signed-off-by: Anders Kaseorg <andersk@mit.edu>
2023-01-18 07:37:27 -05:00
Anders Kaseorg
b23cc31863 Change ast::helpers::has_coments to accept a Range
Co-authored-by: Charlie Marsh <charlie.r.marsh@gmail.com>
Signed-off-by: Anders Kaseorg <andersk@mit.edu>
2023-01-18 07:37:27 -05:00
Anders Kaseorg
462d81beb7 Ensure ast::whitespace::indentation extracts whitespace
Co-authored-by: Charlie Marsh <charlie.r.marsh@gmail.com>
Signed-off-by: Anders Kaseorg <andersk@mit.edu>
2023-01-18 07:37:27 -05:00
Anders Kaseorg
715ea2d374 Accept a Locator for ast::whitespace::indentation
Co-authored-by: Charlie Marsh <charlie.r.marsh@gmail.com>
Signed-off-by: Anders Kaseorg <andersk@mit.edu>
2023-01-18 07:37:27 -05:00
skykasko
6c7e60b4f9 Fix bad link for flake8-no-pep420 (#1952)
See https://github.com/charliermarsh/ruff/pull/1942.
2023-01-18 07:36:05 -05:00
Maksudul Haque
868d0b3e29 [isort] Add constants and variables Options (#1951)
closes https://github.com/charliermarsh/ruff/issues/1819
2023-01-18 07:30:51 -05:00
Charlie Marsh
cdb4700813 Bump version to 0.0.225 2023-01-18 00:22:48 -05:00
Anders Kaseorg
ea4d54a90f Restrict SIM105 to try blocks with a body of one simple statement (#1948)
If a `try` block has multiple statements, a compound statement, or
control flow, rewriting it with `contextlib.suppress` would obfuscate
the fact that the exception still short-circuits further statements in
the block.

Fixes #1947.

Signed-off-by: Anders Kaseorg <andersk@mit.edu>
2023-01-18 00:22:22 -05:00
Charlie Marsh
51b917cfbf Exempt contextlib.ExitStack() for SIM115 rules (#1946)
Since our binding tracking is somewhat limited, I opted to favor false negatives over false positives. So, e.g., this won't trigger SIM115:

```py
with contextlib.ExitStack():
    f = exit_stack.enter_context(open("filename"))
```

(Notice that `exit_stack` is unbound.)

The alternative strategy required us to incorrectly trigger SIM115 on this:

```py
with contextlib.ExitStack() as exit_stack:
    exit_stack_ = exit_stack
    f = exit_stack_.enter_context(open("filename"))
```

Closes #1945.
2023-01-17 22:39:54 -05:00
Edgar R. M
c880d744fd Implement flake8-no-pep420 (#1942)
Closes https://github.com/charliermarsh/ruff/issues/1844.
2023-01-17 22:10:32 -05:00
Charlie Marsh
84d1df08be Avoid broken autofix for SIM103 with elif (#1944)
Also adjusts the generator to avoid the extra parentheses (and skips commented `if` statements).

Closes #1943.
2023-01-17 22:03:17 -05:00
Charlie Marsh
b9bb5acff8 Remove unnecessary setuptools block 2023-01-17 21:17:37 -05:00
Charlie Marsh
ca7c3c2175 Avoid autofixing comma rules when --fix is not set (#1940)
Closes #1939.
2023-01-17 20:09:01 -05:00
Charlie Marsh
8891e2e62b Fix name of ruff-pre-commit event 2023-01-17 17:25:02 -05:00
Martin Fischer
53265e0ed4 cli: Catch panics to tell the user to report them (#1928) 2023-01-17 17:17:09 -05:00
Charlie Marsh
072849a8a9 Move @functools.cache rewrites to their own rule (#1938)
Closes #1934.
2023-01-17 15:12:40 -05:00
Charlie Marsh
70ea4b25e8 Allow duplicate enum values for enum.auto() (#1933)
Closes #1932.
2023-01-17 11:14:11 -05:00
Martin Fischer
30e133f3d8 refactor: Declare defaults once in settings::defaults 2023-01-17 09:20:57 -05:00
Martin Fischer
aa812de07e refactor: Implement Default for Settings 2023-01-17 09:20:57 -05:00
Martin Fischer
57ac6a8444 refactor: Make resolve_codes take IntoIterator instead of Iterator 2023-01-17 09:20:57 -05:00
Martin Fischer
a6566b1b34 refactor: Merge Settings.enabled and Settings.fixable
The Settings struct previously contained the fields:

     pub enabled: HashableHashSet<RuleCode>,
     pub fixable: HashableHashSet<RuleCode>,

This commit merges both fields into one by introducing a new
RuleTable type, wrapping HashableHashMap<RuleCode, bool>,
which has the following benefits:

1. It makes the invalid state that a rule is
   disabled but fixable unrepresentable.

2. It encapsulates the implementation details of the table.
   (It currently uses an FxHashMap but that may change.)

3. It results in more readable code.

       settings.rules.enabled(rule)
       settings.rules.should_fix(rule)

   is more readable than:

       settings.enabled.contains(rule)
       settings.fixable.contains(rule)
2023-01-17 09:20:57 -05:00
Martin Fischer
580da1fa6b refactor: Group Settings fields 2023-01-17 09:20:57 -05:00
Martin Fischer
b78b6f275e refactor: Define origin names & URLs within doc comments 2023-01-17 07:44:40 -05:00
Martin Fischer
6868bb46f5 refactor: Get rid of Platform enum 2023-01-17 07:44:40 -05:00
Martin Fischer
601848d9a8 refactor: Rename RuleOrigin::title to RuleOrigin::name 2023-01-17 07:44:40 -05:00
Martin Fischer
f4da7635f0 Add missing url for flake8-import-conventions 2023-01-17 07:44:40 -05:00
Charlie Marsh
74a8a218f3 Bump version to 0.0.224 2023-01-16 23:43:14 -05:00
Colin Delahunty
1730f2a603 [pyupgrade] Automatically rewrite format-strings to f-strings (#1905) 2023-01-16 23:06:39 -05:00
Charlie Marsh
a4862857de Update PIE796 fixture 2023-01-16 19:29:14 -05:00
Leonardo Esparis
6e88c60c46 Add flake8-pie PIE796: prefer-unique-enum (#1923)
I accept any suggestion. By the way, I have a doubt, I have checked and all flake8-pie plugins can be fixed by ruff, but is it necessary that this one is also fixed automatically ?

rel #1543
2023-01-16 19:27:34 -05:00
Charlie Marsh
2ed1f78873 Add benchmark scripts for no-IO (#1925) 2023-01-16 17:38:40 -05:00
Charlie Marsh
f3bf008aed Avoid removing statements that contain side-effects (#1920)
Closes #1917.
2023-01-16 14:45:02 -05:00
Charlie Marsh
3b4aaa53c1 Add some new testimonials (#1921) 2023-01-16 14:44:52 -05:00
Charlie Marsh
6abf71639f Avoid syntax errors when fixing parenthesized unused variables (#1919)
Closes #1917.
2023-01-16 14:27:41 -05:00
Charlie Marsh
c0845a8c28 Rewrite lru_cache to cache on Python 3.9+ (#1918)
Closes #1913.
2023-01-16 13:14:27 -05:00
Paul Barrett
019ecc4add Trigger update to pre-commit mirror after pypi publish (#1910) 2023-01-16 13:14:18 -05:00
Martin Fischer
f4cf48d885 refactor: Move rule-specific details out of mod.rs via type aliases 2023-01-16 11:27:24 -05:00
Martin Fischer
005f5d7911 refactor: Make flake8_tidy_imports::Settings derive Default 2023-01-16 11:27:24 -05:00
Martin Fischer
2fce580693 refactor: Move flake8_tidy_imports Settings to mod.rs 2023-01-16 11:27:24 -05:00
Martin Fischer
8862565a0f refactor: Split ruff::rules::flake8_tidy_imports::rules 2023-01-16 11:27:24 -05:00
Martin Fischer
5bf6da0db7 refactor: Rename BannedRelativeImport to RelativeImports
The idea is to follow the Rust naming convention for lints[1]:

> the lint name should make sense when read as
> "allow lint-name" or "allow lint-name items"

Following that convention prefixing "Banned" is
redundant as it could be prefixed to any lint name.

[1]: https://rust-lang.github.io/rfcs/0344-conventions-galore.html#lints
2023-01-16 11:27:24 -05:00
Martin Fischer
ee655c1a88 refactor: Rename BannedApi to ApiBan to distinguish it from the violation struct 2023-01-16 11:27:24 -05:00
Harutaka Kawamura
2236b4bd59 Add backticks to B904's message (#1914)
This PR adds backticks to B904's message to improve readability.


Without backticks:

<img width="1480" alt="image" src="https://user-images.githubusercontent.com/17039389/212682457-71f13de9-e3dd-4ead-a82b-98e5b60653c2.png">

With backticks:

<img width="1480" alt="image" src="https://user-images.githubusercontent.com/17039389/212682775-36868401-b63e-47d1-ae25-b43b61866b6c.png">
2023-01-16 11:12:43 -05:00
Charlie Marsh
fbf311f7d5 Add instructions for Pyupgrade benchmark 2023-01-16 03:21:31 -05:00
Martin Fischer
8c18b28bc4 Derive Hash instead of implementing it by hand
The caching mechanism of the CLI (ruff_cli::cache) relies on
ruff::settings::Settings implementing the Hash trait.

The ruff::settings::Settings struct previously couldn't automatically
derive the Hash implementation via the #[derive(Hash)] macro attribute
since some of its field types intentionally[1][2] don't implement Hash
(namely regex::Regex, globset::GlobMatcher and globset::GlobSet and
HashMap and HashSet from the standard library).

The code therefore previously implemented the Hash trait by hand for the
whole struct. Implementing Hash by hand for structs that are subject to
change is a bad idea since it's very easy to forget to update the Hash
implementation when adding a new field to the struct. And the Hash
implementation indeed was already incorrect by omitting several fields
from the hash.

This commit introduces wrapper types for Regex, GlobMatcher, GlobSet,
HashSet & HashMap that implement Hash so that we can still add
#[derive(Hash)] to the Settings struct, guaranteeing a correct hash
implementation.

[1]: https://github.com/rust-lang/regex/issues/364#issuecomment-301082076
[2]: The standard library doesn't impl<T: Hash + Ord> Hash for HashSet<T>
     presumably since sorted() requires an allocation and Hash
     implementations are generally expected to work without allocations.
2023-01-16 01:42:55 -05:00
Charlie Marsh
42031b8574 Re-run benchmark and update documentation (#1907)
Closes #269.
2023-01-16 01:38:58 -05:00
333 changed files with 13427 additions and 8428 deletions

View File

@@ -293,3 +293,6 @@ jobs:
run: |
pip install --upgrade twine
twine upload --skip-existing *
- name: Update pre-commit mirror
run: |
curl -X POST -H "Accept: application/vnd.github+json" -H "Authorization: Bearer ${{ secrets.RUFF_PRE_COMMIT_PAT }}" -H "X-GitHub-Api-Version: 2022-11-28" https://api.github.com/repos/charliermarsh/ruff-pre-commit/dispatches --data '{"event_type": "pypi_release"}'

View File

@@ -1,6 +1,6 @@
repos:
- repo: https://github.com/charliermarsh/ruff-pre-commit
rev: v0.0.223
rev: v0.0.227
hooks:
- id: ruff
@@ -8,3 +8,11 @@ repos:
rev: v0.10.1
hooks:
- id: validate-pyproject
- repo: local
hooks:
- id: cargo-fmt
name: cargo fmt
entry: cargo fmt --
language: rust
types: [rust]

View File

@@ -1,5 +1,21 @@
# Breaking Changes
## 0.0.226
### `misplaced-comparison-constant` (`PLC2201`) was deprecated in favor of `SIM300` ([#1980](https://github.com/charliermarsh/ruff/pull/1980))
These two rules contain (nearly) identical logic. To deduplicate the rule set, we've upgraded
`SIM300` to handle a few more cases, and deprecated `PLC2201` in favor of `SIM300`.
## 0.0.225
### `@functools.cache` rewrites have been moved to a standalone rule (`UP033`) ([#1938](https://github.com/charliermarsh/ruff/pull/1938))
Previously, `UP011` handled both `@functools.lru_cache()`-to-`@functools.lru_cache` conversions,
_and_ `@functools.lru_cache(maxsize=None)`-to-`@functools.cache` conversions. The latter has been
moved out to its own rule (`UP033`). As such, some `# noqa: UP011` comments may need to be updated
to reflect the change in rule code.
## 0.0.222
### `--max-complexity` has been removed from the CLI ([#1877](https://github.com/charliermarsh/ruff/pull/1877))

View File

@@ -54,18 +54,22 @@ prior to merging.
### Example: Adding a new lint rule
There are four phases to adding a new lint rule:
At a high level, the steps involved in adding a new lint rule are as follows:
1. Define the violation struct in `src/violations.rs` (e.g., `ModuleImportNotAtTopOfFile`).
2. Map the violation struct to a rule code in `src/registry.rs` (e.g., `E402`).
3. Define the logic for triggering the violation in `src/checkers/ast.rs` (for AST-based checks),
`src/checkers/tokens.rs` (for token-based checks), or `src/checkers/lines.rs` (for text-based checks).
4. Add a test fixture.
5. Update the generated files (documentation and generated code).
1. Create a file for your rule (e.g., `src/rules/flake8_bugbear/rules/abstract_base_class.rs`).
2. In that file, define a violation struct. You can grep for `define_violation!` to see examples.
3. Map the violation struct to a rule code in `src/registry.rs` (e.g., `E402`).
4. Define the logic for triggering the violation in `src/checkers/ast.rs` (for AST-based checks),
`src/checkers/tokens.rs` (for token-based checks), `src/checkers/lines.rs` (for text-based
checks), or `src/checkers/filesystem.rs` (for filesystem-based checks).
5. Add a test fixture.
6. Update the generated files (documentation and generated code).
To define the violation, open up `src/violations.rs`, and define a new struct using the
`define_violation!` macro. There are plenty of examples in that file, so feel free to pattern-match
against the existing structs.
To define the violation, start by creating a dedicated file for your rule under the appropriate
rule origin (e.g., `src/rules/flake8_bugbear/rules/abstract_base_class.rs`). That file should
contain a struct defined via `define_violation!`, along with a function that creates the violation
based on any required inputs. (Many of the existing examples live in `src/violations.rs`, but we're
looking to place new rules in their own files.)
To trigger the violation, you'll likely want to augment the logic in `src/checkers/ast.rs`, which
defines the Python AST visitor, responsible for iterating over the abstract syntax tree and

20
Cargo.lock generated
View File

@@ -735,7 +735,7 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
[[package]]
name = "flake8-to-ruff"
version = "0.0.223"
version = "0.0.227"
dependencies = [
"anyhow",
"clap 4.0.32",
@@ -1906,7 +1906,7 @@ dependencies = [
[[package]]
name = "ruff"
version = "0.0.223"
version = "0.0.227"
dependencies = [
"anyhow",
"bitflags",
@@ -1946,10 +1946,12 @@ dependencies = [
"serde",
"serde-wasm-bindgen",
"shellexpand",
"smallvec",
"strum",
"strum_macros",
"test-case",
"textwrap",
"thiserror",
"titlecase",
"toml_edit",
"wasm-bindgen",
@@ -1958,7 +1960,7 @@ dependencies = [
[[package]]
name = "ruff_cli"
version = "0.0.223"
version = "0.0.227"
dependencies = [
"annotate-snippets 0.9.1",
"anyhow",
@@ -1995,7 +1997,7 @@ dependencies = [
[[package]]
name = "ruff_dev"
version = "0.0.223"
version = "0.0.227"
dependencies = [
"anyhow",
"clap 4.0.32",
@@ -2016,7 +2018,7 @@ dependencies = [
[[package]]
name = "ruff_macros"
version = "0.0.223"
version = "0.0.227"
dependencies = [
"once_cell",
"proc-macro2",
@@ -2060,7 +2062,7 @@ dependencies = [
[[package]]
name = "rustpython-ast"
version = "0.2.0"
source = "git+https://github.com/RustPython/RustPython.git?rev=acbc517b55406c76da83d7b2711941d8d3f65b87#acbc517b55406c76da83d7b2711941d8d3f65b87"
source = "git+https://github.com/RustPython/RustPython.git?rev=ff90fe52eea578c8ebdd9d95e078cc041a5959fa#ff90fe52eea578c8ebdd9d95e078cc041a5959fa"
dependencies = [
"num-bigint",
"rustpython-common",
@@ -2070,7 +2072,7 @@ dependencies = [
[[package]]
name = "rustpython-common"
version = "0.2.0"
source = "git+https://github.com/RustPython/RustPython.git?rev=acbc517b55406c76da83d7b2711941d8d3f65b87#acbc517b55406c76da83d7b2711941d8d3f65b87"
source = "git+https://github.com/RustPython/RustPython.git?rev=ff90fe52eea578c8ebdd9d95e078cc041a5959fa#ff90fe52eea578c8ebdd9d95e078cc041a5959fa"
dependencies = [
"ascii",
"bitflags",
@@ -2095,7 +2097,7 @@ dependencies = [
[[package]]
name = "rustpython-compiler-core"
version = "0.2.0"
source = "git+https://github.com/RustPython/RustPython.git?rev=acbc517b55406c76da83d7b2711941d8d3f65b87#acbc517b55406c76da83d7b2711941d8d3f65b87"
source = "git+https://github.com/RustPython/RustPython.git?rev=ff90fe52eea578c8ebdd9d95e078cc041a5959fa#ff90fe52eea578c8ebdd9d95e078cc041a5959fa"
dependencies = [
"bincode",
"bitflags",
@@ -2112,7 +2114,7 @@ dependencies = [
[[package]]
name = "rustpython-parser"
version = "0.2.0"
source = "git+https://github.com/RustPython/RustPython.git?rev=acbc517b55406c76da83d7b2711941d8d3f65b87#acbc517b55406c76da83d7b2711941d8d3f65b87"
source = "git+https://github.com/RustPython/RustPython.git?rev=ff90fe52eea578c8ebdd9d95e078cc041a5959fa#ff90fe52eea578c8ebdd9d95e078cc041a5959fa"
dependencies = [
"ahash",
"anyhow",

View File

@@ -8,7 +8,7 @@ default-members = [".", "ruff_cli"]
[package]
name = "ruff"
version = "0.0.223"
version = "0.0.227"
authors = ["Charlie Marsh <charlie.r.marsh@gmail.com>"]
edition = "2021"
rust-version = "1.65.0"
@@ -46,20 +46,22 @@ 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" }
ropey = { version = "1.5.0", features = ["cr_lines", "simd"], default-features = false }
ruff_macros = { version = "0.0.223", path = "ruff_macros" }
ruff_macros = { version = "0.0.227", path = "ruff_macros" }
rustc-hash = { version = "1.1.0" }
rustpython-ast = { features = ["unparse"], git = "https://github.com/RustPython/RustPython.git", rev = "acbc517b55406c76da83d7b2711941d8d3f65b87" }
rustpython-common = { git = "https://github.com/RustPython/RustPython.git", rev = "acbc517b55406c76da83d7b2711941d8d3f65b87" }
rustpython-parser = { features = ["lalrpop"], git = "https://github.com/RustPython/RustPython.git", rev = "acbc517b55406c76da83d7b2711941d8d3f65b87" }
rustpython-ast = { features = ["unparse"], git = "https://github.com/RustPython/RustPython.git", rev = "ff90fe52eea578c8ebdd9d95e078cc041a5959fa" }
rustpython-common = { git = "https://github.com/RustPython/RustPython.git", rev = "ff90fe52eea578c8ebdd9d95e078cc041a5959fa" }
rustpython-parser = { features = ["lalrpop"], git = "https://github.com/RustPython/RustPython.git", rev = "ff90fe52eea578c8ebdd9d95e078cc041a5959fa" }
schemars = { version = "0.8.11" }
semver = { version = "1.0.16" }
serde = { version = "1.0.147", features = ["derive"] }
shellexpand = { version = "3.0.0" }
smallvec = { version = "1.10.0" }
strum = { version = "0.24.1", features = ["strum_macros"] }
strum_macros = { version = "0.24.3" }
textwrap = { version = "0.16.0" }
titlecase = { version = "2.2.1" }
toml_edit = { version = "0.17.1", features = ["easy"] }
thiserror = { version = "1.0" }
# https://docs.rs/getrandom/0.2.7/getrandom/#webassembly-support
# For (future) wasm-pack support

1025
README.md

File diff suppressed because it is too large Load Diff

84
build.rs Normal file
View File

@@ -0,0 +1,84 @@
use std::fs;
use std::io::{BufRead, BufReader, BufWriter, Write};
use std::path::{Path, PathBuf};
fn main() {
let out_dir = PathBuf::from(std::env::var_os("OUT_DIR").unwrap());
generate_origin_name_and_url(&out_dir);
}
const RULES_SUBMODULE_DOC_PREFIX: &str = "//! Rules from ";
/// The `src/rules/*/mod.rs` files are expected to have a first line such as the
/// following:
///
/// //! Rules from [Pyflakes](https://pypi.org/project/pyflakes/2.5.0/).
///
/// This function extracts the link label and url from these comments and
/// generates the `name` and `url` functions for the `RuleOrigin` enum
/// accordingly, so that they can be used by `ruff_dev::generate_rules_table`.
fn generate_origin_name_and_url(out_dir: &Path) {
println!("cargo:rerun-if-changed=src/rules/");
let mut name_match_arms: String = r#"RuleOrigin::Ruff => "Ruff-specific rules","#.into();
let mut url_match_arms: String = r#"RuleOrigin::Ruff => None,"#.into();
for file in fs::read_dir("src/rules/")
.unwrap()
.flatten()
.filter(|f| f.file_type().unwrap().is_dir() && f.file_name() != "ruff")
{
let mod_rs_path = file.path().join("mod.rs");
let mod_rs_path = mod_rs_path.to_str().unwrap();
let first_line = BufReader::new(fs::File::open(mod_rs_path).unwrap())
.lines()
.next()
.unwrap()
.unwrap();
let Some(comment) = first_line.strip_prefix(RULES_SUBMODULE_DOC_PREFIX) else {
panic!("expected first line in {mod_rs_path} to start with `{RULES_SUBMODULE_DOC_PREFIX}`")
};
let md_link = comment.trim_end_matches('.');
let (name, url) = md_link
.strip_prefix('[')
.unwrap()
.strip_suffix(')')
.unwrap()
.split_once("](")
.unwrap();
let dirname = file.file_name();
let dirname = dirname.to_str().unwrap();
let variant_name = dirname
.split('_')
.map(|part| match part {
"errmsg" => "ErrMsg".to_string(),
"mccabe" => "McCabe".to_string(),
"pep8" => "PEP8".to_string(),
_ => format!("{}{}", part[..1].to_uppercase(), &part[1..]),
})
.collect::<String>();
name_match_arms.push_str(&format!(r#"RuleOrigin::{variant_name} => "{name}","#));
url_match_arms.push_str(&format!(r#"RuleOrigin::{variant_name} => Some("{url}"),"#));
}
write!(
BufWriter::new(fs::File::create(out_dir.join("origin.rs")).unwrap()),
"
impl RuleOrigin {{
pub fn name(&self) -> &'static str {{
match self {{ {name_match_arms} }}
}}
pub fn url(&self) -> Option<&'static str> {{
match self {{ {url_match_arms} }}
}}
}}
"
)
.unwrap();
}

View File

@@ -771,7 +771,7 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
[[package]]
name = "flake8_to_ruff"
version = "0.0.223"
version = "0.0.227"
dependencies = [
"anyhow",
"clap",
@@ -1975,7 +1975,7 @@ dependencies = [
[[package]]
name = "ruff"
version = "0.0.223"
version = "0.0.227"
dependencies = [
"anyhow",
"bincode",

View File

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

View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2020 Adam Johnson
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.

View File

@@ -7,7 +7,7 @@ build-backend = "maturin"
[project]
name = "ruff"
version = "0.0.223"
version = "0.0.227"
description = "An extremely fast Python linter, written in Rust."
authors = [
{ name = "Charlie Marsh", email = "charlie.r.marsh@gmail.com" },
@@ -41,9 +41,3 @@ bindings = "bin"
manifest-path = "ruff_cli/Cargo.toml"
python-source = "python"
strip = true
[tool.setuptools]
license-files = [
"LICENSE",
"licenses/*",
]

View File

@@ -0,0 +1 @@
print('hi')

View File

@@ -0,0 +1,2 @@
#!/bin/env/python
print('hi')

View File

@@ -0,0 +1 @@
import os # noqa: INP001

View File

@@ -0,0 +1,66 @@
import enum
from enum import Enum, unique
class FakeEnum1(enum.Enum):
A = "A"
B = "B"
C = "B" # PIE796
class FakeEnum2(Enum):
A = 1
B = 2
C = 2 # PIE796
class FakeEnum3(str, Enum):
A = "1"
B = "2"
C = "2" # PIE796
class FakeEnum4(Enum):
A = 1.0
B = 2.5
C = 2.5 # PIE796
class FakeEnum5(Enum):
A = 1.0
B = True
C = False
D = False # PIE796
class FakeEnum6(Enum):
A = 1
B = 2
C = None
D = None # PIE796
@enum.unique
class FakeEnum7(enum.Enum):
A = "A"
B = "B"
C = "C"
@unique
class FakeEnum8(Enum):
A = 1
B = 2
C = 2 # PIE796
class FakeEnum9(enum.Enum):
A = "A"
B = "B"
C = "C"
class FakeEnum10(enum.Enum):
A = enum.auto()
B = enum.auto()
C = enum.auto()

View File

@@ -53,3 +53,25 @@ def test_list_of_tuples(param1, param2):
)
def test_list_of_lists(param1, param2):
...
@pytest.mark.parametrize(
"param1,param2",
[
[1, 2],
[3, 4],
],
)
def test_csv_name_list_of_lists(param1, param2):
...
@pytest.mark.parametrize(
"param",
[
[1, 2],
[3, 4],
],
)
def test_single_list_of_lists(param):
...

View File

@@ -1,25 +1,88 @@
if a: # SIM102
# SIM102
if a:
if b:
c
# SIM102
if a:
pass
elif b: # SIM102
elif b:
if c:
d
# SIM102
if a:
# Unfixable due to placement of this comment.
if b:
c
# SIM102
if a:
if b:
# Fixable due to placement of this comment.
c
# OK
if a:
if b:
c
else:
d
# OK
if __name__ == "__main__":
if foo():
...
# OK
if a:
d
if b:
c
while True:
# SIM102
if True:
if True:
"""this
is valid"""
"""the indentation on
this line is significant"""
"this is" \
"allowed too"
("so is"
"this for some reason")
# SIM102
if True:
if True:
"""this
is valid"""
"""the indentation on
this line is significant"""
"this is" \
"allowed too"
("so is"
"this for some reason")
while True:
# SIM102
if node.module:
if node.module == "multiprocessing" or node.module.startswith(
"multiprocessing."
):
print("Bad module!")
# SIM102
if node.module:
if node.module == "multiprocessing" or node.module.startswith(
"multiprocessing."
):
print("Bad module!")

View File

@@ -1,12 +1,35 @@
def f():
if a: # SIM103
# SIM103
if a:
return True
else:
return False
def f():
if a: # OK
# SIM103
if a:
return 1
elif b:
return True
else:
return False
def f():
# SIM103
if a:
return 1
else:
if b:
return True
else:
return False
def f():
# OK
if a:
foo()
return True
else:
@@ -14,7 +37,8 @@ def f():
def f():
if a: # OK
# OK
if a:
return "foo"
else:
return False

View File

@@ -41,3 +41,21 @@ except ValueError:
pass
finally:
print('bar')
try:
foo()
foo()
except ValueError:
pass
try:
for i in range(3):
foo()
except ValueError:
pass
def bar():
try:
return foo()
except ValueError:
pass

View File

@@ -1,7 +1,31 @@
# Bad
# SIM109
if a == b or a == c:
d
# Good
# SIM109
if (a == b or a == c) and None:
d
# SIM109
if a == b or a == c or None:
d
# SIM109
if a == b or None or a == c:
d
# OK
if a in (b, c):
d
d
# OK
if a == b or a == c():
d
# OK
if (
a == b
# This comment prevents us from raising SIM109
or a == c
):
d

View File

@@ -1,6 +1,36 @@
f = open('foo.txt') # SIM115
import contextlib
# SIM115
f = open("foo.txt")
data = f.read()
f.close()
with open('foo.txt') as f: # OK
# OK
with open("foo.txt") as f:
data = f.read()
# OK
with contextlib.ExitStack() as exit_stack:
f = exit_stack.enter_context(open("filename"))
# OK
with contextlib.ExitStack() as stack:
files = [stack.enter_context(open(fname)) for fname in filenames]
close_files = stack.pop_all().close
# OK
with contextlib.AsyncExitStack() as exit_stack:
f = await exit_stack.enter_async_context(open("filename"))
# OK (false negative)
with contextlib.ExitStack():
f = exit_stack.enter_context(open("filename"))
# SIM115
with contextlib.ExitStack():
f = open("filename")
# OK
with contextlib.ExitStack() as exit_stack:
exit_stack_ = exit_stack
f = exit_stack_.enter_context(open("filename"))

View File

@@ -1,30 +1,92 @@
with A() as a: # SIM117
# SIM117
with A() as a:
with B() as b:
print("hello")
with A(): # SIM117
# SIM117
with A():
with B():
with C():
print("hello")
# SIM117
with A() as a:
# Unfixable due to placement of this comment.
with B() as b:
print("hello")
# SIM117
with A() as a:
with B() as b:
# Fixable due to placement of this comment.
print("hello")
# OK
with A() as a:
a()
with B() as b:
print("hello")
# OK
with A() as a:
with B() as b:
print("hello")
a()
# OK
async with A() as a:
with B() as b:
print("hello")
# OK
with A() as a:
async with B() as b:
print("hello")
# OK
async with A() as a:
async with B() as b:
print("hello")
while True:
# SIM117
with A() as a:
with B() as b:
"""this
is valid"""
"""the indentation on
this line is significant"""
"this is" \
"allowed too"
("so is"
"this for some reason")
# SIM117
with (
A() as a,
B() as b,
):
with C() as c:
print("hello")
# SIM117
with A() as a:
with (
B() as b,
C() as c,
):
print("hello")
# SIM117
with (
A() as a,
B() as b,
):
with (
C() as c,
D() as d,
):
print("hello")

View File

@@ -1,17 +1,27 @@
if not a == b: # SIM201
# SIM201
if not a == b:
pass
if not a == (b + c): # SIM201
# SIM201
if not a == (b + c):
pass
if not (a + b) == c: # SIM201
# SIM201
if not (a + b) == c:
pass
if not a != b: # OK
# OK
if not a != b:
pass
if a == b: # OK
# OK
if a == b:
pass
if not a == b: # OK
# OK
if not a == b:
raise ValueError()
# OK
def __ne__(self, other):
return not self == other

View File

@@ -1,14 +1,27 @@
if not a != b: # SIM202
# SIM202
if not a != b:
pass
if not a != (b + c): # SIM202
# SIM202
if not a != (b + c):
pass
if not (a + b) != c: # SIM202
# SIM202
if not (a + b) != c:
pass
if not a == b: # OK
# OK
if not a == b:
pass
if a != b: # OK
# OK
if a != b:
pass
# OK
if not a != b:
raise ValueError()
# OK
def __eq__(self, other):
return not self != other

View File

@@ -7,8 +7,12 @@ if (a or b) or True: # SIM223
if a or (b or True): # SIM223
pass
if a and True:
if a and True: # OK
pass
if True:
if True: # OK
pass
def validate(self, value):
return json.loads(value) or True # OK

View File

@@ -2,6 +2,9 @@
"yoda" == compare # SIM300
'yoda' == compare # SIM300
42 == age # SIM300
"yoda" <= compare # SIM300
'yoda' < compare # SIM300
42 > age # SIM300
# OK
compare == "yoda"

View File

@@ -85,3 +85,26 @@ if key in a_dict:
var = a_dict[key]
else:
var = foo()
# OK (complex default value)
if key in a_dict:
var = a_dict[key]
else:
var = a_dict["fallback"]
# OK (false negative for elif)
if foo():
pass
elif key in a_dict:
vars[idx] = a_dict[key]
else:
vars[idx] = "default"
# OK (false negative for nested else)
if foo():
pass
else:
if key in a_dict:
vars[idx] = a_dict[key]
else:
vars[idx] = "default"

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,2 @@
from sklearn.svm import XYZ, func, variable, Const, Klass, constant
from subprocess import First, var, func, Class, konst, A_constant, Last, STDOUT

View File

@@ -0,0 +1,2 @@
from sklearn.svm import VAR, Class, MyVar, CONST, abc
from subprocess import utils, var_ABC, Variable, Klass, CONSTANT, exe

View File

@@ -579,3 +579,30 @@ def multiline_trailing_and_leading_space():
"or exclamation point (not '\"')")
def endswith_quote():
"""Whitespace at the end, but also a quote" """
@expect('D209: Multi-line docstring closing quotes should be on a separate '
'line')
@expect('D213: Multi-line docstring summary should start at the second line')
def asdfljdjgf24():
"""Summary.
Description. """
@expect('D200: One-line docstring should fit on one line with quotes '
'(found 3)')
@expect('D212: Multi-line docstring summary should start at the first line')
def one_liner():
"""
Wrong."""
@expect('D200: One-line docstring should fit on one line with quotes '
'(found 3)')
@expect('D212: Multi-line docstring summary should start at the first line')
def one_liner():
r"""Wrong.
"""

View File

@@ -44,3 +44,25 @@ def f():
1 / 0
except (ValueError, ZeroDivisionError) as x2:
pass
def f(a, b):
x = (
a()
if a is not None
else b
)
y = \
a() if a is not None else b
def f(a, b):
x = (
a
if a is not None
else b
)
y = \
a if a is not None else b

View File

@@ -1,71 +1,69 @@
"""Check that magic values are not used in comparisons"""
import cmath
"""Check that magic values are not used in comparisons."""
user_input = 10
if 10 > user_input: # [magic-value-comparison]
if 10 > user_input: # [magic-value-comparison]
pass
if 10 == 100: # [comparison-of-constants] R0133
if 10 == 100: # [comparison-of-constants] R0133
pass
if 1 == 3: # [comparison-of-constants] R0133
if 1 == 3: # [comparison-of-constants] R0133
pass
x = 0
if 4 == 3 == x: # [comparison-of-constants] R0133
if 4 == 3 == x: # [comparison-of-constants] R0133
pass
time_delta = 7224
ONE_HOUR = 3600
if time_delta > ONE_HOUR: # correct
if time_delta > ONE_HOUR: # correct
pass
argc = 1
if argc != -1: # correct
if argc != -1: # correct
pass
if argc != 0: # correct
if argc != 0: # correct
pass
if argc != 1: # correct
if argc != 1: # correct
pass
if argc != 2: # [magic-value-comparison]
if argc != 2: # [magic-value-comparison]
pass
if __name__ == "__main__": # correct
if __name__ == "__main__": # correct
pass
ADMIN_PASSWORD = "SUPERSECRET"
input_password = "password"
if input_password == "": # correct
if input_password == "": # correct
pass
if input_password == ADMIN_PASSWORD: # correct
if input_password == ADMIN_PASSWORD: # correct
pass
if input_password == "Hunter2": # [magic-value-comparison]
if input_password == "Hunter2": # [magic-value-comparison]
pass
PI = 3.141592653589793238
pi_estimation = 3.14
if pi_estimation == 3.141592653589793238: # [magic-value-comparison]
if pi_estimation == 3.141592653589793238: # [magic-value-comparison]
pass
if pi_estimation == PI: # correct
if pi_estimation == PI: # correct
pass
HELLO_WORLD = b"Hello, World!"
user_input = b"Hello, There!"
if user_input == b"something": # [magic-value-comparison]
if user_input == b"something": # [magic-value-comparison]
pass
if user_input == HELLO_WORLD: # correct
if user_input == HELLO_WORLD: # correct
pass

View File

@@ -1,5 +1,12 @@
type('')
type(b'')
type("")
type(b"")
type(0)
type(0.)
type(0.0)
type(0j)
# OK
y = x.dtype.type(0.0)
# OK
type = lambda *args, **kwargs: None
type("")

View File

@@ -0,0 +1,67 @@
import functools
from functools import lru_cache
@functools.lru_cache()
def fixme():
pass
@lru_cache()
def fixme():
pass
@other_decorator
@functools.lru_cache()
def fixme():
pass
@functools.lru_cache()
@other_decorator
def fixme():
pass
@functools.lru_cache(maxsize=None)
def ok():
pass
@lru_cache(maxsize=None)
def ok():
pass
@functools.lru_cache(maxsize=64)
def ok():
pass
@lru_cache(maxsize=64)
def ok():
pass
def user_func():
pass
@lru_cache(user_func)
def ok():
pass
@lru_cache(user_func, maxsize=None)
def ok():
pass
def lru_cache(maxsize=None):
pass
@lru_cache(maxsize=None)
def ok():
pass

View File

@@ -1,103 +0,0 @@
import functools
from functools import lru_cache
@lru_cache()
def fixme1():
pass
@other_deco_after
@functools.lru_cache()
def fixme2():
pass
@lru_cache(maxsize=None)
def fixme3():
pass
@functools.lru_cache(maxsize=None)
@other_deco_before
def fixme4():
pass
@lru_cache( # A
) # B
def fixme5():
pass
@lru_cache(
# A
) # B
def fixme6():
pass
@functools.lru_cache(
# A
maxsize = None) # B
def fixme7():
pass
@functools.lru_cache(
# A1
maxsize = None
# A2
) # B
def fixme8():
pass
@functools.lru_cache(
# A1
maxsize =
None
# A2
)
def fixme9():
pass
@functools.lru_cache(
# A1
maxsize =
None
# A2
)
def fixme10():
pass
@lru_cache
def correct1():
pass
@functools.lru_cache
def correct2():
pass
@functoools.lru_cache(maxsize=64)
def correct3():
pass
def user_func():
pass
@lru_cache(user_func)
def correct4():
pass
@lru_cache(user_func, maxsize=None)
def correct5():
pass

View File

@@ -1,15 +0,0 @@
import functools
def lru_cache(maxsize=None):
pass
@lru_cache()
def dont_fixme():
pass
@lru_cache(maxsize=None)
def dont_fixme():
pass

View File

@@ -0,0 +1,86 @@
###
# Errors
###
"{} {}".format(a, b)
"{1} {0}".format(a, b)
"{x.y}".format(x=z)
"{.x} {.y}".format(a, b)
"{} {}".format(a.b, c.d)
"{}".format(a())
"{}".format(a.b())
"{}".format(a.b().c())
"hello {}!".format(name)
"{}{b}{}".format(a, c, b=b)
"{}".format(0x0)
"{} {}".format(a, b)
"""{} {}""".format(a, b)
"foo{}".format(1)
r"foo{}".format(1)
x = "{a}".format(a=1)
print("foo {} ".format(x))
"{a[b]}".format(a=a)
"{a.a[b]}".format(a=a)
"{}{{}}{}".format(escaped, y)
"{}".format(a)
###
# Non-errors
###
# False-negative: RustPython doesn't parse the `\N{snowman}`.
"\N{snowman} {}".format(a)
"{".format(a)
"}".format(a)
"{} {}".format(*a)
"{0} {0}".format(arg)
"{x} {x}".format(arg)
"{x.y} {x.z}".format(arg)
b"{} {}".format(a, b)
"{:{}}".format(x, y)
"{}{}".format(a)
"" "{}".format(a["\\"])
"{}".format(a["b"])
r'"\N{snowman} {}".format(a)'
"{a}" "{b}".format(a=1, b=1)
async def c():
return "{}".format(await 3)
async def c():
return "{}".format(1 + await 3)

View File

@@ -0,0 +1,67 @@
import functools
from functools import lru_cache
@functools.lru_cache(maxsize=None)
def fixme():
pass
@lru_cache(maxsize=None)
def fixme():
pass
@other_decorator
@functools.lru_cache(maxsize=None)
def fixme():
pass
@functools.lru_cache(maxsize=None)
@other_decorator
def fixme():
pass
@functools.lru_cache()
def ok():
pass
@lru_cache()
def ok():
pass
@functools.lru_cache(maxsize=64)
def ok():
pass
@lru_cache(maxsize=64)
def ok():
pass
def user_func():
pass
@lru_cache(user_func)
def ok():
pass
@lru_cache(user_func, maxsize=None)
def ok():
pass
def lru_cache(maxsize=None):
pass
@lru_cache(maxsize=None)
def ok():
pass

22
resources/test/fixtures/ruff/RUF005.py vendored Normal file
View File

@@ -0,0 +1,22 @@
class Fun:
words = ("how", "fun!")
def yay(self):
return self.words
yay = Fun().yay
foo = [4, 5, 6]
bar = [1, 2, 3] + foo
zoob = tuple(bar)
quux = (7, 8, 9) + zoob
spam = quux + (10, 11, 12)
spom = list(spam)
eggs = spom + [13, 14, 15]
elatement = ("we all say", ) + yay()
excitement = ("we all think", ) + Fun().yay()
astonishment = ("we all feel", ) + Fun.words
chain = ['a', 'b', 'c'] + eggs + list(('yes', 'no', 'pants') + zoob)
baz = () + zoob

View File

@@ -227,7 +227,7 @@
]
},
"format": {
"description": "The style in which violation messages should be formatted: `\"text\"` (default), `\"grouped\"` (group messages by file), `\"json\"` (machine-readable), `\"junit\"` (machine-readable XML), `\"github\"` (GitHub Actions annotations) or `\"gitlab\"` (GitLab CI code quality report).",
"description": "The style in which violation messages should be formatted: `\"text\"` (default), `\"grouped\"` (group messages by file), `\"json\"` (machine-readable), `\"junit\"` (machine-readable XML), `\"github\"` (GitHub Actions annotations), `\"gitlab\"` (GitLab CI code quality report), or `\"pylint\"` (Pylint text format).",
"anyOf": [
{
"$ref": "#/definitions/SerializationFormat"
@@ -341,6 +341,17 @@
}
]
},
"pylint": {
"description": "Options for the `pylint` plugin.",
"anyOf": [
{
"$ref": "#/definitions/PylintOptions"
},
{
"type": "null"
}
]
},
"pyupgrade": {
"description": "Options for the `pyupgrade` plugin.",
"anyOf": [
@@ -448,7 +459,7 @@
},
"additionalProperties": false,
"definitions": {
"BannedApi": {
"ApiBan": {
"type": "object",
"required": [
"msg"
@@ -461,6 +472,17 @@
},
"additionalProperties": false
},
"ConstantType": {
"type": "string",
"enum": [
"bytes",
"complex",
"float",
"int",
"str",
"tuple"
]
},
"Convention": {
"oneOf": [
{
@@ -743,7 +765,7 @@
"null"
],
"additionalProperties": {
"$ref": "#/definitions/BannedApi"
"$ref": "#/definitions/ApiBan"
}
}
},
@@ -762,6 +784,16 @@
},
"additionalProperties": false
},
"ImportType": {
"type": "string",
"enum": [
"future",
"standard-library",
"third-party",
"first-party",
"local-folder"
]
},
"IsortOptions": {
"type": "object",
"properties": {
@@ -782,6 +814,16 @@
"null"
]
},
"constants": {
"description": "An override list of tokens to always recognize as a CONSTANT for `order-by-type` regardless of casing.",
"type": [
"array",
"null"
],
"items": {
"type": "string"
}
},
"extra-standard-library": {
"description": "A list of modules to consider standard-library, in addition to those known to Ruff in advance.",
"type": [
@@ -833,6 +875,16 @@
"type": "string"
}
},
"no-lines-before": {
"description": "A list of sections that should _not_ be delineated from the previous section via empty lines.",
"type": [
"array",
"null"
],
"items": {
"$ref": "#/definitions/ImportType"
}
},
"order-by-type": {
"description": "Order imports by type, which is determined by case, in addition to alphabetically.",
"type": [
@@ -877,6 +929,16 @@
"boolean",
"null"
]
},
"variables": {
"description": "An override list of tokens to always recognize as a var for `order-by-type` regardless of casing.",
"type": [
"array",
"null"
],
"items": {
"type": "string"
}
}
},
"additionalProperties": false
@@ -1006,6 +1068,22 @@
},
"additionalProperties": false
},
"PylintOptions": {
"type": "object",
"properties": {
"allow-magic-value-types": {
"description": "Constant types to ignore when used as \"magic values\".",
"type": [
"array",
"null"
],
"items": {
"$ref": "#/definitions/ConstantType"
}
}
},
"additionalProperties": false
},
"PythonVersion": {
"type": "string",
"enum": [
@@ -1356,6 +1434,10 @@
"ICN0",
"ICN00",
"ICN001",
"INP",
"INP0",
"INP00",
"INP001",
"ISC",
"ISC0",
"ISC00",
@@ -1431,6 +1513,7 @@
"PIE79",
"PIE790",
"PIE794",
"PIE796",
"PIE8",
"PIE80",
"PIE807",
@@ -1439,10 +1522,6 @@
"PLC04",
"PLC041",
"PLC0414",
"PLC2",
"PLC22",
"PLC220",
"PLC2201",
"PLC3",
"PLC30",
"PLC300",
@@ -1552,6 +1631,7 @@
"RUF002",
"RUF003",
"RUF004",
"RUF005",
"RUF1",
"RUF10",
"RUF100",
@@ -1686,6 +1766,8 @@
"UP029",
"UP03",
"UP030",
"UP032",
"UP033",
"W",
"W2",
"W29",
@@ -1723,7 +1805,8 @@
"junit",
"grouped",
"github",
"gitlab"
"gitlab",
"pylint"
]
},
"Strictness": {

View File

@@ -1,6 +1,6 @@
[package]
name = "ruff_cli"
version = "0.0.223"
version = "0.0.227"
authors = ["Charlie Marsh <charlie.r.marsh@gmail.com>"]
edition = "2021"
rust-version = "1.65.0"

View File

@@ -4,7 +4,7 @@ use clap::{command, Parser};
use regex::Regex;
use ruff::fs;
use ruff::logging::LogLevel;
use ruff::registry::{RuleCode, RuleCodePrefix};
use ruff::registry::{Rule, RuleCodePrefix};
use ruff::resolver::ConfigProcessor;
use ruff::settings::types::{
FilePattern, PatternPrefixPair, PerFileIgnore, PythonVersion, SerializationFormat,
@@ -169,6 +169,7 @@ pub struct Cli {
/// Explain a rule.
#[arg(
long,
value_parser=Rule::from_code,
// Fake subcommands.
conflicts_with = "add_noqa",
conflicts_with = "clean",
@@ -180,7 +181,7 @@ pub struct Cli {
conflicts_with = "stdin_filename",
conflicts_with = "watch",
)]
pub explain: Option<RuleCode>,
pub explain: Option<&'static Rule>,
/// Generate shell completion
#[arg(
long,
@@ -302,7 +303,7 @@ pub struct Arguments {
pub config: Option<PathBuf>,
pub diff: bool,
pub exit_zero: bool,
pub explain: Option<RuleCode>,
pub explain: Option<&'static Rule>,
pub files: Vec<PathBuf>,
pub generate_shell_completion: Option<clap_complete_command::Shell>,
pub isolated: bool,

View File

@@ -15,11 +15,11 @@ use ruff::cache::CACHE_DIR_NAME;
use ruff::linter::add_noqa_to_path;
use ruff::logging::LogLevel;
use ruff::message::{Location, Message};
use ruff::registry::RuleCode;
use ruff::registry::Rule;
use ruff::resolver::{FileDiscovery, PyprojectDiscovery};
use ruff::settings::flags;
use ruff::settings::types::SerializationFormat;
use ruff::{fix, fs, packaging, resolver, warn_user_once, IOError};
use ruff::{fix, fs, packaging, resolver, warn_user_once, AutofixAvailability, IOError};
use serde::Serialize;
use walkdir::WalkDir;
@@ -114,7 +114,7 @@ pub fn run(
.unwrap_or_else(|(path, message)| {
if let Some(path) = &path {
let settings = resolver.resolve(path, pyproject_strategy);
if settings.enabled.contains(&RuleCode::E902) {
if settings.rules.enabled(&Rule::IOError) {
Diagnostics::new(vec![Message {
kind: IOError(message).into(),
location: Location::default(),
@@ -289,24 +289,34 @@ struct Explanation<'a> {
summary: &'a str,
}
/// Explain a `RuleCode` to the user.
pub fn explain(code: &RuleCode, format: SerializationFormat) -> Result<()> {
/// Explain a `Rule` to the user.
pub fn explain(rule: &Rule, format: SerializationFormat) -> Result<()> {
match format {
SerializationFormat::Text | SerializationFormat::Grouped => {
println!(
"{} ({}): {}",
code.as_ref(),
code.origin().title(),
code.kind().summary()
);
println!("{}\n", rule.as_ref());
println!("Code: {} ({})\n", rule.code(), rule.origin().name());
if let Some(autofix) = rule.autofixable() {
println!(
"{}",
match autofix.available {
AutofixAvailability::Sometimes => "Autofix is sometimes available.\n",
AutofixAvailability::Always => "Autofix is always available.\n",
}
);
}
println!("Message formats:\n");
for format in rule.message_formats() {
println!("* {format}");
}
}
SerializationFormat::Json => {
println!(
"{}",
serde_json::to_string_pretty(&Explanation {
code: code.as_ref(),
origin: code.origin().title(),
summary: &code.kind().summary(),
code: rule.code(),
origin: rule.origin().name(),
summary: rule.message_formats()[0],
})?
);
}
@@ -319,6 +329,9 @@ pub fn explain(code: &RuleCode, format: SerializationFormat) -> Result<()> {
SerializationFormat::Gitlab => {
bail!("`--explain` does not support GitLab format")
}
SerializationFormat::Pylint => {
bail!("`--explain` does not support pylint format")
}
};
Ok(())
}

View File

@@ -91,6 +91,22 @@ fn resolve(
pub fn main() -> Result<ExitCode> {
// Extract command-line arguments.
let (cli, overrides) = Cli::parse().partition();
let default_panic_hook = std::panic::take_hook();
std::panic::set_hook(Box::new(move |info| {
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);
}));
let log_level = extract_log_level(&cli);
set_up_logging(&log_level)?;
@@ -141,8 +157,8 @@ pub fn main() -> Result<ExitCode> {
PyprojectDiscovery::Hierarchical(settings) => settings.cli.clone(),
};
if let Some(code) = cli.explain {
commands::explain(&code, format)?;
if let Some(rule) = cli.explain {
commands::explain(rule, format)?;
return Ok(ExitCode::SUCCESS);
}
if cli.show_settings {

View File

@@ -11,7 +11,7 @@ use itertools::iterate;
use ruff::fs::relativize_path;
use ruff::logging::LogLevel;
use ruff::message::{Location, Message};
use ruff::registry::RuleCode;
use ruff::registry::Rule;
use ruff::settings::types::SerializationFormat;
use ruff::{fix, notify_user};
use serde::Serialize;
@@ -35,7 +35,7 @@ struct ExpandedFix<'a> {
#[derive(Serialize)]
struct ExpandedMessage<'a> {
code: &'a RuleCode,
code: SerializeRuleAsCode<'a>,
message: String,
fix: Option<ExpandedFix<'a>>,
location: Location,
@@ -43,6 +43,23 @@ struct ExpandedMessage<'a> {
filename: &'a str,
}
struct SerializeRuleAsCode<'a>(&'a Rule);
impl Serialize for SerializeRuleAsCode<'_> {
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(self.0.code())
}
}
impl<'a> From<&'a Rule> for SerializeRuleAsCode<'a> {
fn from(rule: &'a Rule) -> Self {
Self(rule)
}
}
pub struct Printer<'a> {
format: &'a SerializationFormat,
log_level: &'a LogLevel,
@@ -143,7 +160,7 @@ impl<'a> Printer<'a> {
.messages
.iter()
.map(|message| ExpandedMessage {
code: message.kind.code(),
code: message.kind.rule().into(),
message: message.kind.body(),
fix: message.fix.as_ref().map(|fix| ExpandedFix {
content: &fix.content,
@@ -177,8 +194,10 @@ impl<'a> Printer<'a> {
message.location.column(),
message.kind.body()
));
let mut case =
TestCase::new(format!("org.ruff.{}", message.kind.code()), status);
let mut case = TestCase::new(
format!("org.ruff.{}", message.kind.rule().code()),
status,
);
let file_path = Path::new(filename);
let file_stem = file_path.file_stem().unwrap().to_str().unwrap();
let classname = file_path.parent().unwrap().join(file_stem);
@@ -248,14 +267,14 @@ impl<'a> Printer<'a> {
":",
message.location.column(),
":",
message.kind.code().as_ref(),
message.kind.rule().code(),
message.kind.body(),
);
writeln!(
stdout,
"::error title=Ruff \
({}),file={},line={},col={},endLine={},endColumn={}::{}",
message.kind.code(),
message.kind.rule().code(),
message.filename,
message.location.row(),
message.location.column(),
@@ -266,7 +285,7 @@ impl<'a> Printer<'a> {
}
}
SerializationFormat::Gitlab => {
// Generate JSON with errors in GitLab CI format
// Generate JSON with violations in GitLab CI format
// https://docs.gitlab.com/ee/ci/testing/code_quality.html#implementing-a-custom-tool
writeln!(stdout,
"{}",
@@ -276,9 +295,9 @@ impl<'a> Printer<'a> {
.iter()
.map(|message| {
json!({
"description": format!("({}) {}", message.kind.code(), message.kind.body()),
"description": format!("({}) {}", message.kind.rule().code(), message.kind.body()),
"severity": "major",
"fingerprint": message.kind.code(),
"fingerprint": message.kind.rule().code(),
"location": {
"path": message.filename,
"lines": {
@@ -293,6 +312,20 @@ impl<'a> Printer<'a> {
)?
)?;
}
SerializationFormat::Pylint => {
// Generate violations in Pylint format.
// See: https://flake8.pycqa.org/en/latest/internal/formatters.html#pylint-formatter
for message in &diagnostics.messages {
let label = format!(
"{}:{}: [{}] {}",
relativize_path(Path::new(&message.filename)),
message.location.row(),
message.kind.rule().code(),
message.kind.body(),
);
writeln!(stdout, "{label}")?;
}
}
}
stdout.flush()?;
@@ -361,7 +394,7 @@ fn print_message<T: Write>(stdout: &mut T, message: &Message) -> Result<()> {
":".cyan(),
message.location.column(),
":".cyan(),
message.kind.code().as_ref().red().bold(),
message.kind.rule().code().red().bold(),
message.kind.body(),
);
writeln!(stdout, "{label}")?;
@@ -388,7 +421,7 @@ fn print_message<T: Write>(stdout: &mut T, message: &Message) -> Result<()> {
source: &source.contents,
line_start: message.location.row(),
annotations: vec![SourceAnnotation {
label: message.kind.code().as_ref(),
label: message.kind.rule().code(),
annotation_type: AnnotationType::Error,
range: source.range,
}],
@@ -425,7 +458,7 @@ fn print_grouped_message<T: Write>(
":".cyan(),
message.location.column(),
" ".repeat(column_length - num_digits(message.location.column())),
message.kind.code().as_ref().red().bold(),
message.kind.rule().code().red().bold(),
message.kind.body(),
);
writeln!(stdout, "{label}")?;
@@ -452,7 +485,7 @@ fn print_grouped_message<T: Write>(
source: &source.contents,
line_start: message.location.row(),
annotations: vec![SourceAnnotation {
label: message.kind.code().as_ref(),
label: message.kind.rule().code(),
annotation_type: AnnotationType::Error,
range: source.range,
}],

View File

@@ -151,3 +151,12 @@ fn test_show_source() -> Result<()> {
assert!(str::from_utf8(&output.get_output().stdout)?.contains("l = 1"));
Ok(())
}
#[test]
fn explain_status_codes() -> Result<()> {
let mut cmd = Command::cargo_bin(BIN_NAME)?;
cmd.args(["-", "--explain", "F401"]).assert().success();
let mut cmd = Command::cargo_bin(BIN_NAME)?;
cmd.args(["-", "--explain", "RUF404"]).assert().failure();
Ok(())
}

View File

@@ -1,6 +1,6 @@
[package]
name = "ruff_dev"
version = "0.0.223"
version = "0.0.227"
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 = "acbc517b55406c76da83d7b2711941d8d3f65b87" }
rustpython-common = { git = "https://github.com/RustPython/RustPython.git", rev = "acbc517b55406c76da83d7b2711941d8d3f65b87" }
rustpython-parser = { features = ["lalrpop"], git = "https://github.com/RustPython/RustPython.git", rev = "acbc517b55406c76da83d7b2711941d8d3f65b87" }
rustpython-ast = { features = ["unparse"], git = "https://github.com/RustPython/RustPython.git", rev = "ff90fe52eea578c8ebdd9d95e078cc041a5959fa" }
rustpython-common = { git = "https://github.com/RustPython/RustPython.git", rev = "ff90fe52eea578c8ebdd9d95e078cc041a5959fa" }
rustpython-parser = { features = ["lalrpop"], git = "https://github.com/RustPython/RustPython.git", rev = "ff90fe52eea578c8ebdd9d95e078cc041a5959fa" }
schemars = { version = "0.8.11" }
serde_json = {version="1.0.91"}
strum = { version = "0.24.1", features = ["strum_macros"] }

View File

@@ -25,14 +25,17 @@ fn generate_table(table_out: &mut String, prefix: &RuleCodePrefix) {
table_out.push('\n');
table_out.push_str("| ---- | ---- | ------- | --- |");
table_out.push('\n');
for rule_code in prefix.codes() {
let kind = rule_code.kind();
let fix_token = if kind.fixable() { "🛠" } else { "" };
for rule in prefix.codes() {
let fix_token = match rule.autofixable() {
None => "",
Some(_) => "🛠",
};
table_out.push_str(&format!(
"| {} | {} | {} | {} |",
kind.code().as_ref(),
kind.as_ref(),
kind.summary().replace('|', r"\|"),
rule.code(),
rule.as_ref(),
rule.message_formats()[0].replace('|', r"\|"),
fix_token
));
table_out.push('\n');
@@ -47,24 +50,37 @@ pub fn main(cli: &Cli) -> Result<()> {
for origin in RuleOrigin::iter() {
let prefixes = origin.prefixes();
let codes_csv: String = prefixes.as_list(", ");
table_out.push_str(&format!("### {} ({codes_csv})", origin.title()));
table_out.push_str(&format!("### {} ({codes_csv})", origin.name()));
table_out.push('\n');
table_out.push('\n');
toc_out.push_str(&format!(
" 1. [{} ({})](#{}-{})\n",
origin.title(),
origin.name(),
codes_csv,
origin.title().to_lowercase().replace(' ', "-"),
origin.name().to_lowercase().replace(' ', "-"),
codes_csv.to_lowercase().replace(',', "-").replace(' ', "")
));
if let Some((url, platform)) = origin.url() {
if let Some(url) = origin.url() {
let host = url
.trim_start_matches("https://")
.split('/')
.next()
.unwrap();
table_out.push_str(&format!(
"For more, see [{}]({}) on {}.",
origin.title(),
origin.name(),
url,
platform
match host {
"pypi.org" => "PyPI",
"github.com" => "GitHub",
host => panic!(
"unexpected host in URL of {}, expected pypi.org or github.com but found \
{host}",
origin.name()
),
}
));
table_out.push('\n');
table_out.push('\n');

View File

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

View File

@@ -1,35 +1,44 @@
use std::collections::HashMap;
use proc_macro2::Span;
use quote::quote;
use syn::parse::Parse;
use syn::{Ident, Path, Token};
use syn::{Ident, LitStr, Path, Token};
pub fn define_rule_mapping(mapping: Mapping) -> proc_macro2::TokenStream {
let mut rulecode_variants = quote!();
pub fn define_rule_mapping(mapping: &Mapping) -> proc_macro2::TokenStream {
let mut rule_variants = quote!();
let mut diagkind_variants = quote!();
let mut rulecode_kind_match_arms = quote!();
let mut rulecode_origin_match_arms = quote!();
let mut rule_message_formats_match_arms = quote!();
let mut rule_autofixable_match_arms = quote!();
let mut rule_origin_match_arms = quote!();
let mut rule_code_match_arms = quote!();
let mut rule_from_code_match_arms = quote!();
let mut diagkind_code_match_arms = quote!();
let mut diagkind_body_match_arms = quote!();
let mut diagkind_fixable_match_arms = quote!();
let mut diagkind_commit_match_arms = quote!();
let mut from_impls_for_diagkind = quote!();
for (code, path, name) in mapping.entries {
rulecode_variants.extend(quote! {#code,});
for (code, path, name) in &mapping.entries {
let code_str = LitStr::new(&code.to_string(), Span::call_site());
rule_variants.extend(quote! {
#[doc = #code_str]
#name,
});
diagkind_variants.extend(quote! {#name(#path),});
rulecode_kind_match_arms.extend(
quote! {RuleCode::#code => DiagnosticKind::#name(<#path as Violation>::placeholder()),},
);
let origin = get_origin(&code);
rulecode_origin_match_arms.extend(quote! {RuleCode::#code => RuleOrigin::#origin,});
diagkind_code_match_arms.extend(quote! {DiagnosticKind::#name(..) => &RuleCode::#code, });
diagkind_body_match_arms
.extend(quote! {DiagnosticKind::#name(x) => Violation::message(x), });
rule_message_formats_match_arms
.extend(quote! {Self::#name => <#path as Violation>::message_formats(),});
rule_autofixable_match_arms.extend(quote! {Self::#name => <#path as Violation>::AUTOFIX,});
let origin = get_origin(code);
rule_origin_match_arms.extend(quote! {Self::#name => RuleOrigin::#origin,});
rule_code_match_arms.extend(quote! {Self::#name => #code_str,});
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
.extend(quote! {DiagnosticKind::#name(x) => x.autofix_title_formatter().is_some(),});
diagkind_commit_match_arms.extend(
quote! {DiagnosticKind::#name(x) => x.autofix_title_formatter().map(|f| f(x)), },
);
.extend(quote! {Self::#name(x) => x.autofix_title_formatter().is_some(),});
diagkind_commit_match_arms
.extend(quote! {Self::#name(x) => x.autofix_title_formatter().map(|f| f(x)), });
from_impls_for_diagkind.extend(quote! {
impl From<#path> for DiagnosticKind {
fn from(x: #path) -> Self {
@@ -39,44 +48,73 @@ pub fn define_rule_mapping(mapping: Mapping) -> proc_macro2::TokenStream {
});
}
let code_to_name: HashMap<_, _> = mapping
.entries
.iter()
.map(|(code, _, name)| (code.to_string(), name))
.collect();
let rulecodeprefix = super::rule_code_prefix::expand(
&Ident::new("Rule", Span::call_site()),
&Ident::new("RuleCodePrefix", Span::call_site()),
mapping.entries.iter().map(|(code, ..)| code),
|code| code_to_name[code],
);
quote! {
#[derive(
AsRefStr,
RuleCodePrefix,
EnumIter,
EnumString,
Debug,
Display,
PartialEq,
Eq,
Clone,
Serialize,
Deserialize,
Hash,
PartialOrd,
Ord,
AsRefStr,
)]
pub enum RuleCode { #rulecode_variants }
#[strum(serialize_all = "kebab-case")]
pub enum Rule { #rule_variants }
#[derive(AsRefStr, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum DiagnosticKind { #diagkind_variants }
#[derive(thiserror::Error, Debug)]
pub enum FromCodeError {
#[error("unknown rule code")]
Unknown,
}
impl RuleCode {
/// A placeholder representation of the `DiagnosticKind` for the diagnostic.
pub fn kind(&self) -> DiagnosticKind {
match self { #rulecode_kind_match_arms }
impl Rule {
/// Returns the format strings used to report violations of this rule.
pub fn message_formats(&self) -> &'static [&'static str] {
match self { #rule_message_formats_match_arms }
}
pub fn autofixable(&self) -> Option<crate::violation::AutofixKind> {
match self { #rule_autofixable_match_arms }
}
pub fn origin(&self) -> RuleOrigin {
match self { #rulecode_origin_match_arms }
match self { #rule_origin_match_arms }
}
pub fn code(&self) -> &'static str {
match self { #rule_code_match_arms }
}
pub fn from_code(code: &str) -> Result<&'static Self, FromCodeError> {
match code {
#rule_from_code_match_arms
_ => Err(FromCodeError::Unknown),
}
}
}
impl DiagnosticKind {
/// A four-letter shorthand code for the diagnostic.
pub fn code(&self) -> &'static RuleCode {
/// The rule of the diagnostic.
pub fn rule(&self) -> &'static Rule {
match self { #diagkind_code_match_arms }
}
@@ -97,6 +135,8 @@ pub fn define_rule_mapping(mapping: Mapping) -> proc_macro2::TokenStream {
}
#from_impls_for_diagkind
#rulecodeprefix
}
}

View File

@@ -0,0 +1,55 @@
use proc_macro2::TokenStream;
use quote::{quote, quote_spanned, ToTokens};
use syn::spanned::Spanned;
use syn::{Block, Expr, ItemFn, Stmt};
pub fn derive_message_formats(func: &ItemFn) -> proc_macro2::TokenStream {
let mut strings = quote!();
if let Err(err) = parse_block(&func.block, &mut strings) {
return err;
}
quote! {
#func
fn message_formats() -> &'static [&'static str] {
&[#strings]
}
}
}
fn parse_block(block: &Block, strings: &mut TokenStream) -> Result<(), TokenStream> {
let Some(Stmt::Expr(last)) = block.stmts.last() else {panic!("expected last statement in block to be an expression")};
parse_expr(last, strings)?;
Ok(())
}
fn parse_expr(expr: &Expr, strings: &mut TokenStream) -> Result<(), TokenStream> {
match expr {
Expr::Macro(mac) if mac.mac.path.is_ident("format") => {
let Some(first_token) = mac.mac.tokens.to_token_stream().into_iter().next() else {
return Err(quote_spanned!(expr.span() => compile_error!("expected format! to have an argument")))
};
strings.extend(quote! {#first_token,});
Ok(())
}
Expr::Block(block) => parse_block(&block.block, strings),
Expr::If(expr) => {
parse_block(&expr.then_branch, strings)?;
if let Some((_, then)) = &expr.else_branch {
parse_expr(then, strings)?;
}
Ok(())
}
Expr::Match(block) => {
for arm in &block.arms {
parse_expr(&arm.body, strings)?;
}
Ok(())
}
_ => Err(quote_spanned!(
expr.span() =>
compile_error!("expected last expression to be a format! macro or a match block")
)),
}
}

View File

@@ -13,10 +13,12 @@
)]
#![forbid(unsafe_code)]
use syn::{parse_macro_input, DeriveInput};
use proc_macro::TokenStream;
use syn::{parse_macro_input, DeriveInput, ItemFn};
mod config;
mod define_rule_mapping;
mod derive_message_formats;
mod prefixes;
mod rule_code_prefix;
@@ -29,17 +31,14 @@ pub fn derive_config(input: proc_macro::TokenStream) -> proc_macro::TokenStream
.into()
}
#[proc_macro_derive(RuleCodePrefix)]
pub fn derive_rule_code_prefix(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let input = parse_macro_input!(input as DeriveInput);
rule_code_prefix::derive_impl(input)
.unwrap_or_else(syn::Error::into_compile_error)
.into()
}
#[proc_macro]
pub fn define_rule_mapping(item: proc_macro::TokenStream) -> proc_macro::TokenStream {
let mapping = parse_macro_input!(item as define_rule_mapping::Mapping);
define_rule_mapping::define_rule_mapping(mapping).into()
define_rule_mapping::define_rule_mapping(&mapping).into()
}
#[proc_macro_attribute]
pub fn derive_message_formats(_attr: TokenStream, item: TokenStream) -> TokenStream {
let func = parse_macro_input!(item as ItemFn);
derive_message_formats::derive_message_formats(&func).into()
}

View File

@@ -3,9 +3,7 @@ use std::collections::{BTreeMap, BTreeSet, HashMap};
use once_cell::sync::Lazy;
use proc_macro2::Span;
use quote::quote;
use syn::punctuated::Punctuated;
use syn::token::Comma;
use syn::{DataEnum, DeriveInput, Ident, Variant};
use syn::Ident;
const ALL: &str = "ALL";
@@ -86,43 +84,17 @@ pub static PREFIX_REDIRECTS: Lazy<HashMap<&'static str, &'static str>> = Lazy::n
])
});
pub fn derive_impl(input: DeriveInput) -> syn::Result<proc_macro2::TokenStream> {
let DeriveInput { ident, data, .. } = input;
let syn::Data::Enum(DataEnum { variants, .. }) = data else {
return Err(syn::Error::new(
ident.span(),
"Can only derive `RuleCodePrefix` from enums.",
));
};
let prefix_ident = Ident::new(&format!("{ident}Prefix"), ident.span());
let prefix = expand(&ident, &prefix_ident, &variants);
let expanded = quote! {
#[derive(PartialEq, Eq, PartialOrd, Ord)]
pub enum SuffixLength {
None,
Zero,
One,
Two,
Three,
Four,
}
#prefix
};
Ok(expanded)
}
fn expand(
ident: &Ident,
pub fn expand<'a>(
rule_type: &Ident,
prefix_ident: &Ident,
variants: &Punctuated<Variant, Comma>,
variants: impl Iterator<Item = &'a Ident>,
variant_name: impl Fn(&str) -> &'a Ident,
) -> proc_macro2::TokenStream {
// Build up a map from prefix to matching RuleCodes.
let mut prefix_to_codes: BTreeMap<Ident, BTreeSet<String>> = BTreeMap::default();
for variant in variants {
let span = variant.ident.span();
let code_str = variant.ident.to_string();
let span = variant.span();
let code_str = variant.to_string();
let code_prefix_len = code_str
.chars()
.take_while(|char| char.is_alphabetic())
@@ -158,7 +130,7 @@ fn expand(
}
});
let prefix_impl = generate_impls(ident, prefix_ident, &prefix_to_codes);
let prefix_impl = generate_impls(rule_type, prefix_ident, &prefix_to_codes, variant_name);
let prefix_redirects = PREFIX_REDIRECTS.iter().map(|(alias, rule_code)| {
let code = Ident::new(rule_code, Span::call_site());
@@ -168,6 +140,16 @@ fn expand(
});
quote! {
#[derive(PartialEq, Eq, PartialOrd, Ord)]
pub enum SuffixLength {
None,
Zero,
One,
Two,
Three,
Four,
}
#[derive(
::strum_macros::EnumString,
::strum_macros::AsRefStr,
@@ -196,16 +178,17 @@ fn expand(
}
}
fn generate_impls(
ident: &Ident,
fn generate_impls<'a>(
rule_type: &Ident,
prefix_ident: &Ident,
prefix_to_codes: &BTreeMap<Ident, BTreeSet<String>>,
variant_name: impl Fn(&str) -> &'a Ident,
) -> proc_macro2::TokenStream {
let codes_match_arms = prefix_to_codes.iter().map(|(prefix, codes)| {
let codes = codes.iter().map(|code| {
let code = Ident::new(code, Span::call_site());
let rule_variant = variant_name(code);
quote! {
#ident::#code
#rule_type::#rule_variant
}
});
let prefix_str = prefix.to_string();
@@ -265,7 +248,7 @@ fn generate_impls(
quote! {
impl #prefix_ident {
pub fn codes(&self) -> Vec<#ident> {
pub fn codes(&self) -> Vec<#rule_type> {
use colored::Colorize;
#[allow(clippy::match_same_arms)]

View File

@@ -47,11 +47,11 @@ mod tests {
use anyhow::Result;
use test_case::test_case;
use crate::registry::RuleCode;
use crate::registry::Rule;
use crate::linter::test_path;
use crate::settings;
fn rules(rule_code: RuleCode, path: &Path) -> Result<()> {
fn rules(rule_code: Rule, path: &Path) -> Result<()> {
let snapshot = format!("{}_{}", rule_code.as_ref(), path.to_string_lossy());
let diagnostics =test_path(
Path::new("./resources/test/fixtures/%s")

View File

@@ -44,9 +44,9 @@ def main(*, name: str, code: str, origin: str) -> None:
with open(mod_rs, "w") as fp:
for line in content.splitlines():
if line.strip() == "fn rules(rule_code: RuleCode, path: &Path) -> Result<()> {":
indent = line.split("fn rules(rule_code: RuleCode, path: &Path) -> Result<()> {")[0]
fp.write(f'{indent}#[test_case(RuleCode::{code}, Path::new("{code}.py"); "{code}")]')
if line.strip() == "fn rules(rule_code: Rule, path: &Path) -> Result<()> {":
indent = line.split("fn rules(rule_code: Rule, path: &Path) -> Result<()> {")[0]
fp.write(f'{indent}#[test_case(Rule::{code}, Path::new("{code}.py"); "{code}")]')
fp.write("\n")
fp.write(line)

View File

@@ -0,0 +1,25 @@
# benchmarks
Utilities for benchmarking Ruff.
## Getting Started
Run `./scripts/benchmarks/run.sh` to clone the benchmarking target (CPython).
If you're looking to benchmark Ruff against other tools, you'll also need to run `poetry
install` to create a virtual environment with the required dependencies.
## Running Benchmarks
Run `./scripts/benchmarks/run.sh` to run Ruff over the target repo (CPython). The
`./scripts/benchmarks` folder contains a few other benchmarks (e.g., `scripts/benchmarks/run_comparisons.sh`
compares Ruff to a variety of other tools).
## Generating Plots
The Vega specification for the benchmark plot depicted in the root README can be found at
`scripts/benchmarks/graph-spec.json`. You can render this JSON spec in the [Vega Editor](https://vega.github.io/editor/#/edited).
The images seen in the README are generated by exporting the rendered Vega spec as SVG (at around
688px wide) and manually bolding the Ruff title and benchmark time. The dark mode variant is
generated by changing the fill from `fill="#333333"` to `fill="#C9D1D9"`.

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 9.0 KiB

View File

@@ -0,0 +1,209 @@
{
"$schema": "https://vega.github.io/schema/vega-lite/v5.json",
"data": {
"values": [
{
"tool": "Ruff",
"time": 0.2943,
"timeFormat": "0.29s"
},
{
"tool": "Autoflake",
"time": 6.175,
"timeFormat": "6.18s"
},
{
"tool": "Flake8",
"time": 12.26,
"timeFormat": "12.26s"
},
{
"tool": "Pyflakes",
"time": 15.786,
"timeFormat": "15.79s"
},
{
"tool": "Pycodestyle",
"time": 46.921,
"timeFormat": "46.92s"
},
{
"tool": "Pylint",
"time": 62.0,
"timeFormat": "> 60s"
}
]
},
"config": {
"params": [
{
"name": "defaultFont",
"value": "-apple-system,BlinkMacSystemFont,\"Segoe UI\",Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\""
},
{
"name": "titleColor",
"value": "#333333"
},
{
"name": "labelColor",
"value": "#333333"
}
],
"header": {
"labelFont": {
"expr": "defaultFont"
},
"titleFont": {
"expr": "defaultFont"
},
"titleFontWeight": 500
},
"text": {
"font": {
"expr": "defaultFont"
},
"color": {
"expr": "labelColor"
}
},
"mark": {
"font": {
"expr": "defaultFont"
},
"color": {
"expr": "labelColor"
}
},
"title": {
"font": {
"expr": "defaultFont"
},
"subtitleFont": {
"expr": "defaultFont"
},
"fontWeight": 500
},
"axis": {
"labelColor": {
"expr": "labelColor"
},
"labelFont": {
"expr": "defaultFont"
},
"titleFont": {
"expr": "defaultFont"
},
"titleFontWeight": 500,
"titleColor": {
"expr": "titleColor"
},
"titleFontSize": 12
},
"legend": {
"titleFontWeight": 500,
"titleColor": {
"expr": "titleColor"
},
"titleFontSize": 12,
"labelColor": {
"expr": "labelColor"
},
"labelFont": {
"expr": "defaultFont"
},
"titleFont": {
"expr": "defaultFont"
}
},
"view": {
"stroke": null
},
"background": "transparent"
},
"background": "transparent",
"encoding": {
"y": {
"field": "tool",
"type": "nominal",
"axis": {
"grid": false,
"title": null,
"labelFontSize": 12,
"ticks": false,
"labelPadding": 10,
"domain": false
},
"sort": null
},
"x": {
"field": "time",
"type": "quantitative",
"axis": {
"title": null,
"labelExpr": "datum.value + 's'",
"tickCount": 3,
"tickSize": 0,
"labelPadding": 6,
"labelAlign": "center",
"labelFontSize": 12,
"tickColor": "rgba(127,127,127,0.25)",
"gridColor": "rgba(127,127,127,0.25)",
"domain": false
}
}
},
"height": 140,
"width": "container",
"layer": [
{
"mark": "bar",
"encoding": {
"size": {
"value": 13
},
"color": {
"value": "#E15759"
}
}
},
{
"transform": [
{
"filter": "datum.tool !== 'ruff'"
}
],
"mark": {
"type": "text",
"align": "left",
"baseline": "middle",
"dx": 6,
"fontSize": 12
},
"encoding": {
"text": {
"field": "timeFormat"
}
}
},
{
"transform": [
{
"filter": "datum.tool === 'ruff'"
}
],
"mark": {
"type": "text",
"align": "left",
"baseline": "middle",
"dx": 6,
"fontSize": 12,
"fontWeight": "bold"
},
"encoding": {
"text": {
"field": "timeFormat"
}
}
}
]
}

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 9.0 KiB

1005
scripts/benchmarks/poetry.lock generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,77 @@
[tool.poetry]
name = "scripts"
version = "0.1.0"
description = ""
authors = ["Charles Marsh <charlie.r.marsh@gmail.com>"]
[tool.poetry.dependencies]
python = ">=3.10,<3.12"
autoflake = "^2.0.0"
flake8 = "^6.0.0"
pycodestyle = "^2.10.0"
pyflakes = "^3.0.1"
pylint = "^2.15.10"
black = "^22.12.0"
isort = "^5.11.4"
flake8-2020 = { version = "*", optional = true }
flake8-annotations = { version = "*", optional = true }
flake8-bandit = { version = "*", optional = true }
flake8-blind-except = { version = "*", optional = true }
# flake8-boolean-trap = { version = "*", optional = true }
flake8-bugbear = { version = "*", optional = true }
flake8-builtins = { version = "*", optional = true }
flake8-commas = { version = "*", optional = true }
flake8-comprehensions = { version = "*", optional = true }
flake8-datetimez = { version = "*", optional = true }
flake8-debugger = { version = "*", optional = true }
flake8-docstrings = { version = "*", optional = true }
# flake8-eradicate = { version = "*", optional = true }
flake8-errmsg = { version = "*", optional = true }
flake8-implicit-str-concat = { version = "*", optional = true }
# flake8-import-conventions = { version = "*", optional = true }
flake8-isort = { version = "*", optional = true }
flake8-pie = { version = "*", optional = true }
flake8-print = { version = "*", optional = true }
flake8-quotes = { version = "*", optional = true }
flake8-return = { version = "*", optional = true }
flake8-simplify = { version = "*", optional = true }
flake8-super = { version = "*", optional = true }
flake8-tidy-imports = { version = "*", optional = true }
pandas-vet = { version = "*", optional = true }
pep8-naming = { version = "*", optional = true }
[tool.poetry.dev-dependencies]
[tool.poetry.extras]
plugins = [
"flake8-2020",
"flake8-annotations",
"flake8-bandit",
"flake8-blind-except",
# "flake8-boolean-trap",
"flake8-bugbear",
"flake8-builtins",
"flake8-commas",
"flake8-comprehensions",
"flake8-datetimez",
"flake8-debugger",
"flake8-docstrings",
# "flake8-eradicate",
"flake8-errmsg",
"flake8-implicit-str-concat",
# "flake8-import-conventions",
"flake8-isort",
"flake8-pie",
"flake8-print",
"flake8-quotes",
"flake8-return",
"flake8-simplify",
"flake8-super",
"flake8-tidy-imports",
"pandas-vet",
"pep8-naming",
]
[build-system]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"

8
scripts/benchmarks/run.sh Executable file
View File

@@ -0,0 +1,8 @@
#!/usr/bin/env sh
###
# Benchmark Ruff on the CPython codebase.
###
cargo build --release && hyperfine --ignore-failure --warmup 10 \
"./target/release/ruff ./resources/test/cpython/ --no-cache"

26
scripts/benchmarks/run_all.sh Executable file
View File

@@ -0,0 +1,26 @@
#!/usr/bin/env sh
###
# Benchmark Ruff's performance against a variety of similar tools, suppressing output as much as
# possible (so as to reduce I/O overhead).
###
# Note: Flake8's `checker.py` requires the following variant of `mp_run`:
# def _mp_run(filename: str) -> tuple[str, Results, dict[str, int]]:
# try:
# return FileChecker(
# filename=filename, plugins=_mp_plugins, options=_mp_options
# ).run_checks()
# except:
# return (filename, [], {
# "files": 0,
# "logical lines": 0,
# "physical lines": 0,
# "tokens": 0,
# })
hyperfine --ignore-failure --warmup 5 \
"./target/release/ruff ./resources/test/cpython/ --no-cache --silent --select ALL" \
"flake8 resources/test/cpython -qq --docstring-convention=all" \
"pycodestyle resources/test/cpython -qq" \
"pylint resources/test/cpython -j 0 --recursive=y --disable=E,W,C,R"

View File

@@ -0,0 +1,12 @@
#!/usr/bin/env sh
###
# Benchmark Ruff's performance against a variety of similar tools.
###
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"

View File

@@ -0,0 +1,43 @@
#!/usr/bin/env sh
###
# Benchmark the incremental performance of each subsequent plugin.
###
cargo build --release && hyperfine --ignore-failure --warmup 10 \
"./target/release/ruff ./resources/test/cpython/ --no-cache --silent" \
"./target/release/ruff ./resources/test/cpython/ --no-cache --silent --extend-select C90" \
"./target/release/ruff ./resources/test/cpython/ --no-cache --silent --extend-select I" \
"./target/release/ruff ./resources/test/cpython/ --no-cache --silent --extend-select D" \
"./target/release/ruff ./resources/test/cpython/ --no-cache --silent --extend-select UP" \
"./target/release/ruff ./resources/test/cpython/ --no-cache --silent --extend-select N" \
"./target/release/ruff ./resources/test/cpython/ --no-cache --silent --extend-select YTT" \
"./target/release/ruff ./resources/test/cpython/ --no-cache --silent --extend-select ANN" \
"./target/release/ruff ./resources/test/cpython/ --no-cache --silent --extend-select S" \
"./target/release/ruff ./resources/test/cpython/ --no-cache --silent --extend-select BLE" \
"./target/release/ruff ./resources/test/cpython/ --no-cache --silent --extend-select FBT" \
"./target/release/ruff ./resources/test/cpython/ --no-cache --silent --extend-select B" \
"./target/release/ruff ./resources/test/cpython/ --no-cache --silent --extend-select A" \
"./target/release/ruff ./resources/test/cpython/ --no-cache --silent --extend-select C4" \
"./target/release/ruff ./resources/test/cpython/ --no-cache --silent --extend-select T10" \
"./target/release/ruff ./resources/test/cpython/ --no-cache --silent --extend-select EM" \
"./target/release/ruff ./resources/test/cpython/ --no-cache --silent --extend-select ISC" \
"./target/release/ruff ./resources/test/cpython/ --no-cache --silent --extend-select ICN" \
"./target/release/ruff ./resources/test/cpython/ --no-cache --silent --extend-select T20" \
"./target/release/ruff ./resources/test/cpython/ --no-cache --silent --extend-select PT" \
"./target/release/ruff ./resources/test/cpython/ --no-cache --silent --extend-select Q" \
"./target/release/ruff ./resources/test/cpython/ --no-cache --silent --extend-select RET" \
"./target/release/ruff ./resources/test/cpython/ --no-cache --silent --extend-select SIM" \
"./target/release/ruff ./resources/test/cpython/ --no-cache --silent --extend-select TID" \
"./target/release/ruff ./resources/test/cpython/ --no-cache --silent --extend-select ARG" \
"./target/release/ruff ./resources/test/cpython/ --no-cache --silent --extend-select DTZ" \
"./target/release/ruff ./resources/test/cpython/ --no-cache --silent --extend-select ERA" \
"./target/release/ruff ./resources/test/cpython/ --no-cache --silent --extend-select PD" \
"./target/release/ruff ./resources/test/cpython/ --no-cache --silent --extend-select PGH" \
"./target/release/ruff ./resources/test/cpython/ --no-cache --silent --extend-select PLC" \
"./target/release/ruff ./resources/test/cpython/ --no-cache --silent --extend-select PLE" \
"./target/release/ruff ./resources/test/cpython/ --no-cache --silent --extend-select PLR" \
"./target/release/ruff ./resources/test/cpython/ --no-cache --silent --extend-select PLW" \
"./target/release/ruff ./resources/test/cpython/ --no-cache --silent --extend-select PIE" \
"./target/release/ruff ./resources/test/cpython/ --no-cache --silent --extend-select COM" \
"./target/release/ruff ./resources/test/cpython/ --no-cache --silent --extend-select RUF"

View File

@@ -0,0 +1,12 @@
#!/usr/bin/env sh
###
# Benchmark Ruff's performance against a variety of similar tools, suppressing output as much as
# possible (so as to reduce I/O overhead).
###
hyperfine --ignore-failure --warmup 5 \
"./target/release/ruff ./resources/test/cpython/ --no-cache --silent" \
"pycodestyle resources/test/cpython -qq" \
"flake8 resources/test/cpython -qq" \
"pylint resources/test/cpython -j 0 --recursive=y --disable=E,W,C,R"

7
scripts/benchmarks/setup.sh Executable file
View File

@@ -0,0 +1,7 @@
#!/usr/bin/env sh
###
# Setup the CPython repository to enable benchmarking.
###
git clone --branch 3.10 https://github.com/python/cpython.git resources/test/cpython

305
scripts/poetry.lock generated
View File

@@ -1,305 +0,0 @@
# This file is automatically @generated by Poetry and should not be changed by hand.
[[package]]
name = "astroid"
version = "2.12.13"
description = "An abstract syntax tree for Python with inference support."
category = "main"
optional = false
python-versions = ">=3.7.2"
files = [
{file = "astroid-2.12.13-py3-none-any.whl", hash = "sha256:10e0ad5f7b79c435179d0d0f0df69998c4eef4597534aae44910db060baeb907"},
{file = "astroid-2.12.13.tar.gz", hash = "sha256:1493fe8bd3dfd73dc35bd53c9d5b6e49ead98497c47b2307662556a5692d29d7"},
]
[package.dependencies]
lazy-object-proxy = ">=1.4.0"
wrapt = {version = ">=1.11,<2", markers = "python_version < \"3.11\""}
[[package]]
name = "autoflake"
version = "1.7.8"
description = "Removes unused imports and unused variables"
category = "main"
optional = false
python-versions = ">=3.7"
files = [
{file = "autoflake-1.7.8-py3-none-any.whl", hash = "sha256:46373ef69b6714f5064c923bb28bd797c4f8a9497f557d87fc36665c6d956b39"},
{file = "autoflake-1.7.8.tar.gz", hash = "sha256:e7e46372dee46fa1c97acf310d99d922b63d369718a270809d7c278d34a194cf"},
]
[package.dependencies]
pyflakes = ">=1.1.0,<3"
tomli = {version = ">=2.0.1", markers = "python_version < \"3.11\""}
[[package]]
name = "colorama"
version = "0.4.6"
description = "Cross-platform colored terminal text."
category = "main"
optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
files = [
{file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"},
{file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
]
[[package]]
name = "dill"
version = "0.3.6"
description = "serialize all of python"
category = "main"
optional = false
python-versions = ">=3.7"
files = [
{file = "dill-0.3.6-py3-none-any.whl", hash = "sha256:a07ffd2351b8c678dfc4a856a3005f8067aea51d6ba6c700796a4d9e280f39f0"},
{file = "dill-0.3.6.tar.gz", hash = "sha256:e5db55f3687856d8fbdab002ed78544e1c4559a130302693d839dfe8f93f2373"},
]
[package.extras]
graph = ["objgraph (>=1.7.2)"]
[[package]]
name = "flake8"
version = "5.0.4"
description = "the modular source code checker: pep8 pyflakes and co"
category = "main"
optional = false
python-versions = ">=3.6.1"
files = [
{file = "flake8-5.0.4-py2.py3-none-any.whl", hash = "sha256:7a1cf6b73744f5806ab95e526f6f0d8c01c66d7bbe349562d22dfca20610b248"},
{file = "flake8-5.0.4.tar.gz", hash = "sha256:6fbe320aad8d6b95cec8b8e47bc933004678dc63095be98528b7bdd2a9f510db"},
]
[package.dependencies]
mccabe = ">=0.7.0,<0.8.0"
pycodestyle = ">=2.9.0,<2.10.0"
pyflakes = ">=2.5.0,<2.6.0"
[[package]]
name = "isort"
version = "5.11.4"
description = "A Python utility / library to sort Python imports."
category = "main"
optional = false
python-versions = ">=3.7.0"
files = [
{file = "isort-5.11.4-py3-none-any.whl", hash = "sha256:c033fd0edb91000a7f09527fe5c75321878f98322a77ddcc81adbd83724afb7b"},
{file = "isort-5.11.4.tar.gz", hash = "sha256:6db30c5ded9815d813932c04c2f85a360bcdd35fed496f4d8f35495ef0a261b6"},
]
[package.extras]
colors = ["colorama (>=0.4.3,<0.5.0)"]
pipfile-deprecated-finder = ["pipreqs", "requirementslib"]
plugins = ["setuptools"]
requirements-deprecated-finder = ["pip-api", "pipreqs"]
[[package]]
name = "lazy-object-proxy"
version = "1.8.0"
description = "A fast and thorough lazy object proxy."
category = "main"
optional = false
python-versions = ">=3.7"
files = [
{file = "lazy-object-proxy-1.8.0.tar.gz", hash = "sha256:c219a00245af0f6fa4e95901ed28044544f50152840c5b6a3e7b2568db34d156"},
{file = "lazy_object_proxy-1.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4fd031589121ad46e293629b39604031d354043bb5cdf83da4e93c2d7f3389fe"},
{file = "lazy_object_proxy-1.8.0-cp310-cp310-win32.whl", hash = "sha256:b70d6e7a332eb0217e7872a73926ad4fdc14f846e85ad6749ad111084e76df25"},
{file = "lazy_object_proxy-1.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:eb329f8d8145379bf5dbe722182410fe8863d186e51bf034d2075eb8d85ee25b"},
{file = "lazy_object_proxy-1.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4e2d9f764f1befd8bdc97673261b8bb888764dfdbd7a4d8f55e4fbcabb8c3fb7"},
{file = "lazy_object_proxy-1.8.0-cp311-cp311-win32.whl", hash = "sha256:e20bfa6db17a39c706d24f82df8352488d2943a3b7ce7d4c22579cb89ca8896e"},
{file = "lazy_object_proxy-1.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:14010b49a2f56ec4943b6cf925f597b534ee2fe1f0738c84b3bce0c1a11ff10d"},
{file = "lazy_object_proxy-1.8.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:6850e4aeca6d0df35bb06e05c8b934ff7c533734eb51d0ceb2d63696f1e6030c"},
{file = "lazy_object_proxy-1.8.0-cp37-cp37m-win32.whl", hash = "sha256:5b51d6f3bfeb289dfd4e95de2ecd464cd51982fe6f00e2be1d0bf94864d58acd"},
{file = "lazy_object_proxy-1.8.0-cp37-cp37m-win_amd64.whl", hash = "sha256:6f593f26c470a379cf7f5bc6db6b5f1722353e7bf937b8d0d0b3fba911998858"},
{file = "lazy_object_proxy-1.8.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0c1c7c0433154bb7c54185714c6929acc0ba04ee1b167314a779b9025517eada"},
{file = "lazy_object_proxy-1.8.0-cp38-cp38-win32.whl", hash = "sha256:d176f392dbbdaacccf15919c77f526edf11a34aece58b55ab58539807b85436f"},
{file = "lazy_object_proxy-1.8.0-cp38-cp38-win_amd64.whl", hash = "sha256:afcaa24e48bb23b3be31e329deb3f1858f1f1df86aea3d70cb5c8578bfe5261c"},
{file = "lazy_object_proxy-1.8.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:71d9ae8a82203511a6f60ca5a1b9f8ad201cac0fc75038b2dc5fa519589c9288"},
{file = "lazy_object_proxy-1.8.0-cp39-cp39-win32.whl", hash = "sha256:8f6ce2118a90efa7f62dd38c7dbfffd42f468b180287b748626293bf12ed468f"},
{file = "lazy_object_proxy-1.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:eac3a9a5ef13b332c059772fd40b4b1c3d45a3a2b05e33a361dee48e54a4dad0"},
{file = "lazy_object_proxy-1.8.0-pp37-pypy37_pp73-any.whl", hash = "sha256:ae032743794fba4d171b5b67310d69176287b5bf82a21f588282406a79498891"},
{file = "lazy_object_proxy-1.8.0-pp38-pypy38_pp73-any.whl", hash = "sha256:7e1561626c49cb394268edd00501b289053a652ed762c58e1081224c8d881cec"},
{file = "lazy_object_proxy-1.8.0-pp39-pypy39_pp73-any.whl", hash = "sha256:ce58b2b3734c73e68f0e30e4e725264d4d6be95818ec0a0be4bb6bf9a7e79aa8"},
]
[[package]]
name = "mccabe"
version = "0.7.0"
description = "McCabe checker, plugin for flake8"
category = "main"
optional = false
python-versions = ">=3.6"
files = [
{file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"},
{file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"},
]
[[package]]
name = "platformdirs"
version = "2.6.2"
description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
category = "main"
optional = false
python-versions = ">=3.7"
files = [
{file = "platformdirs-2.6.2-py3-none-any.whl", hash = "sha256:83c8f6d04389165de7c9b6f0c682439697887bca0aa2f1c87ef1826be3584490"},
{file = "platformdirs-2.6.2.tar.gz", hash = "sha256:e1fea1fe471b9ff8332e229df3cb7de4f53eeea4998d3b6bfff542115e998bd2"},
]
[package.extras]
docs = ["furo (>=2022.12.7)", "proselint (>=0.13)", "sphinx (>=5.3)", "sphinx-autodoc-typehints (>=1.19.5)"]
test = ["appdirs (==1.4.4)", "covdefaults (>=2.2.2)", "pytest (>=7.2)", "pytest-cov (>=4)", "pytest-mock (>=3.10)"]
[[package]]
name = "pycodestyle"
version = "2.9.1"
description = "Python style guide checker"
category = "main"
optional = false
python-versions = ">=3.6"
files = [
{file = "pycodestyle-2.9.1-py2.py3-none-any.whl", hash = "sha256:d1735fc58b418fd7c5f658d28d943854f8a849b01a5d0a1e6f3f3fdd0166804b"},
{file = "pycodestyle-2.9.1.tar.gz", hash = "sha256:2c9607871d58c76354b697b42f5d57e1ada7d261c261efac224b664affdc5785"},
]
[[package]]
name = "pyflakes"
version = "2.5.0"
description = "passive checker of Python programs"
category = "main"
optional = false
python-versions = ">=3.6"
files = [
{file = "pyflakes-2.5.0-py2.py3-none-any.whl", hash = "sha256:4579f67d887f804e67edb544428f264b7b24f435b263c4614f384135cea553d2"},
{file = "pyflakes-2.5.0.tar.gz", hash = "sha256:491feb020dca48ccc562a8c0cbe8df07ee13078df59813b83959cbdada312ea3"},
]
[[package]]
name = "pylint"
version = "2.15.9"
description = "python code static checker"
category = "main"
optional = false
python-versions = ">=3.7.2"
files = [
{file = "pylint-2.15.9-py3-none-any.whl", hash = "sha256:349c8cd36aede4d50a0754a8c0218b43323d13d5d88f4b2952ddfe3e169681eb"},
{file = "pylint-2.15.9.tar.gz", hash = "sha256:18783cca3cfee5b83c6c5d10b3cdb66c6594520ffae61890858fe8d932e1c6b4"},
]
[package.dependencies]
astroid = ">=2.12.13,<=2.14.0-dev0"
colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""}
dill = {version = ">=0.2", markers = "python_version < \"3.11\""}
isort = ">=4.2.5,<6"
mccabe = ">=0.6,<0.8"
platformdirs = ">=2.2.0"
tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""}
tomlkit = ">=0.10.1"
[package.extras]
spelling = ["pyenchant (>=3.2,<4.0)"]
testutils = ["gitpython (>3)"]
[[package]]
name = "tomli"
version = "2.0.1"
description = "A lil' TOML parser"
category = "main"
optional = false
python-versions = ">=3.7"
files = [
{file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"},
{file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"},
]
[[package]]
name = "tomlkit"
version = "0.11.6"
description = "Style preserving TOML library"
category = "main"
optional = false
python-versions = ">=3.6"
files = [
{file = "tomlkit-0.11.6-py3-none-any.whl", hash = "sha256:07de26b0d8cfc18f871aec595fda24d95b08fef89d147caa861939f37230bf4b"},
{file = "tomlkit-0.11.6.tar.gz", hash = "sha256:71b952e5721688937fb02cf9d354dbcf0785066149d2855e44531ebdd2b65d73"},
]
[[package]]
name = "wrapt"
version = "1.14.1"
description = "Module for decorators, wrappers and monkey patching."
category = "main"
optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7"
files = [
{file = "wrapt-1.14.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:1b376b3f4896e7930f1f772ac4b064ac12598d1c38d04907e696cc4d794b43d3"},
{file = "wrapt-1.14.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:903500616422a40a98a5a3c4ff4ed9d0066f3b4c951fa286018ecdf0750194ef"},
{file = "wrapt-1.14.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:5a9a0d155deafd9448baff28c08e150d9b24ff010e899311ddd63c45c2445e28"},
{file = "wrapt-1.14.1-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:ddaea91abf8b0d13443f6dac52e89051a5063c7d014710dcb4d4abb2ff811a59"},
{file = "wrapt-1.14.1-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:36f582d0c6bc99d5f39cd3ac2a9062e57f3cf606ade29a0a0d6b323462f4dd87"},
{file = "wrapt-1.14.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:7ef58fb89674095bfc57c4069e95d7a31cfdc0939e2a579882ac7d55aadfd2a1"},
{file = "wrapt-1.14.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:e2f83e18fe2f4c9e7db597e988f72712c0c3676d337d8b101f6758107c42425b"},
{file = "wrapt-1.14.1-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:ee2b1b1769f6707a8a445162ea16dddf74285c3964f605877a20e38545c3c462"},
{file = "wrapt-1.14.1-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:833b58d5d0b7e5b9832869f039203389ac7cbf01765639c7309fd50ef619e0b1"},
{file = "wrapt-1.14.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:80bb5c256f1415f747011dc3604b59bc1f91c6e7150bd7db03b19170ee06b320"},
{file = "wrapt-1.14.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:07f7a7d0f388028b2df1d916e94bbb40624c59b48ecc6cbc232546706fac74c2"},
{file = "wrapt-1.14.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:02b41b633c6261feff8ddd8d11c711df6842aba629fdd3da10249a53211a72c4"},
{file = "wrapt-1.14.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2fe803deacd09a233e4762a1adcea5db5d31e6be577a43352936179d14d90069"},
{file = "wrapt-1.14.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:257fd78c513e0fb5cdbe058c27a0624c9884e735bbd131935fd49e9fe719d310"},
{file = "wrapt-1.14.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:4fcc4649dc762cddacd193e6b55bc02edca674067f5f98166d7713b193932b7f"},
{file = "wrapt-1.14.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:11871514607b15cfeb87c547a49bca19fde402f32e2b1c24a632506c0a756656"},
{file = "wrapt-1.14.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8ad85f7f4e20964db4daadcab70b47ab05c7c1cf2a7c1e51087bfaa83831854c"},
{file = "wrapt-1.14.1-cp310-cp310-win32.whl", hash = "sha256:a9a52172be0b5aae932bef82a79ec0a0ce87288c7d132946d645eba03f0ad8a8"},
{file = "wrapt-1.14.1-cp310-cp310-win_amd64.whl", hash = "sha256:6d323e1554b3d22cfc03cd3243b5bb815a51f5249fdcbb86fda4bf62bab9e164"},
{file = "wrapt-1.14.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:43ca3bbbe97af00f49efb06e352eae40434ca9d915906f77def219b88e85d907"},
{file = "wrapt-1.14.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:6b1a564e6cb69922c7fe3a678b9f9a3c54e72b469875aa8018f18b4d1dd1adf3"},
{file = "wrapt-1.14.1-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:00b6d4ea20a906c0ca56d84f93065b398ab74b927a7a3dbd470f6fc503f95dc3"},
{file = "wrapt-1.14.1-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:a85d2b46be66a71bedde836d9e41859879cc54a2a04fad1191eb50c2066f6e9d"},
{file = "wrapt-1.14.1-cp35-cp35m-win32.whl", hash = "sha256:dbcda74c67263139358f4d188ae5faae95c30929281bc6866d00573783c422b7"},
{file = "wrapt-1.14.1-cp35-cp35m-win_amd64.whl", hash = "sha256:b21bb4c09ffabfa0e85e3a6b623e19b80e7acd709b9f91452b8297ace2a8ab00"},
{file = "wrapt-1.14.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:9e0fd32e0148dd5dea6af5fee42beb949098564cc23211a88d799e434255a1f4"},
{file = "wrapt-1.14.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9736af4641846491aedb3c3f56b9bc5568d92b0692303b5a305301a95dfd38b1"},
{file = "wrapt-1.14.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5b02d65b9ccf0ef6c34cba6cf5bf2aab1bb2f49c6090bafeecc9cd81ad4ea1c1"},
{file = "wrapt-1.14.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21ac0156c4b089b330b7666db40feee30a5d52634cc4560e1905d6529a3897ff"},
{file = "wrapt-1.14.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:9f3e6f9e05148ff90002b884fbc2a86bd303ae847e472f44ecc06c2cd2fcdb2d"},
{file = "wrapt-1.14.1-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:6e743de5e9c3d1b7185870f480587b75b1cb604832e380d64f9504a0535912d1"},
{file = "wrapt-1.14.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:d79d7d5dc8a32b7093e81e97dad755127ff77bcc899e845f41bf71747af0c569"},
{file = "wrapt-1.14.1-cp36-cp36m-win32.whl", hash = "sha256:81b19725065dcb43df02b37e03278c011a09e49757287dca60c5aecdd5a0b8ed"},
{file = "wrapt-1.14.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b014c23646a467558be7da3d6b9fa409b2c567d2110599b7cf9a0c5992b3b471"},
{file = "wrapt-1.14.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:88bd7b6bd70a5b6803c1abf6bca012f7ed963e58c68d76ee20b9d751c74a3248"},
{file = "wrapt-1.14.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b5901a312f4d14c59918c221323068fad0540e34324925c8475263841dbdfe68"},
{file = "wrapt-1.14.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d77c85fedff92cf788face9bfa3ebaa364448ebb1d765302e9af11bf449ca36d"},
{file = "wrapt-1.14.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d649d616e5c6a678b26d15ece345354f7c2286acd6db868e65fcc5ff7c24a77"},
{file = "wrapt-1.14.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7d2872609603cb35ca513d7404a94d6d608fc13211563571117046c9d2bcc3d7"},
{file = "wrapt-1.14.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:ee6acae74a2b91865910eef5e7de37dc6895ad96fa23603d1d27ea69df545015"},
{file = "wrapt-1.14.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:2b39d38039a1fdad98c87279b48bc5dce2c0ca0d73483b12cb72aa9609278e8a"},
{file = "wrapt-1.14.1-cp37-cp37m-win32.whl", hash = "sha256:60db23fa423575eeb65ea430cee741acb7c26a1365d103f7b0f6ec412b893853"},
{file = "wrapt-1.14.1-cp37-cp37m-win_amd64.whl", hash = "sha256:709fe01086a55cf79d20f741f39325018f4df051ef39fe921b1ebe780a66184c"},
{file = "wrapt-1.14.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8c0ce1e99116d5ab21355d8ebe53d9460366704ea38ae4d9f6933188f327b456"},
{file = "wrapt-1.14.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:e3fb1677c720409d5f671e39bac6c9e0e422584e5f518bfd50aa4cbbea02433f"},
{file = "wrapt-1.14.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:642c2e7a804fcf18c222e1060df25fc210b9c58db7c91416fb055897fc27e8cc"},
{file = "wrapt-1.14.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7b7c050ae976e286906dd3f26009e117eb000fb2cf3533398c5ad9ccc86867b1"},
{file = "wrapt-1.14.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ef3f72c9666bba2bab70d2a8b79f2c6d2c1a42a7f7e2b0ec83bb2f9e383950af"},
{file = "wrapt-1.14.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:01c205616a89d09827986bc4e859bcabd64f5a0662a7fe95e0d359424e0e071b"},
{file = "wrapt-1.14.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:5a0f54ce2c092aaf439813735584b9537cad479575a09892b8352fea5e988dc0"},
{file = "wrapt-1.14.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2cf71233a0ed05ccdabe209c606fe0bac7379fdcf687f39b944420d2a09fdb57"},
{file = "wrapt-1.14.1-cp38-cp38-win32.whl", hash = "sha256:aa31fdcc33fef9eb2552cbcbfee7773d5a6792c137b359e82879c101e98584c5"},
{file = "wrapt-1.14.1-cp38-cp38-win_amd64.whl", hash = "sha256:d1967f46ea8f2db647c786e78d8cc7e4313dbd1b0aca360592d8027b8508e24d"},
{file = "wrapt-1.14.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3232822c7d98d23895ccc443bbdf57c7412c5a65996c30442ebe6ed3df335383"},
{file = "wrapt-1.14.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:988635d122aaf2bdcef9e795435662bcd65b02f4f4c1ae37fbee7401c440b3a7"},
{file = "wrapt-1.14.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cca3c2cdadb362116235fdbd411735de4328c61425b0aa9f872fd76d02c4e86"},
{file = "wrapt-1.14.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d52a25136894c63de15a35bc0bdc5adb4b0e173b9c0d07a2be9d3ca64a332735"},
{file = "wrapt-1.14.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40e7bc81c9e2b2734ea4bc1aceb8a8f0ceaac7c5299bc5d69e37c44d9081d43b"},
{file = "wrapt-1.14.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b9b7a708dd92306328117d8c4b62e2194d00c365f18eff11a9b53c6f923b01e3"},
{file = "wrapt-1.14.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:6a9a25751acb379b466ff6be78a315e2b439d4c94c1e99cb7266d40a537995d3"},
{file = "wrapt-1.14.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:34aa51c45f28ba7f12accd624225e2b1e5a3a45206aa191f6f9aac931d9d56fe"},
{file = "wrapt-1.14.1-cp39-cp39-win32.whl", hash = "sha256:dee0ce50c6a2dd9056c20db781e9c1cfd33e77d2d569f5d1d9321c641bb903d5"},
{file = "wrapt-1.14.1-cp39-cp39-win_amd64.whl", hash = "sha256:dee60e1de1898bde3b238f18340eec6148986da0455d8ba7848d50470a7a32fb"},
{file = "wrapt-1.14.1.tar.gz", hash = "sha256:380a85cf89e0e69b7cfbe2ea9f765f004ff419f34194018a6827ac0e3edfed4d"},
]
[metadata]
lock-version = "2.0"
python-versions = ">=3.10,<3.11"
content-hash = "959633dfe6335ab3f943a95c5fdd12ff1bb66cd03c5917704f10ae38d3a5009c"

View File

@@ -1,27 +0,0 @@
[tool.black]
line-length = 120
[tool.ruff]
line-length = 120
select = ["E", "F", "W", "I", "C", "RET", "ANN", "UP"]
target-version = "py310"
[tool.poetry]
name = "scripts"
version = "0.1.0"
description = ""
authors = ["Charles Marsh <charlie.r.marsh@gmail.com>"]
[tool.poetry.dependencies]
python = ">=3.10,<3.11"
autoflake = "^1.4"
flake8 = "^5.0.4"
pycodestyle = "^2.9.1"
pyflakes = "^2.5.0"
pylint = "^2.15.0"
[tool.poetry.dev-dependencies]
[build-system]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"

View File

@@ -1,29 +0,0 @@
#!/usr/bin/env python3
"""Wrapper around Flake8 to enable multiprocessing on all operating systems.
As of Python 3.8, macOS's default "start method" for multiprocessing is `spawn`. Flake8
requires a "start method" of `fork`, and disables multiprocessing if it detects `spawn`
or some other "start method". This script enables the `fork` start method before passing
along any command-line arguments to `flake8`.
This has never caused me any problems, but note that they disabled this for a reason:
Flake8's plugin interface doesn't work with `spawn`, and the maintainer says that `fork`
is "pretty broken" on macOS.
See:
- https://github.com/pycqa/flake8/issues/955
- https://github.com/PyCQA/flake8/issues/1337
- https://github.com/PyCQA/flake8/issues/342
- https://github.com/PyCQA/flake8/pull/1621
Example usage: python -m run_flake8 --select=E501 .
"""
import multiprocessing
import sys
from flake8.main import cli
if __name__ == "__main__":
multiprocessing.set_start_method("fork", force=True)
cli.main(sys.argv[1:])

View File

@@ -33,7 +33,7 @@ pub fn classify(
checker.resolve_call_path(expr).map_or(false, |call_path| {
METACLASS_BASES
.iter()
.any(|(module, member)| call_path == [*module, *member])
.any(|(module, member)| call_path.as_slice() == [*module, *member])
})
})
|| decorator_list.iter().any(|expr| {

View File

@@ -10,8 +10,9 @@ use rustpython_ast::{
use rustpython_parser::lexer;
use rustpython_parser::lexer::Tok;
use rustpython_parser::token::StringKind;
use smallvec::smallvec;
use crate::ast::types::{Binding, BindingKind, Range};
use crate::ast::types::{Binding, BindingKind, CallPath, Range};
use crate::checkers::ast::Checker;
use crate::source_code::{Generator, Indexer, Locator, Stylist};
@@ -39,7 +40,7 @@ pub fn unparse_stmt(stmt: &Stmt, stylist: &Stylist) -> String {
generator.generate()
}
fn collect_call_path_inner<'a>(expr: &'a Expr, parts: &mut Vec<&'a str>) {
fn collect_call_path_inner<'a>(expr: &'a Expr, parts: &mut CallPath<'a>) {
match &expr.node {
ExprKind::Call { func, .. } => {
collect_call_path_inner(func, parts);
@@ -55,9 +56,9 @@ fn collect_call_path_inner<'a>(expr: &'a Expr, parts: &mut Vec<&'a str>) {
}
}
/// Convert an `Expr` to its call path segments (like ["typing", "List"]).
pub fn collect_call_path(expr: &Expr) -> Vec<&str> {
let mut segments = vec![];
/// Convert an `Expr` to its [`CallPath`] segments (like `["typing", "List"]`).
pub fn collect_call_path(expr: &Expr) -> CallPath {
let mut segments = smallvec![];
collect_call_path_inner(expr, &mut segments);
segments
}
@@ -90,7 +91,47 @@ pub fn contains_call_path(checker: &Checker, expr: &Expr, target: &[&str]) -> bo
any_over_expr(expr, &|expr| {
checker
.resolve_call_path(expr)
.map_or(false, |call_path| call_path == target)
.map_or(false, |call_path| call_path.as_slice() == target)
})
}
/// Return `true` if the `Expr` contains an expression that appears to include a
/// side-effect (like a function call).
pub fn contains_effect(checker: &Checker, expr: &Expr) -> bool {
any_over_expr(expr, &|expr| {
// Accept empty initializers.
if let ExprKind::Call {
func,
args,
keywords,
} = &expr.node
{
if args.is_empty() && keywords.is_empty() {
if let ExprKind::Name { id, .. } = &func.node {
let is_empty_initializer = (id == "set"
|| id == "list"
|| id == "tuple"
|| id == "dict"
|| id == "frozenset")
&& checker.is_builtin(id);
return !is_empty_initializer;
}
}
}
// Otherwise, avoid all complex expressions.
matches!(
expr.node,
ExprKind::Await { .. }
| ExprKind::Call { .. }
| ExprKind::DictComp { .. }
| ExprKind::GeneratorExp { .. }
| ExprKind::ListComp { .. }
| ExprKind::SetComp { .. }
| ExprKind::Subscript { .. }
| ExprKind::Yield { .. }
| ExprKind::YieldFrom { .. }
)
})
}
@@ -274,7 +315,9 @@ pub fn has_non_none_keyword(keywords: &[Keyword], keyword: &str) -> bool {
}
/// Extract the names of all handled exceptions.
pub fn extract_handler_names(handlers: &[Excepthandler]) -> Vec<Vec<&str>> {
pub fn extract_handler_names(handlers: &[Excepthandler]) -> Vec<CallPath> {
// TODO(charlie): Use `resolve_call_path` to avoid false positives for
// overridden builtins.
let mut handler_names = vec![];
for handler in handlers {
match &handler.node {
@@ -322,10 +365,9 @@ pub fn collect_arg_names<'a>(arguments: &'a Arguments) -> FxHashSet<&'a str> {
}
/// Returns `true` if a statement or expression includes at least one comment.
pub fn has_comments<T>(located: &Located<T>, locator: &Locator) -> bool {
lexer::make_tokenizer(&locator.slice_source_code_range(&Range::from_located(located)))
.flatten()
.any(|(_, tok, _)| matches!(tok, Tok::Comment(..)))
pub fn has_comments_in(range: Range, locator: &Locator) -> bool {
lexer::make_tokenizer(&locator.slice_source_code_range(&range))
.any(|result| result.map_or(false, |(_, tok, _)| matches!(tok, Tok::Comment(..))))
}
/// Returns `true` if a call is an argumented `super` invocation.
@@ -377,11 +419,11 @@ pub fn format_import_from_member(
}
/// Split a target string (like `typing.List`) into (`typing`, `List`).
pub fn to_call_path(target: &str) -> Vec<&str> {
pub fn to_call_path(target: &str) -> CallPath {
if target.contains('.') {
target.split('.').collect()
} else {
vec!["", target]
smallvec!["", target]
}
}

View File

@@ -242,3 +242,5 @@ impl<'a> From<&RefEquality<'a, Expr>> for &'a Expr {
r.0
}
}
pub type CallPath<'a> = smallvec::SmallVec<[&'a str; 8]>;

View File

@@ -4,15 +4,20 @@ use std::str::Lines;
use rustpython_ast::{Located, Location};
use crate::ast::types::Range;
use crate::checkers::ast::Checker;
use crate::source_code::Locator;
/// Extract the leading indentation from a line.
pub fn indentation<'a, T>(checker: &'a Checker, located: &'a Located<T>) -> Cow<'a, str> {
pub fn indentation<'a, T>(locator: &'a Locator, located: &'a Located<T>) -> Option<Cow<'a, str>> {
let range = Range::from_located(located);
checker.locator.slice_source_code_range(&Range::new(
let indentation = locator.slice_source_code_range(&Range::new(
Location::new(range.location.row(), 0),
Location::new(range.location.row(), range.location.column()),
))
));
if indentation.chars().all(char::is_whitespace) {
Some(indentation)
} else {
None
}
}
/// Extract the leading words from a line of text.

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,18 @@
use std::path::Path;
use crate::registry::{Diagnostic, Rule};
use crate::rules::flake8_no_pep420::rules::implicit_namespace_package;
use crate::settings::Settings;
pub fn check_file_path(path: &Path, settings: &Settings) -> Vec<Diagnostic> {
let mut diagnostics: Vec<Diagnostic> = vec![];
// flake8-no-pep420
if settings.rules.enabled(&Rule::ImplicitNamespacePackage) {
if let Some(diagnostic) = implicit_namespace_package(path) {
diagnostics.push(diagnostic);
}
}
diagnostics
}

View File

@@ -6,7 +6,7 @@ use rustpython_parser::ast::Suite;
use crate::ast::visitor::Visitor;
use crate::directives::IsortDirectives;
use crate::registry::{Diagnostic, RuleCode};
use crate::registry::{Diagnostic, Rule};
use crate::rules::isort;
use crate::rules::isort::track::{Block, ImportTracker};
use crate::settings::{flags, Settings};
@@ -36,7 +36,7 @@ pub fn check_imports(
// Enforce import rules.
let mut diagnostics = vec![];
if settings.enabled.contains(&RuleCode::I001) {
if settings.rules.enabled(&Rule::UnsortedImports) {
for block in &blocks {
if !block.imports.is_empty() {
if let Some(diagnostic) = isort::rules::organize_imports(
@@ -47,7 +47,7 @@ pub fn check_imports(
}
}
}
if settings.enabled.contains(&RuleCode::I002) {
if settings.rules.enabled(&Rule::MissingRequiredImport) {
diagnostics.extend(isort::rules::add_required_imports(
&blocks, python_ast, locator, settings, autofix,
));

View File

@@ -1,6 +1,6 @@
//! Lint rules based on checking raw physical lines.
use crate::registry::{Diagnostic, RuleCode};
use crate::registry::{Diagnostic, Rule};
use crate::rules::pycodestyle::rules::{
doc_line_too_long, line_too_long, no_newline_at_end_of_file,
};
@@ -17,12 +17,14 @@ pub fn check_lines(
) -> Vec<Diagnostic> {
let mut diagnostics: Vec<Diagnostic> = vec![];
let enforce_blanket_noqa = settings.enabled.contains(&RuleCode::PGH004);
let enforce_blanket_type_ignore = settings.enabled.contains(&RuleCode::PGH003);
let enforce_doc_line_too_long = settings.enabled.contains(&RuleCode::W505);
let enforce_line_too_long = settings.enabled.contains(&RuleCode::E501);
let enforce_no_newline_at_end_of_file = settings.enabled.contains(&RuleCode::W292);
let enforce_unnecessary_coding_comment = settings.enabled.contains(&RuleCode::UP009);
let enforce_blanket_noqa = settings.rules.enabled(&Rule::BlanketNOQA);
let enforce_blanket_type_ignore = settings.rules.enabled(&Rule::BlanketTypeIgnore);
let enforce_doc_line_too_long = settings.rules.enabled(&Rule::DocLineTooLong);
let enforce_line_too_long = settings.rules.enabled(&Rule::LineTooLong);
let enforce_no_newline_at_end_of_file = settings.rules.enabled(&Rule::NoNewLineAtEndOfFile);
let enforce_unnecessary_coding_comment = settings
.rules
.enabled(&Rule::PEP3120UnnecessaryCodingComment);
let mut commented_lines_iter = commented_lines.iter().peekable();
let mut doc_lines_iter = doc_lines.iter().peekable();
@@ -37,7 +39,9 @@ pub fn check_lines(
index,
line,
matches!(autofix, flags::Autofix::Enabled)
&& settings.fixable.contains(&RuleCode::UP009),
&& settings
.rules
.should_fix(&Rule::PEP3120UnnecessaryCodingComment),
) {
diagnostics.push(diagnostic);
}
@@ -79,7 +83,7 @@ pub fn check_lines(
if let Some(diagnostic) = no_newline_at_end_of_file(
contents,
matches!(autofix, flags::Autofix::Enabled)
&& settings.fixable.contains(&RuleCode::W292),
&& settings.rules.should_fix(&Rule::NoNewLineAtEndOfFile),
) {
diagnostics.push(diagnostic);
}
@@ -92,7 +96,7 @@ pub fn check_lines(
mod tests {
use super::check_lines;
use crate::registry::RuleCode;
use crate::registry::Rule;
use crate::settings::{flags, Settings};
#[test]
@@ -105,7 +109,7 @@ mod tests {
&[],
&Settings {
line_length,
..Settings::for_rule(RuleCode::E501)
..Settings::for_rule(Rule::LineTooLong)
},
flags::Autofix::Enabled,
)

View File

@@ -1,4 +1,5 @@
pub mod ast;
pub mod filesystem;
pub mod imports;
pub mod lines;
pub mod noqa;

View File

@@ -1,14 +1,12 @@
//! `NoQA` enforcement and validation.
use std::str::FromStr;
use nohash_hasher::IntMap;
use rustpython_parser::ast::Location;
use crate::ast::types::Range;
use crate::fix::Fix;
use crate::noqa::{is_file_exempt, Directive};
use crate::registry::{Diagnostic, DiagnosticKind, RuleCode, CODE_REDIRECTS};
use crate::registry::{Diagnostic, DiagnosticKind, Rule, CODE_REDIRECTS};
use crate::settings::{flags, Settings};
use crate::violations::UnusedCodes;
use crate::{noqa, violations};
@@ -24,7 +22,7 @@ pub fn check_noqa(
let mut noqa_directives: IntMap<usize, (Directive, Vec<&str>)> = IntMap::default();
let mut ignored = vec![];
let enforce_noqa = settings.enabled.contains(&RuleCode::RUF100);
let enforce_noqa = settings.rules.enabled(&Rule::UnusedNOQA);
let lines: Vec<&str> = contents.lines().collect();
for lineno in commented_lines {
@@ -56,13 +54,13 @@ pub fn check_noqa(
});
match noqa {
(Directive::All(..), matches) => {
matches.push(diagnostic.kind.code().as_ref());
matches.push(diagnostic.kind.rule().code());
ignored.push(index);
continue;
}
(Directive::Codes(.., codes), matches) => {
if noqa::includes(diagnostic.kind.code(), codes) {
matches.push(diagnostic.kind.code().as_ref());
if noqa::includes(diagnostic.kind.rule(), codes) {
matches.push(diagnostic.kind.rule().code());
ignored.push(index);
continue;
}
@@ -83,12 +81,12 @@ pub fn check_noqa(
.or_insert_with(|| (noqa::extract_noqa_directive(lines[noqa_lineno - 1]), vec![]));
match noqa {
(Directive::All(..), matches) => {
matches.push(diagnostic.kind.code().as_ref());
matches.push(diagnostic.kind.rule().code());
ignored.push(index);
}
(Directive::Codes(.., codes), matches) => {
if noqa::includes(diagnostic.kind.code(), codes) {
matches.push(diagnostic.kind.code().as_ref());
if noqa::includes(diagnostic.kind.rule(), codes) {
matches.push(diagnostic.kind.rule().code());
ignored.push(index);
}
}
@@ -108,7 +106,7 @@ pub fn check_noqa(
Range::new(Location::new(row + 1, start), Location::new(row + 1, end)),
);
if matches!(autofix, flags::Autofix::Enabled)
&& settings.fixable.contains(diagnostic.kind.code())
&& settings.rules.should_fix(diagnostic.kind.rule())
{
diagnostic.amend(Fix::deletion(
Location::new(row + 1, start - spaces),
@@ -125,8 +123,8 @@ pub fn check_noqa(
let mut valid_codes = vec![];
let mut self_ignore = false;
for code in codes {
let code = CODE_REDIRECTS.get(code).map_or(code, AsRef::as_ref);
if code == RuleCode::RUF100.as_ref() {
let code = CODE_REDIRECTS.get(code).map_or(code, |r| r.code());
if code == Rule::UnusedNOQA.code() {
self_ignore = true;
break;
}
@@ -134,8 +132,8 @@ pub fn check_noqa(
if matches.contains(&code) || settings.external.contains(code) {
valid_codes.push(code);
} else {
if let Ok(rule_code) = RuleCode::from_str(code) {
if settings.enabled.contains(&rule_code) {
if let Ok(rule) = Rule::from_code(code) {
if settings.rules.enabled(rule) {
unmatched_codes.push(code);
} else {
disabled_codes.push(code);
@@ -172,7 +170,7 @@ pub fn check_noqa(
Range::new(Location::new(row + 1, start), Location::new(row + 1, end)),
);
if matches!(autofix, flags::Autofix::Enabled)
&& settings.fixable.contains(diagnostic.kind.code())
&& settings.rules.should_fix(diagnostic.kind.rule())
{
if valid_codes.is_empty() {
diagnostic.amend(Fix::deletion(

View File

@@ -3,7 +3,7 @@
use rustpython_parser::lexer::{LexResult, Tok};
use crate::lex::docstring_detection::StateMachine;
use crate::registry::{Diagnostic, RuleCode};
use crate::registry::{Diagnostic, Rule};
use crate::rules::ruff::rules::Context;
use crate::rules::{
eradicate, flake8_commas, flake8_implicit_str_concat, flake8_quotes, pycodestyle, ruff,
@@ -19,20 +19,32 @@ pub fn check_tokens(
) -> Vec<Diagnostic> {
let mut diagnostics: Vec<Diagnostic> = vec![];
let enforce_ambiguous_unicode_character = settings.enabled.contains(&RuleCode::RUF001)
|| settings.enabled.contains(&RuleCode::RUF002)
|| settings.enabled.contains(&RuleCode::RUF003);
let enforce_quotes = settings.enabled.contains(&RuleCode::Q000)
|| settings.enabled.contains(&RuleCode::Q001)
|| settings.enabled.contains(&RuleCode::Q002)
|| settings.enabled.contains(&RuleCode::Q003);
let enforce_commented_out_code = settings.enabled.contains(&RuleCode::ERA001);
let enforce_invalid_escape_sequence = settings.enabled.contains(&RuleCode::W605);
let enforce_implicit_string_concatenation = settings.enabled.contains(&RuleCode::ISC001)
|| settings.enabled.contains(&RuleCode::ISC002);
let enforce_trailing_comma = settings.enabled.contains(&RuleCode::COM812)
|| settings.enabled.contains(&RuleCode::COM818)
|| settings.enabled.contains(&RuleCode::COM819);
let enforce_ambiguous_unicode_character = settings
.rules
.enabled(&Rule::AmbiguousUnicodeCharacterString)
|| settings
.rules
.enabled(&Rule::AmbiguousUnicodeCharacterDocstring)
|| settings
.rules
.enabled(&Rule::AmbiguousUnicodeCharacterComment);
let enforce_quotes = settings.rules.enabled(&Rule::BadQuotesInlineString)
|| settings.rules.enabled(&Rule::BadQuotesMultilineString)
|| settings.rules.enabled(&Rule::BadQuotesDocstring)
|| settings.rules.enabled(&Rule::AvoidQuoteEscape);
let enforce_commented_out_code = settings.rules.enabled(&Rule::CommentedOutCode);
let enforce_invalid_escape_sequence = settings.rules.enabled(&Rule::InvalidEscapeSequence);
let enforce_implicit_string_concatenation = settings
.rules
.enabled(&Rule::SingleLineImplicitStringConcatenation)
|| settings
.rules
.enabled(&Rule::MultiLineImplicitStringConcatenation);
let enforce_trailing_comma = settings.rules.enabled(&Rule::TrailingCommaMissing)
|| settings
.rules
.enabled(&Rule::TrailingCommaOnBareTupleProhibited)
|| settings.rules.enabled(&Rule::TrailingCommaProhibited);
let mut state_machine = StateMachine::default();
for &(start, ref tok, end) in tokens.iter().flatten() {
@@ -75,7 +87,7 @@ pub fn check_tokens(
settings,
autofix,
) {
if settings.enabled.contains(diagnostic.kind.code()) {
if settings.rules.enabled(diagnostic.kind.rule()) {
diagnostics.push(diagnostic);
}
}
@@ -101,7 +113,7 @@ pub fn check_tokens(
start,
end,
matches!(autofix, flags::Autofix::Enabled)
&& settings.fixable.contains(&RuleCode::W605),
&& settings.rules.should_fix(&Rule::InvalidEscapeSequence),
));
}
}
@@ -112,16 +124,16 @@ pub fn check_tokens(
diagnostics.extend(
flake8_implicit_str_concat::rules::implicit(tokens)
.into_iter()
.filter(|diagnostic| settings.enabled.contains(diagnostic.kind.code())),
.filter(|diagnostic| settings.rules.enabled(diagnostic.kind.rule())),
);
}
// COM812, COM818, COM819
if enforce_trailing_comma {
diagnostics.extend(
flake8_commas::rules::trailing_commas(tokens, locator)
flake8_commas::rules::trailing_commas(tokens, settings, autofix)
.into_iter()
.filter(|diagnostic| settings.enabled.contains(diagnostic.kind.code())),
.filter(|diagnostic| settings.rules.enabled(diagnostic.kind.rule())),
);
}

View File

@@ -18,8 +18,8 @@ bitflags! {
impl Flags {
pub fn from_settings(settings: &Settings) -> Self {
if settings
.enabled
.iter()
.rules
.iter_enabled()
.any(|rule_code| matches!(rule_code.lint_source(), LintSource::Imports))
{
Flags::NOQA | Flags::ISORT

View File

@@ -7,3 +7,7 @@ pub const TRIPLE_QUOTE_PREFIXES: &[&str] = &[
pub const SINGLE_QUOTE_PREFIXES: &[&str] = &[
"u\"", "u'", "r\"", "r'", "u\"", "u'", "r\"", "r'", "U\"", "U'", "R\"", "R'", "\"", "'",
];
pub const TRIPLE_QUOTE_SUFFIXES: &[&str] = &["\"\"\"", "'''"];
pub const SINGLE_QUOTE_SUFFIXES: &[&str] = &["\"", "'"];

View File

@@ -11,7 +11,7 @@ use crate::rules::flake8_pytest_style::types::{
ParametrizeNameType, ParametrizeValuesRowType, ParametrizeValuesType,
};
use crate::rules::flake8_quotes::settings::Quote;
use crate::rules::flake8_tidy_imports::settings::Strictness;
use crate::rules::flake8_tidy_imports::relative_imports::Strictness;
use crate::rules::pydocstyle::settings::Convention;
use crate::rules::{
flake8_annotations, flake8_bugbear, flake8_errmsg, flake8_pytest_style, flake8_quotes,
@@ -93,7 +93,7 @@ pub fn convert(
let mut flake8_errmsg = flake8_errmsg::settings::Options::default();
let mut flake8_pytest_style = flake8_pytest_style::settings::Options::default();
let mut flake8_quotes = flake8_quotes::settings::Options::default();
let mut flake8_tidy_imports = flake8_tidy_imports::settings::Options::default();
let mut flake8_tidy_imports = flake8_tidy_imports::options::Options::default();
let mut mccabe = mccabe::settings::Options::default();
let mut pep8_naming = pep8_naming::settings::Options::default();
let mut pydocstyle = pydocstyle::settings::Options::default();
@@ -354,7 +354,7 @@ pub fn convert(
if flake8_quotes != flake8_quotes::settings::Options::default() {
options.flake8_quotes = Some(flake8_quotes);
}
if flake8_tidy_imports != flake8_tidy_imports::settings::Options::default() {
if flake8_tidy_imports != flake8_tidy_imports::options::Options::default() {
options.flake8_tidy_imports = Some(flake8_tidy_imports);
}
if mccabe != mccabe::settings::Options::default() {
@@ -454,6 +454,7 @@ mod tests {
pep8_naming: None,
pycodestyle: None,
pydocstyle: None,
pylint: None,
pyupgrade: None,
});
assert_eq!(actual, expected);
@@ -520,6 +521,7 @@ mod tests {
pep8_naming: None,
pycodestyle: None,
pydocstyle: None,
pylint: None,
pyupgrade: None,
});
assert_eq!(actual, expected);
@@ -586,6 +588,7 @@ mod tests {
pep8_naming: None,
pycodestyle: None,
pydocstyle: None,
pylint: None,
pyupgrade: None,
});
assert_eq!(actual, expected);
@@ -652,6 +655,7 @@ mod tests {
pep8_naming: None,
pycodestyle: None,
pydocstyle: None,
pylint: None,
pyupgrade: None,
});
assert_eq!(actual, expected);
@@ -723,6 +727,7 @@ mod tests {
pep8_naming: None,
pycodestyle: None,
pydocstyle: None,
pylint: None,
pyupgrade: None,
});
assert_eq!(actual, expected);
@@ -795,6 +800,7 @@ mod tests {
pydocstyle: Some(pydocstyle::settings::Options {
convention: Some(Convention::Numpy),
}),
pylint: None,
pyupgrade: None,
});
assert_eq!(actual, expected);
@@ -867,6 +873,7 @@ mod tests {
pep8_naming: None,
pycodestyle: None,
pydocstyle: None,
pylint: None,
pyupgrade: None,
});
assert_eq!(actual, expected);

View File

@@ -4,11 +4,11 @@ use std::io::{BufReader, Read};
use std::path::{Path, PathBuf};
use anyhow::{anyhow, Result};
use globset::GlobMatcher;
use path_absolutize::{path_dedot, Absolutize};
use rustc_hash::FxHashSet;
use crate::registry::RuleCode;
use crate::registry::Rule;
use crate::settings::hashable::{HashableGlobMatcher, HashableHashSet};
/// Extract the absolute path and basename (as strings) from a Path.
pub fn extract_path_names(path: &Path) -> Result<(&str, &str)> {
@@ -26,15 +26,19 @@ pub fn extract_path_names(path: &Path) -> Result<(&str, &str)> {
/// Create a set with codes matching the pattern/code pairs.
pub(crate) fn ignores_from_path<'a>(
path: &Path,
pattern_code_pairs: &'a [(GlobMatcher, GlobMatcher, FxHashSet<RuleCode>)],
) -> Result<FxHashSet<&'a RuleCode>> {
pattern_code_pairs: &'a [(
HashableGlobMatcher,
HashableGlobMatcher,
HashableHashSet<Rule>,
)],
) -> Result<FxHashSet<&'a Rule>> {
let (file_path, file_basename) = extract_path_names(path)?;
Ok(pattern_code_pairs
.iter()
.filter(|(absolute, basename, _)| {
basename.is_match(file_basename) || absolute.is_match(file_path)
})
.flat_map(|(_, _, codes)| codes)
.flat_map(|(_, _, codes)| codes.iter())
.collect())
}

View File

@@ -18,8 +18,6 @@
)]
#![forbid(unsafe_code)]
extern crate core;
mod ast;
mod autofix;
pub mod cache;
@@ -49,6 +47,7 @@ mod violations;
mod visibility;
use cfg_if::cfg_if;
pub use violation::{AutofixKind, Availability as AutofixAvailability};
pub use violations::IOError;
cfg_if! {

View File

@@ -7,17 +7,16 @@ use wasm_bindgen::prelude::*;
use crate::directives;
use crate::linter::check_path;
use crate::registry::{RuleCode, RuleCodePrefix};
use crate::registry::Rule;
use crate::rules::{
flake8_annotations, flake8_bandit, flake8_bugbear, flake8_errmsg, flake8_import_conventions,
flake8_pytest_style, flake8_quotes, flake8_tidy_imports, flake8_unused_arguments, isort,
mccabe, pep8_naming, pycodestyle, pydocstyle, pyupgrade,
mccabe, pep8_naming, pycodestyle, pydocstyle, pylint, pyupgrade,
};
use crate::rustpython_helpers::tokenize;
use crate::settings::configuration::Configuration;
use crate::settings::options::Options;
use crate::settings::types::PythonVersion;
use crate::settings::{flags, Settings};
use crate::settings::{defaults, flags, Settings};
use crate::source_code::{Indexer, Locator, Stylist};
const VERSION: &str = env!("CARGO_PKG_VERSION");
@@ -52,13 +51,30 @@ export interface Diagnostic {
#[derive(Serialize)]
struct ExpandedMessage {
code: RuleCode,
code: SerializeRuleAsCode,
message: String,
location: Location,
end_location: Location,
fix: Option<ExpandedFix>,
}
struct SerializeRuleAsCode(Rule);
impl Serialize for SerializeRuleAsCode {
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(self.0.code())
}
}
impl From<Rule> for SerializeRuleAsCode {
fn from(rule: Rule) -> Self {
Self(rule)
}
}
#[derive(Serialize)]
struct ExpandedFix {
content: String,
@@ -87,14 +103,14 @@ pub fn defaultSettings() -> Result<JsValue, JsValue> {
// Propagate defaults.
allowed_confusables: Some(Vec::default()),
builtins: Some(Vec::default()),
dummy_variable_rgx: Some("^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$".to_string()),
dummy_variable_rgx: Some(defaults::DUMMY_VARIABLE_RGX.as_str().to_string()),
extend_ignore: Some(Vec::default()),
extend_select: Some(Vec::default()),
external: Some(Vec::default()),
ignore: Some(Vec::default()),
line_length: Some(88),
select: Some(vec![RuleCodePrefix::E, RuleCodePrefix::F]),
target_version: Some(PythonVersion::default()),
line_length: Some(defaults::LINE_LENGTH),
select: Some(defaults::PREFIXES.to_vec()),
target_version: Some(defaults::TARGET_VERSION),
// Ignore a bunch of options that don't make sense in a single-file editor.
cache_dir: None,
exclude: None,
@@ -123,7 +139,7 @@ pub fn defaultSettings() -> Result<JsValue, JsValue> {
flake8_errmsg: Some(flake8_errmsg::settings::Settings::default().into()),
flake8_pytest_style: Some(flake8_pytest_style::settings::Settings::default().into()),
flake8_quotes: Some(flake8_quotes::settings::Settings::default().into()),
flake8_tidy_imports: Some(flake8_tidy_imports::settings::Settings::default().into()),
flake8_tidy_imports: Some(flake8_tidy_imports::Settings::default().into()),
flake8_import_conventions: Some(
flake8_import_conventions::settings::Settings::default().into(),
),
@@ -135,6 +151,7 @@ pub fn defaultSettings() -> Result<JsValue, JsValue> {
pep8_naming: Some(pep8_naming::settings::Settings::default().into()),
pycodestyle: Some(pycodestyle::settings::Settings::default().into()),
pydocstyle: Some(pydocstyle::settings::Settings::default().into()),
pylint: Some(pylint::settings::Settings::default().into()),
pyupgrade: Some(pyupgrade::settings::Settings::default().into()),
})?)
}
@@ -182,7 +199,7 @@ pub fn check(contents: &str, options: JsValue) -> Result<JsValue, JsValue> {
let messages: Vec<ExpandedMessage> = diagnostics
.into_iter()
.map(|diagnostic| ExpandedMessage {
code: diagnostic.kind.code().clone(),
code: diagnostic.kind.rule().clone().into(),
message: diagnostic.kind.body(),
location: diagnostic.location,
end_location: diagnostic.end_location,
@@ -224,7 +241,7 @@ mod test {
"if (1, 2): pass",
r#"{}"#,
[ExpandedMessage {
code: RuleCode::F634,
code: Rule::IfTuple.into(),
message: "If test is a tuple, which is always `True`".to_string(),
location: Location::new(1, 0),
end_location: Location::new(1, 15),

View File

@@ -7,6 +7,7 @@ use rustpython_parser::lexer::LexResult;
use crate::ast::types::Range;
use crate::autofix::fix_file;
use crate::checkers::ast::check_ast;
use crate::checkers::filesystem::check_file_path;
use crate::checkers::imports::check_imports;
use crate::checkers::lines::check_lines;
use crate::checkers::noqa::check_noqa;
@@ -15,7 +16,7 @@ use crate::directives::Directives;
use crate::doc_lines::{doc_lines_from_ast, doc_lines_from_tokens};
use crate::message::{Message, Source};
use crate::noqa::add_noqa;
use crate::registry::{Diagnostic, LintSource, RuleCode};
use crate::registry::{Diagnostic, LintSource, Rule};
use crate::settings::{flags, Settings};
use crate::source_code::{Indexer, Locator, Stylist};
use crate::{directives, fs, rustpython_helpers, violations};
@@ -47,7 +48,7 @@ pub fn check_path(
// Collect doc lines. This requires a rare mix of tokens (for comments) and AST
// (for docstrings), which demands special-casing at this level.
let use_doc_lines = settings.enabled.contains(&RuleCode::W505);
let use_doc_lines = settings.rules.enabled(&Rule::DocLineTooLong);
let mut doc_lines = vec![];
if use_doc_lines {
doc_lines.extend(doc_lines_from_tokens(&tokens));
@@ -55,22 +56,31 @@ pub fn check_path(
// Run the token-based rules.
if settings
.enabled
.iter()
.rules
.iter_enabled()
.any(|rule_code| matches!(rule_code.lint_source(), LintSource::Tokens))
{
diagnostics.extend(check_tokens(locator, &tokens, settings, autofix));
}
// Run the filesystem-based rules.
if settings
.rules
.iter_enabled()
.any(|rule_code| matches!(rule_code.lint_source(), LintSource::Filesystem))
{
diagnostics.extend(check_file_path(path, settings));
}
// Run the AST-based rules.
let use_ast = settings
.enabled
.iter()
.rules
.iter_enabled()
.any(|rule_code| matches!(rule_code.lint_source(), LintSource::Ast));
let use_imports = !directives.isort.skip_file
&& settings
.enabled
.iter()
.rules
.iter_enabled()
.any(|rule_code| matches!(rule_code.lint_source(), LintSource::Imports));
if use_ast || use_imports || use_doc_lines {
match rustpython_helpers::parse_program_tokens(tokens, "<filename>") {
@@ -106,7 +116,7 @@ pub fn check_path(
}
}
Err(parse_error) => {
if settings.enabled.contains(&RuleCode::E999) {
if settings.rules.enabled(&Rule::SyntaxError) {
diagnostics.push(Diagnostic::new(
violations::SyntaxError(parse_error.error.to_string()),
Range::new(parse_error.location, parse_error.location),
@@ -124,8 +134,8 @@ pub fn check_path(
// Run the lines-based rules.
if settings
.enabled
.iter()
.rules
.iter_enabled()
.any(|rule_code| matches!(rule_code.lint_source(), LintSource::Lines))
{
diagnostics.extend(check_lines(
@@ -140,8 +150,8 @@ pub fn check_path(
// Enforce `noqa` directives.
if (matches!(noqa, flags::Noqa::Enabled) && !diagnostics.is_empty())
|| settings
.enabled
.iter()
.rules
.iter_enabled()
.any(|rule_code| matches!(rule_code.lint_source(), LintSource::NoQa))
{
check_noqa(
@@ -160,7 +170,7 @@ pub fn check_path(
if !ignores.is_empty() {
return Ok(diagnostics
.into_iter()
.filter(|diagnostic| !ignores.contains(&diagnostic.kind.code()))
.filter(|diagnostic| !ignores.contains(&diagnostic.kind.rule()))
.collect());
}
}
@@ -340,16 +350,15 @@ pub fn lint_fix(
}
eprintln!(
"
r#"
{}: Failed to converge after {} iterations.
This likely indicates a bug in `{}`. If you could open an issue at:
{}/issues
{}/issues/new?title=%5BInfinite%20loop%5D
quoting the contents of `{}`, along with the `pyproject.toml` settings and executed command, we'd \
be very appreciative!
",
quoting the contents of `{}`, along with the `pyproject.toml` settings and executed command, we'd be very appreciative!
"#,
"warning".yellow().bold(),
MAX_ITERATIONS,
CARGO_PKG_NAME,

View File

@@ -44,13 +44,14 @@ macro_rules! notify_user {
}
}
#[derive(Debug, PartialOrd, Ord, PartialEq, Eq)]
#[derive(Debug, Default, PartialOrd, Ord, PartialEq, Eq)]
pub enum LogLevel {
// No output (+ `log::LevelFilter::Off`).
Silent,
// Only show lint violations, with no decorative output (+ `log::LevelFilter::Off`).
Quiet,
// All user-facing output (+ `log::LevelFilter::Info`).
#[default]
Default,
// All user-facing output (+ `log::LevelFilter::Debug`).
Verbose,
@@ -67,12 +68,6 @@ impl LogLevel {
}
}
impl Default for LogLevel {
fn default() -> Self {
LogLevel::Default
}
}
pub fn set_up_logging(level: &LogLevel) -> Result<()> {
fern::Dispatch::new()
.format(|out, message, record| {

View File

@@ -8,7 +8,8 @@ use once_cell::sync::Lazy;
use regex::Regex;
use rustc_hash::{FxHashMap, FxHashSet};
use crate::registry::{Diagnostic, RuleCode, CODE_REDIRECTS};
use crate::registry::{Diagnostic, Rule, CODE_REDIRECTS};
use crate::settings::hashable::HashableHashSet;
use crate::source_code::LineEnding;
static NOQA_LINE_REGEX: Lazy<Regex> = Lazy::new(|| {
@@ -68,11 +69,11 @@ pub fn extract_noqa_directive(line: &str) -> Directive {
/// Returns `true` if the string list of `codes` includes `code` (or an alias
/// thereof).
pub fn includes(needle: &RuleCode, haystack: &[&str]) -> bool {
let needle: &str = needle.as_ref();
pub fn includes(needle: &Rule, haystack: &[&str]) -> bool {
let needle: &str = needle.code();
haystack.iter().any(|candidate| {
if let Some(candidate) = CODE_REDIRECTS.get(candidate) {
needle == candidate.as_ref()
needle == candidate.code()
} else {
&needle == candidate
}
@@ -84,7 +85,7 @@ pub fn add_noqa(
diagnostics: &[Diagnostic],
contents: &str,
noqa_line_for: &IntMap<usize, usize>,
external: &FxHashSet<String>,
external: &HashableHashSet<String>,
line_ending: &LineEnding,
) -> Result<usize> {
let (count, output) =
@@ -97,24 +98,24 @@ fn add_noqa_inner(
diagnostics: &[Diagnostic],
contents: &str,
noqa_line_for: &IntMap<usize, usize>,
external: &FxHashSet<String>,
external: &HashableHashSet<String>,
line_ending: &LineEnding,
) -> (usize, String) {
let mut matches_by_line: FxHashMap<usize, FxHashSet<&RuleCode>> = FxHashMap::default();
let mut matches_by_line: FxHashMap<usize, FxHashSet<&Rule>> = FxHashMap::default();
for (lineno, line) in contents.lines().enumerate() {
// If we hit an exemption for the entire file, bail.
if is_file_exempt(line) {
return (0, contents.to_string());
}
let mut codes: FxHashSet<&RuleCode> = FxHashSet::default();
let mut codes: FxHashSet<&Rule> = FxHashSet::default();
for diagnostic in diagnostics {
// TODO(charlie): Consider respecting parent `noqa` directives. For now, we'll
// add a `noqa` for every diagnostic, on its own line. This could lead to
// duplication, whereby some parent `noqa` directives become
// redundant.
if diagnostic.location.row() == lineno + 1 {
codes.insert(diagnostic.kind.code());
codes.insert(diagnostic.kind.rule());
}
}
@@ -137,7 +138,7 @@ fn add_noqa_inner(
output.push_str(line);
output.push_str(line_ending);
}
Some(codes) => {
Some(rules) => {
match extract_noqa_directive(line) {
Directive::None => {
// Add existing content.
@@ -147,7 +148,7 @@ fn add_noqa_inner(
output.push_str(" # noqa: ");
// Add codes.
let codes: Vec<&str> = codes.iter().map(AsRef::as_ref).collect();
let codes: Vec<&str> = rules.iter().map(|r| r.code()).collect();
let suffix = codes.join(", ");
output.push_str(&suffix);
output.push_str(line_ending);
@@ -162,7 +163,7 @@ fn add_noqa_inner(
// Add codes.
let codes: Vec<&str> =
codes.iter().map(AsRef::as_ref).sorted_unstable().collect();
rules.iter().map(|r| r.code()).sorted_unstable().collect();
let suffix = codes.join(", ");
output.push_str(&suffix);
output.push_str(line_ending);
@@ -180,9 +181,9 @@ fn add_noqa_inner(
formatted.push_str(" # noqa: ");
// Add codes.
let codes: Vec<&str> = codes
let codes: Vec<&str> = rules
.iter()
.map(AsRef::as_ref)
.map(|r| r.code())
.chain(existing.into_iter().filter(|code| external.contains(*code)))
.sorted_unstable()
.collect();
@@ -208,12 +209,12 @@ fn add_noqa_inner(
#[cfg(test)]
mod tests {
use nohash_hasher::IntMap;
use rustc_hash::FxHashSet;
use rustpython_parser::ast::Location;
use crate::ast::types::Range;
use crate::noqa::{add_noqa_inner, NOQA_LINE_REGEX};
use crate::registry::Diagnostic;
use crate::settings::hashable::HashableHashSet;
use crate::source_code::LineEnding;
use crate::violations;
@@ -236,7 +237,7 @@ mod tests {
let diagnostics = vec![];
let contents = "x = 1";
let noqa_line_for = IntMap::default();
let external = FxHashSet::default();
let external = HashableHashSet::default();
let (count, output) = add_noqa_inner(
&diagnostics,
contents,
@@ -253,7 +254,7 @@ mod tests {
)];
let contents = "x = 1";
let noqa_line_for = IntMap::default();
let external = FxHashSet::default();
let external = HashableHashSet::default();
let (count, output) = add_noqa_inner(
&diagnostics,
contents,
@@ -276,7 +277,7 @@ mod tests {
];
let contents = "x = 1 # noqa: E741\n";
let noqa_line_for = IntMap::default();
let external = FxHashSet::default();
let external = HashableHashSet::default();
let (count, output) = add_noqa_inner(
&diagnostics,
contents,
@@ -299,7 +300,7 @@ mod tests {
];
let contents = "x = 1 # noqa";
let noqa_line_for = IntMap::default();
let external = FxHashSet::default();
let external = HashableHashSet::default();
let (count, output) = add_noqa_inner(
&diagnostics,
contents,

View File

@@ -249,3 +249,13 @@ pub fn is_pep585_builtin(checker: &Checker, expr: &Expr) -> bool {
PEP_585_BUILTINS_ELIGIBLE.contains(&call_path.as_slice())
})
}
pub enum Callable {
ForwardRef,
Cast,
NewType,
TypeVar,
NamedTuple,
TypedDict,
MypyExtension,
}

View File

@@ -1,19 +1,16 @@
//! Registry of [`RuleCode`] to [`DiagnosticKind`] mappings.
use std::fmt;
//! Registry of [`Rule`] to [`DiagnosticKind`] mappings.
use itertools::Itertools;
use once_cell::sync::Lazy;
use ruff_macros::RuleCodePrefix;
use rustc_hash::FxHashMap;
use rustpython_parser::ast::Location;
use serde::{Deserialize, Serialize};
use strum_macros::{AsRefStr, Display, EnumIter, EnumString};
use strum_macros::{AsRefStr, EnumIter};
use crate::ast::types::Range;
use crate::fix::Fix;
use crate::violation::Violation;
use crate::violations;
use crate::{rules, violations};
ruff_macros::define_rule_mapping!(
// pycodestyle errors
@@ -82,7 +79,6 @@ ruff_macros::define_rule_mapping!(
F901 => violations::RaiseNotImplemented,
// pylint
PLC0414 => violations::UselessImportAlias,
PLC2201 => violations::MisplacedComparisonConstant,
PLC3002 => violations::UnnecessaryDirectLambdaCall,
PLE0117 => violations::NonlocalWithoutBinding,
PLE0118 => violations::UsedPriorGlobalDeclaration,
@@ -152,8 +148,8 @@ ruff_macros::define_rule_mapping!(
// mccabe
C901 => violations::FunctionIsTooComplex,
// flake8-tidy-imports
TID251 => violations::BannedApi,
TID252 => violations::BannedRelativeImport,
TID251 => rules::flake8_tidy_imports::banned_api::BannedApi,
TID252 => rules::flake8_tidy_imports::relative_imports::RelativeImports,
// flake8-return
RET501 => violations::UnnecessaryReturnNone,
RET502 => violations::ImplicitReturnValue,
@@ -234,7 +230,7 @@ ruff_macros::define_rule_mapping!(
UP008 => violations::SuperCallWithParameters,
UP009 => violations::PEP3120UnnecessaryCodingComment,
UP010 => violations::UnnecessaryFutureImport,
UP011 => violations::UnnecessaryLRUCacheParams,
UP011 => violations::LRUCacheWithoutParameters,
UP012 => violations::UnnecessaryEncodeUTF8,
UP013 => violations::ConvertTypedDictFunctionalToClass,
UP014 => violations::ConvertNamedTupleFunctionalToClass,
@@ -254,6 +250,8 @@ ruff_macros::define_rule_mapping!(
UP028 => violations::RewriteYieldFrom,
UP029 => violations::UnnecessaryBuiltinImport,
UP030 => violations::FormatLiterals,
UP032 => violations::FString,
UP033 => violations::FunctoolsCache,
// pydocstyle
D100 => violations::PublicModule,
D101 => violations::PublicClass,
@@ -410,16 +408,20 @@ ruff_macros::define_rule_mapping!(
// flake8-pie
PIE790 => violations::NoUnnecessaryPass,
PIE794 => violations::DupeClassFieldDefinitions,
PIE796 => violations::PreferUniqueEnums,
PIE807 => violations::PreferListBuiltin,
// flake8-commas
COM812 => violations::TrailingCommaMissing,
COM818 => violations::TrailingCommaOnBareTupleProhibited,
COM819 => violations::TrailingCommaProhibited,
// flake8-no-pep420
INP001 => violations::ImplicitNamespacePackage,
// Ruff
RUF001 => violations::AmbiguousUnicodeCharacterString,
RUF002 => violations::AmbiguousUnicodeCharacterDocstring,
RUF003 => violations::AmbiguousUnicodeCharacterComment,
RUF004 => violations::KeywordArgumentBeforeStarArgument,
RUF005 => violations::UnpackInsteadOfConcatenatingToCollectionLiteral,
RUF100 => violations::UnusedNOQA,
);
@@ -458,23 +460,10 @@ pub enum RuleOrigin {
Pylint,
Flake8Pie,
Flake8Commas,
Flake8NoPep420,
Ruff,
}
pub enum Platform {
PyPI,
GitHub,
}
impl fmt::Display for Platform {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
match self {
Platform::PyPI => fmt.write_str("PyPI"),
Platform::GitHub => fmt.write_str("GitHub"),
}
}
}
pub enum Prefixes {
Single(RuleCodePrefix),
Multiple(Vec<(RuleCodePrefix, &'static str)>),
@@ -492,46 +481,9 @@ impl Prefixes {
}
}
impl RuleOrigin {
pub fn title(&self) -> &'static str {
match self {
RuleOrigin::Eradicate => "eradicate",
RuleOrigin::Flake82020 => "flake8-2020",
RuleOrigin::Flake8Annotations => "flake8-annotations",
RuleOrigin::Flake8Bandit => "flake8-bandit",
RuleOrigin::Flake8BlindExcept => "flake8-blind-except",
RuleOrigin::Flake8BooleanTrap => "flake8-boolean-trap",
RuleOrigin::Flake8Bugbear => "flake8-bugbear",
RuleOrigin::Flake8Builtins => "flake8-builtins",
RuleOrigin::Flake8Comprehensions => "flake8-comprehensions",
RuleOrigin::Flake8Debugger => "flake8-debugger",
RuleOrigin::Flake8ErrMsg => "flake8-errmsg",
RuleOrigin::Flake8ImplicitStrConcat => "flake8-implicit-str-concat",
RuleOrigin::Flake8ImportConventions => "flake8-import-conventions",
RuleOrigin::Flake8Print => "flake8-print",
RuleOrigin::Flake8PytestStyle => "flake8-pytest-style",
RuleOrigin::Flake8Quotes => "flake8-quotes",
RuleOrigin::Flake8Return => "flake8-return",
RuleOrigin::Flake8TidyImports => "flake8-tidy-imports",
RuleOrigin::Flake8Simplify => "flake8-simplify",
RuleOrigin::Flake8UnusedArguments => "flake8-unused-arguments",
RuleOrigin::Flake8Datetimez => "flake8-datetimez",
RuleOrigin::Isort => "isort",
RuleOrigin::McCabe => "mccabe",
RuleOrigin::PandasVet => "pandas-vet",
RuleOrigin::PEP8Naming => "pep8-naming",
RuleOrigin::Pycodestyle => "pycodestyle",
RuleOrigin::Pydocstyle => "pydocstyle",
RuleOrigin::Pyflakes => "Pyflakes",
RuleOrigin::PygrepHooks => "pygrep-hooks",
RuleOrigin::Pylint => "Pylint",
RuleOrigin::Pyupgrade => "pyupgrade",
RuleOrigin::Flake8Pie => "flake8-pie",
RuleOrigin::Flake8Commas => "flake8-commas",
RuleOrigin::Ruff => "Ruff-specific rules",
}
}
include!(concat!(env!("OUT_DIR"), "/origin.rs"));
impl RuleOrigin {
pub fn prefixes(&self) -> Prefixes {
match self {
RuleOrigin::Eradicate => Prefixes::Single(RuleCodePrefix::ERA),
@@ -575,134 +527,10 @@ impl RuleOrigin {
RuleOrigin::Pyupgrade => Prefixes::Single(RuleCodePrefix::UP),
RuleOrigin::Flake8Pie => Prefixes::Single(RuleCodePrefix::PIE),
RuleOrigin::Flake8Commas => Prefixes::Single(RuleCodePrefix::COM),
RuleOrigin::Flake8NoPep420 => Prefixes::Single(RuleCodePrefix::INP),
RuleOrigin::Ruff => Prefixes::Single(RuleCodePrefix::RUF),
}
}
pub fn url(&self) -> Option<(&'static str, &'static Platform)> {
match self {
RuleOrigin::Eradicate => {
Some(("https://pypi.org/project/eradicate/2.1.0/", &Platform::PyPI))
}
RuleOrigin::Flake82020 => Some((
"https://pypi.org/project/flake8-2020/1.7.0/",
&Platform::PyPI,
)),
RuleOrigin::Flake8Annotations => Some((
"https://pypi.org/project/flake8-annotations/2.9.1/",
&Platform::PyPI,
)),
RuleOrigin::Flake8Bandit => Some((
"https://pypi.org/project/flake8-bandit/4.1.1/",
&Platform::PyPI,
)),
RuleOrigin::Flake8BlindExcept => Some((
"https://pypi.org/project/flake8-blind-except/0.2.1/",
&Platform::PyPI,
)),
RuleOrigin::Flake8BooleanTrap => Some((
"https://pypi.org/project/flake8-boolean-trap/0.1.0/",
&Platform::PyPI,
)),
RuleOrigin::Flake8Bugbear => Some((
"https://pypi.org/project/flake8-bugbear/22.10.27/",
&Platform::PyPI,
)),
RuleOrigin::Flake8Builtins => Some((
"https://pypi.org/project/flake8-builtins/2.0.1/",
&Platform::PyPI,
)),
RuleOrigin::Flake8Comprehensions => Some((
"https://pypi.org/project/flake8-comprehensions/3.10.1/",
&Platform::PyPI,
)),
RuleOrigin::Flake8Debugger => Some((
"https://pypi.org/project/flake8-debugger/4.1.2/",
&Platform::PyPI,
)),
RuleOrigin::Flake8ErrMsg => Some((
"https://pypi.org/project/flake8-errmsg/0.4.0/",
&Platform::PyPI,
)),
RuleOrigin::Flake8ImplicitStrConcat => Some((
"https://pypi.org/project/flake8-implicit-str-concat/0.3.0/",
&Platform::PyPI,
)),
RuleOrigin::Flake8ImportConventions => None,
RuleOrigin::Flake8Print => Some((
"https://pypi.org/project/flake8-print/5.0.0/",
&Platform::PyPI,
)),
RuleOrigin::Flake8PytestStyle => Some((
"https://pypi.org/project/flake8-pytest-style/1.6.0/",
&Platform::PyPI,
)),
RuleOrigin::Flake8Quotes => Some((
"https://pypi.org/project/flake8-quotes/3.3.1/",
&Platform::PyPI,
)),
RuleOrigin::Flake8Return => Some((
"https://pypi.org/project/flake8-return/1.2.0/",
&Platform::PyPI,
)),
RuleOrigin::Flake8Simplify => Some((
"https://pypi.org/project/flake8-simplify/0.19.3/",
&Platform::PyPI,
)),
RuleOrigin::Flake8TidyImports => Some((
"https://pypi.org/project/flake8-tidy-imports/4.8.0/",
&Platform::PyPI,
)),
RuleOrigin::Flake8UnusedArguments => Some((
"https://pypi.org/project/flake8-unused-arguments/0.0.12/",
&Platform::PyPI,
)),
RuleOrigin::Flake8Datetimez => Some((
"https://pypi.org/project/flake8-datetimez/20.10.0/",
&Platform::PyPI,
)),
RuleOrigin::Isort => Some(("https://pypi.org/project/isort/5.10.1/", &Platform::PyPI)),
RuleOrigin::McCabe => Some(("https://pypi.org/project/mccabe/0.7.0/", &Platform::PyPI)),
RuleOrigin::PandasVet => Some((
"https://pypi.org/project/pandas-vet/0.2.3/",
&Platform::PyPI,
)),
RuleOrigin::PEP8Naming => Some((
"https://pypi.org/project/pep8-naming/0.13.2/",
&Platform::PyPI,
)),
RuleOrigin::Pycodestyle => Some((
"https://pypi.org/project/pycodestyle/2.9.1/",
&Platform::PyPI,
)),
RuleOrigin::Pydocstyle => Some((
"https://pypi.org/project/pydocstyle/6.1.1/",
&Platform::PyPI,
)),
RuleOrigin::Pyflakes => {
Some(("https://pypi.org/project/pyflakes/2.5.0/", &Platform::PyPI))
}
RuleOrigin::Pylint => {
Some(("https://pypi.org/project/pylint/2.15.7/", &Platform::PyPI))
}
RuleOrigin::PygrepHooks => Some((
"https://github.com/pre-commit/pygrep-hooks",
&Platform::GitHub,
)),
RuleOrigin::Pyupgrade => {
Some(("https://pypi.org/project/pyupgrade/3.2.0/", &Platform::PyPI))
}
RuleOrigin::Flake8Pie => Some((
"https://pypi.org/project/flake8-pie/0.16.0/",
&Platform::PyPI,
)),
RuleOrigin::Flake8Commas => Some((
"https://pypi.org/project/flake8-commas/2.1.0/",
&Platform::PyPI,
)),
RuleOrigin::Ruff => None,
}
}
}
pub enum LintSource {
@@ -712,82 +540,43 @@ pub enum LintSource {
Tokens,
Imports,
NoQa,
Filesystem,
}
impl RuleCode {
impl Rule {
/// The source for the diagnostic (either the AST, the filesystem, or the
/// physical lines).
pub fn lint_source(&self) -> &'static LintSource {
match self {
RuleCode::RUF100 => &LintSource::NoQa,
RuleCode::E501
| RuleCode::W292
| RuleCode::W505
| RuleCode::UP009
| RuleCode::PGH003
| RuleCode::PGH004 => &LintSource::Lines,
RuleCode::ERA001
| RuleCode::ISC001
| RuleCode::ISC002
| RuleCode::Q000
| RuleCode::Q001
| RuleCode::Q002
| RuleCode::Q003
| RuleCode::W605
| RuleCode::COM812
| RuleCode::COM818
| RuleCode::COM819
| RuleCode::RUF001
| RuleCode::RUF002
| RuleCode::RUF003 => &LintSource::Tokens,
RuleCode::E902 => &LintSource::Io,
RuleCode::I001 | RuleCode::I002 => &LintSource::Imports,
Rule::UnusedNOQA => &LintSource::NoQa,
Rule::LineTooLong
| Rule::NoNewLineAtEndOfFile
| Rule::DocLineTooLong
| Rule::PEP3120UnnecessaryCodingComment
| Rule::BlanketTypeIgnore
| Rule::BlanketNOQA => &LintSource::Lines,
Rule::CommentedOutCode
| Rule::SingleLineImplicitStringConcatenation
| Rule::MultiLineImplicitStringConcatenation
| Rule::BadQuotesInlineString
| Rule::BadQuotesMultilineString
| Rule::BadQuotesDocstring
| Rule::AvoidQuoteEscape
| Rule::InvalidEscapeSequence
| Rule::TrailingCommaMissing
| Rule::TrailingCommaOnBareTupleProhibited
| Rule::TrailingCommaProhibited
| Rule::AmbiguousUnicodeCharacterString
| Rule::AmbiguousUnicodeCharacterDocstring
| Rule::AmbiguousUnicodeCharacterComment => &LintSource::Tokens,
Rule::IOError => &LintSource::Io,
Rule::UnsortedImports | Rule::MissingRequiredImport => &LintSource::Imports,
Rule::ImplicitNamespacePackage => &LintSource::Filesystem,
_ => &LintSource::Ast,
}
}
}
impl DiagnosticKind {
/// The summary text for the diagnostic. Typically a truncated form of the
/// body text.
pub fn summary(&self) -> String {
match self {
DiagnosticKind::UnaryPrefixIncrement(..) => {
"Python does not support the unary prefix increment".to_string()
}
DiagnosticKind::UnusedLoopControlVariable(violations::UnusedLoopControlVariable(
name,
)) => {
format!("Loop control variable `{name}` not used within the loop body")
}
DiagnosticKind::NoAssertRaisesException(..) => {
"`assertRaises(Exception)` should be considered evil".to_string()
}
DiagnosticKind::StarArgUnpackingAfterKeywordArg(..) => {
"Star-arg unpacking after a keyword argument is strongly discouraged".to_string()
}
// flake8-datetimez
DiagnosticKind::CallDatetimeToday(..) => {
"The use of `datetime.datetime.today()` is not allowed".to_string()
}
DiagnosticKind::CallDatetimeUtcnow(..) => {
"The use of `datetime.datetime.utcnow()` is not allowed".to_string()
}
DiagnosticKind::CallDatetimeUtcfromtimestamp(..) => {
"The use of `datetime.datetime.utcfromtimestamp()` is not allowed".to_string()
}
DiagnosticKind::CallDateToday(..) => {
"The use of `datetime.date.today()` is not allowed.".to_string()
}
DiagnosticKind::CallDateFromtimestamp(..) => {
"The use of `datetime.date.fromtimestamp()` is not allowed".to_string()
}
_ => self.body(),
}
}
}
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct Diagnostic {
pub kind: DiagnosticKind,
@@ -820,95 +609,80 @@ impl Diagnostic {
}
/// Pairs of checks that shouldn't be enabled together.
pub const INCOMPATIBLE_CODES: &[(RuleCode, RuleCode, &str)] = &[(
RuleCode::D203,
RuleCode::D211,
pub const INCOMPATIBLE_CODES: &[(Rule, Rule, &str)] = &[(
Rule::OneBlankLineBeforeClass,
Rule::NoBlankLineBeforeClass,
"`D203` (OneBlankLineBeforeClass) and `D211` (NoBlankLinesBeforeClass) are incompatible. \
Consider adding `D203` to `ignore`.",
)];
/// A hash map from deprecated to latest `RuleCode`.
pub static CODE_REDIRECTS: Lazy<FxHashMap<&'static str, RuleCode>> = Lazy::new(|| {
/// A hash map from deprecated to latest `Rule`.
pub static CODE_REDIRECTS: Lazy<FxHashMap<&'static str, Rule>> = Lazy::new(|| {
FxHashMap::from_iter([
// TODO(charlie): Remove by 2023-01-01.
("U001", RuleCode::UP001),
("U003", RuleCode::UP003),
("U004", RuleCode::UP004),
("U005", RuleCode::UP005),
("U006", RuleCode::UP006),
("U007", RuleCode::UP007),
("U008", RuleCode::UP008),
("U009", RuleCode::UP009),
("U010", RuleCode::UP010),
("U011", RuleCode::UP011),
("U012", RuleCode::UP012),
("U013", RuleCode::UP013),
("U014", RuleCode::UP014),
("U015", RuleCode::UP015),
("U016", RuleCode::UP016),
("U017", RuleCode::UP017),
("U019", RuleCode::UP019),
("U001", Rule::UselessMetaclassType),
("U003", Rule::TypeOfPrimitive),
("U004", Rule::UselessObjectInheritance),
("U005", Rule::DeprecatedUnittestAlias),
("U006", Rule::UsePEP585Annotation),
("U007", Rule::UsePEP604Annotation),
("U008", Rule::SuperCallWithParameters),
("U009", Rule::PEP3120UnnecessaryCodingComment),
("U010", Rule::UnnecessaryFutureImport),
("U011", Rule::LRUCacheWithoutParameters),
("U012", Rule::UnnecessaryEncodeUTF8),
("U013", Rule::ConvertTypedDictFunctionalToClass),
("U014", Rule::ConvertNamedTupleFunctionalToClass),
("U015", Rule::RedundantOpenModes),
("U016", Rule::RemoveSixCompat),
("U017", Rule::DatetimeTimezoneUTC),
("U019", Rule::TypingTextStrAlias),
// TODO(charlie): Remove by 2023-02-01.
("I252", RuleCode::TID252),
("M001", RuleCode::RUF100),
("I252", Rule::RelativeImports),
("M001", Rule::UnusedNOQA),
// TODO(charlie): Remove by 2023-02-01.
("PDV002", RuleCode::PD002),
("PDV003", RuleCode::PD003),
("PDV004", RuleCode::PD004),
("PDV007", RuleCode::PD007),
("PDV008", RuleCode::PD008),
("PDV009", RuleCode::PD009),
("PDV010", RuleCode::PD010),
("PDV011", RuleCode::PD011),
("PDV012", RuleCode::PD012),
("PDV013", RuleCode::PD013),
("PDV015", RuleCode::PD015),
("PDV901", RuleCode::PD901),
("PDV002", Rule::UseOfInplaceArgument),
("PDV003", Rule::UseOfDotIsNull),
("PDV004", Rule::UseOfDotNotNull),
("PDV007", Rule::UseOfDotIx),
("PDV008", Rule::UseOfDotAt),
("PDV009", Rule::UseOfDotIat),
("PDV010", Rule::UseOfDotPivotOrUnstack),
("PDV011", Rule::UseOfDotValues),
("PDV012", Rule::UseOfDotReadTable),
("PDV013", Rule::UseOfDotStack),
("PDV015", Rule::UseOfPdMerge),
("PDV901", Rule::DfIsABadVariableName),
// TODO(charlie): Remove by 2023-02-01.
("R501", RuleCode::RET501),
("R502", RuleCode::RET502),
("R503", RuleCode::RET503),
("R504", RuleCode::RET504),
("R505", RuleCode::RET505),
("R506", RuleCode::RET506),
("R507", RuleCode::RET507),
("R508", RuleCode::RET508),
("R501", Rule::UnnecessaryReturnNone),
("R502", Rule::ImplicitReturnValue),
("R503", Rule::ImplicitReturn),
("R504", Rule::UnnecessaryAssign),
("R505", Rule::SuperfluousElseReturn),
("R506", Rule::SuperfluousElseRaise),
("R507", Rule::SuperfluousElseContinue),
("R508", Rule::SuperfluousElseBreak),
// TODO(charlie): Remove by 2023-02-01.
("IC001", RuleCode::ICN001),
("IC002", RuleCode::ICN001),
("IC003", RuleCode::ICN001),
("IC004", RuleCode::ICN001),
("IC001", Rule::ImportAliasIsNotConventional),
("IC002", Rule::ImportAliasIsNotConventional),
("IC003", Rule::ImportAliasIsNotConventional),
("IC004", Rule::ImportAliasIsNotConventional),
])
});
#[cfg(test)]
mod tests {
use std::str::FromStr;
use strum::IntoEnumIterator;
use crate::registry::RuleCode;
use crate::registry::Rule;
#[test]
fn check_code_serialization() {
for check_code in RuleCode::iter() {
for rule in Rule::iter() {
assert!(
RuleCode::from_str(check_code.as_ref()).is_ok(),
"{check_code:?} could not be round-trip serialized."
Rule::from_code(rule.code()).is_ok(),
"{rule:?} could not be round-trip serialized."
);
}
}
#[test]
fn fixable_codes() {
for check_code in RuleCode::iter() {
let kind = check_code.kind();
if kind.fixable() {
assert!(
kind.commit().is_some(),
"{check_code:?} is fixable but has no commit message."
);
}
}
}
}

View File

@@ -1,21 +1,21 @@
//! Rules from [eradicate](https://pypi.org/project/eradicate/2.1.0/).
pub(crate) mod detection;
pub(crate) mod rules;
#[cfg(test)]
mod tests {
use std::convert::AsRef;
use std::path::Path;
use anyhow::Result;
use test_case::test_case;
use crate::linter::test_path;
use crate::registry::RuleCode;
use crate::registry::Rule;
use crate::settings;
#[test_case(RuleCode::ERA001, Path::new("ERA001.py"); "ERA001")]
fn rules(rule_code: RuleCode, path: &Path) -> Result<()> {
let snapshot = format!("{}_{}", rule_code.as_ref(), path.to_string_lossy());
#[test_case(Rule::CommentedOutCode, Path::new("ERA001.py"); "ERA001")]
fn rules(rule_code: Rule, path: &Path) -> Result<()> {
let snapshot = format!("{}_{}", rule_code.code(), path.to_string_lossy());
let diagnostics = test_path(
Path::new("./resources/test/fixtures/eradicate")
.join(path)

View File

@@ -3,7 +3,7 @@ use rustpython_ast::Location;
use super::detection::comment_contains_code;
use crate::ast::types::Range;
use crate::fix::Fix;
use crate::registry::{Diagnostic, RuleCode};
use crate::registry::{Diagnostic, Rule};
use crate::settings::{flags, Settings};
use crate::source_code::Locator;
use crate::violations;
@@ -35,7 +35,7 @@ pub fn commented_out_code(
if is_standalone_comment(&line) && comment_contains_code(&line, &settings.task_tags[..]) {
let mut diagnostic = Diagnostic::new(violations::CommentedOutCode, Range::new(start, end));
if matches!(autofix, flags::Autofix::Enabled)
&& settings.fixable.contains(&RuleCode::ERA001)
&& settings.rules.should_fix(&Rule::CommentedOutCode)
{
diagnostic.amend(Fix::deletion(location, end_location));
}

View File

@@ -1,29 +1,29 @@
//! Rules from [flake8-2020](https://pypi.org/project/flake8-2020/1.7.0/).
pub(crate) mod rules;
#[cfg(test)]
mod tests {
use std::convert::AsRef;
use std::path::Path;
use anyhow::Result;
use test_case::test_case;
use crate::linter::test_path;
use crate::registry::RuleCode;
use crate::registry::Rule;
use crate::settings;
#[test_case(RuleCode::YTT101, Path::new("YTT101.py"); "YTT101")]
#[test_case(RuleCode::YTT102, Path::new("YTT102.py"); "YTT102")]
#[test_case(RuleCode::YTT103, Path::new("YTT103.py"); "YTT103")]
#[test_case(RuleCode::YTT201, Path::new("YTT201.py"); "YTT201")]
#[test_case(RuleCode::YTT202, Path::new("YTT202.py"); "YTT202")]
#[test_case(RuleCode::YTT203, Path::new("YTT203.py"); "YTT203")]
#[test_case(RuleCode::YTT204, Path::new("YTT204.py"); "YTT204")]
#[test_case(RuleCode::YTT301, Path::new("YTT301.py"); "YTT301")]
#[test_case(RuleCode::YTT302, Path::new("YTT302.py"); "YTT302")]
#[test_case(RuleCode::YTT303, Path::new("YTT303.py"); "YTT303")]
fn rules(rule_code: RuleCode, path: &Path) -> Result<()> {
let snapshot = format!("{}_{}", rule_code.as_ref(), path.to_string_lossy());
#[test_case(Rule::SysVersionSlice3Referenced, Path::new("YTT101.py"); "YTT101")]
#[test_case(Rule::SysVersion2Referenced, Path::new("YTT102.py"); "YTT102")]
#[test_case(Rule::SysVersionCmpStr3, Path::new("YTT103.py"); "YTT103")]
#[test_case(Rule::SysVersionInfo0Eq3Referenced, Path::new("YTT201.py"); "YTT201")]
#[test_case(Rule::SixPY3Referenced, Path::new("YTT202.py"); "YTT202")]
#[test_case(Rule::SysVersionInfo1CmpInt, Path::new("YTT203.py"); "YTT203")]
#[test_case(Rule::SysVersionInfoMinorCmpInt, Path::new("YTT204.py"); "YTT204")]
#[test_case(Rule::SysVersion0Referenced, Path::new("YTT301.py"); "YTT301")]
#[test_case(Rule::SysVersionCmpStr10, Path::new("YTT302.py"); "YTT302")]
#[test_case(Rule::SysVersionSlice1Referenced, Path::new("YTT303.py"); "YTT303")]
fn rules(rule_code: Rule, path: &Path) -> Result<()> {
let snapshot = format!("{}_{}", rule_code.code(), path.to_string_lossy());
let diagnostics = test_path(
Path::new("./resources/test/fixtures/flake8_2020")
.join(path)

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