Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1e36c109c6 | ||
|
|
3960016d55 | ||
|
|
75d669fa86 | ||
|
|
20989e12ba | ||
|
|
5a1b6c32eb | ||
|
|
46bdcb9080 | ||
|
|
d16a7252af | ||
|
|
ca6551eb37 | ||
|
|
43a4f5749e | ||
|
|
6fef4db433 | ||
|
|
7470d6832f | ||
|
|
63ba0bfeef | ||
|
|
91666fcaf6 | ||
|
|
643e27221d |
14
.editorconfig
Normal file
14
.editorconfig
Normal file
@@ -0,0 +1,14 @@
|
||||
# Check http://editorconfig.org for more information
|
||||
# This is the main config file for this project:
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
end_of_line = lf
|
||||
indent_style = space
|
||||
insert_final_newline = true
|
||||
indent_size = 2
|
||||
|
||||
[*.{rs,py}]
|
||||
indent_size = 4
|
||||
128
CODE_OF_CONDUCT.md
Normal file
128
CODE_OF_CONDUCT.md
Normal file
@@ -0,0 +1,128 @@
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
We as members, contributors, and leaders pledge to make participation in our
|
||||
community a harassment-free experience for everyone, regardless of age, body
|
||||
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
||||
identity and expression, level of experience, education, socio-economic status,
|
||||
nationality, personal appearance, race, religion, or sexual identity
|
||||
and orientation.
|
||||
|
||||
We pledge to act and interact in ways that contribute to an open, welcoming,
|
||||
diverse, inclusive, and healthy community.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to a positive environment for our
|
||||
community include:
|
||||
|
||||
* Demonstrating empathy and kindness toward other people
|
||||
* Being respectful of differing opinions, viewpoints, and experiences
|
||||
* Giving and gracefully accepting constructive feedback
|
||||
* Accepting responsibility and apologizing to those affected by our mistakes,
|
||||
and learning from the experience
|
||||
* Focusing on what is best not just for us as individuals, but for the
|
||||
overall community
|
||||
|
||||
Examples of unacceptable behavior include:
|
||||
|
||||
* The use of sexualized language or imagery, and sexual attention or
|
||||
advances of any kind
|
||||
* Trolling, insulting or derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or email
|
||||
address, without their explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Enforcement Responsibilities
|
||||
|
||||
Community leaders are responsible for clarifying and enforcing our standards of
|
||||
acceptable behavior and will take appropriate and fair corrective action in
|
||||
response to any behavior that they deem inappropriate, threatening, offensive,
|
||||
or harmful.
|
||||
|
||||
Community leaders have the right and responsibility to remove, edit, or reject
|
||||
comments, commits, code, wiki edits, issues, and other contributions that are
|
||||
not aligned to this Code of Conduct, and will communicate reasons for moderation
|
||||
decisions when appropriate.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies within all community spaces, and also applies when
|
||||
an individual is officially representing the community in public spaces.
|
||||
Examples of representing our community include using an official e-mail address,
|
||||
posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported to the community leaders responsible for enforcement at
|
||||
charlie.r.marsh@gmail.com.
|
||||
All complaints will be reviewed and investigated promptly and fairly.
|
||||
|
||||
All community leaders are obligated to respect the privacy and security of the
|
||||
reporter of any incident.
|
||||
|
||||
## Enforcement Guidelines
|
||||
|
||||
Community leaders will follow these Community Impact Guidelines in determining
|
||||
the consequences for any action they deem in violation of this Code of Conduct:
|
||||
|
||||
### 1. Correction
|
||||
|
||||
**Community Impact**: Use of inappropriate language or other behavior deemed
|
||||
unprofessional or unwelcome in the community.
|
||||
|
||||
**Consequence**: A private, written warning from community leaders, providing
|
||||
clarity around the nature of the violation and an explanation of why the
|
||||
behavior was inappropriate. A public apology may be requested.
|
||||
|
||||
### 2. Warning
|
||||
|
||||
**Community Impact**: A violation through a single incident or series
|
||||
of actions.
|
||||
|
||||
**Consequence**: A warning with consequences for continued behavior. No
|
||||
interaction with the people involved, including unsolicited interaction with
|
||||
those enforcing the Code of Conduct, for a specified period of time. This
|
||||
includes avoiding interactions in community spaces as well as external channels
|
||||
like social media. Violating these terms may lead to a temporary or
|
||||
permanent ban.
|
||||
|
||||
### 3. Temporary Ban
|
||||
|
||||
**Community Impact**: A serious violation of community standards, including
|
||||
sustained inappropriate behavior.
|
||||
|
||||
**Consequence**: A temporary ban from any sort of interaction or public
|
||||
communication with the community for a specified period of time. No public or
|
||||
private interaction with the people involved, including unsolicited interaction
|
||||
with those enforcing the Code of Conduct, is allowed during this period.
|
||||
Violating these terms may lead to a permanent ban.
|
||||
|
||||
### 4. Permanent Ban
|
||||
|
||||
**Community Impact**: Demonstrating a pattern of violation of community
|
||||
standards, including sustained inappropriate behavior, harassment of an
|
||||
individual, or aggression toward or disparagement of classes of individuals.
|
||||
|
||||
**Consequence**: A permanent ban from any sort of public interaction within
|
||||
the community.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
||||
version 2.0, available at
|
||||
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
|
||||
|
||||
Community Impact Guidelines were inspired by [Mozilla's code of conduct
|
||||
enforcement ladder](https://github.com/mozilla/diversity).
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
|
||||
For answers to common questions about this code of conduct, see the FAQ at
|
||||
https://www.contributor-covenant.org/faq. Translations are available at
|
||||
https://www.contributor-covenant.org/translations.
|
||||
105
CONTRIBUTING.md
Normal file
105
CONTRIBUTING.md
Normal file
@@ -0,0 +1,105 @@
|
||||
# Contributing to ruff
|
||||
|
||||
Welcome! We're happy to have you here. Thank you in advance for your contribution to ruff.
|
||||
|
||||
## The basics
|
||||
|
||||
ruff welcomes contributions in the form of Pull Requests. For small changes (e.g., bug fixes), feel
|
||||
free to submit a PR. For larger changes (e.g., new lint rules, new functionality, new configuration
|
||||
options), consider submitting an [Issue](https://github.com/charliermarsh/ruff/issues) outlining
|
||||
your proposed change.
|
||||
|
||||
### Prerequisites
|
||||
|
||||
ruff is written in Rust (1.63.0). You'll need to install the
|
||||
[Rust toolchain](https://www.rust-lang.org/tools/install) for development.
|
||||
|
||||
### Development
|
||||
|
||||
After cloning the repository, run ruff locally with:
|
||||
|
||||
```shell
|
||||
cargo run resources/test/fixtures --no-cache
|
||||
```
|
||||
|
||||
Prior to opening a pull request, ensure that your code has been auto-formatted, and that it passes
|
||||
both the lint and test validation checks:
|
||||
|
||||
```shell
|
||||
cargo fmt # Auto-formatting...
|
||||
cargo clippy # Linting...
|
||||
cargo test # Testing...
|
||||
```
|
||||
|
||||
These checks will run on GitHub Actions when you open your Pull Request, but running them locally
|
||||
will save you time and expedite the merge process.
|
||||
|
||||
Your Pull Request will be reviewed by a maintainer, which may involve a few rounds of iteration
|
||||
prior to merging.
|
||||
|
||||
### Example: Adding a new lint rule
|
||||
|
||||
There are three phases to adding a new lint rule:
|
||||
|
||||
1. Define the rule in `src/checks.rs`.
|
||||
2. Define the _logic_ for triggering the rule in `src/check_ast.rs` (for AST-based checks)
|
||||
or `src/check_lines.rs` (for text-based checks).
|
||||
3. Add a test fixture.
|
||||
|
||||
To define the rule, open up `src/checks.rs`. You'll need to define both a `CheckCode` and
|
||||
`CheckKind`. As an example, you can grep for `E402` and `ModuleImportNotAtTopOfFile`, and follow the
|
||||
pattern implemented therein.
|
||||
|
||||
To trigger the rule, you'll likely want to augment the logic in `src/check_ast.rs`, which defines
|
||||
the Python AST visitor, responsible for iterating over the abstract syntax tree and collecting
|
||||
lint-rule violations as it goes. Grep for the `Check::new` invocations to understand how other,
|
||||
similar rules are implemented.
|
||||
|
||||
To add a test fixture, create a file under `resources/test/fixtures`, named to match the `CheckCode`
|
||||
you defined earlier (e.g., `E402.py`). This file should contain a variety of violations and
|
||||
non-violations designed to evaluate and demonstrate the behavior of your lint rule. Run ruff locally
|
||||
with (e.g.) `cargo run resources/test/fixtures/E402.py`. Once you're satisified with the output,
|
||||
codify the behavior as a snapshot test by adding a new function to the `mod tests` section of
|
||||
`src/linter.rs`, like so:
|
||||
|
||||
```rust
|
||||
#[test]
|
||||
fn e402() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/E402.py"),
|
||||
&settings::Settings::for_rule(CheckCode::E402),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!(checks);
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
Then, run `cargo test`. Your test will fail, but you'll be prompted to follow-up with
|
||||
`cargo insta review`. Accept the generated snapshot, then commit the snapshot file alongside the
|
||||
rest of your changes.
|
||||
|
||||
### Example: Adding a new configuration option
|
||||
|
||||
ruff's user-facing settings live in two places: first, the command-line options defined with
|
||||
[clap](https://docs.rs/clap/latest/clap/) via the `Cli` struct in `src/main.rs`; and second, the
|
||||
`Config` struct defined `src/pyproject.rs`, which is responsible for extracting user-defined
|
||||
settings from a `pyproject.toml` file.
|
||||
|
||||
Ultimately, these two sources of configuration are merged into the `Settings` struct defined
|
||||
in `src/settings.rs`, which is then threaded through the codebase.
|
||||
|
||||
To add a new configuration option, you'll likely want to _both_ add a CLI option to `src/main.rs`
|
||||
_and_ a `pyproject.toml` parameter to `src/pyproject.rs`. If you want to pattern-match against an
|
||||
existing example, grep for `dummy_variable_rgx`, which defines a regular expression to match against
|
||||
acceptable unused variables (e.g., `_`).
|
||||
|
||||
## Release process
|
||||
|
||||
As of now, ruff has an ad hoc release process: releases are cut with high frequency via GitHub
|
||||
Actions, which automatically generates the appropriate wheels across architectures and publishes
|
||||
them to [PyPI](https://pypi.org/project/ruff/).
|
||||
|
||||
ruff follows the [semver](https://semver.org/) versioning standard. However, as pre-1.0 software,
|
||||
even patch releases may contain [non-backwards-compatible changes](https://semver.org/#spec-item-4).
|
||||
2
Cargo.lock
generated
2
Cargo.lock
generated
@@ -1799,7 +1799,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.0.47"
|
||||
version = "0.0.49"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bincode",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff"
|
||||
version = "0.0.47"
|
||||
version = "0.0.49"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
|
||||
54
README.md
54
README.md
@@ -20,7 +20,7 @@ An extremely fast Python linter, written in Rust.
|
||||
- 🤝 Python 3.10 compatibility
|
||||
- 🛠️ `pyproject.toml` support
|
||||
- 📦 [ESLint](https://eslint.org/docs/latest/user-guide/command-line-interface#caching)-inspired cache support
|
||||
- 🔧 [ESLint](https://eslint.org/docs/latest/user-guide/command-line-interface#caching)-inspired `--fix` support
|
||||
- 🔧 [ESLint](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix)-inspired `--fix` support
|
||||
- 👀 [TypeScript](https://www.typescriptlang.org/docs/handbook/configuring-watch.html)-inspired `--watch` support
|
||||
- ⚖️ [Near-complete parity](#Parity-with-Flake8) with the built-in Flake8 rule set
|
||||
|
||||
@@ -57,7 +57,7 @@ ruff also works with [pre-commit](https://pre-commit.com):
|
||||
```yaml
|
||||
repos:
|
||||
- repo: https://github.com/charliermarsh/ruff-pre-commit
|
||||
rev: v0.0.40
|
||||
rev: v0.0.48
|
||||
hooks:
|
||||
- id: lint
|
||||
```
|
||||
@@ -211,7 +211,8 @@ variables.)
|
||||
Beyond rule-set parity, ruff suffers from the following limitations vis-à-vis Flake8:
|
||||
|
||||
1. Flake8 has a plugin architecture and supports writing custom lint rules.
|
||||
2. ruff does not yet support parenthesized context managers.
|
||||
2. ruff does not yet support a few Python 3.9 and 3.10 language features, including structural
|
||||
pattern matching and parenthesized context managers.
|
||||
|
||||
## Rules
|
||||
|
||||
@@ -259,10 +260,52 @@ Beyond rule-set parity, ruff suffers from the following limitations vis-à-vis F
|
||||
| F831 | DuplicateArgumentName | Duplicate argument name in function definition |
|
||||
| F841 | UnusedVariable | Local variable `...` is assigned to but never used |
|
||||
| F901 | RaiseNotImplemented | `raise NotImplemented` should be `raise NotImplementedError` |
|
||||
| A001 | BuiltinVariableShadowing | Variable `...` is shadowing a python builtin |
|
||||
| A002 | BuiltinArgumentShadowing | Argument `...` is shadowing a python builtin |
|
||||
| A003 | BuiltinAttributeShadowing | class attribute `...` is shadowing a python builtin |
|
||||
| SPR001 | SuperCallWithParameters | Use `super()` instead of `super(__class__, self)` |
|
||||
| R001 | UselessObjectInheritance | Class `...` inherits from object |
|
||||
| R002 | NoAssertEquals | `assertEquals` is deprecated, use `assertEqual` instead |
|
||||
| M001 | UnusedNOQA | Unused `noqa` directive |
|
||||
|
||||
## Integrations
|
||||
|
||||
### PyCharm
|
||||
|
||||
ruff can be installed as an [External Tool](https://www.jetbrains.com/help/pycharm/configuring-third-party-tools.html)
|
||||
in PyCharm. Open the Preferences pane, then navigate to "Tools", then "External Tools". From there,
|
||||
add a new tool with the following configuration:
|
||||
|
||||

|
||||
|
||||
ruff should then appear as a runnable action:
|
||||
|
||||

|
||||
|
||||
### GitHub Actions
|
||||
|
||||
GitHub Actions has everything you need to run ruff out-of-the-box:
|
||||
|
||||
```yaml
|
||||
name: CI
|
||||
on: push
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Install Python
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: "3.10"
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install ruff
|
||||
- name: Run ruff
|
||||
run: ruff .
|
||||
```
|
||||
|
||||
## Development
|
||||
|
||||
ruff is written in Rust (1.63.0). You'll need to install the [Rust toolchain](https://www.rust-lang.org/tools/install)
|
||||
@@ -422,3 +465,8 @@ Summary
|
||||
## License
|
||||
|
||||
MIT
|
||||
|
||||
## Contributing
|
||||
|
||||
Contributions are welcome and hugely appreciated. To get started, check out the
|
||||
[contributing guidelines](https://github.com/charliermarsh/ruff/blob/main/CONTRIBUTORS.md).
|
||||
|
||||
27
resources/test/fixtures/A001.py
vendored
Normal file
27
resources/test/fixtures/A001.py
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
import some as sum
|
||||
from some import other as int
|
||||
|
||||
print = 1
|
||||
copyright: 'annotation' = 2
|
||||
(complex := 3)
|
||||
float = object = 4
|
||||
min, max = 5, 6
|
||||
|
||||
def bytes():
|
||||
pass
|
||||
|
||||
class slice:
|
||||
pass
|
||||
|
||||
try:
|
||||
...
|
||||
except ImportError as ValueError:
|
||||
...
|
||||
|
||||
for memoryview, *bytearray in []:
|
||||
pass
|
||||
|
||||
with open('file') as str, open('file2') as (all, any):
|
||||
pass
|
||||
|
||||
[0 for sum in ()]
|
||||
9
resources/test/fixtures/A002.py
vendored
Normal file
9
resources/test/fixtures/A002.py
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
def func1(str, /, type, *complex, Exception, **getattr):
|
||||
pass
|
||||
|
||||
|
||||
async def func2(bytes):
|
||||
pass
|
||||
|
||||
|
||||
map([], lambda float: ...)
|
||||
8
resources/test/fixtures/A003.py
vendored
Normal file
8
resources/test/fixtures/A003.py
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
class MyClass:
|
||||
ImportError = 4
|
||||
|
||||
def __init__(self):
|
||||
self.float = 5 # is fine
|
||||
|
||||
def str(self):
|
||||
pass
|
||||
22
resources/test/fixtures/SPR001.py
vendored
Normal file
22
resources/test/fixtures/SPR001.py
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
class Parent:
|
||||
def method(self):
|
||||
pass
|
||||
|
||||
def wrong(self):
|
||||
pass
|
||||
|
||||
|
||||
class Child(Parent):
|
||||
def method(self):
|
||||
parent = super() # ok
|
||||
super().method() # ok
|
||||
Parent.method(self) # ok
|
||||
Parent.super(1, 2) # ok
|
||||
|
||||
def wrong(self):
|
||||
parent = super(Child, self) # wrong
|
||||
super(Child, self).method # wrong
|
||||
super(
|
||||
Child,
|
||||
self,
|
||||
).method() # wrong
|
||||
@@ -11,6 +11,7 @@ use crate::ast::operations::SourceCodeLocator;
|
||||
use crate::ast::types::{Binding, BindingKind, CheckLocator, FunctionScope, Scope, ScopeKind};
|
||||
use crate::autofix::{fixer, fixes};
|
||||
use crate::checks::{Check, CheckKind, Fix, RejectedCmpop};
|
||||
use crate::python::builtins::BUILTINS;
|
||||
|
||||
/// Check IfTuple compliance.
|
||||
pub fn check_if_tuple(test: &Expr, location: Location) -> Option<Check> {
|
||||
@@ -652,3 +653,45 @@ pub fn check_continue_outside_loop(
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
// flake8-builtins
|
||||
pub enum ShadowingType {
|
||||
Variable,
|
||||
Argument,
|
||||
Attribute,
|
||||
}
|
||||
|
||||
/// Check builtin name shadowing
|
||||
pub fn check_builtin_shadowing(
|
||||
name: &str,
|
||||
location: Location,
|
||||
node_type: ShadowingType,
|
||||
) -> Option<Check> {
|
||||
if BUILTINS.contains(&name) {
|
||||
Some(Check::new(
|
||||
match node_type {
|
||||
ShadowingType::Variable => CheckKind::BuiltinVariableShadowing(name.to_string()),
|
||||
ShadowingType::Argument => CheckKind::BuiltinArgumentShadowing(name.to_string()),
|
||||
ShadowingType::Attribute => CheckKind::BuiltinAttributeShadowing(name.to_string()),
|
||||
},
|
||||
location,
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
// flake8-super
|
||||
/// Check that `super()` has no args
|
||||
pub fn check_super_args(expr: &Expr, args: &Vec<Expr>) -> Option<Check> {
|
||||
if let ExprKind::Name { id, .. } = &expr.node {
|
||||
if id == "super" && !args.is_empty() {
|
||||
return Some(Check::new(
|
||||
CheckKind::SuperCallWithParameters,
|
||||
expr.location,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
@@ -246,6 +246,9 @@ where
|
||||
self.checks.push(check);
|
||||
}
|
||||
}
|
||||
|
||||
self.check_builtin_shadowing(name, stmt.location, true);
|
||||
|
||||
for expr in decorator_list {
|
||||
self.visit_expr(expr);
|
||||
}
|
||||
@@ -342,6 +345,8 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
self.check_builtin_shadowing(name, self.locate_check(stmt.location), false);
|
||||
|
||||
for expr in bases {
|
||||
self.visit_expr(expr)
|
||||
}
|
||||
@@ -382,6 +387,10 @@ where
|
||||
},
|
||||
)
|
||||
} else {
|
||||
if let Some(asname) = &alias.node.asname {
|
||||
self.check_builtin_shadowing(asname, stmt.location, false);
|
||||
}
|
||||
|
||||
self.add_binding(
|
||||
alias
|
||||
.node
|
||||
@@ -504,6 +513,10 @@ where
|
||||
.expect("No current scope found."))];
|
||||
scope.import_starred = true;
|
||||
} else {
|
||||
if let Some(asname) = &alias.node.asname {
|
||||
self.check_builtin_shadowing(asname, stmt.location, false);
|
||||
}
|
||||
|
||||
let binding = Binding {
|
||||
kind: BindingKind::Importation(match module {
|
||||
None => name.clone(),
|
||||
@@ -667,19 +680,29 @@ where
|
||||
self.checks.push(check);
|
||||
}
|
||||
}
|
||||
|
||||
self.check_builtin_shadowing(id, expr.location, true);
|
||||
|
||||
let parent =
|
||||
self.parents[*(self.parent_stack.last().expect("No parent found."))];
|
||||
self.handle_node_store(expr, parent);
|
||||
}
|
||||
ExprContext::Del => self.handle_node_delete(expr),
|
||||
},
|
||||
ExprKind::Call { func, .. } => {
|
||||
ExprKind::Call { func, args, .. } => {
|
||||
if self.settings.select.contains(&CheckCode::R002) {
|
||||
if let Some(check) = checks::check_assert_equals(func, self.autofix) {
|
||||
self.checks.push(check)
|
||||
}
|
||||
}
|
||||
|
||||
// flake8-super
|
||||
if self.settings.select.contains(&CheckCode::SPR001) {
|
||||
if let Some(check) = checks::check_super_args(func, args) {
|
||||
self.checks.push(check)
|
||||
}
|
||||
}
|
||||
|
||||
if let ExprKind::Name { id, ctx } = &func.node {
|
||||
if id == "locals" && matches!(ctx, ExprContext::Load) {
|
||||
let scope = &mut self.scopes[*(self
|
||||
@@ -977,6 +1000,9 @@ where
|
||||
self.checks.push(check);
|
||||
}
|
||||
}
|
||||
|
||||
self.check_builtin_shadowing(name, excepthandler.location, false);
|
||||
|
||||
let scope = &self.scopes
|
||||
[*(self.scope_stack.last().expect("No current scope found."))];
|
||||
if scope.values.contains_key(name) {
|
||||
@@ -1080,6 +1106,8 @@ where
|
||||
self.checks.push(check);
|
||||
}
|
||||
}
|
||||
|
||||
self.check_builtin_arg_shadowing(&arg.node.arg, arg.location);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1505,6 +1533,44 @@ impl<'a> Checker<'a> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn check_builtin_shadowing(&mut self, name: &str, location: Location, is_attribute: bool) {
|
||||
let scope = &self.scopes[*(self.scope_stack.last().expect("No current scope found."))];
|
||||
|
||||
// flake8-builtins
|
||||
if is_attribute
|
||||
&& matches!(scope.kind, ScopeKind::Class)
|
||||
&& self.settings.select.contains(&CheckCode::A003)
|
||||
{
|
||||
if let Some(check) = checks::check_builtin_shadowing(
|
||||
name,
|
||||
self.locate_check(location),
|
||||
checks::ShadowingType::Attribute,
|
||||
) {
|
||||
self.checks.push(check);
|
||||
}
|
||||
} else if self.settings.select.contains(&CheckCode::A001) {
|
||||
if let Some(check) = checks::check_builtin_shadowing(
|
||||
name,
|
||||
self.locate_check(location),
|
||||
checks::ShadowingType::Variable,
|
||||
) {
|
||||
self.checks.push(check);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn check_builtin_arg_shadowing(&mut self, name: &str, location: Location) {
|
||||
if self.settings.select.contains(&CheckCode::A002) {
|
||||
if let Some(check) = checks::check_builtin_shadowing(
|
||||
name,
|
||||
self.locate_check(location),
|
||||
checks::ShadowingType::Argument,
|
||||
) {
|
||||
self.checks.push(check);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn check_ast(
|
||||
|
||||
@@ -4,7 +4,8 @@ use anyhow::Result;
|
||||
use rustpython_parser::ast::Location;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub const DEFAULT_CHECK_CODES: [CheckCode; 42] = [
|
||||
pub const DEFAULT_CHECK_CODES: [CheckCode; 46] = [
|
||||
// pycodestyle
|
||||
CheckCode::E402,
|
||||
CheckCode::E501,
|
||||
CheckCode::E711,
|
||||
@@ -19,6 +20,7 @@ pub const DEFAULT_CHECK_CODES: [CheckCode; 42] = [
|
||||
CheckCode::E743,
|
||||
CheckCode::E902,
|
||||
CheckCode::E999,
|
||||
// pyflakes
|
||||
CheckCode::F401,
|
||||
CheckCode::F402,
|
||||
CheckCode::F403,
|
||||
@@ -47,9 +49,16 @@ pub const DEFAULT_CHECK_CODES: [CheckCode; 42] = [
|
||||
CheckCode::F831,
|
||||
CheckCode::F841,
|
||||
CheckCode::F901,
|
||||
// flake8-builtins
|
||||
CheckCode::A001,
|
||||
CheckCode::A002,
|
||||
CheckCode::A003,
|
||||
// flake8-super
|
||||
CheckCode::SPR001,
|
||||
];
|
||||
|
||||
pub const ALL_CHECK_CODES: [CheckCode; 45] = [
|
||||
pub const ALL_CHECK_CODES: [CheckCode; 49] = [
|
||||
// pycodestyle
|
||||
CheckCode::E402,
|
||||
CheckCode::E501,
|
||||
CheckCode::E711,
|
||||
@@ -64,6 +73,7 @@ pub const ALL_CHECK_CODES: [CheckCode; 45] = [
|
||||
CheckCode::E743,
|
||||
CheckCode::E902,
|
||||
CheckCode::E999,
|
||||
// pyflakes
|
||||
CheckCode::F401,
|
||||
CheckCode::F402,
|
||||
CheckCode::F403,
|
||||
@@ -92,13 +102,22 @@ pub const ALL_CHECK_CODES: [CheckCode; 45] = [
|
||||
CheckCode::F831,
|
||||
CheckCode::F841,
|
||||
CheckCode::F901,
|
||||
// flake8-builtins
|
||||
CheckCode::A001,
|
||||
CheckCode::A002,
|
||||
CheckCode::A003,
|
||||
// flake8-super
|
||||
CheckCode::SPR001,
|
||||
// Meta
|
||||
CheckCode::M001,
|
||||
// Refactor
|
||||
CheckCode::R001,
|
||||
CheckCode::R002,
|
||||
];
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, Hash, PartialOrd, Ord)]
|
||||
pub enum CheckCode {
|
||||
// pycodestyle
|
||||
E402,
|
||||
E501,
|
||||
E711,
|
||||
@@ -113,6 +132,7 @@ pub enum CheckCode {
|
||||
E743,
|
||||
E902,
|
||||
E999,
|
||||
// pyflakes
|
||||
F401,
|
||||
F402,
|
||||
F403,
|
||||
@@ -141,8 +161,16 @@ pub enum CheckCode {
|
||||
F831,
|
||||
F841,
|
||||
F901,
|
||||
// flake8-builtins
|
||||
A001,
|
||||
A002,
|
||||
A003,
|
||||
// flake8-super
|
||||
SPR001,
|
||||
// Refactor
|
||||
R001,
|
||||
R002,
|
||||
// Meta
|
||||
M001,
|
||||
}
|
||||
|
||||
@@ -151,6 +179,7 @@ impl FromStr for CheckCode {
|
||||
|
||||
fn from_str(s: &str) -> Result<Self> {
|
||||
match s {
|
||||
// pycodestyle
|
||||
"E402" => Ok(CheckCode::E402),
|
||||
"E501" => Ok(CheckCode::E501),
|
||||
"E711" => Ok(CheckCode::E711),
|
||||
@@ -165,6 +194,7 @@ impl FromStr for CheckCode {
|
||||
"E743" => Ok(CheckCode::E743),
|
||||
"E902" => Ok(CheckCode::E902),
|
||||
"E999" => Ok(CheckCode::E999),
|
||||
// pyflakes
|
||||
"F401" => Ok(CheckCode::F401),
|
||||
"F402" => Ok(CheckCode::F402),
|
||||
"F403" => Ok(CheckCode::F403),
|
||||
@@ -193,8 +223,16 @@ impl FromStr for CheckCode {
|
||||
"F831" => Ok(CheckCode::F831),
|
||||
"F841" => Ok(CheckCode::F841),
|
||||
"F901" => Ok(CheckCode::F901),
|
||||
// flake8-builtins
|
||||
"A001" => Ok(CheckCode::A001),
|
||||
"A002" => Ok(CheckCode::A002),
|
||||
"A003" => Ok(CheckCode::A003),
|
||||
// flake8-super
|
||||
"SPR001" => Ok(CheckCode::SPR001),
|
||||
// Refactor
|
||||
"R001" => Ok(CheckCode::R001),
|
||||
"R002" => Ok(CheckCode::R002),
|
||||
// Meta
|
||||
"M001" => Ok(CheckCode::M001),
|
||||
_ => Err(anyhow::anyhow!("Unknown check code: {s}")),
|
||||
}
|
||||
@@ -204,6 +242,7 @@ impl FromStr for CheckCode {
|
||||
impl CheckCode {
|
||||
pub fn as_str(&self) -> &str {
|
||||
match self {
|
||||
// pycodestyle
|
||||
CheckCode::E402 => "E402",
|
||||
CheckCode::E501 => "E501",
|
||||
CheckCode::E711 => "E711",
|
||||
@@ -218,6 +257,7 @@ impl CheckCode {
|
||||
CheckCode::E743 => "E743",
|
||||
CheckCode::E902 => "E902",
|
||||
CheckCode::E999 => "E999",
|
||||
// pyflakes
|
||||
CheckCode::F401 => "F401",
|
||||
CheckCode::F402 => "F402",
|
||||
CheckCode::F403 => "F403",
|
||||
@@ -246,8 +286,16 @@ impl CheckCode {
|
||||
CheckCode::F831 => "F831",
|
||||
CheckCode::F841 => "F841",
|
||||
CheckCode::F901 => "F901",
|
||||
// flake8-builtins
|
||||
CheckCode::A001 => "A001",
|
||||
CheckCode::A002 => "A002",
|
||||
CheckCode::A003 => "A003",
|
||||
// flake8-super
|
||||
CheckCode::SPR001 => "SPR001",
|
||||
// Refactor
|
||||
CheckCode::R001 => "R001",
|
||||
CheckCode::R002 => "R002",
|
||||
// Meta
|
||||
CheckCode::M001 => "M001",
|
||||
}
|
||||
}
|
||||
@@ -264,6 +312,7 @@ impl CheckCode {
|
||||
/// A placeholder representation of the CheckKind for the check.
|
||||
pub fn kind(&self) -> CheckKind {
|
||||
match self {
|
||||
// pycodestyle
|
||||
CheckCode::E402 => CheckKind::ModuleImportNotAtTopOfFile,
|
||||
CheckCode::E501 => CheckKind::LineTooLong(89, 88),
|
||||
CheckCode::E711 => CheckKind::NoneComparison(RejectedCmpop::Eq),
|
||||
@@ -278,6 +327,7 @@ impl CheckCode {
|
||||
CheckCode::E743 => CheckKind::AmbiguousFunctionName("...".to_string()),
|
||||
CheckCode::E902 => CheckKind::IOError("...".to_string()),
|
||||
CheckCode::E999 => CheckKind::SyntaxError("...".to_string()),
|
||||
// pyflakes
|
||||
CheckCode::F401 => CheckKind::UnusedImport("...".to_string()),
|
||||
CheckCode::F402 => CheckKind::ImportShadowedByLoopVar("...".to_string(), 1),
|
||||
CheckCode::F403 => CheckKind::ImportStarUsed("...".to_string()),
|
||||
@@ -306,9 +356,17 @@ impl CheckCode {
|
||||
CheckCode::F831 => CheckKind::DuplicateArgumentName,
|
||||
CheckCode::F841 => CheckKind::UnusedVariable("...".to_string()),
|
||||
CheckCode::F901 => CheckKind::RaiseNotImplemented,
|
||||
CheckCode::M001 => CheckKind::UnusedNOQA(None),
|
||||
// flake8-builtins
|
||||
CheckCode::A001 => CheckKind::BuiltinVariableShadowing("...".to_string()),
|
||||
CheckCode::A002 => CheckKind::BuiltinArgumentShadowing("...".to_string()),
|
||||
CheckCode::A003 => CheckKind::BuiltinAttributeShadowing("...".to_string()),
|
||||
// flake8-super
|
||||
CheckCode::SPR001 => CheckKind::SuperCallWithParameters,
|
||||
// Refactor
|
||||
CheckCode::R001 => CheckKind::UselessObjectInheritance("...".to_string()),
|
||||
CheckCode::R002 => CheckKind::NoAssertEquals,
|
||||
// Meta
|
||||
CheckCode::M001 => CheckKind::UnusedNOQA(None),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -373,6 +431,12 @@ pub enum CheckKind {
|
||||
UnusedVariable(String),
|
||||
UselessObjectInheritance(String),
|
||||
YieldOutsideFunction,
|
||||
// flake8-builtin
|
||||
BuiltinVariableShadowing(String),
|
||||
BuiltinArgumentShadowing(String),
|
||||
BuiltinAttributeShadowing(String),
|
||||
// flake8-super
|
||||
SuperCallWithParameters,
|
||||
}
|
||||
|
||||
impl CheckKind {
|
||||
@@ -426,6 +490,12 @@ impl CheckKind {
|
||||
CheckKind::UselessObjectInheritance(_) => "UselessObjectInheritance",
|
||||
CheckKind::YieldOutsideFunction => "YieldOutsideFunction",
|
||||
CheckKind::UnusedNOQA(_) => "UnusedNOQA",
|
||||
// flake8-builtins
|
||||
CheckKind::BuiltinVariableShadowing(_) => "BuiltinVariableShadowing",
|
||||
CheckKind::BuiltinArgumentShadowing(_) => "BuiltinArgumentShadowing",
|
||||
CheckKind::BuiltinAttributeShadowing(_) => "BuiltinAttributeShadowing",
|
||||
// flake8-super
|
||||
CheckKind::SuperCallWithParameters => "SuperCallWithParameters",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -477,6 +547,12 @@ impl CheckKind {
|
||||
CheckKind::UnusedVariable(_) => &CheckCode::F841,
|
||||
CheckKind::UselessObjectInheritance(_) => &CheckCode::R001,
|
||||
CheckKind::YieldOutsideFunction => &CheckCode::F704,
|
||||
// flake8-builtins
|
||||
CheckKind::BuiltinVariableShadowing(_) => &CheckCode::A001,
|
||||
CheckKind::BuiltinArgumentShadowing(_) => &CheckCode::A002,
|
||||
CheckKind::BuiltinAttributeShadowing(_) => &CheckCode::A003,
|
||||
// flake8-super
|
||||
CheckKind::SuperCallWithParameters => &CheckCode::SPR001,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -611,6 +687,20 @@ impl CheckKind {
|
||||
None => "Unused `noqa` directive".to_string(),
|
||||
Some(code) => format!("Unused `noqa` directive for: {code}"),
|
||||
},
|
||||
// flake8-builtins
|
||||
CheckKind::BuiltinVariableShadowing(name) => {
|
||||
format!("Variable `{name}` is shadowing a python builtin")
|
||||
}
|
||||
CheckKind::BuiltinArgumentShadowing(name) => {
|
||||
format!("Argument `{name}` is shadowing a python builtin")
|
||||
}
|
||||
CheckKind::BuiltinAttributeShadowing(name) => {
|
||||
format!("class attribute `{name}` is shadowing a python builtin")
|
||||
}
|
||||
// flake8-super
|
||||
CheckKind::SuperCallWithParameters => {
|
||||
"Use `super()` instead of `super(__class__, self)`".to_string()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -76,7 +76,6 @@ pub fn iter_python_files<'a>(
|
||||
.all(|pattern| matches!(pattern, FilePattern::Simple(_)));
|
||||
|
||||
WalkDir::new(normalize_path(path))
|
||||
.follow_links(true)
|
||||
.into_iter()
|
||||
.filter_entry(move |entry| {
|
||||
if !has_exclude && !has_extend_exclude {
|
||||
@@ -112,7 +111,9 @@ pub fn iter_python_files<'a>(
|
||||
})
|
||||
.filter(|entry| {
|
||||
entry.as_ref().map_or(true, |entry| {
|
||||
(entry.depth() == 0 && !entry.file_type().is_dir()) || is_included(entry.path())
|
||||
(entry.depth() == 0 || is_included(entry.path()))
|
||||
&& !entry.file_type().is_dir()
|
||||
&& !(entry.file_type().is_symlink() && entry.path().is_dir())
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
extern crate core;
|
||||
|
||||
mod ast;
|
||||
mod autofix;
|
||||
pub mod cache;
|
||||
|
||||
@@ -726,4 +726,52 @@ mod tests {
|
||||
insta::assert_yaml_snapshot!(checks);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a001() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/A001.py"),
|
||||
&settings::Settings::for_rule(CheckCode::A001),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!(checks);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a002() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/A002.py"),
|
||||
&settings::Settings::for_rule(CheckCode::A002),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!(checks);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a003() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/A003.py"),
|
||||
&settings::Settings::for_rule(CheckCode::A003),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!(checks);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn spr001() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/SPR001.py"),
|
||||
&settings::Settings::for_rule(CheckCode::SPR001),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!(checks);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
extern crate core;
|
||||
|
||||
use std::io;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::ExitCode;
|
||||
|
||||
@@ -39,7 +39,6 @@ impl fmt::Display for Message {
|
||||
f,
|
||||
"{}{}{}{}{}{} {} {}",
|
||||
relativize_path(Path::new(&self.filename)).white().bold(),
|
||||
// self.filename.white(),
|
||||
":".cyan(),
|
||||
self.location.row(),
|
||||
":".cyan(),
|
||||
|
||||
112
src/snapshots/ruff__linter__tests__a001.snap
Normal file
112
src/snapshots/ruff__linter__tests__a001.snap
Normal file
@@ -0,0 +1,112 @@
|
||||
---
|
||||
source: src/linter.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind:
|
||||
BuiltinVariableShadowing: sum
|
||||
location:
|
||||
row: 1
|
||||
column: 1
|
||||
fix: ~
|
||||
- kind:
|
||||
BuiltinVariableShadowing: int
|
||||
location:
|
||||
row: 2
|
||||
column: 1
|
||||
fix: ~
|
||||
- kind:
|
||||
BuiltinVariableShadowing: print
|
||||
location:
|
||||
row: 4
|
||||
column: 1
|
||||
fix: ~
|
||||
- kind:
|
||||
BuiltinVariableShadowing: copyright
|
||||
location:
|
||||
row: 5
|
||||
column: 1
|
||||
fix: ~
|
||||
- kind:
|
||||
BuiltinVariableShadowing: complex
|
||||
location:
|
||||
row: 6
|
||||
column: 2
|
||||
fix: ~
|
||||
- kind:
|
||||
BuiltinVariableShadowing: float
|
||||
location:
|
||||
row: 7
|
||||
column: 1
|
||||
fix: ~
|
||||
- kind:
|
||||
BuiltinVariableShadowing: object
|
||||
location:
|
||||
row: 7
|
||||
column: 9
|
||||
fix: ~
|
||||
- kind:
|
||||
BuiltinVariableShadowing: min
|
||||
location:
|
||||
row: 8
|
||||
column: 1
|
||||
fix: ~
|
||||
- kind:
|
||||
BuiltinVariableShadowing: max
|
||||
location:
|
||||
row: 8
|
||||
column: 6
|
||||
fix: ~
|
||||
- kind:
|
||||
BuiltinVariableShadowing: bytes
|
||||
location:
|
||||
row: 10
|
||||
column: 1
|
||||
fix: ~
|
||||
- kind:
|
||||
BuiltinVariableShadowing: slice
|
||||
location:
|
||||
row: 13
|
||||
column: 1
|
||||
fix: ~
|
||||
- kind:
|
||||
BuiltinVariableShadowing: ValueError
|
||||
location:
|
||||
row: 18
|
||||
column: 1
|
||||
fix: ~
|
||||
- kind:
|
||||
BuiltinVariableShadowing: memoryview
|
||||
location:
|
||||
row: 21
|
||||
column: 5
|
||||
fix: ~
|
||||
- kind:
|
||||
BuiltinVariableShadowing: bytearray
|
||||
location:
|
||||
row: 21
|
||||
column: 18
|
||||
fix: ~
|
||||
- kind:
|
||||
BuiltinVariableShadowing: str
|
||||
location:
|
||||
row: 24
|
||||
column: 22
|
||||
fix: ~
|
||||
- kind:
|
||||
BuiltinVariableShadowing: all
|
||||
location:
|
||||
row: 24
|
||||
column: 45
|
||||
fix: ~
|
||||
- kind:
|
||||
BuiltinVariableShadowing: any
|
||||
location:
|
||||
row: 24
|
||||
column: 50
|
||||
fix: ~
|
||||
- kind:
|
||||
BuiltinVariableShadowing: sum
|
||||
location:
|
||||
row: 27
|
||||
column: 8
|
||||
fix: ~
|
||||
46
src/snapshots/ruff__linter__tests__a002.snap
Normal file
46
src/snapshots/ruff__linter__tests__a002.snap
Normal file
@@ -0,0 +1,46 @@
|
||||
---
|
||||
source: src/linter.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind:
|
||||
BuiltinArgumentShadowing: str
|
||||
location:
|
||||
row: 1
|
||||
column: 11
|
||||
fix: ~
|
||||
- kind:
|
||||
BuiltinArgumentShadowing: type
|
||||
location:
|
||||
row: 1
|
||||
column: 19
|
||||
fix: ~
|
||||
- kind:
|
||||
BuiltinArgumentShadowing: complex
|
||||
location:
|
||||
row: 1
|
||||
column: 26
|
||||
fix: ~
|
||||
- kind:
|
||||
BuiltinArgumentShadowing: Exception
|
||||
location:
|
||||
row: 1
|
||||
column: 35
|
||||
fix: ~
|
||||
- kind:
|
||||
BuiltinArgumentShadowing: getattr
|
||||
location:
|
||||
row: 1
|
||||
column: 48
|
||||
fix: ~
|
||||
- kind:
|
||||
BuiltinArgumentShadowing: bytes
|
||||
location:
|
||||
row: 5
|
||||
column: 17
|
||||
fix: ~
|
||||
- kind:
|
||||
BuiltinArgumentShadowing: float
|
||||
location:
|
||||
row: 9
|
||||
column: 16
|
||||
fix: ~
|
||||
18
src/snapshots/ruff__linter__tests__a003.snap
Normal file
18
src/snapshots/ruff__linter__tests__a003.snap
Normal file
@@ -0,0 +1,18 @@
|
||||
---
|
||||
source: src/linter.rs
|
||||
assertion_line: 762
|
||||
expression: checks
|
||||
---
|
||||
- kind:
|
||||
BuiltinAttributeShadowing: ImportError
|
||||
location:
|
||||
row: 2
|
||||
column: 5
|
||||
fix: ~
|
||||
- kind:
|
||||
BuiltinAttributeShadowing: str
|
||||
location:
|
||||
row: 7
|
||||
column: 5
|
||||
fix: ~
|
||||
|
||||
19
src/snapshots/ruff__linter__tests__spr001.snap
Normal file
19
src/snapshots/ruff__linter__tests__spr001.snap
Normal file
@@ -0,0 +1,19 @@
|
||||
---
|
||||
source: src/linter.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind: SuperCallWithParameters
|
||||
location:
|
||||
row: 17
|
||||
column: 18
|
||||
fix: ~
|
||||
- kind: SuperCallWithParameters
|
||||
location:
|
||||
row: 18
|
||||
column: 9
|
||||
fix: ~
|
||||
- kind: SuperCallWithParameters
|
||||
location:
|
||||
row: 19
|
||||
column: 9
|
||||
fix: ~
|
||||
Reference in New Issue
Block a user