[`pyupgrade`] Mark fixes for `convert-typed-dict-functional-to-class` and `convert-named-tuple-functional-to-class` as unsafe if they will remove comments (`UP013`, `UP014`) (#14842)

This commit is contained in:
Alex Waygood 2024-12-08 18:51:37 +00:00 committed by GitHub
parent 8d9e408dbb
commit 9b8ceb9a2e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 102 additions and 17 deletions

View File

@ -41,3 +41,8 @@ MyType = TypedDict("MyType", {})
# Empty dict call
MyType = TypedDict("MyType", dict())
# Unsafe fix if comments are present
X = TypedDict("X", {
"some_config": int, # important
})

View File

@ -31,3 +31,9 @@ S3File = NamedTuple(
("dataHPK",* str),
],
)
# Unsafe fix if comments are present
X = NamedTuple("X", [
("some_config", int), # important
])

View File

@ -1,6 +1,6 @@
use log::debug;
use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation};
use ruff_diagnostics::{Applicability, Diagnostic, Edit, Fix, FixAvailability, Violation};
use ruff_macros::{derive_message_formats, ViolationMetadata};
use ruff_python_ast::helpers::is_dunder;
use ruff_python_ast::name::Name;
@ -8,6 +8,7 @@ use ruff_python_ast::{self as ast, Arguments, Expr, ExprContext, Identifier, Key
use ruff_python_codegen::Generator;
use ruff_python_semantic::SemanticModel;
use ruff_python_stdlib::identifiers::is_identifier;
use ruff_python_trivia::CommentRanges;
use ruff_source_file::LineRanges;
use ruff_text_size::{Ranged, TextRange};
@ -41,6 +42,11 @@ use crate::checkers::ast::Checker;
/// b: str
/// ```
///
/// ## Fix safety
/// This rule's fix is marked as unsafe if there are any comments within the
/// range of the `NamedTuple` definition, as these will be dropped by the
/// autofix.
///
/// ## References
/// - [Python documentation: `typing.NamedTuple`](https://docs.python.org/3/library/typing.html#typing.NamedTuple)
#[derive(ViolationMetadata)]
@ -121,6 +127,7 @@ pub(crate) fn convert_named_tuple_functional_to_class(
fields,
base_class,
checker.generator(),
checker.comment_ranges(),
));
}
checker.diagnostics.push(diagnostic);
@ -241,9 +248,17 @@ fn convert_to_class(
body: Vec<Stmt>,
base_class: &Expr,
generator: Generator,
comment_ranges: &CommentRanges,
) -> Fix {
Fix::safe_edit(Edit::range_replacement(
generator.stmt(&create_class_def_stmt(typename, body, base_class)),
stmt.range(),
))
Fix::applicable_edit(
Edit::range_replacement(
generator.stmt(&create_class_def_stmt(typename, body, base_class)),
stmt.range(),
),
if comment_ranges.intersects(stmt.range()) {
Applicability::Unsafe
} else {
Applicability::Safe
},
)
}

View File

