Skip to content

Commit b1a4236

Browse files
committed
Don't escape unicode escape braces in print_literal
1 parent 5436dba commit b1a4236

File tree

7 files changed

+54
-30
lines changed

7 files changed

+54
-30
lines changed

clippy_lints/Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ serde = { version = "1.0", features = ["derive"] }
2222
serde_json = { version = "1.0", optional = true }
2323
tempfile = { version = "3.3.0", optional = true }
2424
toml = "0.7.3"
25-
regex = { version = "1.5", optional = true }
25+
regex = { version = "1.5" }
2626
unicode-normalization = "0.1"
2727
unicode-script = { version = "0.5", default-features = false }
2828
semver = "1.0"
@@ -32,7 +32,7 @@ url = "2.2"
3232
[features]
3333
deny-warnings = ["clippy_utils/deny-warnings"]
3434
# build clippy with internal lints enabled, off by default
35-
internal = ["clippy_utils/internal", "serde_json", "tempfile", "regex"]
35+
internal = ["clippy_utils/internal", "serde_json", "tempfile"]
3636

3737
[package.metadata.rust-analyzer]
3838
# This crate uses #[feature(rustc_private)]

clippy_lints/src/declared_lints.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -573,8 +573,8 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
573573
crate::ref_option_ref::REF_OPTION_REF_INFO,
574574
crate::ref_patterns::REF_PATTERNS_INFO,
575575
crate::reference::DEREF_ADDROF_INFO,
576-
crate::regex::INVALID_REGEX_INFO,
577-
crate::regex::TRIVIAL_REGEX_INFO,
576+
crate::regular_expressions::INVALID_REGEX_INFO,
577+
crate::regular_expressions::TRIVIAL_REGEX_INFO,
578578
crate::return_self_not_must_use::RETURN_SELF_NOT_MUST_USE_INFO,
579579
crate::returns::LET_AND_RETURN_INFO,
580580
crate::returns::NEEDLESS_RETURN_INFO,

clippy_lints/src/lib.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
#![feature(box_patterns)]
44
#![feature(if_let_guard)]
55
#![feature(iter_intersperse)]
6+
#![feature(lazy_cell)]
67
#![feature(let_chains)]
78
#![feature(lint_reasons)]
89
#![feature(never_type)]
@@ -283,7 +284,7 @@ mod redundant_type_annotations;
283284
mod ref_option_ref;
284285
mod ref_patterns;
285286
mod reference;
286-
mod regex;
287+
mod regular_expressions;
287288
mod return_self_not_must_use;
288289
mod returns;
289290
mod same_name_method;
@@ -726,7 +727,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
726727
store.register_late_pass(|_| Box::new(drop_forget_ref::DropForgetRef));
727728
store.register_late_pass(|_| Box::new(empty_enum::EmptyEnum));
728729
store.register_late_pass(|_| Box::new(invalid_upcast_comparisons::InvalidUpcastComparisons));
729-
store.register_late_pass(|_| Box::<regex::Regex>::default());
730+
store.register_late_pass(|_| Box::<regular_expressions::Regex>::default());
730731
let ignore_interior_mutability = conf.ignore_interior_mutability.clone();
731732
store.register_late_pass(move |_| Box::new(copies::CopyAndPaste::new(ignore_interior_mutability.clone())));
732733
store.register_late_pass(|_| Box::new(copy_iterator::CopyIterator));
File renamed without changes.

clippy_lints/src/write.rs

Lines changed: 31 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,15 @@ use clippy_utils::diagnostics::{span_lint, span_lint_and_then};
22
use clippy_utils::macros::{find_format_args, format_arg_removal_span, root_macro_call_first_node, MacroCall};
33
use clippy_utils::source::{expand_past_previous_comma, snippet_opt};
44
use clippy_utils::{is_in_cfg_test, is_in_test_function};
5+
use regex::{Captures, Regex};
56
use rustc_ast::token::LitKind;
67
use rustc_ast::{FormatArgPosition, FormatArgs, FormatArgsPiece, FormatOptions, FormatPlaceholder, FormatTrait};
78
use rustc_errors::Applicability;
89
use rustc_hir::{Expr, Impl, Item, ItemKind};
910
use rustc_lint::{LateContext, LateLintPass, LintContext};
1011
use rustc_session::{declare_tool_lint, impl_lint_pass};
1112
use rustc_span::{sym, BytePos};
13+
use std::sync::LazyLock;
1214

