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.
|
||||
"""
|
||||
|
||||
- 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:
|
||||
"""
|
||||
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-pie (PIE)](#flake8-pie-pie)
|
||||
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-quotes (Q)](#flake8-quotes-q)
|
||||
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 | |
|
||||
| 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)
|
||||
|
||||
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-pie](https://pypi.org/project/flake8-pie/)
|
||||
* [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-quotes](https://pypi.org/project/flake8-quotes/)
|
||||
* [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_bugbear, flake8_builtins, flake8_comprehensions, flake8_datetimez, flake8_debugger,
|
||||
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_simplify, flake8_tidy_imports, flake8_type_checking, flake8_unused_arguments,
|
||||
flake8_use_pathlib, mccabe, pandas_vet, pep8_naming, pycodestyle, pydocstyle, pyflakes,
|
||||
pygrep_hooks, pylint, pyupgrade, ruff, tryceratops,
|
||||
flake8_pie, flake8_print, flake8_pyi, flake8_pytest_style, flake8_raise, flake8_return,
|
||||
flake8_self, flake8_simplify, flake8_tidy_imports, flake8_type_checking,
|
||||
flake8_unused_arguments, flake8_use_pathlib, mccabe, pandas_vet, pep8_naming, pycodestyle,
|
||||
pydocstyle, pyflakes, pygrep_hooks, pylint, pyupgrade, ruff, tryceratops,
|
||||
};
|
||||
use crate::settings::types::PythonVersion;
|
||||
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) {
|
||||
pyupgrade::rules::useless_metaclass_type(self, stmt, value, targets);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -442,6 +442,8 @@ ruff_macros::define_rule_mapping!(
|
|||
EM101 => rules::flake8_errmsg::rules::RawStringInException,
|
||||
EM102 => rules::flake8_errmsg::rules::FStringInException,
|
||||
EM103 => rules::flake8_errmsg::rules::DotFormatInException,
|
||||
// flake8-pyi
|
||||
PYI001 => rules::flake8_pyi::rules::PrefixTypeParams,
|
||||
// flake8-pytest-style
|
||||
PT001 => rules::flake8_pytest_style::rules::IncorrectFixtureParenthesesStyle,
|
||||
PT002 => rules::flake8_pytest_style::rules::FixturePositionalArgs,
|
||||
|
|
@ -632,6 +634,9 @@ pub enum Linter {
|
|||
/// [flake8-print](https://pypi.org/project/flake8-print/)
|
||||
#[prefix = "T20"]
|
||||
Flake8Print,
|
||||
/// [flake8-pyi](https://pypi.org/project/flake8-pyi/)
|
||||
#[prefix = "PYI"]
|
||||
Flake8Pyi,
|
||||
/// [flake8-pytest-style](https://pypi.org/project/flake8-pytest-style/)
|
||||
#[prefix = "PT"]
|
||||
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_pie;
|
||||
pub mod flake8_print;
|
||||
pub mod flake8_pyi;
|
||||
pub mod flake8_pytest_style;
|
||||
pub mod flake8_quotes;
|
||||
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",
|
||||
"PTH123",
|
||||
"PTH124",
|
||||
"PYI",
|
||||
"PYI0",
|
||||
"PYI00",
|
||||
"PYI001",
|
||||
"Q",
|
||||
"Q0",
|
||||
"Q00",
|
||||
|
|
|
|||
Loading…
Reference in New Issue