mirror of https://github.com/astral-sh/ruff
Add flake8-pyi with one rule (#2682)
Add basic scaffold for [flake8-pyi](https://github.com/PyCQA/flake8-pyi) and the first rule, Y001 rel: https://github.com/charliermarsh/ruff/issues/848
This commit is contained in:
parent
233be0e074
commit
67e58a024a
25
LICENSE
25
LICENSE
|
|
@ -245,6 +245,31 @@ are:
|
||||||
SOFTWARE.
|
SOFTWARE.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
- flake8-pyi, licensed as follows:
|
||||||
|
"""
|
||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2016 Łukasz Langa
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
|
"""
|
||||||
|
|
||||||
- flake8-print, licensed as follows:
|
- flake8-print, licensed as follows:
|
||||||
"""
|
"""
|
||||||
MIT License
|
MIT License
|
||||||
|
|
|
||||||
10
README.md
10
README.md
|
|
@ -145,6 +145,7 @@ This README is also available as [documentation](https://beta.ruff.rs/docs/).
|
||||||
1. [flake8-no-pep420 (INP)](#flake8-no-pep420-inp)
|
1. [flake8-no-pep420 (INP)](#flake8-no-pep420-inp)
|
||||||
1. [flake8-pie (PIE)](#flake8-pie-pie)
|
1. [flake8-pie (PIE)](#flake8-pie-pie)
|
||||||
1. [flake8-print (T20)](#flake8-print-t20)
|
1. [flake8-print (T20)](#flake8-print-t20)
|
||||||
|
1. [flake8-pyi (PYI)](#flake8-pyi-pyi)
|
||||||
1. [flake8-pytest-style (PT)](#flake8-pytest-style-pt)
|
1. [flake8-pytest-style (PT)](#flake8-pytest-style-pt)
|
||||||
1. [flake8-quotes (Q)](#flake8-quotes-q)
|
1. [flake8-quotes (Q)](#flake8-quotes-q)
|
||||||
1. [flake8-return (RET)](#flake8-return-ret)
|
1. [flake8-return (RET)](#flake8-return-ret)
|
||||||
|
|
@ -1142,6 +1143,14 @@ For more, see [flake8-print](https://pypi.org/project/flake8-print/) on PyPI.
|
||||||
| T201 | print-found | `print` found | |
|
| T201 | print-found | `print` found | |
|
||||||
| T203 | p-print-found | `pprint` found | |
|
| T203 | p-print-found | `pprint` found | |
|
||||||
|
|
||||||
|
### flake8-pyi (PYI)
|
||||||
|
|
||||||
|
For more, see [flake8-pyi](https://pypi.org/project/flake8-pyi/) on PyPI.
|
||||||
|
|
||||||
|
| Code | Name | Message | Fix |
|
||||||
|
| ---- | ---- | ------- | --- |
|
||||||
|
| [PYI001](https://github.com/charliermarsh/ruff/blob/main/docs/rules/prefix-type-params.md) | [prefix-type-params](https://github.com/charliermarsh/ruff/blob/main/docs/rules/prefix-type-params.md) | Name of private `{kind}` must start with _ | |
|
||||||
|
|
||||||
### flake8-pytest-style (PT)
|
### flake8-pytest-style (PT)
|
||||||
|
|
||||||
For more, see [flake8-pytest-style](https://pypi.org/project/flake8-pytest-style/) on PyPI.
|
For more, see [flake8-pytest-style](https://pypi.org/project/flake8-pytest-style/) on PyPI.
|
||||||
|
|
@ -1701,6 +1710,7 @@ natively, including:
|
||||||
* [flake8-no-pep420](https://pypi.org/project/flake8-no-pep420)
|
* [flake8-no-pep420](https://pypi.org/project/flake8-no-pep420)
|
||||||
* [flake8-pie](https://pypi.org/project/flake8-pie/)
|
* [flake8-pie](https://pypi.org/project/flake8-pie/)
|
||||||
* [flake8-print](https://pypi.org/project/flake8-print/)
|
* [flake8-print](https://pypi.org/project/flake8-print/)
|
||||||
|
* [flake8-pyi](https://pypi.org/project/flake8-pyi/)
|
||||||
* [flake8-pytest-style](https://pypi.org/project/flake8-pytest-style/)
|
* [flake8-pytest-style](https://pypi.org/project/flake8-pytest-style/)
|
||||||
* [flake8-quotes](https://pypi.org/project/flake8-quotes/)
|
* [flake8-quotes](https://pypi.org/project/flake8-quotes/)
|
||||||
* [flake8-raise](https://pypi.org/project/flake8-raise/)
|
* [flake8-raise](https://pypi.org/project/flake8-raise/)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
from typing import ParamSpec, TypeVar, TypeVarTuple
|
||||||
|
|
||||||
|
T = TypeVar("T") # OK
|
||||||
|
|
||||||
|
TTuple = TypeVarTuple("TTuple") # OK
|
||||||
|
|
||||||
|
P = ParamSpec("P") # OK
|
||||||
|
|
||||||
|
_T = TypeVar("_T") # OK
|
||||||
|
|
||||||
|
_TTuple = TypeVarTuple("_TTuple") # OK
|
||||||
|
|
||||||
|
_P = ParamSpec("_P") # OK
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
from typing import ParamSpec, TypeVar, TypeVarTuple
|
||||||
|
|
||||||
|
T = TypeVar("T") # Error: TypeVars in stubs must start with _
|
||||||
|
|
||||||
|
TTuple = TypeVarTuple("TTuple") # Error: TypeVarTuples must also start with _
|
||||||
|
|
||||||
|
P = ParamSpec("P") # Error: ParamSpecs must start with _
|
||||||
|
|
||||||
|
_T = TypeVar("_T") # OK
|
||||||
|
|
||||||
|
_TTuple = TypeVarTuple("_TTuple") # OK
|
||||||
|
|
||||||
|
_P = ParamSpec("_P") # OK
|
||||||
|
|
@ -36,10 +36,10 @@ use crate::rules::{
|
||||||
flake8_2020, flake8_annotations, flake8_bandit, flake8_blind_except, flake8_boolean_trap,
|
flake8_2020, flake8_annotations, flake8_bandit, flake8_blind_except, flake8_boolean_trap,
|
||||||
flake8_bugbear, flake8_builtins, flake8_comprehensions, flake8_datetimez, flake8_debugger,
|
flake8_bugbear, flake8_builtins, flake8_comprehensions, flake8_datetimez, flake8_debugger,
|
||||||
flake8_errmsg, flake8_implicit_str_concat, flake8_import_conventions, flake8_logging_format,
|
flake8_errmsg, flake8_implicit_str_concat, flake8_import_conventions, flake8_logging_format,
|
||||||
flake8_pie, flake8_print, flake8_pytest_style, flake8_raise, flake8_return, flake8_self,
|
flake8_pie, flake8_print, flake8_pyi, flake8_pytest_style, flake8_raise, flake8_return,
|
||||||
flake8_simplify, flake8_tidy_imports, flake8_type_checking, flake8_unused_arguments,
|
flake8_self, flake8_simplify, flake8_tidy_imports, flake8_type_checking,
|
||||||
flake8_use_pathlib, mccabe, pandas_vet, pep8_naming, pycodestyle, pydocstyle, pyflakes,
|
flake8_unused_arguments, flake8_use_pathlib, mccabe, pandas_vet, pep8_naming, pycodestyle,
|
||||||
pygrep_hooks, pylint, pyupgrade, ruff, tryceratops,
|
pydocstyle, pyflakes, pygrep_hooks, pylint, pyupgrade, ruff, tryceratops,
|
||||||
};
|
};
|
||||||
use crate::settings::types::PythonVersion;
|
use crate::settings::types::PythonVersion;
|
||||||
use crate::settings::{flags, Settings};
|
use crate::settings::{flags, Settings};
|
||||||
|
|
@ -1713,6 +1713,12 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if self.settings.rules.enabled(&Rule::PrefixTypeParams) {
|
||||||
|
if self.path.extension().map_or(false, |ext| ext == "pyi") {
|
||||||
|
flake8_pyi::rules::prefix_type_params(self, value, targets);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if self.settings.rules.enabled(&Rule::UselessMetaclassType) {
|
if self.settings.rules.enabled(&Rule::UselessMetaclassType) {
|
||||||
pyupgrade::rules::useless_metaclass_type(self, stmt, value, targets);
|
pyupgrade::rules::useless_metaclass_type(self, stmt, value, targets);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -442,6 +442,8 @@ ruff_macros::define_rule_mapping!(
|
||||||
EM101 => rules::flake8_errmsg::rules::RawStringInException,
|
EM101 => rules::flake8_errmsg::rules::RawStringInException,
|
||||||
EM102 => rules::flake8_errmsg::rules::FStringInException,
|
EM102 => rules::flake8_errmsg::rules::FStringInException,
|
||||||
EM103 => rules::flake8_errmsg::rules::DotFormatInException,
|
EM103 => rules::flake8_errmsg::rules::DotFormatInException,
|
||||||
|
// flake8-pyi
|
||||||
|
PYI001 => rules::flake8_pyi::rules::PrefixTypeParams,
|
||||||
// flake8-pytest-style
|
// flake8-pytest-style
|
||||||
PT001 => rules::flake8_pytest_style::rules::IncorrectFixtureParenthesesStyle,
|
PT001 => rules::flake8_pytest_style::rules::IncorrectFixtureParenthesesStyle,
|
||||||
PT002 => rules::flake8_pytest_style::rules::FixturePositionalArgs,
|
PT002 => rules::flake8_pytest_style::rules::FixturePositionalArgs,
|
||||||
|
|
@ -632,6 +634,9 @@ pub enum Linter {
|
||||||
/// [flake8-print](https://pypi.org/project/flake8-print/)
|
/// [flake8-print](https://pypi.org/project/flake8-print/)
|
||||||
#[prefix = "T20"]
|
#[prefix = "T20"]
|
||||||
Flake8Print,
|
Flake8Print,
|
||||||
|
/// [flake8-pyi](https://pypi.org/project/flake8-pyi/)
|
||||||
|
#[prefix = "PYI"]
|
||||||
|
Flake8Pyi,
|
||||||
/// [flake8-pytest-style](https://pypi.org/project/flake8-pytest-style/)
|
/// [flake8-pytest-style](https://pypi.org/project/flake8-pytest-style/)
|
||||||
#[prefix = "PT"]
|
#[prefix = "PT"]
|
||||||
Flake8PytestStyle,
|
Flake8PytestStyle,
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
//! Rules from [flake8-pyi](https://pypi.org/project/flake8-pyi/).
|
||||||
|
pub(crate) mod rules;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
|
use test_case::test_case;
|
||||||
|
|
||||||
|
use crate::registry::Rule;
|
||||||
|
use crate::test::test_path;
|
||||||
|
use crate::{assert_yaml_snapshot, settings};
|
||||||
|
|
||||||
|
#[test_case(Rule::PrefixTypeParams, Path::new("PYI001.pyi"))]
|
||||||
|
#[test_case(Rule::PrefixTypeParams, Path::new("PYI001.py"))]
|
||||||
|
fn rules(rule_code: Rule, path: &Path) -> Result<()> {
|
||||||
|
let snapshot = format!("{}_{}", rule_code.code(), path.to_string_lossy());
|
||||||
|
let diagnostics = test_path(
|
||||||
|
Path::new("flake8_pyi").join(path).as_path(),
|
||||||
|
&settings::Settings::for_rule(rule_code),
|
||||||
|
)?;
|
||||||
|
assert_yaml_snapshot!(snapshot, diagnostics);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,92 @@
|
||||||
|
use ruff_macros::{define_violation, derive_message_formats};
|
||||||
|
use rustpython_parser::ast::{Expr, ExprKind};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
|
use crate::ast::types::Range;
|
||||||
|
use crate::checkers::ast::Checker;
|
||||||
|
use crate::registry::Diagnostic;
|
||||||
|
use crate::violation::Violation;
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
|
pub enum VarKind {
|
||||||
|
TypeVar,
|
||||||
|
ParamSpec,
|
||||||
|
TypeVarTuple,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for VarKind {
|
||||||
|
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
VarKind::TypeVar => fmt.write_str("TypeVar"),
|
||||||
|
VarKind::ParamSpec => fmt.write_str("ParamSpec"),
|
||||||
|
VarKind::TypeVarTuple => fmt.write_str("TypeVarTuple"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
define_violation!(
|
||||||
|
/// ### What it does
|
||||||
|
/// Checks that type `TypeVar`, `ParamSpec`, and `TypeVarTuple` definitions in
|
||||||
|
/// stubs are prefixed with `_`.
|
||||||
|
///
|
||||||
|
/// ### Why is this bad?
|
||||||
|
/// By prefixing type parameters with `_`, we can avoid accidentally exposing
|
||||||
|
/// names internal to the stub.
|
||||||
|
///
|
||||||
|
/// ### Example
|
||||||
|
/// ```python
|
||||||
|
/// from typing import TypeVar
|
||||||
|
///
|
||||||
|
/// T = TypeVar("T")
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// Use instead:
|
||||||
|
/// ```python
|
||||||
|
/// from typing import TypeVar
|
||||||
|
///
|
||||||
|
/// _T = TypeVar("_T")
|
||||||
|
/// ```
|
||||||
|
pub struct PrefixTypeParams {
|
||||||
|
pub kind: VarKind,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
impl Violation for PrefixTypeParams {
|
||||||
|
#[derive_message_formats]
|
||||||
|
fn message(&self) -> String {
|
||||||
|
let PrefixTypeParams { kind } = self;
|
||||||
|
format!("Name of private `{kind}` must start with _")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// PYI001
|
||||||
|
pub fn prefix_type_params(checker: &mut Checker, value: &Expr, targets: &[Expr]) {
|
||||||
|
if targets.len() != 1 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if let ExprKind::Name { id, .. } = &targets[0].node {
|
||||||
|
if id.starts_with('_') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if let ExprKind::Call { func, .. } = &value.node {
|
||||||
|
let Some(kind) = checker.resolve_call_path(func).and_then(|call_path| {
|
||||||
|
if checker.match_typing_call_path(&call_path, "ParamSpec") {
|
||||||
|
Some(VarKind::ParamSpec)
|
||||||
|
} else if checker.match_typing_call_path(&call_path, "TypeVar") {
|
||||||
|
Some(VarKind::TypeVar)
|
||||||
|
} else if checker.match_typing_call_path(&call_path, "TypeVarTuple") {
|
||||||
|
Some(VarKind::TypeVarTuple)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
checker.diagnostics.push(Diagnostic::new(
|
||||||
|
PrefixTypeParams { kind },
|
||||||
|
Range::from_located(value),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
---
|
||||||
|
source: crates/ruff/src/rules/flake8_pyi/mod.rs
|
||||||
|
expression: diagnostics
|
||||||
|
---
|
||||||
|
[]
|
||||||
|
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
---
|
||||||
|
source: crates/ruff/src/rules/flake8_pyi/mod.rs
|
||||||
|
expression: diagnostics
|
||||||
|
---
|
||||||
|
- kind:
|
||||||
|
PrefixTypeParams:
|
||||||
|
kind: TypeVar
|
||||||
|
location:
|
||||||
|
row: 3
|
||||||
|
column: 4
|
||||||
|
end_location:
|
||||||
|
row: 3
|
||||||
|
column: 16
|
||||||
|
fix: ~
|
||||||
|
parent: ~
|
||||||
|
- kind:
|
||||||
|
PrefixTypeParams:
|
||||||
|
kind: TypeVarTuple
|
||||||
|
location:
|
||||||
|
row: 5
|
||||||
|
column: 9
|
||||||
|
end_location:
|
||||||
|
row: 5
|
||||||
|
column: 31
|
||||||
|
fix: ~
|
||||||
|
parent: ~
|
||||||
|
- kind:
|
||||||
|
PrefixTypeParams:
|
||||||
|
kind: ParamSpec
|
||||||
|
location:
|
||||||
|
row: 7
|
||||||
|
column: 4
|
||||||
|
end_location:
|
||||||
|
row: 7
|
||||||
|
column: 18
|
||||||
|
fix: ~
|
||||||
|
parent: ~
|
||||||
|
|
||||||
|
|
@ -19,6 +19,7 @@ pub mod flake8_logging_format;
|
||||||
pub mod flake8_no_pep420;
|
pub mod flake8_no_pep420;
|
||||||
pub mod flake8_pie;
|
pub mod flake8_pie;
|
||||||
pub mod flake8_print;
|
pub mod flake8_print;
|
||||||
|
pub mod flake8_pyi;
|
||||||
pub mod flake8_pytest_style;
|
pub mod flake8_pytest_style;
|
||||||
pub mod flake8_quotes;
|
pub mod flake8_quotes;
|
||||||
pub mod flake8_raise;
|
pub mod flake8_raise;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
# prefix-type-params (PYI001)
|
||||||
|
|
||||||
|
Derived from the **flake8-pyi** linter.
|
||||||
|
|
||||||
|
### What it does
|
||||||
|
Checks that type `TypeVar`, `ParamSpec`, and `TypeVarTuple` definitions in
|
||||||
|
stubs are prefixed with `_`.
|
||||||
|
|
||||||
|
### Why is this bad?
|
||||||
|
By prefixing type parameters with `_`, we can avoid accidentally exposing
|
||||||
|
names internal to the stub.
|
||||||
|
|
||||||
|
### Example
|
||||||
|
```python
|
||||||
|
from typing import TypeVar
|
||||||
|
|
||||||
|
T = TypeVar("T")
|
||||||
|
```
|
||||||
|
|
||||||
|
Use instead:
|
||||||
|
```python
|
||||||
|
from typing import TypeVar
|
||||||
|
|
||||||
|
_T = TypeVar("_T")
|
||||||
|
```
|
||||||
|
|
@ -1790,6 +1790,10 @@
|
||||||
"PTH122",
|
"PTH122",
|
||||||
"PTH123",
|
"PTH123",
|
||||||
"PTH124",
|
"PTH124",
|
||||||
|
"PYI",
|
||||||
|
"PYI0",
|
||||||
|
"PYI00",
|
||||||
|
"PYI001",
|
||||||
"Q",
|
"Q",
|
||||||
"Q0",
|
"Q0",
|
||||||
"Q00",
|
"Q00",
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue