-
Notifications
You must be signed in to change notification settings - Fork 903
/
string_or_bytes_too_long.rs
121 lines (110 loc) · 3.68 KB
/
string_or_bytes_too_long.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::helpers::is_docstring_stmt;
use ruff_python_ast::{self as ast, StringLike};
use ruff_python_semantic::SemanticModel;
use ruff_text_size::Ranged;
use crate::checkers::ast::Checker;
/// ## What it does
/// Checks for the use of string and bytes literals longer than 50 characters
/// in stub (`.pyi`) files.
///
/// ## Why is this bad?
/// If a function has a default value where the string or bytes representation
/// is greater than 50 characters, it is likely to be an implementation detail
/// or a constant that varies depending on the system you're running on.
///
/// Consider replacing such constants with ellipses (`...`).
///
/// ## Example
/// ```python
/// def foo(arg: str = "51 character stringgggggggggggggggggggggggggggggggg") -> None:
/// ...
/// ```
///
/// Use instead:
/// ```python
/// def foo(arg: str = ...) -> None:
/// ...
/// ```
#[violation]
pub struct StringOrBytesTooLong;
impl AlwaysFixableViolation for StringOrBytesTooLong {
#[derive_message_formats]
fn message(&self) -> String {
format!("String and bytes literals longer than 50 characters are not permitted")
}
fn fix_title(&self) -> String {
"Replace with `...`".to_string()
}
}
/// PYI053
pub(crate) fn string_or_bytes_too_long(checker: &mut Checker, string: StringLike) {
let semantic = checker.semantic();
// Ignore docstrings.
if is_docstring_stmt(semantic.current_statement()) {
return;
}
if is_warnings_dot_deprecated(semantic.current_expression_parent(), semantic) {
return;
}
let length = match string {
StringLike::String(ast::ExprStringLiteral { value, .. }) => {
value.chars().count(),
}
StringLike::Bytes(ast::ExprBytesLiteral { value, .. }) => value.len(),
StringLike::FString(node) => count_f_string_chars(node),
};
if length <= 50 {
return;
}
let mut diagnostic = Diagnostic::new(StringOrBytesTooLong, string.range());
diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
"...".to_string(),
string.range(),
)));
checker.diagnostics.push(diagnostic);
}
/// Count the number of characters in an f-string. This accounts for implicitly concatenated
/// f-strings as well. For example, the following f-string has 12 characters as highlighted
/// by the caret symbols:
///
/// ```python
/// x = "one" f"one{expr}one" f"one" f"{expr}"
/// # ^^^ ^^^ ^^^ ^^^
/// ````
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| {
element
.as_literal()
.map_or(0, |literal| literal.chars().count())
})
.sum(),
})
.sum()
}
fn is_warnings_dot_deprecated(expr: Option<&ast::Expr>, semantic: &SemanticModel) -> bool {
// Does `expr` represent a call to `warnings.deprecated` or `typing_extensions.deprecated`?
let Some(expr) = expr else {
return false;
};
let Some(call) = expr.as_call_expr() else {
return false;
};
semantic
.resolve_qualified_name(&call.func)
.is_some_and(|qualified_name| {
matches!(
qualified_name.segments(),
["warnings" | "typing_extensions", "deprecated"]
)
})
}