Skip to content

Commit

Permalink
First pass PYI042, PYI043
Browse files Browse the repository at this point in the history
  • Loading branch information
arya-k committed May 4, 2023
1 parent 37aae66 commit 8ee31a6
Show file tree
Hide file tree
Showing 15 changed files with 253 additions and 0 deletions.
21 changes: 21 additions & 0 deletions crates/ruff/resources/test/fixtures/flake8_pyi/PYI042.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import typing
from collections.abc import Mapping
from typing import (
Annotated,
TypeAlias,
Union,
Literal,
)

just_literals_pipe_union: TypeAlias = Literal[True] | Literal['idk'] # PYI042, since not camel case
PublicAliasT: TypeAlias = str | int
PublicAliasT2: TypeAlias = Union[str, bytes]
_ABCDEFGHIJKLMNOPQRST: TypeAlias = typing.Any
_PrivateAliasS: TypeAlias = Literal["I", "guess", "this", "is", "okay"]
_PrivateAliasS2: TypeAlias = Annotated[str, "also okay"]

snake_case_alias1: TypeAlias = str | int # PYI042, since not camel case
_snake_case_alias2: TypeAlias = Literal["whatever"] # PYI042, since not camel case

# check that this edge case doesn't crash
_: TypeAlias = str | int
21 changes: 21 additions & 0 deletions crates/ruff/resources/test/fixtures/flake8_pyi/PYI042.pyi
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import typing
from collections.abc import Mapping
from typing import (
Annotated,
TypeAlias,
Union,
Literal,
)

just_literals_pipe_union: TypeAlias = Literal[True] | Literal['idk'] # PYI042, since not camel case
PublicAliasT: TypeAlias = str | int
PublicAliasT2: TypeAlias = Union[str, bytes]
_ABCDEFGHIJKLMNOPQRST: TypeAlias = typing.Any
_PrivateAliasS: TypeAlias = Literal["I", "guess", "this", "is", "okay"]
_PrivateAliasS2: TypeAlias = Annotated[str, "also okay"]

snake_case_alias1: TypeAlias = str | int # PYI042, since not camel case
_snake_case_alias2: TypeAlias = Literal["whatever"] # PYI042, since not camel case

# check that this edge case doesn't crash
_: TypeAlias = str | int
21 changes: 21 additions & 0 deletions crates/ruff/resources/test/fixtures/flake8_pyi/PYI043.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import typing
from collections.abc import Mapping
from typing import (
Annotated,
TypeAlias,
Union,
Literal,
)

_PrivateAliasT: TypeAlias = str | int # PYI043, since this ends in a T
_PrivateAliasT2: TypeAlias = typing.Any # PYI043, since this ends in a T
_PrivateAliasT3: TypeAlias = Literal["not", "a", "chance"] # PYI043, since this ends in a T
just_literals_pipe_union: TypeAlias = Literal[True] | Literal['idk']
PublicAliasT: TypeAlias = str | int
PublicAliasT2: TypeAlias = Union[str, bytes]
_ABCDEFGHIJKLMNOPQRST: TypeAlias = typing.Any
_PrivateAliasS: TypeAlias = Literal["I", "guess", "this", "is", "okay"]
_PrivateAliasS2: TypeAlias = Annotated[str, "also okay"]

# check that this edge case doesn't crash
_: TypeAlias = str | int
21 changes: 21 additions & 0 deletions crates/ruff/resources/test/fixtures/flake8_pyi/PYI043.pyi
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import typing
from collections.abc import Mapping
from typing import (
Annotated,
TypeAlias,
Union,
Literal,
)

_PrivateAliasT: TypeAlias = str | int # PYI043, since this ends in a T
_PrivateAliasT2: TypeAlias = typing.Any # PYI043, since this ends in a T
_PrivateAliasT3: TypeAlias = Literal["not", "a", "chance"] # PYI043, since this ends in a T
just_literals_pipe_union: TypeAlias = Literal[True] | Literal['idk']
PublicAliasT: TypeAlias = str | int
PublicAliasT2: TypeAlias = Union[str, bytes]
_ABCDEFGHIJKLMNOPQRST: TypeAlias = typing.Any
_PrivateAliasS: TypeAlias = Literal["I", "guess", "this", "is", "okay"]
_PrivateAliasS2: TypeAlias = Annotated[str, "also okay"]

