Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,17 @@ def f():
def test_annotated_non_typing_reference(user: Annotated[str, Depends(get_foo)]):
pass


def f():
from typing import Literal
from third_party import Type

def test_string_contains_opposite_quote_do_not_fix(self, type1: Type[Literal["'"]], type2: Type[Literal["\'"]]):
pass

def f():
from typing import Literal
from third_party import Type

def test_quote_contains_backslash(self, type1: Type[Literal["\n"]], type2: Type[Literal["\""]]):
pass
18 changes: 14 additions & 4 deletions crates/ruff_linter/src/rules/flake8_type_checking/helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,11 @@ pub(crate) fn quote_annotation(
let quote = stylist.quote();
let mut quote_annotator = QuoteAnnotator::new(semantic, stylist);
quote_annotator.visit_expr(expr);
if quote_annotator.failed {
return Err(anyhow::anyhow!(
"Annotation already contains quotes that require escaping"
));
}
let annotation = quote_annotator.into_annotation();

Ok(Edit::range_replacement(
Expand Down Expand Up @@ -313,6 +318,7 @@ pub(crate) struct QuoteAnnotator<'a> {
semantic: &'a SemanticModel<'a>,
state: Vec<QuoteAnnotatorState>,
annotation: String,
failed: bool,
}

impl<'a> QuoteAnnotator<'a> {
Expand All @@ -322,6 +328,7 @@ impl<'a> QuoteAnnotator<'a> {
semantic,
state: Vec::new(),
annotation: String::new(),
failed: false,
}
}

Expand Down Expand Up @@ -388,10 +395,13 @@ impl<'a> source_order::SourceOrderVisitor<'a> for QuoteAnnotator<'a> {
let source = match self.state.last().copied() {
Some(QuoteAnnotatorState::Literal | QuoteAnnotatorState::AnnotatedRest) => {
let mut source = generator.expr(expr);
source = source.replace(
self.stylist.quote().as_char(),
&self.stylist.quote().opposite().as_char().to_string(),
);
let opposite_quote = &self.stylist.quote().opposite().as_char().to_string();
// If the quotes we are going to insert in this source already exists set the auto quote outcome
// to failed. Because this means we are inserting quotes that are in the string and they collect.
if source.contains(opposite_quote) || source.contains('\\') {
self.failed = true;
}
source = source.replace(self.stylist.quote().as_char(), opposite_quote);
source
}
None
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
---
source: crates/ruff_linter/src/rules/flake8_type_checking/mod.rs
snapshot_kind: text
---
quote.py:2:24: TCH002 [*] Move third-party import `pandas.DataFrame` into a type-checking block
|
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
---
source: crates/ruff_linter/src/rules/flake8_type_checking/mod.rs
snapshot_kind: text
---
quote2.py:2:44: TCH002 [*] Move third-party import `django.contrib.auth.models.AbstractBaseUser` into a type-checking block
|
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
---
source: crates/ruff_linter/src/rules/flake8_type_checking/mod.rs
snapshot_kind: text
---
quote3.py:4:44: TCH002 [*] Move third-party import `django.contrib.auth.models.AbstractBaseUser` into a type-checking block
|
Expand Down Expand Up @@ -150,4 +151,26 @@ quote3.py:40:37: TCH002 [*] Move third-party import `django.contrib.auth.models`
45 |+ def test_attribute_typing_literal(arg: 'models.AbstractBaseUser[Literal["admin"]]'):
43 46 | pass
44 47 |
45 48 |
45 48 |

quote3.py:59:29: TCH002 Move third-party import `third_party.Type` into a type-checking block
|
57 | def f():
58 | from typing import Literal
59 | from third_party import Type
| ^^^^ TCH002
60 |
61 | def test_string_contains_opposite_quote_do_not_fix(self, type1: Type[Literal["'"]], type2: Type[Literal["\'"]]):
|
= help: Move into type-checking block

quote3.py:66:29: TCH002 Move third-party import `third_party.Type` into a type-checking block
|
64 | def f():
65 | from typing import Literal
66 | from third_party import Type
| ^^^^ TCH002
67 |
68 | def test_quote_contains_backslash(self, type1: Type[Literal["\n"]], type2: Type[Literal["\""]]):
|
= help: Move into type-checking block
Loading