From ee16c3312a722cf7b492c85ed6c1c331558835c9 Mon Sep 17 00:00:00 2001 From: Victorien Elvinger Date: Thu, 25 Apr 2024 16:36:55 +0200 Subject: [PATCH] fix(lint/useTemplate); correctly escape characters --- CHANGELOG.md | 6 +++ .../style/useTemplate/invalidIssue2580.js | 2 + .../useTemplate/invalidIssue2580.js.snap | 28 +++++++++++++ crates/biome_js_factory/src/utils.rs | 39 +++++++++---------- 4 files changed, 54 insertions(+), 21 deletions(-) create mode 100644 crates/biome_js_analyze/tests/specs/style/useTemplate/invalidIssue2580.js create mode 100644 crates/biome_js_analyze/tests/specs/style/useTemplate/invalidIssue2580.js.snap diff --git a/CHANGELOG.md b/CHANGELOG.md index 10770333b5b2..9858bcbab20a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -79,6 +79,12 @@ our [guidelines for writing a good changelog entry](https://github.com/biomejs/b Contributed by @Conaclos +- Fix [useTemplate](https://biomejs.dev/linter/rules/use-template/) that wrongly escaped strings in some edge cases ([#2580](https://github.com/biomejs/biome/issues/2580)). + + Previously, the rule didn't correctly escaped characters preceded by an escaped character. + + Contributed by @Conaclos + ### Parser ## 1.7.1 (2024-04-22) diff --git a/crates/biome_js_analyze/tests/specs/style/useTemplate/invalidIssue2580.js b/crates/biome_js_analyze/tests/specs/style/useTemplate/invalidIssue2580.js new file mode 100644 index 000000000000..db5793aff977 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/style/useTemplate/invalidIssue2580.js @@ -0,0 +1,2 @@ +// Issue https://github.com/biomejs/biome/issues/2580 +'```ts\n' + x + '\n```'; \ No newline at end of file diff --git a/crates/biome_js_analyze/tests/specs/style/useTemplate/invalidIssue2580.js.snap b/crates/biome_js_analyze/tests/specs/style/useTemplate/invalidIssue2580.js.snap new file mode 100644 index 000000000000..da417ce241b1 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/style/useTemplate/invalidIssue2580.js.snap @@ -0,0 +1,28 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +expression: invalidIssue2580.js +--- +# Input +```jsx +// Issue https://github.com/biomejs/biome/issues/2580 +'```ts\n' + x + '\n```'; +``` + +# Diagnostics +``` +invalidIssue2580.js:2:1 lint/style/useTemplate FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Template literals are preferred over string concatenation. + + 1 │ // Issue https://github.com/biomejs/biome/issues/2580 + > 2 │ '```ts\n' + x + '\n```'; + │ ^^^^^^^^^^^^^^^^^^^^^^^ + + i Unsafe fix: Use a template literal. + + 1 1 │ // Issue https://github.com/biomejs/biome/issues/2580 + 2 │ - '```ts\n'·+·x·+·'\n```'; + 2 │ + `\`\`\`ts\n${x}\n\`\`\``; + + +``` diff --git a/crates/biome_js_factory/src/utils.rs b/crates/biome_js_factory/src/utils.rs index de43cfc7bbf5..1fa07e1fcff9 100644 --- a/crates/biome_js_factory/src/utils.rs +++ b/crates/biome_js_factory/src/utils.rs @@ -17,41 +17,36 @@ pub fn escape<'a>( needs_escaping: &[&str], escaping_char: char, ) -> std::borrow::Cow<'a, str> { + debug_assert!(!needs_escaping.is_empty()); let mut escaped = String::new(); let mut iter = unescaped_string.char_indices(); - let mut is_escaped = false; - 'unescaped: while let Some((idx, chr)) = iter.next() { + let mut last_copied_idx = 0; + while let Some((idx, chr)) = iter.next() { if chr == escaping_char { - is_escaped = !is_escaped; + // The next character is esacaped + iter.next(); } else { for candidate in needs_escaping { if unescaped_string[idx..].starts_with(candidate) { - if !is_escaped { - if escaped.is_empty() { - escaped = String::with_capacity(unescaped_string.len() * 2); - escaped.push_str(&unescaped_string[0..idx]); - } - escaped.push(escaping_char); - escaped.push_str(candidate); - for _ in candidate.chars().skip(1) { - iter.next(); - } - continue 'unescaped; - } else { - is_escaped = false; + if escaped.is_empty() { + escaped = String::with_capacity(unescaped_string.len() * 2 - idx); } + escaped.push_str(&unescaped_string[last_copied_idx..idx]); + escaped.push(escaping_char); + escaped.push_str(candidate); + for _ in candidate.chars().skip(1) { + iter.next(); + } + last_copied_idx = idx + candidate.len(); + break; } } } - - if !escaped.is_empty() { - escaped.push(chr); - } } - if escaped.is_empty() { std::borrow::Cow::Borrowed(unescaped_string) } else { + escaped.push_str(&unescaped_string[last_copied_idx..unescaped_string.len()]); std::borrow::Cow::Owned(escaped) } } @@ -87,5 +82,7 @@ mod tests { ); assert_eq!(escape(r"abc \${bca}", &["${", "`"], '\\'), r"abc \${bca}"); assert_eq!(escape(r"abc \`bca", &["${", "`"], '\\'), r"abc \`bca"); + + assert_eq!(escape(r"\n`", &["`"], '\\'), r"\n\`"); } }