# check that this edge case doesn't crash
_: TypeAlias = str | int
8 changes: 8 additions & 0 deletions crates/ruff/src/checkers/ast/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1914,6 +1914,14 @@ where
}
}
}
if self.ctx.match_typing_expr(annotation, "TypeAlias") {
if self.settings.rules.enabled(Rule::SnakeCaseTypeAlias) {
flake8_pyi::rules::snake_case_type_alias(self, target);
}
if self.settings.rules.enabled(Rule::TSuffixedTypeAlias) {
flake8_pyi::rules::t_suffixed_type_alias(self, target);
}
}
}
}
StmtKind::Delete { targets } => {
Expand Down
2 changes: 2 additions & 0 deletions crates/ruff/src/codes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -595,6 +595,8 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<Rule> {
(Flake8Pyi, "020") => Rule::QuotedAnnotationInStub,
(Flake8Pyi, "021") => Rule::DocstringInStub,
(Flake8Pyi, "033") => Rule::TypeCommentInStub,
(Flake8Pyi, "042") => Rule::SnakeCaseTypeAlias,
(Flake8Pyi, "043") => Rule::TSuffixedTypeAlias,

// flake8-pytest-style
(Flake8PytestStyle, "001") => Rule::PytestFixtureIncorrectParenthesesStyle,
Expand Down
2 changes: 2 additions & 0 deletions crates/ruff/src/registry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -540,6 +540,8 @@ ruff_macros::register_rules!(
rules::flake8_pyi::rules::PassInClassBody,
rules::flake8_pyi::rules::DuplicateUnionMember,
rules::flake8_pyi::rules::QuotedAnnotationInStub,
rules::flake8_pyi::rules::SnakeCaseTypeAlias,
rules::flake8_pyi::rules::TSuffixedTypeAlias,
// flake8-pytest-style
rules::flake8_pytest_style::rules::PytestFixtureIncorrectParenthesesStyle,
rules::flake8_pytest_style::rules::PytestFixturePositionalArgs,
Expand Down
4 changes: 4 additions & 0 deletions crates/ruff/src/rules/flake8_pyi/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ mod tests {
#[test_case(Rule::DocstringInStub, Path::new("PYI021.pyi"))]
#[test_case(Rule::TypeCommentInStub, Path::new("PYI033.py"))]
#[test_case(Rule::TypeCommentInStub, Path::new("PYI033.pyi"))]
#[test_case(Rule::SnakeCaseTypeAlias, Path::new("PYI042.py"))]
#[test_case(Rule::SnakeCaseTypeAlias, Path::new("PYI042.pyi"))]
#[test_case(Rule::TSuffixedTypeAlias, Path::new("PYI043.py"))]
#[test_case(Rule::TSuffixedTypeAlias, Path::new("PYI043.pyi"))]
fn rules(rule_code: Rule, path: &Path) -> Result<()> {
let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy());
let diagnostics = test_path(
Expand Down
4 changes: 4 additions & 0 deletions crates/ruff/src/rules/flake8_pyi/rules/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ pub use simple_defaults::{
typed_argument_simple_defaults, ArgumentDefaultInStub, AssignmentDefaultInStub,
TypedArgumentDefaultInStub,
};
pub use type_alias_naming::{
snake_case_type_alias, t_suffixed_type_alias, SnakeCaseTypeAlias, TSuffixedTypeAlias,
};
pub use type_comment_in_stub::{type_comment_in_stub, TypeCommentInStub};
pub use unrecognized_platform::{
unrecognized_platform, UnrecognizedPlatformCheck, UnrecognizedPlatformName,
Expand All @@ -25,5 +28,6 @@ mod pass_statement_stub_body;
mod prefix_type_params;
mod quoted_annotation_in_stub;
mod simple_defaults;
mod type_alias_naming;
mod type_comment_in_stub;
mod unrecognized_platform;
73 changes: 73 additions & 0 deletions crates/ruff/src/rules/flake8_pyi/rules/type_alias_naming.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
use once_cell::sync::Lazy;
use regex::Regex;
use rustpython_parser::ast::{Expr, ExprKind};

use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation};

use crate::checkers::ast::Checker;

static CAMEL_CASE_REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r"^_?[a-z]").unwrap());

// PYI043: Error for alias names in "T"
// (plus possibly a single digit afterwards), but only if:
//
// - The name starts with "_"
// - The penultimate character in the name is an ASCII-lowercase letter
static T_SUFFIXED_REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r"^_.*[a-z]T\d?$").unwrap());

#[violation]
pub struct SnakeCaseTypeAlias {
pub name: String,
}

impl Violation for SnakeCaseTypeAlias {
#[derive_message_formats]
fn message(&self) -> String {
let Self { name } = self;
format!("Type alias `{name}` should be CamelCase")
}
}

#[violation]
pub struct TSuffixedTypeAlias {
pub name: String,
}

impl Violation for TSuffixedTypeAlias {
#[derive_message_formats]
fn message(&self) -> String {
let Self { name } = self;
format!("Private type alias `{name}` should not be suffixed with `T` (the `T` suffix implies that an object is a TypeVar)")
}
}

pub fn snake_case_type_alias(checker: &mut Checker, target: &Expr) {
if let ExprKind::Name { id, .. } = target.node() {
if !CAMEL_CASE_REGEX.is_match(id) {
return;
}

checker.diagnostics.push(Diagnostic::new(
SnakeCaseTypeAlias {
name: id.to_string(),
},
target.range(),
));
}
}

pub fn t_suffixed_type_alias(checker: &mut Checker, target: &Expr) {
if let ExprKind::Name { id, .. } = target.node() {
if !T_SUFFIXED_REGEX.is_match(id) {
return;
}

checker.diagnostics.push(Diagnostic::new(
TSuffixedTypeAlias {
name: id.to_string(),
},
target.range(),
));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
---
source: crates/ruff/src/rules/flake8_pyi/mod.rs
---

Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
---
source: crates/ruff/src/rules/flake8_pyi/mod.rs
---
PYI042.pyi:10:1: PYI042 Type alias `just_literals_pipe_union` should be CamelCase
|
10 | )
11 |
12 | just_literals_pipe_union: TypeAlias = Literal[True] | Literal['idk'] # PYI042, since not camel case
| ^^^^^^^^^^^^^^^^^^^^^^^^ PYI042
13 | PublicAliasT: TypeAlias = str | int
14 | PublicAliasT2: TypeAlias = Union[str, bytes]
|

PYI042.pyi:17:1: PYI042 Type alias `snake_case_alias1` should be CamelCase
|
17 | _PrivateAliasS2: TypeAlias = Annotated[str, "also okay"]
18 |
19 | snake_case_alias1: TypeAlias = str | int # PYI042, since not camel case
| ^^^^^^^^^^^^^^^^^ PYI042
20 | _snake_case_alias2: TypeAlias = Literal["whatever"] # PYI042, since not camel case
|

PYI042.pyi:18:1: PYI042 Type alias `_snake_case_alias2` should be CamelCase
|
18 | snake_case_alias1: TypeAlias = str | int # PYI042, since not camel case
19 | _snake_case_alias2: TypeAlias = Literal["whatever"] # PYI042, since not camel case
| ^^^^^^^^^^^^^^^^^^ PYI042
20 |
21 | # check that this edge case doesn't crash
|


Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
---
source: crates/ruff/src/rules/flake8_pyi/mod.rs
---

Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
---
source: crates/ruff/src/rules/flake8_pyi/mod.rs
---
PYI043.pyi:10:1: PYI043 Private type alias `_PrivateAliasT` should not be suffixed with `T` (the `T` suffix implies that an object is a TypeVar)
|
10 | )
11 |
12 | _PrivateAliasT: TypeAlias = str | int # PYI043, since this ends in a T
| ^^^^^^^^^^^^^^ PYI043
13 | _PrivateAliasT2: TypeAlias = typing.Any # PYI043, since this ends in a T
14 | _PrivateAliasT3: TypeAlias = Literal["not", "a", "chance"] # PYI043, since this ends in a T
|

PYI043.pyi:11:1: PYI043 Private type alias `_PrivateAliasT2` should not be suffixed with `T` (the `T` suffix implies that an object is a TypeVar)
|
11 | _PrivateAliasT: TypeAlias = str | int # PYI043, since this ends in a T
12 | _PrivateAliasT2: TypeAlias = typing.Any # PYI043, since this ends in a T
| ^^^^^^^^^^^^^^^ PYI043
13 | _PrivateAliasT3: TypeAlias = Literal["not", "a", "chance"] # PYI043, since this ends in a T
14 | just_literals_pipe_union: TypeAlias = Literal[True] | Literal['idk']
|

PYI043.pyi:12:1: PYI043 Private type alias `_PrivateAliasT3` should not be suffixed with `T` (the `T` suffix implies that an object is a TypeVar)
|
12 | _PrivateAliasT: TypeAlias = str | int # PYI043, since this ends in a T
13 | _PrivateAliasT2: TypeAlias = typing.Any # PYI043, since this ends in a T
14 | _PrivateAliasT3: TypeAlias = Literal["not", "a", "chance"] # PYI043, since this ends in a T
| ^^^^^^^^^^^^^^^ PYI043
15 | just_literals_pipe_union: TypeAlias = Literal[True] | Literal['idk']
16 | PublicAliasT: TypeAlias = str | int
|


3 changes: 3 additions & 0 deletions ruff.schema.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 8ee31a6

Please sign in to comment.