[flake8-commas] Add support for trailing comma checks in type parameter lists (COM812,COM819) (#19390)
Some checks are pending
CI / Determine changes (push) Waiting to run
CI / cargo fmt (push) Waiting to run
CI / cargo clippy (push) Blocked by required conditions
CI / cargo test (linux) (push) Blocked by required conditions
CI / cargo test (linux, release) (push) Blocked by required conditions
CI / cargo test (windows) (push) Blocked by required conditions
CI / cargo test (wasm) (push) Blocked by required conditions
CI / cargo build (release) (push) Waiting to run
CI / mkdocs (push) Waiting to run
CI / cargo build (msrv) (push) Blocked by required conditions
CI / cargo fuzz build (push) Blocked by required conditions
CI / fuzz parser (push) Blocked by required conditions
CI / test scripts (push) Blocked by required conditions
CI / ecosystem (push) Blocked by required conditions
CI / Fuzz for new ty panics (push) Blocked by required conditions
CI / cargo shear (push) Blocked by required conditions
CI / python package (push) Waiting to run
CI / pre-commit (push) Waiting to run
CI / formatter instabilities and black similarity (push) Blocked by required conditions
CI / test ruff-lsp (push) Blocked by required conditions
CI / check playground (push) Blocked by required conditions
CI / benchmarks-instrumented (push) Blocked by required conditions
CI / benchmarks-walltime (push) Blocked by required conditions
[ty Playground] Release / publish (push) Waiting to run

## Summary

Fixes #18844

I'm not too sure if the solution is as simple as the way I implemented
it, but I'm curious to see if we are covering all cases correctly here.

---------

Co-authored-by: Brent Westbrook <36778786+ntBre@users.noreply.github.com>
Co-authored-by: Brent Westbrook <brentrwestbrook@gmail.com>
This commit is contained in:
Dan Parizher 2025-07-28 10:53:04 -04:00 committed by GitHub
parent 6d0f3ef3a5
commit e63dfa3d18
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 1185 additions and 2 deletions

View file

@ -650,3 +650,17 @@ f"""This is a test. {
if True else if True else
"Don't add a trailing comma here ->" "Don't add a trailing comma here ->"
}""" }"""
type X[
T
] = T
def f[
T
](): pass
class C[
T
]: pass
type X[T,] = T
def f[T,](): pass
class C[T,]: pass

View file

@ -118,7 +118,7 @@ pub(crate) fn check_tokens(
Rule::TrailingCommaOnBareTuple, Rule::TrailingCommaOnBareTuple,
Rule::ProhibitedTrailingComma, Rule::ProhibitedTrailingComma,
]) { ]) {
flake8_commas::rules::trailing_commas(context, tokens, locator, indexer); flake8_commas::rules::trailing_commas(context, tokens, locator, indexer, settings);
} }
if context.is_rule_enabled(Rule::ExtraneousParentheses) { if context.is_rule_enabled(Rule::ExtraneousParentheses) {

View file

@ -225,3 +225,8 @@ pub(crate) const fn is_assert_raises_exception_call_enabled(settings: &LinterSet
pub(crate) const fn is_add_future_annotations_imports_enabled(settings: &LinterSettings) -> bool { pub(crate) const fn is_add_future_annotations_imports_enabled(settings: &LinterSettings) -> bool {
settings.preview.is_enabled() settings.preview.is_enabled()
} }
// https://github.com/astral-sh/ruff/pull/19390
pub(crate) const fn is_trailing_comma_type_params_enabled(settings: &LinterSettings) -> bool {
settings.preview.is_enabled()
}

View file

@ -27,4 +27,23 @@ mod tests {
assert_diagnostics!(snapshot, diagnostics); assert_diagnostics!(snapshot, diagnostics);
Ok(()) Ok(())
} }
#[test_case(Path::new("COM81.py"))]
#[test_case(Path::new("COM81_syntax_error.py"))]
fn preview_rules(path: &Path) -> Result<()> {
let snapshot = format!("preview__{}", path.to_string_lossy());
let diagnostics = test_path(
Path::new("flake8_commas").join(path).as_path(),
&settings::LinterSettings {
preview: crate::settings::types::PreviewMode::Enabled,
..settings::LinterSettings::for_rules(vec![
Rule::MissingTrailingComma,
Rule::TrailingCommaOnBareTuple,
Rule::ProhibitedTrailingComma,
])
},
)?;
assert_diagnostics!(snapshot, diagnostics);
Ok(())
}
} }

View file

@ -5,6 +5,8 @@ use ruff_text_size::{Ranged, TextRange};
use crate::Locator; use crate::Locator;
use crate::checkers::ast::LintContext; use crate::checkers::ast::LintContext;
use crate::preview::is_trailing_comma_type_params_enabled;
use crate::settings::LinterSettings;
use crate::{AlwaysFixableViolation, Violation}; use crate::{AlwaysFixableViolation, Violation};
use crate::{Edit, Fix}; use crate::{Edit, Fix};
@ -24,6 +26,8 @@ enum TokenType {
Def, Def,
For, For,
Lambda, Lambda,
Class,
Type,
Irrelevant, Irrelevant,
} }
@ -69,6 +73,8 @@ impl From<(TokenKind, TextRange)> for SimpleToken {
TokenKind::Lbrace => TokenType::OpeningCurlyBracket, TokenKind::Lbrace => TokenType::OpeningCurlyBracket,
TokenKind::Rbrace => TokenType::ClosingBracket, TokenKind::Rbrace => TokenType::ClosingBracket,
TokenKind::Def => TokenType::Def, TokenKind::Def => TokenType::Def,
TokenKind::Class => TokenType::Class,
TokenKind::Type => TokenType::Type,
TokenKind::For => TokenType::For, TokenKind::For => TokenType::For,
TokenKind::Lambda => TokenType::Lambda, TokenKind::Lambda => TokenType::Lambda,
// Import treated like a function. // Import treated like a function.
@ -98,6 +104,8 @@ enum ContextType {
Dict, Dict,
/// Lambda parameter list, e.g. `lambda a, b`. /// Lambda parameter list, e.g. `lambda a, b`.
LambdaParameters, LambdaParameters,
/// Type parameter list, e.g. `def foo[T, U](): ...`
TypeParameters,
} }
/// Comma context - described a comma-delimited "situation". /// Comma context - described a comma-delimited "situation".
@ -243,6 +251,7 @@ pub(crate) fn trailing_commas(
tokens: &Tokens, tokens: &Tokens,
locator: &Locator, locator: &Locator,
indexer: &Indexer, indexer: &Indexer,
settings: &LinterSettings,
) { ) {
let mut fstrings = 0u32; let mut fstrings = 0u32;
let simple_tokens = tokens.iter().filter_map(|token| { let simple_tokens = tokens.iter().filter_map(|token| {
@ -290,7 +299,7 @@ pub(crate) fn trailing_commas(
} }
// Update the comma context stack. // Update the comma context stack.
let context = update_context(token, prev, prev_prev, &mut stack); let context = update_context(token, prev, prev_prev, &mut stack, settings);
check_token(token, prev, prev_prev, context, locator, lint_context); check_token(token, prev, prev_prev, context, locator, lint_context);
@ -326,6 +335,7 @@ fn check_token(
ContextType::No => false, ContextType::No => false,
ContextType::FunctionParameters => true, ContextType::FunctionParameters => true,
ContextType::CallArguments => true, ContextType::CallArguments => true,
ContextType::TypeParameters => true,
// `(1)` is not equivalent to `(1,)`. // `(1)` is not equivalent to `(1,)`.
ContextType::Tuple => context.num_commas != 0, ContextType::Tuple => context.num_commas != 0,
// `x[1]` is not equivalent to `x[1,]`. // `x[1]` is not equivalent to `x[1,]`.
@ -408,6 +418,7 @@ fn update_context(
prev: SimpleToken, prev: SimpleToken,
prev_prev: SimpleToken, prev_prev: SimpleToken,
stack: &mut Vec<Context>, stack: &mut Vec<Context>,
settings: &LinterSettings,
) -> Context { ) -> Context {
let new_context = match token.ty { let new_context = match token.ty {
TokenType::OpeningBracket => match (prev.ty, prev_prev.ty) { TokenType::OpeningBracket => match (prev.ty, prev_prev.ty) {
@ -417,6 +428,17 @@ fn update_context(
} }
_ => Context::new(ContextType::Tuple), _ => Context::new(ContextType::Tuple),
}, },
TokenType::OpeningSquareBracket if is_trailing_comma_type_params_enabled(settings) => {
match (prev.ty, prev_prev.ty) {
(TokenType::Named, TokenType::Def | TokenType::Class | TokenType::Type) => {
Context::new(ContextType::TypeParameters)
}
(TokenType::ClosingBracket | TokenType::Named | TokenType::String, _) => {
Context::new(ContextType::Subscript)
}
_ => Context::new(ContextType::List),
}
}
TokenType::OpeningSquareBracket => match prev.ty { TokenType::OpeningSquareBracket => match prev.ty {
TokenType::ClosingBracket | TokenType::Named | TokenType::String => { TokenType::ClosingBracket | TokenType::Named | TokenType::String => {
Context::new(ContextType::Subscript) Context::new(ContextType::Subscript)

View file

@ -0,0 +1,30 @@
---
source: crates/ruff_linter/src/rules/flake8_commas/mod.rs
---
COM81_syntax_error.py:3:5: SyntaxError: Starred expression cannot be used here
|
1 | # Check for `flake8-commas` violation for a file containing syntax errors.
2 | (
3 | *args
| ^^^^^
4 | )
|
COM81_syntax_error.py:6:9: SyntaxError: Type parameter list cannot be empty
|
4 | )
5 |
6 | def foo[(param1='test', param2='test',):
| ^
7 | pass
|
COM81_syntax_error.py:6:38: COM819 Trailing comma prohibited
|
4 | )
5 |
6 | def foo[(param1='test', param2='test',):
| ^ COM819
7 | pass
|
= help: Remove trailing comma