Skip to content

Commit

Permalink
feat(linter): no_template_curly_in_string (#2763)
Browse files Browse the repository at this point in the history
Rule detail:
https://eslint.org/docs/latest/rules/no-template-curly-in-string

At first I implemented it with regex, but I think you are trying to
avoid it for performance reasons, so I did it by searching for the
chars.

I had some problems showing the span in the case of 'Hello, ${{foo:
"bar"}.foo}' I think it turned out more or less well, however, if you
think it can be done in another way I am willing to do it .

---------

Co-authored-by: j.buendia <j.buendia>
  • Loading branch information
JoSeBu1 committed Mar 19, 2024
1 parent 134e15e commit ac813a6
Show file tree
Hide file tree
Showing 3 changed files with 164 additions and 0 deletions.
2 changes: 2 additions & 0 deletions crates/oxc_linter/src/rules.rs
Expand Up @@ -96,6 +96,7 @@ mod eslint {
pub mod no_setter_return;
pub mod no_shadow_restricted_names;
pub mod no_sparse_arrays;
pub mod no_template_curly_in_string;
pub mod no_ternary;
pub mod no_this_before_super;
pub mod no_undef;
Expand Down Expand Up @@ -369,6 +370,7 @@ oxc_macros::declare_all_lint_rules! {
eslint::max_params,
eslint::no_ternary,
eslint::no_this_before_super,
eslint::no_template_curly_in_string,
eslint::no_array_constructor,
eslint::no_async_promise_executor,
eslint::no_bitwise,
Expand Down
110 changes: 110 additions & 0 deletions crates/oxc_linter/src/rules/eslint/no_template_curly_in_string.rs
@@ -0,0 +1,110 @@
use oxc_ast::AstKind;
use oxc_diagnostics::{
miette::{self, Diagnostic},
thiserror::Error,
};
use oxc_macros::declare_oxc_lint;
use oxc_span::Span;

use crate::{context::LintContext, rule::Rule, AstNode};

#[derive(Debug, Error, Diagnostic)]
#[error("eslint(no-template-curly-in-string): Unexpected template string expression")]
#[diagnostic(
severity(warning),
help("Disallow template literal placeholder syntax in regular strings")
)]
struct NoTemplateCurlyInStringDiagnostic(#[label] pub Span);

#[derive(Debug, Default, Clone)]
pub struct NoTemplateCurlyInString;

declare_oxc_lint!(
/// ### What it does
/// Disallow template literal placeholder syntax in regular strings
///
/// ### Why is this bad?
/// ECMAScript 6 allows programmers to create strings containing variable or expressions using template literals, instead of string concatenation, by writing expressions like ${variable} between two backtick quotes (`). It can be easy to use the wrong quotes when wanting to use template literals, by writing "${variable}", and end up with the literal value "${variable}" instead of a string containing the value of the injected expressions.
///
/// ### Example
/// ```javascript
/// /*eslint no-template-curly-in-string: "error"*/
/// "Hello ${name}!";
/// 'Hello ${name}!';
/// "Time: ${12 * 60 * 60 * 1000}";
/// ```
NoTemplateCurlyInString,
style
);

impl Rule for NoTemplateCurlyInString {
fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) {
if let AstKind::StringLiteral(literal) = node.kind() {
let text = literal.value.as_str();
if let Some(start_index) = text.find("${") {
let mut open_braces_count = 0;
let mut end_index = None;

for (i, c) in text[start_index..].char_indices() {
let real_index = start_index + i;
if c == '{' {
open_braces_count += 1;
} else if c == '}' && open_braces_count > 0 {
open_braces_count -= 1;
if open_braces_count == 0 {
end_index = Some(real_index);
break;
}
}
}

if let Some(end_index) = end_index {
let literal_span_start = literal.span.start + 1;
let match_start = u32::try_from(start_index)
.expect("Conversion from usize to u32 failed for match_start");
let match_end = u32::try_from(end_index + 1)
.expect("Conversion from usize to u32 failed for match_end");
ctx.diagnostic(NoTemplateCurlyInStringDiagnostic(Span::new(
literal_span_start + match_start,
literal_span_start + match_end,
)));
}
}
}
}
}

#[test]
fn test() {
use crate::tester::Tester;

let pass = vec![
"`Hello, ${name}`;",
"templateFunction`Hello, ${name}`;",
"`Hello, name`;",
"'Hello, name';",
"'Hello, ' + name;",
"`Hello, ${index + 1}`",
r#"`Hello, ${name + " foo"}`"#,
r#"`Hello, ${name || "foo"}`"#,
r#"`Hello, ${{foo: "bar"}.foo}`"#,
"'$2'",
"'${'",
"'$}'",
"'{foo}'",
r#"'{foo: "bar"}'"#,
"const number = 3",
];

let fail = vec![
"'Hello, ${name}'",
r#""Hello, ${name}""#,
"'${greeting}, ${name}'",
"'Hello, ${index + 1}'",
r#"'Hello, ${name + " foo"}'"#,
r#"'Hello, ${name || "foo"}'"#,
r#"'Hello, ${{foo: "bar"}.foo}'"#,
];

Tester::new(NoTemplateCurlyInString::NAME, pass, fail).test_and_snapshot();
}
52 changes: 52 additions & 0 deletions crates/oxc_linter/src/snapshots/no_template_curly_in_string.snap
@@ -0,0 +1,52 @@
---
source: crates/oxc_linter/src/tester.rs
expression: no_template_curly_in_string
---
eslint(no-template-curly-in-string): Unexpected template string expression
╭─[no_template_curly_in_string.tsx:1:9]
1'Hello, ${name}'
· ───────
╰────
help: Disallow template literal placeholder syntax in regular strings

eslint(no-template-curly-in-string): Unexpected template string expression
╭─[no_template_curly_in_string.tsx:1:9]
1"Hello, ${name}"
· ───────
╰────
help: Disallow template literal placeholder syntax in regular strings

eslint(no-template-curly-in-string): Unexpected template string expression
╭─[no_template_curly_in_string.tsx:1:2]
1'${greeting}, ${name}'
· ───────────
╰────
help: Disallow template literal placeholder syntax in regular strings

eslint(no-template-curly-in-string): Unexpected template string expression
╭─[no_template_curly_in_string.tsx:1:9]
1'Hello, ${index + 1}'
· ────────────
╰────
help: Disallow template literal placeholder syntax in regular strings

eslint(no-template-curly-in-string): Unexpected template string expression
╭─[no_template_curly_in_string.tsx:1:9]
1'Hello, ${name + " foo"}'
· ────────────────
╰────
help: Disallow template literal placeholder syntax in regular strings

eslint(no-template-curly-in-string): Unexpected template string expression
╭─[no_template_curly_in_string.tsx:1:9]
1'Hello, ${name || "foo"}'
· ────────────────
╰────
help: Disallow template literal placeholder syntax in regular strings

eslint(no-template-curly-in-string): Unexpected template string expression
╭─[no_template_curly_in_string.tsx:1:9]
1'Hello, ${{foo: "bar"}.foo}'
· ───────────────────
╰────
help: Disallow template literal placeholder syntax in regular strings

0 comments on commit ac813a6

Please sign in to comment.