Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(linter): no_template_curly_in_string (#2763)
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
Showing
3 changed files
with
164 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
110 changes: 110 additions & 0 deletions
110
crates/oxc_linter/src/rules/eslint/no_template_curly_in_string.rs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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
52
crates/oxc_linter/src/snapshots/no_template_curly_in_string.snap
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |