mirror of
https://github.com/astral-sh/ruff.git
synced 2025-08-14 23:51:03 +00:00
Use ExprFString
for StringLike::FString
variant (#10311)
## Summary This PR updates the `StringLike::FString` variant to use `ExprFString` instead of `FStringLiteralElement`. For context, the reason it used `FStringLiteralElement` is that the node is actually the string part of an f-string ("foo" in `f"foo{x}"`). But, this is inconsistent with other variants where the captured value is the _entire_ string. This is also problematic w.r.t. implicitly concatenated strings. Any rules which work with `StringLike::FString` doesn't account for the string part in an implicitly concatenated f-strings. For example, we don't flag confusable character in the first part of `"𝐁ad" f"𝐁ad string"`, but only the second part (https://play.ruff.rs/16071c4c-a1dd-4920-b56f-e2ce2f69c843). ### Update `PYI053` _This is included in this PR because otherwise it requires a temporary workaround to be compatible with the old logic._ This PR also updates the `PYI053` (`string-or-bytes-too-long`) rule for f-string to consider _all_ the visible characters in a f-string, including the ones which are implicitly concatenated. This is consistent with implicitly concatenated strings and bytes. For example, ```python def foo( # We count all the characters here arg1: str = '51 character ' 'stringgggggggggggggggggggggggggggggggg', # But not here because of the `{x}` replacement field which _breaks_ them up into two chunks arg2: str = f'51 character {x} stringgggggggggggggggggggggggggggggggggggggggggggg', ) -> None: ... ``` This PR fixes it to consider all _visible_ characters inside an f-string which includes expressions as well. fixes: #10310 fixes: #10307 ## Test Plan Add new test cases and update the snapshots. ## Review To facilitate the review process, the change have been split into two commits: one which has the code change while the other has the test cases and updated snapshots.
This commit is contained in:
parent
f7802ad5de
commit
5f40371ffc
16 changed files with 251 additions and 63 deletions
|
@ -18,3 +18,7 @@ func("0.0.0.0")
|
||||||
def my_func():
|
def my_func():
|
||||||
x = "0.0.0.0"
|
x = "0.0.0.0"
|
||||||
print(x)
|
print(x)
|
||||||
|
|
||||||
|
|
||||||
|
# Implicit string concatenation
|
||||||
|
"0.0.0.0" f"0.0.0.0{expr}0.0.0.0"
|
||||||
|
|
|
@ -18,6 +18,13 @@ with open("/dev/shm/unit/test", "w") as f:
|
||||||
with open("/foo/bar", "w") as f:
|
with open("/foo/bar", "w") as f:
|
||||||
f.write("def")
|
f.write("def")
|
||||||
|
|
||||||
|
# Implicit string concatenation
|
||||||
|
with open("/tmp/" "abc", "w") as f:
|
||||||
|
f.write("def")
|
||||||
|
|
||||||
|
with open("/tmp/abc" f"/tmp/abc", "w") as f:
|
||||||
|
f.write("def")
|
||||||
|
|
||||||
# Using `tempfile` module should be ok
|
# Using `tempfile` module should be ok
|
||||||
import tempfile
|
import tempfile
|
||||||
from tempfile import TemporaryDirectory
|
from tempfile import TemporaryDirectory
|
||||||
|
|
|
@ -64,3 +64,5 @@ def not_warnings_dot_deprecated(
|
||||||
"Not warnings.deprecated, so this one *should* lead to PYI053 in a stub!" # Error: PYI053
|
"Not warnings.deprecated, so this one *should* lead to PYI053 in a stub!" # Error: PYI053
|
||||||
)
|
)
|
||||||
def not_a_deprecated_function() -> None: ...
|
def not_a_deprecated_function() -> None: ...
|
||||||
|
|
||||||
|
fbaz: str = f"51 character {foo} stringgggggggggggggggggggggggggg" # Error: PYI053
|
||||||
|
|
|
@ -53,3 +53,6 @@ class Labware:
|
||||||
|
|
||||||
|
|
||||||
assert getattr(Labware(), "µL") == 1.5
|
assert getattr(Labware(), "µL") == 1.5
|
||||||
|
|
||||||
|
# Implicit string concatenation
|
||||||
|
x = "𝐁ad" f"𝐁ad string"
|
||||||
|
|
|
@ -45,7 +45,7 @@ use ruff_python_ast::helpers::{
|
||||||
use ruff_python_ast::identifier::Identifier;
|
use ruff_python_ast::identifier::Identifier;
|
||||||
use ruff_python_ast::name::QualifiedName;
|
use ruff_python_ast::name::QualifiedName;
|
||||||
use ruff_python_ast::str::Quote;
|
use ruff_python_ast::str::Quote;
|
||||||
use ruff_python_ast::visitor::{walk_except_handler, walk_f_string_element, walk_pattern, Visitor};
|
use ruff_python_ast::visitor::{walk_except_handler, walk_pattern, Visitor};
|
||||||
use ruff_python_ast::{helpers, str, visitor, PySourceType};
|
use ruff_python_ast::{helpers, str, visitor, PySourceType};
|
||||||
use ruff_python_codegen::{Generator, Stylist};
|
use ruff_python_codegen::{Generator, Stylist};
|
||||||
use ruff_python_index::Indexer;
|
use ruff_python_index::Indexer;
|
||||||
|
@ -1407,6 +1407,7 @@ impl<'a> Visitor<'a> for Checker<'a> {
|
||||||
analyze::string_like(string_literal.into(), self);
|
analyze::string_like(string_literal.into(), self);
|
||||||
}
|
}
|
||||||
Expr::BytesLiteral(bytes_literal) => analyze::string_like(bytes_literal.into(), self),
|
Expr::BytesLiteral(bytes_literal) => analyze::string_like(bytes_literal.into(), self),
|
||||||
|
Expr::FString(f_string) => analyze::string_like(f_string.into(), self),
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1573,16 +1574,6 @@ impl<'a> Visitor<'a> for Checker<'a> {
|
||||||
.push((bound, self.semantic.snapshot()));
|
.push((bound, self.semantic.snapshot()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn visit_f_string_element(&mut self, f_string_element: &'a ast::FStringElement) {
|
|
||||||
// Step 2: Traversal
|
|
||||||
walk_f_string_element(self, f_string_element);
|
|
||||||
|
|
||||||
// Step 4: Analysis
|
|
||||||
if let Some(literal) = f_string_element.as_literal() {
|
|
||||||
analyze::string_like(literal.into(), self);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Checker<'a> {
|
impl<'a> Checker<'a> {
|
||||||
|
|
|
@ -38,17 +38,37 @@ impl Violation for HardcodedBindAllInterfaces {
|
||||||
|
|
||||||
/// S104
|
/// S104
|
||||||
pub(crate) fn hardcoded_bind_all_interfaces(checker: &mut Checker, string: StringLike) {
|
pub(crate) fn hardcoded_bind_all_interfaces(checker: &mut Checker, string: StringLike) {
|
||||||
let is_bind_all_interface = match string {
|
match string {
|
||||||
StringLike::StringLiteral(ast::ExprStringLiteral { value, .. }) => value == "0.0.0.0",
|
StringLike::String(ast::ExprStringLiteral { value, .. }) => {
|
||||||
StringLike::FStringLiteral(ast::FStringLiteralElement { value, .. }) => {
|
if value == "0.0.0.0" {
|
||||||
&**value == "0.0.0.0"
|
|
||||||
}
|
|
||||||
StringLike::BytesLiteral(_) => return,
|
|
||||||
};
|
|
||||||
|
|
||||||
if is_bind_all_interface {
|
|
||||||
checker
|
checker
|
||||||
.diagnostics
|
.diagnostics
|
||||||
.push(Diagnostic::new(HardcodedBindAllInterfaces, string.range()));
|
.push(Diagnostic::new(HardcodedBindAllInterfaces, string.range()));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
StringLike::FString(ast::ExprFString { value, .. }) => {
|
||||||
|
for part in value {
|
||||||
|
match part {
|
||||||
|
ast::FStringPart::Literal(literal) => {
|
||||||
|
if &**literal == "0.0.0.0" {
|
||||||
|
checker
|
||||||
|
.diagnostics
|
||||||
|
.push(Diagnostic::new(HardcodedBindAllInterfaces, literal.range()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ast::FStringPart::FString(f_string) => {
|
||||||
|
for literal in f_string.literals() {
|
||||||
|
if &**literal == "0.0.0.0" {
|
||||||
|
checker.diagnostics.push(Diagnostic::new(
|
||||||
|
HardcodedBindAllInterfaces,
|
||||||
|
literal.range(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
StringLike::Bytes(_) => (),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use ruff_python_ast::{self as ast, Expr, StringLike};
|
use ruff_python_ast::{self as ast, Expr, StringLike};
|
||||||
use ruff_text_size::Ranged;
|
use ruff_text_size::{Ranged, TextRange};
|
||||||
|
|
||||||
use ruff_diagnostics::{Diagnostic, Violation};
|
use ruff_diagnostics::{Diagnostic, Violation};
|
||||||
use ruff_macros::{derive_message_formats, violation};
|
use ruff_macros::{derive_message_formats, violation};
|
||||||
|
@ -53,12 +53,29 @@ impl Violation for HardcodedTempFile {
|
||||||
|
|
||||||
/// S108
|
/// S108
|
||||||
pub(crate) fn hardcoded_tmp_directory(checker: &mut Checker, string: StringLike) {
|
pub(crate) fn hardcoded_tmp_directory(checker: &mut Checker, string: StringLike) {
|
||||||
let value = match string {
|
match string {
|
||||||
StringLike::StringLiteral(ast::ExprStringLiteral { value, .. }) => value.to_str(),
|
StringLike::String(ast::ExprStringLiteral { value, .. }) => {
|
||||||
StringLike::FStringLiteral(ast::FStringLiteralElement { value, .. }) => value,
|
check(checker, value.to_str(), string.range());
|
||||||
StringLike::BytesLiteral(_) => return,
|
}
|
||||||
};
|
StringLike::FString(ast::ExprFString { value, .. }) => {
|
||||||
|
for part in value {
|
||||||
|
match part {
|
||||||
|
ast::FStringPart::Literal(literal) => {
|
||||||
|
check(checker, literal, literal.range());
|
||||||
|
}
|
||||||
|
ast::FStringPart::FString(f_string) => {
|
||||||
|
for literal in f_string.literals() {
|
||||||
|
check(checker, literal, literal.range());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
StringLike::Bytes(_) => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check(checker: &mut Checker, value: &str, range: TextRange) {
|
||||||
if !checker
|
if !checker
|
||||||
.settings
|
.settings
|
||||||
.flake8_bandit
|
.flake8_bandit
|
||||||
|
@ -85,6 +102,6 @@ pub(crate) fn hardcoded_tmp_directory(checker: &mut Checker, string: StringLike)
|
||||||
HardcodedTempFile {
|
HardcodedTempFile {
|
||||||
string: value.to_string(),
|
string: value.to_string(),
|
||||||
},
|
},
|
||||||
string.range(),
|
range,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,4 +42,23 @@ S104.py:19:9: S104 Possible binding to all interfaces
|
||||||
20 | print(x)
|
20 | print(x)
|
||||||
|
|
|
|
||||||
|
|
||||||
|
S104.py:24:1: S104 Possible binding to all interfaces
|
||||||
|
|
|
||||||
|
23 | # Implicit string concatenation
|
||||||
|
24 | "0.0.0.0" f"0.0.0.0{expr}0.0.0.0"
|
||||||
|
| ^^^^^^^^^ S104
|
||||||
|
|
|
||||||
|
|
||||||
|
S104.py:24:13: S104 Possible binding to all interfaces
|
||||||
|
|
|
||||||
|
23 | # Implicit string concatenation
|
||||||
|
24 | "0.0.0.0" f"0.0.0.0{expr}0.0.0.0"
|
||||||
|
| ^^^^^^^ S104
|
||||||
|
|
|
||||||
|
|
||||||
|
S104.py:24:26: S104 Possible binding to all interfaces
|
||||||
|
|
|
||||||
|
23 | # Implicit string concatenation
|
||||||
|
24 | "0.0.0.0" f"0.0.0.0{expr}0.0.0.0"
|
||||||
|
| ^^^^^^^ S104
|
||||||
|
|
|
||||||
|
|
|
@ -37,4 +37,28 @@ S108.py:14:11: S108 Probable insecure usage of temporary file or directory: "/de
|
||||||
15 | f.write("def")
|
15 | f.write("def")
|
||||||
|
|
|
|
||||||
|
|
||||||
|
S108.py:22:11: S108 Probable insecure usage of temporary file or directory: "/tmp/abc"
|
||||||
|
|
|
||||||
|
21 | # Implicit string concatenation
|
||||||
|
22 | with open("/tmp/" "abc", "w") as f:
|
||||||
|
| ^^^^^^^^^^^^^ S108
|
||||||
|
23 | f.write("def")
|
||||||
|
|
|
||||||
|
|
||||||
|
S108.py:25:11: S108 Probable insecure usage of temporary file or directory: "/tmp/abc"
|
||||||
|
|
|
||||||
|
23 | f.write("def")
|
||||||
|
24 |
|
||||||
|
25 | with open("/tmp/abc" f"/tmp/abc", "w") as f:
|
||||||
|
| ^^^^^^^^^^ S108
|
||||||
|
26 | f.write("def")
|
||||||
|
|
|
||||||
|
|
||||||
|
S108.py:25:24: S108 Probable insecure usage of temporary file or directory: "/tmp/abc"
|
||||||
|
|
|
||||||
|
23 | f.write("def")
|
||||||
|
24 |
|
||||||
|
25 | with open("/tmp/abc" f"/tmp/abc", "w") as f:
|
||||||
|
| ^^^^^^^^ S108
|
||||||
|
26 | f.write("def")
|
||||||
|
|
|
||||||
|
|
|
@ -45,4 +45,28 @@ S108.py:18:11: S108 Probable insecure usage of temporary file or directory: "/fo
|
||||||
19 | f.write("def")
|
19 | f.write("def")
|
||||||
|
|
|
|
||||||
|
|
||||||
|
S108.py:22:11: S108 Probable insecure usage of temporary file or directory: "/tmp/abc"
|
||||||
|
|
|
||||||
|
21 | # Implicit string concatenation
|
||||||
|
22 | with open("/tmp/" "abc", "w") as f:
|
||||||
|
| ^^^^^^^^^^^^^ S108
|
||||||
|
23 | f.write("def")
|
||||||
|
|
|
||||||
|
|
||||||
|
S108.py:25:11: S108 Probable insecure usage of temporary file or directory: "/tmp/abc"
|
||||||
|
|
|
||||||
|
23 | f.write("def")
|
||||||
|
24 |
|
||||||
|
25 | with open("/tmp/abc" f"/tmp/abc", "w") as f:
|
||||||
|
| ^^^^^^^^^^ S108
|
||||||
|
26 | f.write("def")
|
||||||
|
|
|
||||||
|
|
||||||
|
S108.py:25:24: S108 Probable insecure usage of temporary file or directory: "/tmp/abc"
|
||||||
|
|
|
||||||
|
23 | f.write("def")
|
||||||
|
24 |
|
||||||
|
25 | with open("/tmp/abc" f"/tmp/abc", "w") as f:
|
||||||
|
| ^^^^^^^^ S108
|
||||||
|
26 | f.write("def")
|
||||||
|
|
|
||||||
|
|
|
@ -57,11 +57,9 @@ pub(crate) fn string_or_bytes_too_long(checker: &mut Checker, string: StringLike
|
||||||
}
|
}
|
||||||
|
|
||||||
let length = match string {
|
let length = match string {
|
||||||
StringLike::StringLiteral(ast::ExprStringLiteral { value, .. }) => value.chars().count(),
|
StringLike::String(ast::ExprStringLiteral { value, .. }) => value.chars().count(),
|
||||||
StringLike::BytesLiteral(ast::ExprBytesLiteral { value, .. }) => value.len(),
|
StringLike::Bytes(ast::ExprBytesLiteral { value, .. }) => value.len(),
|
||||||
StringLike::FStringLiteral(ast::FStringLiteralElement { value, .. }) => {
|
StringLike::FString(node) => count_f_string_chars(node),
|
||||||
value.chars().count()
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
if length <= 50 {
|
if length <= 50 {
|
||||||
return;
|
return;
|
||||||
|
@ -75,6 +73,26 @@ pub(crate) fn string_or_bytes_too_long(checker: &mut Checker, string: StringLike
|
||||||
checker.diagnostics.push(diagnostic);
|
checker.diagnostics.push(diagnostic);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Count the number of visible characters in an f-string. This accounts for
|
||||||
|
/// implicitly concatenated f-strings as well.
|
||||||
|
fn count_f_string_chars(f_string: &ast::ExprFString) -> usize {
|
||||||
|
f_string
|
||||||
|
.value
|
||||||
|
.iter()
|
||||||
|
.map(|part| match part {
|
||||||
|
ast::FStringPart::Literal(string) => string.chars().count(),
|
||||||
|
ast::FStringPart::FString(f_string) => f_string
|
||||||
|
.elements
|
||||||
|
.iter()
|
||||||
|
.map(|element| match element {
|
||||||
|
ast::FStringElement::Literal(string) => string.chars().count(),
|
||||||
|
ast::FStringElement::Expression(expr) => expr.range().len().to_usize(),
|
||||||
|
})
|
||||||
|
.sum(),
|
||||||
|
})
|
||||||
|
.sum()
|
||||||
|
}
|
||||||
|
|
||||||
fn is_warnings_dot_deprecated(expr: Option<&ast::Expr>, semantic: &SemanticModel) -> bool {
|
fn is_warnings_dot_deprecated(expr: Option<&ast::Expr>, semantic: &SemanticModel) -> bool {
|
||||||
// Does `expr` represent a call to `warnings.deprecated` or `typing_extensions.deprecated`?
|
// Does `expr` represent a call to `warnings.deprecated` or `typing_extensions.deprecated`?
|
||||||
let Some(expr) = expr else {
|
let Some(expr) = expr else {
|
||||||
|
|
|
@ -105,12 +105,12 @@ PYI053.pyi:34:14: PYI053 [*] String and bytes literals longer than 50 characters
|
||||||
36 36 | ffoo: str = f"50 character stringggggggggggggggggggggggggggggggg" # OK
|
36 36 | ffoo: str = f"50 character stringggggggggggggggggggggggggggggggg" # OK
|
||||||
37 37 |
|
37 37 |
|
||||||
|
|
||||||
PYI053.pyi:38:15: PYI053 [*] String and bytes literals longer than 50 characters are not permitted
|
PYI053.pyi:38:13: PYI053 [*] String and bytes literals longer than 50 characters are not permitted
|
||||||
|
|
|
|
||||||
36 | ffoo: str = f"50 character stringggggggggggggggggggggggggggggggg" # OK
|
36 | ffoo: str = f"50 character stringggggggggggggggggggggggggggggggg" # OK
|
||||||
37 |
|
37 |
|
||||||
38 | fbar: str = f"51 character stringgggggggggggggggggggggggggggggggg" # Error: PYI053
|
38 | fbar: str = f"51 character stringgggggggggggggggggggggggggggggggg" # Error: PYI053
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI053
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI053
|
||||||
39 |
|
39 |
|
||||||
40 | class Demo:
|
40 | class Demo:
|
||||||
|
|
|
|
||||||
|
@ -121,7 +121,7 @@ PYI053.pyi:38:15: PYI053 [*] String and bytes literals longer than 50 characters
|
||||||
36 36 | ffoo: str = f"50 character stringggggggggggggggggggggggggggggggg" # OK
|
36 36 | ffoo: str = f"50 character stringggggggggggggggggggggggggggggggg" # OK
|
||||||
37 37 |
|
37 37 |
|
||||||
38 |-fbar: str = f"51 character stringgggggggggggggggggggggggggggggggg" # Error: PYI053
|
38 |-fbar: str = f"51 character stringgggggggggggggggggggggggggggggggg" # Error: PYI053
|
||||||
38 |+fbar: str = f"..." # Error: PYI053
|
38 |+fbar: str = ... # Error: PYI053
|
||||||
39 39 |
|
39 39 |
|
||||||
40 40 | class Demo:
|
40 40 | class Demo:
|
||||||
41 41 | """Docstrings are excluded from this rule. Some padding.""" # OK
|
41 41 | """Docstrings are excluded from this rule. Some padding.""" # OK
|
||||||
|
@ -144,5 +144,20 @@ PYI053.pyi:64:5: PYI053 [*] String and bytes literals longer than 50 characters
|
||||||
64 |+ ... # Error: PYI053
|
64 |+ ... # Error: PYI053
|
||||||
65 65 | )
|
65 65 | )
|
||||||
66 66 | def not_a_deprecated_function() -> None: ...
|
66 66 | def not_a_deprecated_function() -> None: ...
|
||||||
|
67 67 |
|
||||||
|
|
||||||
|
PYI053.pyi:68:13: PYI053 [*] String and bytes literals longer than 50 characters are not permitted
|
||||||
|
|
|
||||||
|
66 | def not_a_deprecated_function() -> None: ...
|
||||||
|
67 |
|
||||||
|
68 | fbaz: str = f"51 character {foo} stringgggggggggggggggggggggggggg" # Error: PYI053
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI053
|
||||||
|
|
|
||||||
|
= help: Replace with `...`
|
||||||
|
|
||||||
|
ℹ Safe fix
|
||||||
|
65 65 | )
|
||||||
|
66 66 | def not_a_deprecated_function() -> None: ...
|
||||||
|
67 67 |
|
||||||
|
68 |-fbaz: str = f"51 character {foo} stringgggggggggggggggggggggggggg" # Error: PYI053
|
||||||
|
68 |+fbaz: str = ... # Error: PYI053
|
||||||
|
|
|
@ -4,7 +4,7 @@ use bitflags::bitflags;
|
||||||
|
|
||||||
use ruff_diagnostics::{Diagnostic, DiagnosticKind, Violation};
|
use ruff_diagnostics::{Diagnostic, DiagnosticKind, Violation};
|
||||||
use ruff_macros::{derive_message_formats, violation};
|
use ruff_macros::{derive_message_formats, violation};
|
||||||
use ruff_python_ast::StringLike;
|
use ruff_python_ast::{self as ast, StringLike};
|
||||||
use ruff_source_file::Locator;
|
use ruff_source_file::Locator;
|
||||||
use ruff_text_size::{Ranged, TextLen, TextRange, TextSize};
|
use ruff_text_size::{Ranged, TextLen, TextRange, TextSize};
|
||||||
|
|
||||||
|
@ -193,29 +193,47 @@ pub(crate) fn ambiguous_unicode_character_string(checker: &mut Checker, string_l
|
||||||
};
|
};
|
||||||
|
|
||||||
match string_like {
|
match string_like {
|
||||||
StringLike::StringLiteral(string_literal) => {
|
StringLike::String(node) => {
|
||||||
for string in &string_literal.value {
|
for literal in &node.value {
|
||||||
let text = checker.locator().slice(string);
|
let text = checker.locator().slice(literal);
|
||||||
ambiguous_unicode_character(
|
ambiguous_unicode_character(
|
||||||
&mut checker.diagnostics,
|
&mut checker.diagnostics,
|
||||||
text,
|
text,
|
||||||
string.range(),
|
literal.range(),
|
||||||
context,
|
context,
|
||||||
checker.settings,
|
checker.settings,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
StringLike::FStringLiteral(f_string_literal) => {
|
StringLike::FString(node) => {
|
||||||
let text = checker.locator().slice(f_string_literal);
|
for part in &node.value {
|
||||||
|
match part {
|
||||||
|
ast::FStringPart::Literal(literal) => {
|
||||||
|
let text = checker.locator().slice(literal);
|
||||||
ambiguous_unicode_character(
|
ambiguous_unicode_character(
|
||||||
&mut checker.diagnostics,
|
&mut checker.diagnostics,
|
||||||
text,
|
text,
|
||||||
f_string_literal.range(),
|
literal.range(),
|
||||||
context,
|
context,
|
||||||
checker.settings,
|
checker.settings,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
StringLike::BytesLiteral(_) => (),
|
ast::FStringPart::FString(f_string) => {
|
||||||
|
for literal in f_string.literals() {
|
||||||
|
let text = checker.locator().slice(literal);
|
||||||
|
ambiguous_unicode_character(
|
||||||
|
&mut checker.diagnostics,
|
||||||
|
text,
|
||||||
|
literal.range(),
|
||||||
|
context,
|
||||||
|
checker.settings,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
StringLike::Bytes(_) => (),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -155,4 +155,16 @@ confusables.py:46:62: RUF003 Comment contains ambiguous `᜵` (PHILIPPINE SINGLE
|
||||||
47 | }"
|
47 | }"
|
||||||
|
|
|
|
||||||
|
|
||||||
|
confusables.py:58:6: RUF001 String contains ambiguous `𝐁` (MATHEMATICAL BOLD CAPITAL B). Did you mean `B` (LATIN CAPITAL LETTER B)?
|
||||||
|
|
|
||||||
|
57 | # Implicit string concatenation
|
||||||
|
58 | x = "𝐁ad" f"𝐁ad string"
|
||||||
|
| ^ RUF001
|
||||||
|
|
|
||||||
|
|
||||||
|
confusables.py:58:13: RUF001 String contains ambiguous `𝐁` (MATHEMATICAL BOLD CAPITAL B). Did you mean `B` (LATIN CAPITAL LETTER B)?
|
||||||
|
|
|
||||||
|
57 | # Implicit string concatenation
|
||||||
|
58 | x = "𝐁ad" f"𝐁ad string"
|
||||||
|
| ^ RUF001
|
||||||
|
|
|
||||||
|
|
|
@ -159,6 +159,20 @@ confusables.py:55:28: RUF001 String contains ambiguous `µ` (MICRO SIGN). Did yo
|
||||||
|
|
|
|
||||||
55 | assert getattr(Labware(), "µL") == 1.5
|
55 | assert getattr(Labware(), "µL") == 1.5
|
||||||
| ^ RUF001
|
| ^ RUF001
|
||||||
|
56 |
|
||||||
|
57 | # Implicit string concatenation
|
||||||
|
|
|
|
||||||
|
|
||||||
|
confusables.py:58:6: RUF001 String contains ambiguous `𝐁` (MATHEMATICAL BOLD CAPITAL B). Did you mean `B` (LATIN CAPITAL LETTER B)?
|
||||||
|
|
|
||||||
|
57 | # Implicit string concatenation
|
||||||
|
58 | x = "𝐁ad" f"𝐁ad string"
|
||||||
|
| ^ RUF001
|
||||||
|
|
|
||||||
|
|
||||||
|
confusables.py:58:13: RUF001 String contains ambiguous `𝐁` (MATHEMATICAL BOLD CAPITAL B). Did you mean `B` (LATIN CAPITAL LETTER B)?
|
||||||
|
|
|
||||||
|
57 | # Implicit string concatenation
|
||||||
|
58 | x = "𝐁ad" f"𝐁ad string"
|
||||||
|
| ^ RUF001
|
||||||
|
|
|
||||||
|
|
|
@ -399,35 +399,35 @@ impl LiteralExpressionRef<'_> {
|
||||||
/// f-strings.
|
/// f-strings.
|
||||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||||
pub enum StringLike<'a> {
|
pub enum StringLike<'a> {
|
||||||
StringLiteral(&'a ast::ExprStringLiteral),
|
String(&'a ast::ExprStringLiteral),
|
||||||
BytesLiteral(&'a ast::ExprBytesLiteral),
|
Bytes(&'a ast::ExprBytesLiteral),
|
||||||
FStringLiteral(&'a ast::FStringLiteralElement),
|
FString(&'a ast::ExprFString),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> From<&'a ast::ExprStringLiteral> for StringLike<'a> {
|
impl<'a> From<&'a ast::ExprStringLiteral> for StringLike<'a> {
|
||||||
fn from(value: &'a ast::ExprStringLiteral) -> Self {
|
fn from(value: &'a ast::ExprStringLiteral) -> Self {
|
||||||
StringLike::StringLiteral(value)
|
StringLike::String(value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> From<&'a ast::ExprBytesLiteral> for StringLike<'a> {
|
impl<'a> From<&'a ast::ExprBytesLiteral> for StringLike<'a> {
|
||||||
fn from(value: &'a ast::ExprBytesLiteral) -> Self {
|
fn from(value: &'a ast::ExprBytesLiteral) -> Self {
|
||||||
StringLike::BytesLiteral(value)
|
StringLike::Bytes(value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> From<&'a ast::FStringLiteralElement> for StringLike<'a> {
|
impl<'a> From<&'a ast::ExprFString> for StringLike<'a> {
|
||||||
fn from(value: &'a ast::FStringLiteralElement) -> Self {
|
fn from(value: &'a ast::ExprFString) -> Self {
|
||||||
StringLike::FStringLiteral(value)
|
StringLike::FString(value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Ranged for StringLike<'_> {
|
impl Ranged for StringLike<'_> {
|
||||||
fn range(&self) -> TextRange {
|
fn range(&self) -> TextRange {
|
||||||
match self {
|
match self {
|
||||||
StringLike::StringLiteral(literal) => literal.range(),
|
StringLike::String(literal) => literal.range(),
|
||||||
StringLike::BytesLiteral(literal) => literal.range(),
|
StringLike::Bytes(literal) => literal.range(),
|
||||||
StringLike::FStringLiteral(literal) => literal.range(),
|
StringLike::FString(literal) => literal.range(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue