biome nursery/noUnnecessaryTemplateExpression
labelled safe
Analysis Nemotron 3 Super
Flags template literals containing only string literal interpolations that can be rewritten as concatenated or plain string literals.
Fix: Replaces the template literal with either a plain string literal if it has a single interpolation, or a string concatenation using + operator if it has multiple adjacent string literal interpolations, preserving original whitespace and quotes where applicable.
12 hazards
- Rewriting `${'hello'}` to 'hello' changes behavior if the code relies on the template literal being a distinct object identity (e.g., for === comparison with another template literal).
- Converting `${'a'}${'b'}` to 'a'+'b' may alter evaluation order if the original template was part of a larger expression where left-to-right timing mattered for side effects in surrounding expressions.
- If the template literal appears in a context expecting a TemplateLiteral type (e.g., tagged template inference), replacing it with a string literal breaks type checking in TypeScript.
- The autofix may incorrectly remove escaped characters like \n or \t within the string literals if they are present, altering the resulting string value.
- When the template literal is immediately followed by a plus (+) without space, rewriting `'a'+'b'` could be misparsed as part of a larger concatenation chain due to operator precedence.
- Comments inside the template literal (e.g., `${'hello' /* comment */}`) would be lost during the rewrite since string literals do not preserve internal comments.
- If the string literal contains a line terminator (e.g., `${'a\nb'}`), rewriting it as a plain string literal may break if the surrounding code depends on the template literal's multi-line nature for ASI or parsing.
- In strict mode, if the original template literal was used in an eval-like context, replacing it with a string literal could change how the string is interpreted due to differing escape handling.
- The fix assumes all interpolated parts are pure string literals; if any interpolation is actually a non-literal (e.g., due to macro expansion), the rewrite would incorrectly remove necessary template syntax.
- When the template literal is part of a sequence expression or comma operator, rewriting it may change how the expression is parsed or optimized by minifiers.
- If the template literal is used as a property key in an object literal, rewriting it to a string literal may cause a syntax error if the original relied on template literal flexibility (though rare, as keys are coerced).
- The autofix may insert unnecessary parentheses around the rewritten expression if not carefully formatted, potentially altering precedence in edge cases involving exponentiation or nullish coalescing.
Programs
| id | result | title | hazard |
|---|---|---|---|
| r1-p001 | fix ok | Evaluation order changed when template literal part of larger expression | Converting `${'a'}${'b'}` to 'a'+'b' may alter evaluation order if the original template was part of a larger expression where left-to-right timing mattered for side effects in surrounding expressions |
| r1-p002 | silent prediction off | Type checking broken in tagged template context | If the template literal appears in a context expecting a TemplateLiteral type (e.g., tagged template inference), replacing it with a string literal breaks type checking in TypeScript. |
| r1-p003 | fix ok | Sequence expression parsing changed by rewrite | When the template literal is part of a sequence expression or comma operator, rewriting it may change how the expression is parsed or optimized by minifiers. |
| r1-p004 | invalid | Internal comments lost during rewrite | Comments inside the template literal (e.g., `${'hello' /* comment */}`) would be lost during the rewrite since string literals do not preserve internal comments. |
| r1-p005 | fix ok | Template literal as object property key | If the template literal is used as a property key in an object literal, rewriting it to a string literal may cause a syntax error if the original relied on template literal flexibility (though rare, a |
| r1-p006 | fix ok | __proto__ key handling affected by rewrite | __proto__ keys, prototype access, Object.assign vs spread |
| r1-p007 | invalid | this binding affected in method extraction | this binding: arrow vs function, methods extracted from objects, call/apply/bind |
| r1-p008 | invalid | Exceptions in removed code change behavior | exceptions thrown inside the code the fix removes or rewrites |
Sources
r1-p001 · Evaluation order changed when template literal part of larger expression
1let x = 0;
2const log = v => { console.log('log:', v); return ++x; };
3expr = (log(1), `${'a'}${'b'}`, log(2));
4console.log('Result:', expr);
--- orig.js 2026-10-17 16:47:42.635184249 -0400 +++ fixed.js 2026-10-17 16:47:42.918190776 -0400 @@ -1,4 +1,4 @@ let x = 0; const log = v => { console.log('log:', v); return ++x; }; -expr = (log(1), `${'a'}${'b'}`, log(2)); +expr = (log(1), "ab", log(2)); console.log('Result:', expr);
log: 1 log: 2
r1-p002 · Type checking broken in tagged template context
1function tag(strings, ...values) {
2 return { raw: strings, cooked: values };
3}
4const result = tag`${'hello'}${'world'}`;
5console.log('Tagged result:', JSON.stringify(result));
Tagged result: {"raw":["","",""],"cooked":["hello","world"]}r1-p003 · Sequence expression parsing changed by rewrite
1let evalOrder = [];
2const push = v => { evalOrder.push(v); return v; };
3const expr = (push(1), `${'a'}${'b'}`, push(2), push(3));
4console.log('Eval order:', evalOrder.join(','));
5console.log('Expr value:', expr);
--- orig.js 2026-10-17 16:47:43.314199901 -0400 +++ fixed.js 2026-10-17 16:47:43.587206189 -0400 @@ -1,5 +1,5 @@ let evalOrder = []; const push = v => { evalOrder.push(v); return v; }; -const expr = (push(1), `${'a'}${'b'}`, push(2), push(3)); +const expr = (push(1), "ab", push(2), push(3)); console.log('Eval order:', evalOrder.join(',')); console.log('Expr value:', expr);
Eval order: 1,2,3 Expr value: 3
r1-p004 · Internal comments lost during rewrite (generated program does not parse: /home/sun/dev/diaghunt/runs/2026-10-17T20-47-3)
1// This tests near-miss: comment inside interpolation - rule should NOT trigger
2const msg = `${'hello' /* ignored */}${'world'`;
3console.log('Message:', msg);
r1-p005 · Template literal as object property key
1const obj = {
2 [`${'key'}${'name'}`]: 'value'
3};
4console.log('Key exists:', Object.hasOwn(obj, 'keyname'));
5console.log('Value:', obj.keyname);
--- orig.js 2026-10-17 16:47:43.812211373 -0400 +++ fixed.js 2026-10-17 16:47:44.086217682 -0400 @@ -1,5 +1,5 @@ const obj = { - [`${'key'}${'name'}`]: 'value' + ["keyname"]: 'value' }; console.log('Key exists:', Object.hasOwn(obj, 'keyname')); console.log('Value:', obj.keyname);
Key exists: true Value: value
r1-p006 · __proto__ key handling affected by rewrite
1const protoObj = { __proto__: null };
2const key = `${'__proto__'}${'sub'}`;
3try {
4 protoObj[key] = 'should not set proto';
5 console.log('Assignment succeeded, proto unchanged:', protoObj.__proto__ === null);
6} catch (e) {
7 console.log('Error:', e.name, e.message);
8}
--- orig.js 2026-10-17 16:47:44.265221801 -0400 +++ fixed.js 2026-10-17 16:47:44.535228014 -0400 @@ -1,5 +1,5 @@ const protoObj = { __proto__: null }; -const key = `${'__proto__'}${'sub'}`; +const key = "__proto__sub"; try { protoObj[key] = 'should not set proto'; console.log('Assignment succeeded, proto unchanged:', protoObj.__proto__ === null);
Assignment succeeded, proto unchanged: false
r1-p007 · this binding affected in method extraction (generated program does not parse: /home/sun/dev/diaghunt/runs/2026-10-17T20-47-3)
1const obj = {
2 getKey() { return `${'prefix'}${'suffix'`; }
3};
4const fn = obj.getKey;
5console.log('Direct call:', obj.getKey());
6try {
7 console.log('Extracted call:', fn());
8} catch (e) {
9 console.log('Error:', e.name, e.message);
10}
r1-p008 · Exceptions in removed code change behavior (generated program does not parse: /home/sun/dev/diaghunt/runs/2026-10-17T20-47-3)
1let threw = false;
2try {
3 const x = `${'ok'}${(function(){ throw new Error('side effect'); })()}${'fail'`;
4} catch (e) {
5 threw = true;
6 console.log('Caught:', e.name, e.message);
7}
8console.log('Threw:', threw);