Compare commits

...

14 Commits

Author SHA1 Message Date
Charlie Marsh
1e36c109c6 Bump version to 0.0.49 2022-09-30 09:15:32 -04:00
Nikita Sobolev
3960016d55 Create .editorconfig (#290) 2022-09-30 09:15:08 -04:00
Charlie Marsh
75d669fa86 Update check ordering 2022-09-30 09:14:41 -04:00
Nikita Sobolev
20989e12ba Implement flake8-super check (#291) 2022-09-30 09:12:09 -04:00
Charlie Marsh
5a1b6c32eb Add CONTRIBUTING.md (#288) 2022-09-30 07:51:30 -04:00
Charlie Marsh
46bdcb9080 Add instructions on GitHub Actions integration 2022-09-29 19:05:02 -04:00
Charlie Marsh
d16a7252af Add instructions on PyCharm integration 2022-09-29 18:52:45 -04:00
Charlie Marsh
ca6551eb37 Remove misc. unnecessary statements 2022-09-29 18:45:10 -04:00
Charlie Marsh
43a4f5749e Create CODE_OF_CONDUCT.md (#287) 2022-09-29 16:59:17 -04:00
Charlie Marsh
6fef4db433 Bump version to 0.0.48 2022-09-29 16:40:01 -04:00
Charlie Marsh
7470d6832f Add pattern matching limitation to README.md 2022-09-29 16:39:25 -04:00
Nikita Sobolev
63ba0bfeef Adds flake8-builtins (#284) 2022-09-29 16:37:43 -04:00
Anders Kaseorg
91666fcaf6 Don’t follow directory symlinks found while walking (#280) 2022-09-29 15:10:25 -04:00
Heyward Fann
643e27221d chore: fix eslint fix link (#281) 2022-09-29 07:15:07 -04:00
22 changed files with 815 additions and 16 deletions

14
.editorconfig Normal file
View 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
View 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
View 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
View File

@@ -1799,7 +1799,7 @@ dependencies = [
[[package]]
name = "ruff"
version = "0.0.47"
version = "0.0.49"
dependencies = [
"anyhow",
"bincode",

View File

@@ -1,6 +1,6 @@
[package]
name = "ruff"
version = "0.0.47"
version = "0.0.49"
edition = "2021"
[lib]

View File

@@ -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:
![Install ruff as an External Tool](https://user-images.githubusercontent.com/1309177/193155720-336e43f0-1a8d-46b4-bc12-e60f9ae01f7e.png)
ruff should then appear as a runnable action:
![ruff as a runnable action](https://user-images.githubusercontent.com/1309177/193156026-732b0aaf-3dd9-4549-9b4d-2de6d2168a33.png)
### 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
View 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
View 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
View 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
View 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

View File

@@ -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
}

View File

@@ -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(

View File

@@ -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()
}
}
}

View File

@@ -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())
})
})
}

View File

@@ -1,5 +1,3 @@
extern crate core;
mod ast;
mod autofix;
pub mod cache;

View File

@@ -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(())
}
}

View File

@@ -1,5 +1,3 @@
extern crate core;
use std::io;
use std::path::{Path, PathBuf};
use std::process::ExitCode;

View File

@@ -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(),

View 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: ~

View 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: ~

View 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: ~

View 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: ~