@ -1,10 +1,11 @@
use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation};
use ruff_diagnostics::{Applicability, Diagnostic, Edit, Fix, FixAvailability, Violation};
use ruff_macros::{derive_message_formats, ViolationMetadata};
use ruff_python_ast::helpers::is_dunder;
use ruff_python_ast::{self as ast, Arguments, Expr, ExprContext, Identifier, Keyword, Stmt};
use ruff_python_codegen::Generator;
use ruff_python_semantic::SemanticModel;
use ruff_python_stdlib::identifiers::is_identifier;
use ruff_python_trivia::CommentRanges;
use ruff_source_file::LineRanges;
use ruff_text_size::{Ranged, TextRange};
@ -37,6 +38,11 @@ use crate::checkers::ast::Checker;
/// b: str
/// ```
///
/// ## Fix safety
/// This rule's fix is marked as unsafe if there are any comments within the
/// range of the `TypedDict` definition, as these will be dropped by the
/// autofix.
///
/// ## References
/// - [Python documentation: `typing.TypedDict`](https://docs.python.org/3/library/typing.html#typing.TypedDict)
#[derive(ViolationMetadata)]
@ -91,6 +97,7 @@ pub(crate) fn convert_typed_dict_functional_to_class(
total_keyword,
base_class,
checker.generator(),
checker.comment_ranges(),
));
}
checker.diagnostics.push(diagnostic);
@ -265,14 +272,22 @@ fn convert_to_class(
total_keyword: Option<&Keyword>,
base_class: &Expr,
generator: Generator,
comment_ranges: &CommentRanges,
) -> Fix {
Fix::safe_edit(Edit::range_replacement(
generator.stmt(&create_class_def_stmt(
class_name,
body,
total_keyword,
base_class,
)),
stmt.range(),
))
Fix::applicable_edit(
Edit::range_replacement(
generator.stmt(&create_class_def_stmt(
class_name,
body,
total_keyword,
base_class,
)),
stmt.range(),
),
if comment_ranges.intersects(stmt.range()) {
Applicability::Unsafe
} else {
Applicability::Safe
},
)
}

View File

@ -1,6 +1,5 @@
---
source: crates/ruff_linter/src/rules/pyupgrade/mod.rs
snapshot_kind: text
---
UP013.py:5:1: UP013 [*] Convert `MyType` from `TypedDict` functional to class syntax
|
@ -242,6 +241,8 @@ UP013.py:43:1: UP013 [*] Convert `MyType` from `TypedDict` functional to class s
42 | # Empty dict call
43 | MyType = TypedDict("MyType", dict())
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UP013
44 |
45 | # Unsafe fix if comments are present
|
= help: Convert `MyType` to class syntax
@ -252,3 +253,26 @@ UP013.py:43:1: UP013 [*] Convert `MyType` from `TypedDict` functional to class s
43 |-MyType = TypedDict("MyType", dict())
43 |+class MyType(TypedDict):
44 |+ pass
44 45 |
45 46 | # Unsafe fix if comments are present
46 47 | X = TypedDict("X", {
UP013.py:46:1: UP013 [*] Convert `X` from `TypedDict` functional to class syntax
|
45 | # Unsafe fix if comments are present
46 | / X = TypedDict("X", {
47 | | "some_config": int, # important
48 | | })
| |__^ UP013
|
= help: Convert `X` to class syntax
Unsafe fix
43 43 | MyType = TypedDict("MyType", dict())
44 44 |
45 45 | # Unsafe fix if comments are present
46 |-X = TypedDict("X", {
47 |- "some_config": int, # important
48 |-})
46 |+class X(TypedDict):
47 |+ some_config: int

View File

@ -1,6 +1,5 @@
---
source: crates/ruff_linter/src/rules/pyupgrade/mod.rs
snapshot_kind: text
---
UP014.py:5:1: UP014 [*] Convert `MyType` from `NamedTuple` functional to class syntax
|
@ -109,3 +108,24 @@ UP014.py:20:1: UP014 [*] Convert `MyType` from `NamedTuple` functional to class
21 23 |
22 24 | # unfixable
23 25 | MyType = typing.NamedTuple("MyType", [("a", int)], [("b", str)])
UP014.py:36:1: UP014 [*] Convert `X` from `NamedTuple` functional to class syntax
|
35 | # Unsafe fix if comments are present
36 | / X = NamedTuple("X", [
37 | | ("some_config", int), # important
38 | | ])
| |__^ UP014
|
= help: Convert `X` to class syntax
Unsafe fix
33 33 | )
34 34 |
35 35 | # Unsafe fix if comments are present
36 |-X = NamedTuple("X", [
37 |- ("some_config", int), # important
38 |-])
36 |+class X(NamedTuple):
37 |+ some_config: int
39 38 |