From 8f8546915b1b62299095ba5f1d7c655c4ada14ec Mon Sep 17 00:00:00 2001 From: Victorien Elvinger Date: Wed, 30 Aug 2023 12:47:25 +0200 Subject: [PATCH] fix(lint/useTemplate): preserve leading non-string addition (#70) --- CHANGELOG.md | 42 ++ .../src/analyzers/style/use_template.rs | 319 ++++----- crates/rome_js_analyze/src/utils.rs | 1 - .../tests/specs/style/useTemplate/invalid.js | 51 ++ .../specs/style/useTemplate/invalid.js.snap | 656 ++++++++++++++++++ .../specs/style/useTemplate/invalid.jsonc | 25 - .../style/useTemplate/invalid.jsonc.snap | 444 ------------ .../tests/specs/style/useTemplate/valid.js | 10 + .../specs/style/useTemplate/valid.js.snap | 20 + .../tests/specs/style/useTemplate/valid.jsonc | 1 - .../specs/style/useTemplate/valid.jsonc.snap | 16 - crates/rome_js_factory/src/lib.rs | 2 + crates/rome_js_factory/src/make.rs | 11 + .../src/utils.rs} | 14 - .../src/configuration/linter/rules.rs | 2 +- editors/vscode/configuration_schema.json | 2 +- .../@biomejs/backend-jsonrpc/src/workspace.ts | 2 +- .../@biomejs/biome/configuration_schema.json | 2 +- website/src/pages/internals/changelog.mdx | 42 ++ website/src/pages/lint/rules/index.mdx | 2 +- website/src/pages/lint/rules/useTemplate.md | 83 +-- 21 files changed, 1014 insertions(+), 733 deletions(-) create mode 100644 crates/rome_js_analyze/tests/specs/style/useTemplate/invalid.js create mode 100644 crates/rome_js_analyze/tests/specs/style/useTemplate/invalid.js.snap delete mode 100644 crates/rome_js_analyze/tests/specs/style/useTemplate/invalid.jsonc delete mode 100644 crates/rome_js_analyze/tests/specs/style/useTemplate/invalid.jsonc.snap create mode 100644 crates/rome_js_analyze/tests/specs/style/useTemplate/valid.js create mode 100644 crates/rome_js_analyze/tests/specs/style/useTemplate/valid.js.snap delete mode 100644 crates/rome_js_analyze/tests/specs/style/useTemplate/valid.jsonc delete mode 100644 crates/rome_js_analyze/tests/specs/style/useTemplate/valid.jsonc.snap rename crates/{rome_js_analyze/src/utils/escape.rs => rome_js_factory/src/utils.rs} (89%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 223adfac055f..00a0aa8bb027 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,48 @@ Read our [guidelines for writing a good changelog entry](https://github.com/biom ### Formatter ### JavaScript APIs ### Linter + +### Enhancements + +- [useTemplate](https://biomejs.dev/lint/rules/useTemplate/) now reports all string concatenations. + + Previously, the rule ignored concatenation of a value and a newline or a backquote. + For example, the following concatenation was not reported: + + ```js + v + "\n"; + "`" + v + "`"; + ``` + + The rule now reports these cases and suggests the following code fixes: + + ```diff + - v + "\n"; + + `${v}\n`; + - v + "`"; + + `\`${v}\``; + ``` + +### Bug fixes + +- Fix [rome#4713](https://github.com/rome/tools/issues/4713). + + Previously, [useTemplate](https://biomejs.dev/lint/rules/useTemplate/) made the following suggestion: + + ```diff + - a + b + "px" + + `${a}${b}px` + ``` + + This breaks code where `a` and `b` are numbers. + + Now, the rule makes the following suggestion: + + ```diff + - a + b + "px" + + `${a + b}px` + ``` + ### Parser ### VSCode diff --git a/crates/rome_js_analyze/src/analyzers/style/use_template.rs b/crates/rome_js_analyze/src/analyzers/style/use_template.rs index 351c9c7e837e..f86bce24cc95 100644 --- a/crates/rome_js_analyze/src/analyzers/style/use_template.rs +++ b/crates/rome_js_analyze/src/analyzers/style/use_template.rs @@ -1,49 +1,48 @@ -use rome_analyze::RuleSuppressions; use rome_analyze::{context::RuleContext, declare_rule, ActionCategory, Ast, Rule, RuleDiagnostic}; use rome_console::markup; use rome_diagnostics::Applicability; use rome_js_factory::make; use rome_js_syntax::AnyJsTemplateElement::{self, JsTemplateElement}; use rome_js_syntax::{ - AnyJsExpression, AnyJsLiteralExpression, JsBinaryExpression, JsBinaryOperator, JsLanguage, - JsSyntaxKind, JsSyntaxToken, JsTemplateElementList, JsTemplateExpression, WalkEvent, T, + AnyJsExpression, AnyJsLiteralExpression, JsBinaryExpression, JsBinaryOperator, + JsParenthesizedExpression, JsStringLiteralExpression, JsSyntaxKind, JsSyntaxToken, + JsTemplateElementList, JsTemplateExpression, T, }; -use rome_rowan::{AstNode, AstNodeList, BatchMutationExt, SyntaxToken}; +use rome_rowan::{AstNode, BatchMutationExt, WalkEvent}; -use crate::{utils::escape::escape, utils::escape_string, JsRuleAction}; +use crate::JsRuleAction; declare_rule! { - /// Template literals are preferred over string concatenation. + /// Prefer template literals over string concatenation. /// /// ## Examples /// /// ### Invalid /// /// ```js,expect_diagnostic - /// console.log(foo + "baz"); + /// const s = foo + "baz"; /// ``` /// /// ```js,expect_diagnostic - /// console.log(1 * 2 + "foo"); + /// const s = 1 + 2 + "foo" + 3; /// ``` /// /// ```js,expect_diagnostic - /// console.log(1 + "foo" + 2 + "bar" + "baz" + 3); + /// const s = 1 * 2 + "foo"; /// ``` /// /// ```js,expect_diagnostic - /// console.log((1 + "foo") * 2); - /// ``` - /// - /// ```js,expect_diagnostic - /// console.log("foo" + 1); + /// const s = 1 + "foo" + 2 + "bar" + "baz" + 3; /// ``` /// /// ### Valid /// /// ```js - /// console.log("foo" + "bar"); - /// console.log(foo() + "\n"); + /// let s = "foo" + "bar" + `baz`; + /// ``` + /// + /// ```js + /// let s = `value: ${1}`; /// ``` pub(crate) UseTemplate { version: "1.0.0", @@ -54,52 +53,28 @@ declare_rule! { impl Rule for UseTemplate { type Query = Ast; - type State = Vec; + type State = (); type Signals = Option; type Options = (); fn run(ctx: &RuleContext) -> Option { - let binary_expr = ctx.query(); - - let need_process = is_unnecessary_string_concat_expression(binary_expr)?; - if !need_process { + let node = ctx.query(); + // Do not handle binary operations contained in a binary operation with operator `+` + if node + .syntax() + .ancestors() + .skip(1) // skip node + .find(|x| !JsParenthesizedExpression::can_cast(x.kind())) + .and_then(JsBinaryExpression::cast) + .is_some_and(|parent| parent.operator() == Ok(JsBinaryOperator::Plus)) + { return None; } - - let collections = collect_binary_add_expression(binary_expr)?; - collections - .iter() - .any(|expr| { - !matches!( - expr, - AnyJsExpression::AnyJsLiteralExpression( - rome_js_syntax::AnyJsLiteralExpression::JsStringLiteralExpression(_) - ) - ) - }) - .then_some(collections) - } - - fn suppressed_nodes( - ctx: &RuleContext, - _state: &Self::State, - suppressions: &mut RuleSuppressions, - ) { - let mut iter = ctx.query().syntax().preorder(); - while let Some(node) = iter.next() { - if let WalkEvent::Enter(node) = node { - if node.kind() == JsSyntaxKind::JS_BINARY_EXPRESSION { - suppressions.suppress_node(node); - } else { - iter.skip_subtree(); - } - } - } + can_be_template_literal(node)?.then_some(()) } fn diagnostic(ctx: &RuleContext, _: &Self::State) -> Option { let node = ctx.query(); - Some(RuleDiagnostic::new( rule_category!(), node.range(), @@ -109,76 +84,141 @@ impl Rule for UseTemplate { )) } - fn action(ctx: &RuleContext, state: &Self::State) -> Option { + fn action(ctx: &RuleContext, _: &Self::State) -> Option { let node = ctx.query(); let mut mutation = ctx.root().begin(); - - let template = convert_expressions_to_js_template(state)?; + let template = template_expression_from_binary_expression(node)?; mutation.replace_node( AnyJsExpression::JsBinaryExpression(node.clone()), AnyJsExpression::JsTemplateExpression(template), ); - Some(JsRuleAction { category: ActionCategory::QuickFix, applicability: Applicability::MaybeIncorrect, - message: markup! { "Use a ""TemplateLiteral""." }.to_owned(), + message: markup! { "Use a ""template literal""." }.to_owned(), mutation, }) } } -/// Merge `Vec` into a `JsTemplate` -fn convert_expressions_to_js_template( - exprs: &Vec, -) -> Option { - let mut reduced_exprs = Vec::with_capacity(exprs.len()); - for expr in exprs.iter() { - match expr { - AnyJsExpression::AnyJsLiteralExpression( - AnyJsLiteralExpression::JsStringLiteralExpression(string), - ) => { - let trimmed_string = string.syntax().text_trimmed().to_string(); - let string_without_quotes = &trimmed_string[1..trimmed_string.len() - 1]; - let chunk_element = AnyJsTemplateElement::JsTemplateChunkElement( - make::js_template_chunk_element(JsSyntaxToken::new_detached( - JsSyntaxKind::TEMPLATE_CHUNK, - &escape(string_without_quotes, &["${", "`"], '\\'), - [], - [], - )), - ); - reduced_exprs.push(chunk_element); +/// Returns true if `node` can be converted to a template literal. +/// +/// This is the case, if: +/// +/// - the binary expression contains the `+` operator, +/// - the binary expression contains a string-like literal and a non-string-like +/// +/// String-like literals are string literals and untagged template literals. +fn can_be_template_literal(node: &JsBinaryExpression) -> Option { + let mut iter = node.syntax().preorder(); + let mut has_constant_string_constituent = false; + let mut has_non_constant_string_constituent = false; + while let Some(walk) = iter.next() { + if let WalkEvent::Enter(node) = walk { + let node = AnyJsExpression::cast(node)?; + match node { + AnyJsExpression::JsParenthesizedExpression(_) => continue, + AnyJsExpression::JsBinaryExpression(node) + if node.operator().ok()? == JsBinaryOperator::Plus => + { + continue + } + AnyJsExpression::JsTemplateExpression(ref template) if template.is_constant() => { + has_constant_string_constituent = true; + } + AnyJsExpression::AnyJsLiteralExpression( + AnyJsLiteralExpression::JsStringLiteralExpression(_), + ) => { + has_constant_string_constituent = true; + } + _ => { + has_non_constant_string_constituent = true; + } } - AnyJsExpression::JsTemplateExpression(template) => { - reduced_exprs.extend(flatten_template_element_list(template.elements())?); + if has_non_constant_string_constituent && has_constant_string_constituent { + return Some(true); } - _ => { - let template_element = - AnyJsTemplateElement::JsTemplateElement(make::js_template_element( - SyntaxToken::new_detached(JsSyntaxKind::DOLLAR_CURLY, "${", [], []), - // Trim spaces to make the generated `JsTemplate` a little nicer, - // if we don't do this the `1 * (2 + "foo") + "bar"` will become: - // ```js - // `${1 * (2 + "foo") }bar` - // ``` - expr.clone().trim()?, - SyntaxToken::new_detached(JsSyntaxKind::DOLLAR_CURLY, "}", [], []), - )); - reduced_exprs.push(template_element); + iter.skip_subtree(); + } + } + Some(false) +} + +fn template_expression_from_binary_expression( + node: &JsBinaryExpression, +) -> Option { + // While `template_elements` is empty, we keep track of the last left node. + // Once we see the first string/template literal, + // we insert `last_left_node` in `template_elements` and the seen string/template literal. + // ANy subsequent expression is directly inserted in `template_elements`. + let mut template_elements = vec![]; + let mut last_left_node = None; + let mut iter = node.syntax().preorder(); + while let Some(walk) = iter.next() { + match walk { + WalkEvent::Enter(node) => { + let node = AnyJsExpression::cast(node)?; + match node { + AnyJsExpression::JsParenthesizedExpression(_) => continue, + AnyJsExpression::JsBinaryExpression(node) + if matches!(node.operator().ok()?, JsBinaryOperator::Plus) => + { + continue; + } + AnyJsExpression::JsTemplateExpression(ref template) + if template.tag().is_none() => + { + if let Some(last_node) = last_left_node.take() { + template_elements.push(template_element_from(last_node)?) + } + flatten_template_element_list(&mut template_elements, template.elements())?; + } + AnyJsExpression::AnyJsLiteralExpression( + AnyJsLiteralExpression::JsStringLiteralExpression(string_literal), + ) => { + if let Some(last_node) = last_left_node.take() { + template_elements.push(template_element_from(last_node)?) + } + template_elements.push(template_chuck_from(&string_literal)?); + } + node if !template_elements.is_empty() => { + template_elements.push(template_element_from(node)?) + } + _ => {} + } + iter.skip_subtree(); + } + WalkEvent::Leave(node) if template_elements.is_empty() => { + last_left_node = AnyJsExpression::cast(node); } + _ => {} } } Some( make::js_template_expression( make::token(T!['`']), - make::js_template_element_list(reduced_exprs), + make::js_template_element_list(template_elements), make::token(T!['`']), ) .build(), ) } +fn template_chuck_from(string_literal: &JsStringLiteralExpression) -> Option { + let text = string_literal.inner_string_text().ok()?; + Some(AnyJsTemplateElement::from(make::js_template_chunk_element( + make::js_template_chunk(text.text()), + ))) +} + +fn template_element_from(expr: AnyJsExpression) -> Option { + Some(AnyJsTemplateElement::from(make::js_template_element( + JsSyntaxToken::new_detached(JsSyntaxKind::DOLLAR_CURLY, "${", [], []), + expr.trim()?, + make::token(T!['}']), + ))) +} + /// Flatten a [JsTemplateElementList] of [JsTemplate] which could possibly be recursive, into a `Vec` /// ## Example /// flatten @@ -187,100 +227,25 @@ fn convert_expressions_to_js_template( /// ``` /// into /// `[1, 2, a, "test", "bar"]` -fn flatten_template_element_list(list: JsTemplateElementList) -> Option> { - let mut ret = Vec::with_capacity(list.len()); +fn flatten_template_element_list( + result: &mut Vec, + list: JsTemplateElementList, +) -> Option<()> { for element in list { match element { - AnyJsTemplateElement::JsTemplateChunkElement(_) => ret.push(element), + AnyJsTemplateElement::JsTemplateChunkElement(_) => result.push(element), JsTemplateElement(ref ele) => { let expr = ele.expression().ok()?; match expr { AnyJsExpression::JsTemplateExpression(template) => { - ret.extend(flatten_template_element_list(template.elements())?); + flatten_template_element_list(result, template.elements())?; } _ => { - ret.push(element); + result.push(element); } } } } } - Some(ret) -} - -fn is_unnecessary_string_concat_expression(node: &JsBinaryExpression) -> Option { - if node.operator().ok()? != JsBinaryOperator::Plus { - return None; - } - match node.left().ok()? { - rome_js_syntax::AnyJsExpression::JsBinaryExpression(binary) => { - if is_unnecessary_string_concat_expression(&binary) == Some(true) { - return Some(true); - } - } - rome_js_syntax::AnyJsExpression::JsTemplateExpression(_) => return Some(true), - rome_js_syntax::AnyJsExpression::AnyJsLiteralExpression( - rome_js_syntax::AnyJsLiteralExpression::JsStringLiteralExpression(string_literal), - ) => { - if has_new_line_or_tick(string_literal).is_none() { - return Some(true); - } - } - _ => (), - } - match node.right().ok()? { - rome_js_syntax::AnyJsExpression::JsBinaryExpression(binary) => { - if is_unnecessary_string_concat_expression(&binary) == Some(true) { - return Some(true); - } - } - rome_js_syntax::AnyJsExpression::JsTemplateExpression(_) => return Some(true), - rome_js_syntax::AnyJsExpression::AnyJsLiteralExpression( - rome_js_syntax::AnyJsLiteralExpression::JsStringLiteralExpression(string_literal), - ) => { - if has_new_line_or_tick(string_literal).is_none() { - return Some(true); - } - } - _ => (), - } - None -} - -/// Check if the string literal has new line or tick -fn has_new_line_or_tick( - string_literal: rome_js_syntax::JsStringLiteralExpression, -) -> Option { - escape_string(string_literal.value_token().ok()?.text_trimmed()) - .ok()? - .find(|ch| matches!(ch, '\n' | '`')) -} - -/// Convert [JsBinaryExpression] recursively only if the `operator` is `+` into Vec<[JsAnyExpression]> -/// ## Example -/// - from: `1 + 2 + 3 + (1 * 2)` -/// - to: `[1, 2, 3, (1 * 2)]` -fn collect_binary_add_expression(node: &JsBinaryExpression) -> Option> { - let mut result = vec![]; - match node.left().ok()? { - AnyJsExpression::JsBinaryExpression(left) - if matches!(left.operator().ok()?, JsBinaryOperator::Plus) => - { - result.append(&mut collect_binary_add_expression(&left)?); - } - left => { - result.push(left); - } - }; - match node.right().ok()? { - AnyJsExpression::JsBinaryExpression(right) - if matches!(right.operator().ok()?, JsBinaryOperator::Plus) => - { - result.append(&mut collect_binary_add_expression(&right)?); - } - right => { - result.push(right); - } - }; - Some(result) + Some(()) } diff --git a/crates/rome_js_analyze/src/utils.rs b/crates/rome_js_analyze/src/utils.rs index 284d407bf68e..7b34a6ae8243 100644 --- a/crates/rome_js_analyze/src/utils.rs +++ b/crates/rome_js_analyze/src/utils.rs @@ -9,7 +9,6 @@ use std::iter; pub mod batch; pub mod case; -pub mod escape; pub mod rename; #[cfg(test)] pub mod tests; diff --git a/crates/rome_js_analyze/tests/specs/style/useTemplate/invalid.js b/crates/rome_js_analyze/tests/specs/style/useTemplate/invalid.js new file mode 100644 index 000000000000..cf700d5635d6 --- /dev/null +++ b/crates/rome_js_analyze/tests/specs/style/useTemplate/invalid.js @@ -0,0 +1,51 @@ +foo + 'baz'; + +1 * 2 + 'foo'; + +1 + 2 + 3 + "px" + 4 + 5; + +a + b + c + 'px' + d + e; + +1 + 'foo' + 2 + 'bar' + 'baz' + 3; + +(1 + 'foo') * 2; + +1 * (2 + 'foo') + 'bar'; + +'foo' + 1; + +'foo' + `bar${`baz${'bat' + 'bam'}`}` + 'boo'; + +'foo' + 1 + 2; + +1 + '2' - 3; + +foo() + ' bar'; + +foo() + '\n'; + +1 * /**leading*/'foo' /**trailing */ + 'bar'; + +// strings including `${` + +'${foo.' + bar + '.baz}'; + +'foo: ${bar.' + baz + '.bat}'; + +'foo: `bar.' + baz + '.bat}'; + +'${foo}: `bar.' + baz + '.bat}'; + +'foo: ${bar.' + baz + '.bat}'; + +'foo: `bar.' + baz + '.bat}'; + +'foo: \\${bar.' + baz + '.bat}'; + +'foo: \\${bar.' + baz + '.bat}'; + +// parentheses + +const x = a + ("b") + c; + +("a") + (b) + ("c"); diff --git a/crates/rome_js_analyze/tests/specs/style/useTemplate/invalid.js.snap b/crates/rome_js_analyze/tests/specs/style/useTemplate/invalid.js.snap new file mode 100644 index 000000000000..efd1fe091446 --- /dev/null +++ b/crates/rome_js_analyze/tests/specs/style/useTemplate/invalid.js.snap @@ -0,0 +1,656 @@ +--- +source: crates/rome_js_analyze/tests/spec_tests.rs +expression: invalid.js +--- +# Input +```js +foo + 'baz'; + +1 * 2 + 'foo'; + +1 + 2 + 3 + "px" + 4 + 5; + +a + b + c + 'px' + d + e; + +1 + 'foo' + 2 + 'bar' + 'baz' + 3; + +(1 + 'foo') * 2; + +1 * (2 + 'foo') + 'bar'; + +'foo' + 1; + +'foo' + `bar${`baz${'bat' + 'bam'}`}` + 'boo'; + +'foo' + 1 + 2; + +1 + '2' - 3; + +foo() + ' bar'; + +foo() + '\n'; + +1 * /**leading*/'foo' /**trailing */ + 'bar'; + +// strings including `${` + +'${foo.' + bar + '.baz}'; + +'foo: ${bar.' + baz + '.bat}'; + +'foo: `bar.' + baz + '.bat}'; + +'${foo}: `bar.' + baz + '.bat}'; + +'foo: ${bar.' + baz + '.bat}'; + +'foo: `bar.' + baz + '.bat}'; + +'foo: \\${bar.' + baz + '.bat}'; + +'foo: \\${bar.' + baz + '.bat}'; + +// parentheses + +const x = a + ("b") + c; + +("a") + (b) + ("c"); + +``` + +# Diagnostics +``` +invalid.js:1:1 lint/style/useTemplate FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Template literals are preferred over string concatenation. + + > 1 │ foo + 'baz'; + │ ^^^^^^^^^^^ + 2 │ + 3 │ 1 * 2 + 'foo'; + + i Suggested fix: Use a template literal. + + 1 │ - foo·+·'baz'; + 1 │ + `${foo}baz`; + 2 2 │ + 3 3 │ 1 * 2 + 'foo'; + + +``` + +``` +invalid.js:3:1 lint/style/useTemplate FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Template literals are preferred over string concatenation. + + 1 │ foo + 'baz'; + 2 │ + > 3 │ 1 * 2 + 'foo'; + │ ^^^^^^^^^^^^^ + 4 │ + 5 │ 1 + 2 + 3 + "px" + 4 + 5; + + i Suggested fix: Use a template literal. + + 1 1 │ foo + 'baz'; + 2 2 │ + 3 │ - 1·*·2·+·'foo'; + 3 │ + `${1·*·2}foo`; + 4 4 │ + 5 5 │ 1 + 2 + 3 + "px" + 4 + 5; + + +``` + +``` +invalid.js:5:1 lint/style/useTemplate FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Template literals are preferred over string concatenation. + + 3 │ 1 * 2 + 'foo'; + 4 │ + > 5 │ 1 + 2 + 3 + "px" + 4 + 5; + │ ^^^^^^^^^^^^^^^^^^^^^^^^ + 6 │ + 7 │ a + b + c + 'px' + d + e; + + i Suggested fix: Use a template literal. + + 3 3 │ 1 * 2 + 'foo'; + 4 4 │ + 5 │ - 1·+·2·+·3·+·"px"·+·4·+·5; + 5 │ + `${1·+·2·+·3}px${4}${5}`; + 6 6 │ + 7 7 │ a + b + c + 'px' + d + e; + + +``` + +``` +invalid.js:7:1 lint/style/useTemplate FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Template literals are preferred over string concatenation. + + 5 │ 1 + 2 + 3 + "px" + 4 + 5; + 6 │ + > 7 │ a + b + c + 'px' + d + e; + │ ^^^^^^^^^^^^^^^^^^^^^^^^ + 8 │ + 9 │ 1 + 'foo' + 2 + 'bar' + 'baz' + 3; + + i Suggested fix: Use a template literal. + + 5 5 │ 1 + 2 + 3 + "px" + 4 + 5; + 6 6 │ + 7 │ - a·+·b·+·c·+·'px'·+·d·+·e; + 7 │ + `${a·+·b·+·c}px${d}${e}`; + 8 8 │ + 9 9 │ 1 + 'foo' + 2 + 'bar' + 'baz' + 3; + + +``` + +``` +invalid.js:9:1 lint/style/useTemplate FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Template literals are preferred over string concatenation. + + 7 │ a + b + c + 'px' + d + e; + 8 │ + > 9 │ 1 + 'foo' + 2 + 'bar' + 'baz' + 3; + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 10 │ + 11 │ (1 + 'foo') * 2; + + i Suggested fix: Use a template literal. + + 7 7 │ a + b + c + 'px' + d + e; + 8 8 │ + 9 │ - 1·+·'foo'·+·2·+·'bar'·+·'baz'·+·3; + 9 │ + `${1}foo${2}barbaz${3}`; + 10 10 │ + 11 11 │ (1 + 'foo') * 2; + + +``` + +``` +invalid.js:11:2 lint/style/useTemplate FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Template literals are preferred over string concatenation. + + 9 │ 1 + 'foo' + 2 + 'bar' + 'baz' + 3; + 10 │ + > 11 │ (1 + 'foo') * 2; + │ ^^^^^^^^^ + 12 │ + 13 │ 1 * (2 + 'foo') + 'bar'; + + i Suggested fix: Use a template literal. + + 9 9 │ 1 + 'foo' + 2 + 'bar' + 'baz' + 3; + 10 10 │ + 11 │ - (1·+·'foo')·*·2; + 11 │ + (`${1}foo`)·*·2; + 12 12 │ + 13 13 │ 1 * (2 + 'foo') + 'bar'; + + +``` + +``` +invalid.js:13:1 lint/style/useTemplate FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Template literals are preferred over string concatenation. + + 11 │ (1 + 'foo') * 2; + 12 │ + > 13 │ 1 * (2 + 'foo') + 'bar'; + │ ^^^^^^^^^^^^^^^^^^^^^^^ + 14 │ + 15 │ 'foo' + 1; + + i Suggested fix: Use a template literal. + + 11 11 │ (1 + 'foo') * 2; + 12 12 │ + 13 │ - 1·*·(2·+·'foo')·+·'bar'; + 13 │ + `${1·*·(2·+·'foo')}bar`; + 14 14 │ + 15 15 │ 'foo' + 1; + + +``` + +``` +invalid.js:13:6 lint/style/useTemplate FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Template literals are preferred over string concatenation. + + 11 │ (1 + 'foo') * 2; + 12 │ + > 13 │ 1 * (2 + 'foo') + 'bar'; + │ ^^^^^^^^^ + 14 │ + 15 │ 'foo' + 1; + + i Suggested fix: Use a template literal. + + 11 11 │ (1 + 'foo') * 2; + 12 12 │ + 13 │ - 1·*·(2·+·'foo')·+·'bar'; + 13 │ + 1·*·(`${2}foo`)·+·'bar'; + 14 14 │ + 15 15 │ 'foo' + 1; + + +``` + +``` +invalid.js:15:1 lint/style/useTemplate FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Template literals are preferred over string concatenation. + + 13 │ 1 * (2 + 'foo') + 'bar'; + 14 │ + > 15 │ 'foo' + 1; + │ ^^^^^^^^^ + 16 │ + 17 │ 'foo' + `bar${`baz${'bat' + 'bam'}`}` + 'boo'; + + i Suggested fix: Use a template literal. + + 13 13 │ 1 * (2 + 'foo') + 'bar'; + 14 14 │ + 15 │ - 'foo'·+·1; + 15 │ + `foo${1}`; + 16 16 │ + 17 17 │ 'foo' + `bar${`baz${'bat' + 'bam'}`}` + 'boo'; + + +``` + +``` +invalid.js:17:1 lint/style/useTemplate FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Template literals are preferred over string concatenation. + + 15 │ 'foo' + 1; + 16 │ + > 17 │ 'foo' + `bar${`baz${'bat' + 'bam'}`}` + 'boo'; + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 18 │ + 19 │ 'foo' + 1 + 2; + + i Suggested fix: Use a template literal. + + 15 15 │ 'foo' + 1; + 16 16 │ + 17 │ - 'foo'·+·`bar${`baz${'bat'·+·'bam'}`}`·+·'boo'; + 17 │ + `foobarbaz${'bat'·+·'bam'}boo`; + 18 18 │ + 19 19 │ 'foo' + 1 + 2; + + +``` + +``` +invalid.js:19:1 lint/style/useTemplate FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Template literals are preferred over string concatenation. + + 17 │ 'foo' + `bar${`baz${'bat' + 'bam'}`}` + 'boo'; + 18 │ + > 19 │ 'foo' + 1 + 2; + │ ^^^^^^^^^^^^^ + 20 │ + 21 │ 1 + '2' - 3; + + i Suggested fix: Use a template literal. + + 17 17 │ 'foo' + `bar${`baz${'bat' + 'bam'}`}` + 'boo'; + 18 18 │ + 19 │ - 'foo'·+·1·+·2; + 19 │ + `foo${1}${2}`; + 20 20 │ + 21 21 │ 1 + '2' - 3; + + +``` + +``` +invalid.js:21:1 lint/style/useTemplate FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Template literals are preferred over string concatenation. + + 19 │ 'foo' + 1 + 2; + 20 │ + > 21 │ 1 + '2' - 3; + │ ^^^^^^^ + 22 │ + 23 │ foo() + ' bar'; + + i Suggested fix: Use a template literal. + + 19 19 │ 'foo' + 1 + 2; + 20 20 │ + 21 │ - 1·+·'2'·-·3; + 21 │ + `${1}2`·-·3; + 22 22 │ + 23 23 │ foo() + ' bar'; + + +``` + +``` +invalid.js:23:1 lint/style/useTemplate FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Template literals are preferred over string concatenation. + + 21 │ 1 + '2' - 3; + 22 │ + > 23 │ foo() + ' bar'; + │ ^^^^^^^^^^^^^^ + 24 │ + 25 │ foo() + '\n'; + + i Suggested fix: Use a template literal. + + 21 21 │ 1 + '2' - 3; + 22 22 │ + 23 │ - foo()·+·'·bar'; + 23 │ + `${foo()}·bar`; + 24 24 │ + 25 25 │ foo() + '\n'; + + +``` + +``` +invalid.js:25:1 lint/style/useTemplate FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Template literals are preferred over string concatenation. + + 23 │ foo() + ' bar'; + 24 │ + > 25 │ foo() + '\n'; + │ ^^^^^^^^^^^^ + 26 │ + 27 │ 1 * /**leading*/'foo' /**trailing */ + 'bar'; + + i Suggested fix: Use a template literal. + + 23 23 │ foo() + ' bar'; + 24 24 │ + 25 │ - foo()·+·'\n'; + 25 │ + `${foo()}\n`; + 26 26 │ + 27 27 │ 1 * /**leading*/'foo' /**trailing */ + 'bar'; + + +``` + +``` +invalid.js:27:1 lint/style/useTemplate FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Template literals are preferred over string concatenation. + + 25 │ foo() + '\n'; + 26 │ + > 27 │ 1 * /**leading*/'foo' /**trailing */ + 'bar'; + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 28 │ + 29 │ // strings including `${` + + i Suggested fix: Use a template literal. + + 25 25 │ foo() + '\n'; + 26 26 │ + 27 │ - 1·*·/**leading*/'foo'····/**trailing·*/···················+·'bar'; + 27 │ + `${1·*·/**leading*/'foo'····/**trailing·*/}bar`; + 28 28 │ + 29 29 │ // strings including `${` + + +``` + +``` +invalid.js:31:1 lint/style/useTemplate FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Template literals are preferred over string concatenation. + + 29 │ // strings including `${` + 30 │ + > 31 │ '${foo.' + bar + '.baz}'; + │ ^^^^^^^^^^^^^^^^^^^^^^^^ + 32 │ + 33 │ 'foo: ${bar.' + baz + '.bat}'; + + i Suggested fix: Use a template literal. + + 29 29 │ // strings including `${` + 30 30 │ + 31 │ - '${foo.'·+·bar·+·'.baz}'; + 31 │ + `\${foo.${bar}.baz}`; + 32 32 │ + 33 33 │ 'foo: ${bar.' + baz + '.bat}'; + + +``` + +``` +invalid.js:33:1 lint/style/useTemplate FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Template literals are preferred over string concatenation. + + 31 │ '${foo.' + bar + '.baz}'; + 32 │ + > 33 │ 'foo: ${bar.' + baz + '.bat}'; + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 34 │ + 35 │ 'foo: `bar.' + baz + '.bat}'; + + i Suggested fix: Use a template literal. + + 31 31 │ '${foo.' + bar + '.baz}'; + 32 32 │ + 33 │ - 'foo:·${bar.'·+·baz·+·'.bat}'; + 33 │ + `foo:·\${bar.${baz}.bat}`; + 34 34 │ + 35 35 │ 'foo: `bar.' + baz + '.bat}'; + + +``` + +``` +invalid.js:35:1 lint/style/useTemplate FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Template literals are preferred over string concatenation. + + 33 │ 'foo: ${bar.' + baz + '.bat}'; + 34 │ + > 35 │ 'foo: `bar.' + baz + '.bat}'; + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 36 │ + 37 │ '${foo}: `bar.' + baz + '.bat}'; + + i Suggested fix: Use a template literal. + + 33 33 │ 'foo: ${bar.' + baz + '.bat}'; + 34 34 │ + 35 │ - 'foo:·`bar.'·+·baz·+·'.bat}'; + 35 │ + `foo:·\`bar.${baz}.bat}`; + 36 36 │ + 37 37 │ '${foo}: `bar.' + baz + '.bat}'; + + +``` + +``` +invalid.js:37:1 lint/style/useTemplate FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Template literals are preferred over string concatenation. + + 35 │ 'foo: `bar.' + baz + '.bat}'; + 36 │ + > 37 │ '${foo}: `bar.' + baz + '.bat}'; + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 38 │ + 39 │ 'foo: ${bar.' + baz + '.bat}'; + + i Suggested fix: Use a template literal. + + 35 35 │ 'foo: `bar.' + baz + '.bat}'; + 36 36 │ + 37 │ - '${foo}:·`bar.'·+·baz·+·'.bat}'; + 37 │ + `\${foo}:·\`bar.${baz}.bat}`; + 38 38 │ + 39 39 │ 'foo: ${bar.' + baz + '.bat}'; + + +``` + +``` +invalid.js:39:1 lint/style/useTemplate FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Template literals are preferred over string concatenation. + + 37 │ '${foo}: `bar.' + baz + '.bat}'; + 38 │ + > 39 │ 'foo: ${bar.' + baz + '.bat}'; + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 40 │ + 41 │ 'foo: `bar.' + baz + '.bat}'; + + i Suggested fix: Use a template literal. + + 37 37 │ '${foo}: `bar.' + baz + '.bat}'; + 38 38 │ + 39 │ - 'foo:·${bar.'·+·baz·+·'.bat}'; + 39 │ + `foo:·\${bar.${baz}.bat}`; + 40 40 │ + 41 41 │ 'foo: `bar.' + baz + '.bat}'; + + +``` + +``` +invalid.js:41:1 lint/style/useTemplate FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Template literals are preferred over string concatenation. + + 39 │ 'foo: ${bar.' + baz + '.bat}'; + 40 │ + > 41 │ 'foo: `bar.' + baz + '.bat}'; + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 42 │ + 43 │ 'foo: \\${bar.' + baz + '.bat}'; + + i Suggested fix: Use a template literal. + + 39 39 │ 'foo: ${bar.' + baz + '.bat}'; + 40 40 │ + 41 │ - 'foo:·`bar.'·+·baz·+·'.bat}'; + 41 │ + `foo:·\`bar.${baz}.bat}`; + 42 42 │ + 43 43 │ 'foo: \\${bar.' + baz + '.bat}'; + + +``` + +``` +invalid.js:43:1 lint/style/useTemplate FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Template literals are preferred over string concatenation. + + 41 │ 'foo: `bar.' + baz + '.bat}'; + 42 │ + > 43 │ 'foo: \\${bar.' + baz + '.bat}'; + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 44 │ + 45 │ 'foo: \\${bar.' + baz + '.bat}'; + + i Suggested fix: Use a template literal. + + 41 41 │ 'foo: `bar.' + baz + '.bat}'; + 42 42 │ + 43 │ - 'foo:·\\${bar.'·+·baz·+·'.bat}'; + 43 │ + `foo:·\\\${bar.${baz}.bat}`; + 44 44 │ + 45 45 │ 'foo: \\${bar.' + baz + '.bat}'; + + +``` + +``` +invalid.js:45:1 lint/style/useTemplate FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Template literals are preferred over string concatenation. + + 43 │ 'foo: \\${bar.' + baz + '.bat}'; + 44 │ + > 45 │ 'foo: \\${bar.' + baz + '.bat}'; + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 46 │ + 47 │ // parentheses + + i Suggested fix: Use a template literal. + + 43 43 │ 'foo: \\${bar.' + baz + '.bat}'; + 44 44 │ + 45 │ - 'foo:·\\${bar.'·+·baz·+·'.bat}'; + 45 │ + `foo:·\\\${bar.${baz}.bat}`; + 46 46 │ + 47 47 │ // parentheses + + +``` + +``` +invalid.js:49:11 lint/style/useTemplate FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Template literals are preferred over string concatenation. + + 47 │ // parentheses + 48 │ + > 49 │ const x = a + ("b") + c; + │ ^^^^^^^^^^^^^ + 50 │ + 51 │ ("a") + (b) + ("c"); + + i Suggested fix: Use a template literal. + + 47 47 │ // parentheses + 48 48 │ + 49 │ - const·x·=·a·+·("b")·+·c; + 49 │ + const·x·=·`${a}b${c}`; + 50 50 │ + 51 51 │ ("a") + (b) + ("c"); + + +``` + +``` +invalid.js:51:1 lint/style/useTemplate FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Template literals are preferred over string concatenation. + + 49 │ const x = a + ("b") + c; + 50 │ + > 51 │ ("a") + (b) + ("c"); + │ ^^^^^^^^^^^^^^^^^^^ + 52 │ + + i Suggested fix: Use a template literal. + + 49 49 │ const x = a + ("b") + c; + 50 50 │ + 51 │ - ("a")·+·(b)·+·("c"); + 51 │ + `a${b}c`; + 52 52 │ + + +``` + + diff --git a/crates/rome_js_analyze/tests/specs/style/useTemplate/invalid.jsonc b/crates/rome_js_analyze/tests/specs/style/useTemplate/invalid.jsonc deleted file mode 100644 index 0622f64e5bf3..000000000000 --- a/crates/rome_js_analyze/tests/specs/style/useTemplate/invalid.jsonc +++ /dev/null @@ -1,25 +0,0 @@ -[ - "const foo = 'bar';", - "console.log(foo + 'baz');", - "console.log(1 * 2 + 'foo');", - "console.log(1 + 'foo' + 2 + 'bar' + 'baz' + 3);", - "console.log((1 + 'foo') * 2);", - "console.log(1 * (2 + 'foo') + 'bar');", - "console.log('foo' + 1);", - "console.log('foo' + `bar${`baz${'bat' + 'bam'}`}` + 'boo');", - "console.log('foo' + 1 + 2);", - "1 + '2' - 3;", - "foo() + ' bar';", - - "1 * /**leading*/'foo' /**trailing */ + 'bar'", - - "console.log('${foo.' + bar + '.baz}');", - "console.log('foo: ${bar.' + baz + '.bat}');", - "console.log('foo: `bar.' + baz + '.bat}');", - "console.log('${foo}: `bar.' + baz + '.bat}');", - - "console.log('foo: ${bar.' + baz + '.bat}');", - "console.log('foo: `bar.' + baz + '.bat}');", - "console.log('foo: \\${bar.' + baz + '.bat}');", - "console.log('foo: \\${bar.' + baz + '.bat}');" -] diff --git a/crates/rome_js_analyze/tests/specs/style/useTemplate/invalid.jsonc.snap b/crates/rome_js_analyze/tests/specs/style/useTemplate/invalid.jsonc.snap deleted file mode 100644 index cee61cea0ea5..000000000000 --- a/crates/rome_js_analyze/tests/specs/style/useTemplate/invalid.jsonc.snap +++ /dev/null @@ -1,444 +0,0 @@ ---- -source: crates/rome_js_analyze/tests/spec_tests.rs -expression: invalid.jsonc ---- -# Input -```js -const foo = 'bar'; -``` - -# Input -```js -console.log(foo + 'baz'); -``` - -# Diagnostics -``` -invalid.jsonc:1:13 lint/style/useTemplate FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - ! Template literals are preferred over string concatenation. - - > 1 │ console.log(foo + 'baz'); - │ ^^^^^^^^^^^ - - i Suggested fix: Use a TemplateLiteral. - - - console.log(foo·+·'baz'); - + console.log(`${foo}baz`); - - -``` - -# Input -```js -console.log(1 * 2 + 'foo'); -``` - -# Diagnostics -``` -invalid.jsonc:1:13 lint/style/useTemplate FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - ! Template literals are preferred over string concatenation. - - > 1 │ console.log(1 * 2 + 'foo'); - │ ^^^^^^^^^^^^^ - - i Suggested fix: Use a TemplateLiteral. - - - console.log(1·*·2·+·'foo'); - + console.log(`${1·*·2}foo`); - - -``` - -# Input -```js -console.log(1 + 'foo' + 2 + 'bar' + 'baz' + 3); -``` - -# Diagnostics -``` -invalid.jsonc:1:13 lint/style/useTemplate FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - ! Template literals are preferred over string concatenation. - - > 1 │ console.log(1 + 'foo' + 2 + 'bar' + 'baz' + 3); - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - - i Suggested fix: Use a TemplateLiteral. - - - console.log(1·+·'foo'·+·2·+·'bar'·+·'baz'·+·3); - + console.log(`${1}foo${2}barbaz${3}`); - - -``` - -# Input -```js -console.log((1 + 'foo') * 2); -``` - -# Diagnostics -``` -invalid.jsonc:1:14 lint/style/useTemplate FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - ! Template literals are preferred over string concatenation. - - > 1 │ console.log((1 + 'foo') * 2); - │ ^^^^^^^^^ - - i Suggested fix: Use a TemplateLiteral. - - - console.log((1·+·'foo')·*·2); - + console.log((`${1}foo`)·*·2); - - -``` - -# Input -```js -console.log(1 * (2 + 'foo') + 'bar'); -``` - -# Diagnostics -``` -invalid.jsonc:1:13 lint/style/useTemplate FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - ! Template literals are preferred over string concatenation. - - > 1 │ console.log(1 * (2 + 'foo') + 'bar'); - │ ^^^^^^^^^^^^^^^^^^^^^^^ - - i Suggested fix: Use a TemplateLiteral. - - - console.log(1·*·(2·+·'foo')·+·'bar'); - + console.log(`${1·*·(2·+·'foo')}bar`); - - -``` - -``` -invalid.jsonc:1:18 lint/style/useTemplate FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - ! Template literals are preferred over string concatenation. - - > 1 │ console.log(1 * (2 + 'foo') + 'bar'); - │ ^^^^^^^^^ - - i Suggested fix: Use a TemplateLiteral. - - - console.log(1·*·(2·+·'foo')·+·'bar'); - + console.log(1·*·(`${2}foo`)·+·'bar'); - - -``` - -# Input -```js -console.log('foo' + 1); -``` - -# Diagnostics -``` -invalid.jsonc:1:13 lint/style/useTemplate FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - ! Template literals are preferred over string concatenation. - - > 1 │ console.log('foo' + 1); - │ ^^^^^^^^^ - - i Suggested fix: Use a TemplateLiteral. - - - console.log('foo'·+·1); - + console.log(`foo${1}`); - - -``` - -# Input -```js -console.log('foo' + `bar${`baz${'bat' + 'bam'}`}` + 'boo'); -``` - -# Diagnostics -``` -invalid.jsonc:1:13 lint/style/useTemplate FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - ! Template literals are preferred over string concatenation. - - > 1 │ console.log('foo' + `bar${`baz${'bat' + 'bam'}`}` + 'boo'); - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - - i Suggested fix: Use a TemplateLiteral. - - - console.log('foo'·+·`bar${`baz${'bat'·+·'bam'}`}`·+·'boo'); - + console.log(`foobarbaz${'bat'·+·'bam'}boo`); - - -``` - -# Input -```js -console.log('foo' + 1 + 2); -``` - -# Diagnostics -``` -invalid.jsonc:1:13 lint/style/useTemplate FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - ! Template literals are preferred over string concatenation. - - > 1 │ console.log('foo' + 1 + 2); - │ ^^^^^^^^^^^^^ - - i Suggested fix: Use a TemplateLiteral. - - - console.log('foo'·+·1·+·2); - + console.log(`foo${1}${2}`); - - -``` - -# Input -```js -1 + '2' - 3; -``` - -# Diagnostics -``` -invalid.jsonc:1:1 lint/style/useTemplate FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - ! Template literals are preferred over string concatenation. - - > 1 │ 1 + '2' - 3; - │ ^^^^^^^ - - i Suggested fix: Use a TemplateLiteral. - - - 1·+·'2'·-·3; - + `${1}2`·-·3; - - -``` - -# Input -```js -foo() + ' bar'; -``` - -# Diagnostics -``` -invalid.jsonc:1:1 lint/style/useTemplate FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - ! Template literals are preferred over string concatenation. - - > 1 │ foo() + ' bar'; - │ ^^^^^^^^^^^^^^ - - i Suggested fix: Use a TemplateLiteral. - - - foo()·+·'·bar'; - + `${foo()}·bar`; - - -``` - -# Input -```js -1 * /**leading*/'foo' /**trailing */ + 'bar' -``` - -# Diagnostics -``` -invalid.jsonc:1:1 lint/style/useTemplate FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - ! Template literals are preferred over string concatenation. - - > 1 │ 1 * /**leading*/'foo' /**trailing */ + 'bar' - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - - i Suggested fix: Use a TemplateLiteral. - - - 1·*·/**leading*/'foo'····/**trailing·*/···················+·'bar' - + `${1·*·/**leading*/'foo'····/**trailing·*/}bar` - - -``` - -# Input -```js -console.log('${foo.' + bar + '.baz}'); -``` - -# Diagnostics -``` -invalid.jsonc:1:13 lint/style/useTemplate FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - ! Template literals are preferred over string concatenation. - - > 1 │ console.log('${foo.' + bar + '.baz}'); - │ ^^^^^^^^^^^^^^^^^^^^^^^^ - - i Suggested fix: Use a TemplateLiteral. - - - console.log('${foo.'·+·bar·+·'.baz}'); - + console.log(`\${foo.${bar}.baz}`); - - -``` - -# Input -```js -console.log('foo: ${bar.' + baz + '.bat}'); -``` - -# Diagnostics -``` -invalid.jsonc:1:13 lint/style/useTemplate FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - ! Template literals are preferred over string concatenation. - - > 1 │ console.log('foo: ${bar.' + baz + '.bat}'); - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - - i Suggested fix: Use a TemplateLiteral. - - - console.log('foo:·${bar.'·+·baz·+·'.bat}'); - + console.log(`foo:·\${bar.${baz}.bat}`); - - -``` - -# Input -```js -console.log('foo: `bar.' + baz + '.bat}'); -``` - -# Diagnostics -``` -invalid.jsonc:1:13 lint/style/useTemplate FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - ! Template literals are preferred over string concatenation. - - > 1 │ console.log('foo: `bar.' + baz + '.bat}'); - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - - i Suggested fix: Use a TemplateLiteral. - - - console.log('foo:·`bar.'·+·baz·+·'.bat}'); - + console.log(`foo:·\`bar.${baz}.bat}`); - - -``` - -# Input -```js -console.log('${foo}: `bar.' + baz + '.bat}'); -``` - -# Diagnostics -``` -invalid.jsonc:1:13 lint/style/useTemplate FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - ! Template literals are preferred over string concatenation. - - > 1 │ console.log('${foo}: `bar.' + baz + '.bat}'); - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - - i Suggested fix: Use a TemplateLiteral. - - - console.log('${foo}:·`bar.'·+·baz·+·'.bat}'); - + console.log(`\${foo}:·\`bar.${baz}.bat}`); - - -``` - -# Input -```js -console.log('foo: ${bar.' + baz + '.bat}'); -``` - -# Diagnostics -``` -invalid.jsonc:1:13 lint/style/useTemplate FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - ! Template literals are preferred over string concatenation. - - > 1 │ console.log('foo: ${bar.' + baz + '.bat}'); - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - - i Suggested fix: Use a TemplateLiteral. - - - console.log('foo:·${bar.'·+·baz·+·'.bat}'); - + console.log(`foo:·\${bar.${baz}.bat}`); - - -``` - -# Input -```js -console.log('foo: `bar.' + baz + '.bat}'); -``` - -# Diagnostics -``` -invalid.jsonc:1:13 lint/style/useTemplate FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - ! Template literals are preferred over string concatenation. - - > 1 │ console.log('foo: `bar.' + baz + '.bat}'); - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - - i Suggested fix: Use a TemplateLiteral. - - - console.log('foo:·`bar.'·+·baz·+·'.bat}'); - + console.log(`foo:·\`bar.${baz}.bat}`); - - -``` - -# Input -```js -console.log('foo: \${bar.' + baz + '.bat}'); -``` - -# Diagnostics -``` -invalid.jsonc:1:13 lint/style/useTemplate FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - ! Template literals are preferred over string concatenation. - - > 1 │ console.log('foo: \${bar.' + baz + '.bat}'); - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - - i Suggested fix: Use a TemplateLiteral. - - - console.log('foo:·\${bar.'·+·baz·+·'.bat}'); - + console.log(`foo:·\${bar.${baz}.bat}`); - - -``` - -# Input -```js -console.log('foo: \${bar.' + baz + '.bat}'); -``` - -# Diagnostics -``` -invalid.jsonc:1:13 lint/style/useTemplate FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - ! Template literals are preferred over string concatenation. - - > 1 │ console.log('foo: \${bar.' + baz + '.bat}'); - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - - i Suggested fix: Use a TemplateLiteral. - - - console.log('foo:·\${bar.'·+·baz·+·'.bat}'); - + console.log(`foo:·\${bar.${baz}.bat}`); - - -``` - - diff --git a/crates/rome_js_analyze/tests/specs/style/useTemplate/valid.js b/crates/rome_js_analyze/tests/specs/style/useTemplate/valid.js new file mode 100644 index 000000000000..5ba10d1d986f --- /dev/null +++ b/crates/rome_js_analyze/tests/specs/style/useTemplate/valid.js @@ -0,0 +1,10 @@ +'foo' + 'bar'; + +// tagged template +number`5` + 1 + number`4`; + +`a` + `b`; + +`a` + "b"; + +("a" * 2) + 5 diff --git a/crates/rome_js_analyze/tests/specs/style/useTemplate/valid.js.snap b/crates/rome_js_analyze/tests/specs/style/useTemplate/valid.js.snap new file mode 100644 index 000000000000..e7a90f149874 --- /dev/null +++ b/crates/rome_js_analyze/tests/specs/style/useTemplate/valid.js.snap @@ -0,0 +1,20 @@ +--- +source: crates/rome_js_analyze/tests/spec_tests.rs +expression: valid.js +--- +# Input +```js +'foo' + 'bar'; + +// tagged template +number`5` + 1 + number`4`; + +`a` + `b`; + +`a` + "b"; + +("a" * 2) + 5 + +``` + + diff --git a/crates/rome_js_analyze/tests/specs/style/useTemplate/valid.jsonc b/crates/rome_js_analyze/tests/specs/style/useTemplate/valid.jsonc deleted file mode 100644 index 4d284087e6f7..000000000000 --- a/crates/rome_js_analyze/tests/specs/style/useTemplate/valid.jsonc +++ /dev/null @@ -1 +0,0 @@ -["console.log('foo' + 'bar');", "console.log(foo() + '\n');"] diff --git a/crates/rome_js_analyze/tests/specs/style/useTemplate/valid.jsonc.snap b/crates/rome_js_analyze/tests/specs/style/useTemplate/valid.jsonc.snap deleted file mode 100644 index 0ffccdda26f3..000000000000 --- a/crates/rome_js_analyze/tests/specs/style/useTemplate/valid.jsonc.snap +++ /dev/null @@ -1,16 +0,0 @@ ---- -source: crates/rome_js_analyze/tests/spec_tests.rs -expression: valid.jsonc ---- -# Input -```js -console.log('foo' + 'bar'); -``` - -# Input -```js -console.log(foo() + ' -'); -``` - - diff --git a/crates/rome_js_factory/src/lib.rs b/crates/rome_js_factory/src/lib.rs index 0f1d22b8fb21..39327c49e401 100644 --- a/crates/rome_js_factory/src/lib.rs +++ b/crates/rome_js_factory/src/lib.rs @@ -5,6 +5,8 @@ mod generated; pub use crate::generated::JsSyntaxFactory; pub mod make; +mod utils; + // Re-exported for tests #[doc(hidden)] pub use rome_js_syntax as syntax; diff --git a/crates/rome_js_factory/src/make.rs b/crates/rome_js_factory/src/make.rs index b32a1cc635fd..9356f44add5d 100644 --- a/crates/rome_js_factory/src/make.rs +++ b/crates/rome_js_factory/src/make.rs @@ -5,6 +5,8 @@ use rome_rowan::TriviaPiece; pub use crate::generated::node_factory::*; +use crate::utils; + /// Create a new identifier token with no attached trivia pub fn ident(text: &str) -> JsSyntaxToken { JsSyntaxToken::new_detached(JsSyntaxKind::IDENT, text, [], []) @@ -35,6 +37,15 @@ pub fn jsx_string_literal(text: &str) -> JsSyntaxToken { ) } +pub fn js_template_chunk(text: &str) -> JsSyntaxToken { + JsSyntaxToken::new_detached( + JsSyntaxKind::TEMPLATE_CHUNK, + &utils::escape(text, &["${", "`"], '\\'), + [], + [], + ) +} + /// Create a new string literal token with no attached trivia pub fn js_number_literal(text: N) -> JsSyntaxToken where diff --git a/crates/rome_js_analyze/src/utils/escape.rs b/crates/rome_js_factory/src/utils.rs similarity index 89% rename from crates/rome_js_analyze/src/utils/escape.rs rename to crates/rome_js_factory/src/utils.rs index 6a7a0e3c0c82..c9462b64f573 100644 --- a/crates/rome_js_analyze/src/utils/escape.rs +++ b/crates/rome_js_factory/src/utils.rs @@ -12,20 +12,6 @@ /// | `${` | `\${abc` | `\${abc` | /// | `${` | `\\${abc` | `\\\${abc` | /// -/// # Examples -/// -/// ``` -/// use rome_js_analyze::utils::escape::escape; -/// -/// let escaped_str_1 = escape("${abc", &["${"], '\\'); -/// assert_eq!(escaped_str_1, r#"\${abc"#); -/// -/// let escaped_str_2 = escape(r#"\${abc"#, &["${"], '\\'); -/// assert_eq!(escaped_str_2, r#"\${abc"#); -/// -/// let escaped_str_3 = escape("${ `", &["${", "`"], '\\'); -/// assert_eq!(escaped_str_3, r#"\${ \`"#); -/// ``` pub fn escape<'a>( unescaped_string: &'a str, needs_escaping: &[&str], diff --git a/crates/rome_service/src/configuration/linter/rules.rs b/crates/rome_service/src/configuration/linter/rules.rs index 512d55fa371c..14d235e1a331 100644 --- a/crates/rome_service/src/configuration/linter/rules.rs +++ b/crates/rome_service/src/configuration/linter/rules.rs @@ -2921,7 +2921,7 @@ pub struct Style { )] #[serde(skip_serializing_if = "Option::is_none")] pub use_single_var_declarator: Option, - #[doc = "Template literals are preferred over string concatenation."] + #[doc = "Prefer template literals over string concatenation."] #[bpaf(long("use-template"), argument("on|off|warn"), optional, hide)] #[serde(skip_serializing_if = "Option::is_none")] pub use_template: Option, diff --git a/editors/vscode/configuration_schema.json b/editors/vscode/configuration_schema.json index 937620f1e2ec..0fbe20f0529c 100644 --- a/editors/vscode/configuration_schema.json +++ b/editors/vscode/configuration_schema.json @@ -1401,7 +1401,7 @@ ] }, "useTemplate": { - "description": "Template literals are preferred over string concatenation.", + "description": "Prefer template literals over string concatenation.", "anyOf": [ { "$ref": "#/definitions/RuleConfiguration" }, { "type": "null" } diff --git a/packages/@biomejs/backend-jsonrpc/src/workspace.ts b/packages/@biomejs/backend-jsonrpc/src/workspace.ts index ab7d129c8716..4beed9180664 100644 --- a/packages/@biomejs/backend-jsonrpc/src/workspace.ts +++ b/packages/@biomejs/backend-jsonrpc/src/workspace.ts @@ -839,7 +839,7 @@ export interface Style { */ useSingleVarDeclarator?: RuleConfiguration; /** - * Template literals are preferred over string concatenation. + * Prefer template literals over string concatenation. */ useTemplate?: RuleConfiguration; /** diff --git a/packages/@biomejs/biome/configuration_schema.json b/packages/@biomejs/biome/configuration_schema.json index 937620f1e2ec..0fbe20f0529c 100644 --- a/packages/@biomejs/biome/configuration_schema.json +++ b/packages/@biomejs/biome/configuration_schema.json @@ -1401,7 +1401,7 @@ ] }, "useTemplate": { - "description": "Template literals are preferred over string concatenation.", + "description": "Prefer template literals over string concatenation.", "anyOf": [ { "$ref": "#/definitions/RuleConfiguration" }, { "type": "null" } diff --git a/website/src/pages/internals/changelog.mdx b/website/src/pages/internals/changelog.mdx index 928c6bdfd10f..e6324b41cfd3 100644 --- a/website/src/pages/internals/changelog.mdx +++ b/website/src/pages/internals/changelog.mdx @@ -23,6 +23,48 @@ Read our [guidelines for writing a good changelog entry](https://github.com/biom ### Formatter ### JavaScript APIs ### Linter + +### Enhancements + +- [useTemplate](https://biomejs.dev/lint/rules/useTemplate/) now reports all string concatenations. + + Previously, the rule ignored concatenation of a value and a newline or a backquote. + For example, the following concatenation was not reported: + + ```js + v + "\n"; + "`" + v + "`"; + ``` + + The rule now reports these cases and suggests the following code fixes: + + ```diff + - v + "\n"; + + `${v}\n`; + - v + "`"; + + `\`${v}\``; + ``` + +### Bug fixes + +- Fix [rome#4713](https://github.com/rome/tools/issues/4713). + + Previously, [useTemplate](https://biomejs.dev/lint/rules/useTemplate/) made the following suggestion: + + ```diff + - a + b + "px" + + `${a}${b}px` + ``` + + This breaks code where `a` and `b` are numbers. + + Now, the rule makes the following suggestion: + + ```diff + - a + b + "px" + + `${a + b}px` + ``` + ### Parser ### VSCode diff --git a/website/src/pages/lint/rules/index.mdx b/website/src/pages/lint/rules/index.mdx index 768ad8b6d2fd..dcdedf54981e 100644 --- a/website/src/pages/lint/rules/index.mdx +++ b/website/src/pages/lint/rules/index.mdx @@ -680,7 +680,7 @@ Disallow multiple variable declarations in the same variable statement useTemplate recommended -Template literals are preferred over string concatenation. +Prefer template literals over string concatenation.

diff --git a/website/src/pages/lint/rules/useTemplate.md b/website/src/pages/lint/rules/useTemplate.md index 3db08931bc80..29d11354f01e 100644 --- a/website/src/pages/lint/rules/useTemplate.md +++ b/website/src/pages/lint/rules/useTemplate.md @@ -7,117 +7,100 @@ parent: lint/rules/index > This rule is recommended by Biome. -Template literals are preferred over string concatenation. +Prefer template literals over string concatenation. ## Examples ### Invalid ```jsx -console.log(foo + "baz"); +const s = foo + "baz"; ``` -
style/useTemplate.js:1:13 lint/style/useTemplate  FIXABLE  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+
style/useTemplate.js:1:11 lint/style/useTemplate  FIXABLE  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 
    Template literals are preferred over string concatenation.
   
-  > 1 │ console.log(foo + "baz");
-               ^^^^^^^^^^^
+  > 1 │ const s = foo + "baz";
+             ^^^^^^^^^^^
     2 │ 
   
-   Suggested fix: Use a TemplateLiteral.
+   Suggested fix: Use a template literal.
   
-    1  - console.log(foo·+·"baz");
-      1+ console.log(`${foo}baz`);
+    1  - const·s·=·foo·+·"baz";
+      1+ const·s·=·`${foo}baz`;
     2 2  
   
 
```jsx -console.log(1 * 2 + "foo"); +const s = 1 + 2 + "foo" + 3; ``` -
style/useTemplate.js:1:13 lint/style/useTemplate  FIXABLE  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+
style/useTemplate.js:1:11 lint/style/useTemplate  FIXABLE  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 
    Template literals are preferred over string concatenation.
   
-  > 1 │ console.log(1 * 2 + "foo");
-               ^^^^^^^^^^^^^
+  > 1 │ const s = 1 + 2 + "foo" + 3;
+             ^^^^^^^^^^^^^^^^^
     2 │ 
   
-   Suggested fix: Use a TemplateLiteral.
+   Suggested fix: Use a template literal.
   
-    1  - console.log(1·*·2·+·"foo");
-      1+ console.log(`${1·*·2}foo`);
+    1  - const·s·=·1·+·2·+·"foo"·+·3;
+      1+ const·s·=·`${1·+·2}foo${3}`;
     2 2  
   
 
```jsx -console.log(1 + "foo" + 2 + "bar" + "baz" + 3); +const s = 1 * 2 + "foo"; ``` -
style/useTemplate.js:1:13 lint/style/useTemplate  FIXABLE  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+
style/useTemplate.js:1:11 lint/style/useTemplate  FIXABLE  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 
    Template literals are preferred over string concatenation.
   
-  > 1 │ console.log(1 + "foo" + 2 + "bar" + "baz" + 3);
-               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+  > 1 │ const s = 1 * 2 + "foo";
+             ^^^^^^^^^^^^^
     2 │ 
   
-   Suggested fix: Use a TemplateLiteral.
+   Suggested fix: Use a template literal.
   
-    1  - console.log(1·+·"foo"·+·2·+·"bar"·+·"baz"·+·3);
-      1+ console.log(`${1}foo${2}barbaz${3}`);
+    1  - const·s·=·1·*·2·+·"foo";
+      1+ const·s·=·`${1·*·2}foo`;
     2 2  
   
 
```jsx -console.log((1 + "foo") * 2); +const s = 1 + "foo" + 2 + "bar" + "baz" + 3; ``` -
style/useTemplate.js:1:14 lint/style/useTemplate  FIXABLE  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+
style/useTemplate.js:1:11 lint/style/useTemplate  FIXABLE  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 
    Template literals are preferred over string concatenation.
   
-  > 1 │ console.log((1 + "foo") * 2);
-                ^^^^^^^^^
+  > 1 │ const s = 1 + "foo" + 2 + "bar" + "baz" + 3;
+             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
     2 │ 
   
-   Suggested fix: Use a TemplateLiteral.
+   Suggested fix: Use a template literal.
   
-    1  - console.log((1·+·"foo")·*·2);
-      1+ console.log((`${1}foo`)·*·2);
+    1  - const·s·=·1·+·"foo"·+·2·+·"bar"·+·"baz"·+·3;
+      1+ const·s·=·`${1}foo${2}barbaz${3}`;
     2 2  
   
 
+### Valid + ```jsx -console.log("foo" + 1); +let s = "foo" + "bar" + `baz`; ``` -
style/useTemplate.js:1:13 lint/style/useTemplate  FIXABLE  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
-
-   Template literals are preferred over string concatenation.
-  
-  > 1 │ console.log("foo" + 1);
-               ^^^^^^^^^
-    2 │ 
-  
-   Suggested fix: Use a TemplateLiteral.
-  
-    1  - console.log("foo"·+·1);
-      1+ console.log(`foo${1}`);
-    2 2  
-  
-
- -### Valid - ```jsx -console.log("foo" + "bar"); -console.log(foo() + "\n"); +let s = `value: ${1}`; ``` ## Related links