Skip to content

Commit 258c6d5

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

File tree

6 files changed

+53
-28
lines changed

6 files changed

+53
-28
lines changed

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: 4 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)]
@@ -21,6 +22,7 @@
2122

2223
// FIXME: switch to something more ergonomic here, once available.
2324
// (Currently there is no way to opt into sysroot crates without `extern crate`.)
25+
extern crate regex;
2426
extern crate rustc_arena;
2527
extern crate rustc_ast;
2628
extern crate rustc_ast_pretty;
@@ -283,7 +285,7 @@ mod redundant_type_annotations;
283285
mod ref_option_ref;
284286
mod ref_patterns;
285287
mod reference;
286-
mod regex;
288+
mod regular_expressions;
287289
mod return_self_not_must_use;
288290
mod returns;
289291
mod same_name_method;
@@ -726,7 +728,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
726728
store.register_late_pass(|_| Box::new(drop_forget_ref::DropForgetRef));
727729
store.register_late_pass(|_| Box::new(empty_enum::EmptyEnum));
728730
store.register_late_pass(|_| Box::new(invalid_upcast_comparisons::InvalidUpcastComparisons));
729-
store.register_late_pass(|_| Box::<regex::Regex>::default());
731+
store.register_late_pass(|_| Box::<regular_expressions::Regex>::default());
730732
let ignore_interior_mutability = conf.ignore_interior_mutability.clone();
731733
store.register_late_pass(move |_| Box::new(copies::CopyAndPaste::new(ignore_interior_mutability.clone())));
732734
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)