1315
declare_clippy_lint! {
1416
/// ### What it does
@@ -471,9 +473,9 @@ fn check_literal(cx: &LateContext<'_>, format_args: &FormatArgs, name: &str) {
471473
&& let rustc_ast::ExprKind::Lit(lit) = &arg.expr.kind
472474
&& !arg.expr.span.from_expansion()
473475
&& let Some(value_string) = snippet_opt(cx, arg.expr.span)
474-
{
476+
{
475477
let (replacement, replace_raw) = match lit.kind {
476-
LitKind::Str | LitKind::StrRaw(_) => match extract_str_literal(&value_string) {
478+
LitKind::Str | LitKind::StrRaw(_) => match extract_str_literal(&value_string) {
477479
Some(extracted) => extracted,
478480
None => return,
479481
},
@@ -519,27 +521,33 @@ fn check_literal(cx: &LateContext<'_>, format_args: &FormatArgs, name: &str) {
519521
},
520522
};
521523

522-
span_lint_and_then(
523-
cx,
524-
lint,
525-
arg.expr.span,
526-
"literal with an empty format string",
527-
|diag| {
528-
if let Some(replacement) = replacement
529-
// `format!("{}", "a")`, `format!("{named}", named = "b")
530-
// ~~~~~ ~~~~~~~~~~~~~
531-
&& let Some(removal_span) = format_arg_removal_span(format_args, index)
532-
{
533-
let replacement = replacement.replace('{', "{{").replace('}', "}}");
534-
diag.multipart_suggestion(
535-
"try",
536-
vec![(*placeholder_span, replacement), (removal_span, String::new())],
537-
Applicability::MachineApplicable,
538-
);
539-
}
540-
},
541-
);
542-
524+
span_lint_and_then(cx, lint, arg.expr.span, "literal with an empty format string", |diag| {
525+
static ESCAPE_OR_BRACE: LazyLock<Regex> =
526+
LazyLock::new(|| Regex::new(r"\\u\{[[:xdigit:]]*\}|[{}]").unwrap());
527+
528+
if let Some(replacement) = replacement
529+
// `format!("{}", "a")`, `format!("{named}", named = "b")
530+
// ~~~~~ ~~~~~~~~~~~~~
531+
&& let Some(removal_span) = format_arg_removal_span(format_args, index)
532+
{
533+
let replacement = ESCAPE_OR_BRACE.replace_all(&replacement, |captures: &Captures<'_>| {
534+
match &captures[0] {
535+
"{" => "{{",
536+
"}" => "}}",
537+
escape => escape,
538+
}
539+
.to_string()
540+
});
541+
diag.multipart_suggestion(
542+
"try",
543+
vec![
544+
(*placeholder_span, replacement.into_owned()),
545+
(removal_span, String::new()),
546+
],
547+
Applicability::MachineApplicable,
548+
);
549+
}
550+
});
543551
}
544552
}
545553
}

tests/ui/print_literal.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,4 +42,7 @@ fn main() {
4242
// The string literal from `file!()` has a callsite span that isn't marked as coming from an
4343
// expansion
4444
println!("file: {}", file!());
45+
46+
// Braces in unicode escapes should not be escaped
47+
println!("{}", "{} \u{ab123} {}");
4548
}

tests/ui/print_literal.stderr

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,5 +143,17 @@ LL - println!("{bar} {foo}", foo = "hello", bar = "world");
143143
LL + println!("{bar} hello", bar = "world");
144144
|
145145

146-
error: aborting due to 12 previous errors
146+
error: literal with an empty format string
147+
--> $DIR/print_literal.rs:47:20
148+
|
149+
LL | println!("{}", "{} /u{ab123} {}");
150+
| ^^^^^^^^^^^^^^^^^
151+
|
152+
help: try
153+
|
154+
LL - println!("{}", "{} /u{ab123} {}");
155+
LL + println!("{{}} /u{ab123} {{}}");
156+
|
157+
158+
error: aborting due to 13 previous errors
147159

0 commit comments

Comments
 (0)