@angular-eslint/eslint-plugin-template 21.0.1-alpha.1 → 21.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"prefer-contextual-for-variables.d.ts","sourceRoot":"","sources":["../../src/rules/prefer-contextual-for-variables.ts"],"names":[],"mappings":"AAoBA,MAAM,MAAM,OAAO,GAAG;IACpB;QACE,QAAQ,CAAC,cAAc,CAAC,EAAE;YACxB,MAAM,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;YAC3B,MAAM,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;YAC3B,MAAM,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;YAC3B,KAAK,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;YAC1B,KAAK,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;YAC1B,IAAI,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;SAC1B,CAAC;KACH;CACF,CAAC;AACF,MAAM,MAAM,UAAU,GAClB,0BAA0B,GAC1B,aAAa,GACb,aAAa,GACb,YAAY,GACZ,YAAY,GACZ,WAAW,CAAC;AAChB,eAAO,MAAM,SAAS,oCAAoC,CAAC;;AAc3D,wBAkaG;
|
|
1
|
+
{"version":3,"file":"prefer-contextual-for-variables.d.ts","sourceRoot":"","sources":["../../src/rules/prefer-contextual-for-variables.ts"],"names":[],"mappings":"AAoBA,MAAM,MAAM,OAAO,GAAG;IACpB;QACE,QAAQ,CAAC,cAAc,CAAC,EAAE;YACxB,MAAM,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;YAC3B,MAAM,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;YAC3B,MAAM,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;YAC3B,KAAK,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;YAC1B,KAAK,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;YAC1B,IAAI,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;SAC1B,CAAC;KACH;CACF,CAAC;AACF,MAAM,MAAM,UAAU,GAClB,0BAA0B,GAC1B,aAAa,GACb,aAAa,GACb,YAAY,GACZ,YAAY,GACZ,WAAW,CAAC;AAChB,eAAO,MAAM,SAAS,oCAAoC,CAAC;;AAc3D,wBAkaG;AAmRH,eAAO,MAAM,mBAAmB;;CAG/B,CAAC"}
|
|
@@ -406,17 +406,21 @@ function getAllowedAliases(allowedAliases, variableName) {
|
|
|
406
406
|
function getVariableRangeToRemove(problem, sourceCode, variableCount) {
|
|
407
407
|
let start = problem.variable.sourceSpan.start.offset;
|
|
408
408
|
let end = problem.variable.sourceSpan.end.offset;
|
|
409
|
-
if
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
409
|
+
// Check if this variable has its own `let` keyword (semicolon-separated)
|
|
410
|
+
// vs being part of a comma-separated list after a single `let`.
|
|
411
|
+
const letIndex = getStartOfPreviousToken('let', start, sourceCode);
|
|
412
|
+
const hasOwnLet = letIndex !== undefined &&
|
|
413
|
+
hasOwnLetKeyword(letIndex, start, end, sourceCode);
|
|
414
|
+
if (variableCount === 1 || hasOwnLet) {
|
|
415
|
+
// Either there's only one variable, or this variable has its own
|
|
416
|
+
// `let` keyword (semicolon-separated), so remove the `let` as well.
|
|
413
417
|
if (letIndex !== undefined) {
|
|
414
418
|
// We also want to remove the preceding semicolon.
|
|
415
419
|
start = getStartOfPreviousToken(';', letIndex, sourceCode) ?? letIndex;
|
|
416
420
|
}
|
|
417
421
|
}
|
|
418
422
|
else if (problem.index === 0) {
|
|
419
|
-
// There are multiple variables,
|
|
423
|
+
// There are multiple comma-separated variables, and we're removing
|
|
420
424
|
// the first one. We need to keep the `let` keyword, but
|
|
421
425
|
// remove the trailing comma and any whitespace after it.
|
|
422
426
|
const commaIndex = getStartOfNextToken(',', end, sourceCode);
|
|
@@ -427,12 +431,40 @@ function getVariableRangeToRemove(problem, sourceCode, variableCount) {
|
|
|
427
431
|
}
|
|
428
432
|
}
|
|
429
433
|
else {
|
|
430
|
-
// There is a variable before this one, so we
|
|
434
|
+
// There is a comma-separated variable before this one, so we
|
|
431
435
|
// need to remove the preceding comma as well.
|
|
432
436
|
start = getStartOfPreviousToken(',', start, sourceCode) ?? start;
|
|
433
437
|
}
|
|
434
438
|
return [start, end];
|
|
435
439
|
}
|
|
440
|
+
/**
|
|
441
|
+
* Checks if the `let` keyword at `letIndex` belongs solely to this variable
|
|
442
|
+
* (i.e., this is a semicolon-separated declaration where this variable
|
|
443
|
+
* has its own `let` keyword that isn't shared with other variables).
|
|
444
|
+
*
|
|
445
|
+
* A variable has its own `let` if:
|
|
446
|
+
* 1. It's the first variable after the `let` (no comma before it), AND
|
|
447
|
+
* 2. There are no comma-separated variables after it (next char after
|
|
448
|
+
* the variable is `;` or `)`, not `,`)
|
|
449
|
+
*/
|
|
450
|
+
function hasOwnLetKeyword(letIndex, variableStart, variableEnd, sourceCode) {
|
|
451
|
+
const text = sourceCode.text;
|
|
452
|
+
// Check if there's a comma between `let` and the variable start.
|
|
453
|
+
// If there is, this variable is not the first after `let`.
|
|
454
|
+
const betweenLetAndVar = text.slice(letIndex + 3, variableStart);
|
|
455
|
+
if (betweenLetAndVar.includes(',')) {
|
|
456
|
+
return false;
|
|
457
|
+
}
|
|
458
|
+
// This variable is the first after `let`. Now check if there are more
|
|
459
|
+
// comma-separated variables after it. If so, this `let` is shared.
|
|
460
|
+
// Find the next non-whitespace character after the variable.
|
|
461
|
+
let nextIndex = variableEnd;
|
|
462
|
+
while (nextIndex < text.length && /\s/.test(text[nextIndex])) {
|
|
463
|
+
nextIndex++;
|
|
464
|
+
}
|
|
465
|
+
// If the next character is a comma, there are more variables sharing this `let`.
|
|
466
|
+
return text[nextIndex] !== ',';
|
|
467
|
+
}
|
|
436
468
|
function getStartOfPreviousToken(tokenToFind, startIndex, sourceCode) {
|
|
437
469
|
const text = sourceCode.text;
|
|
438
470
|
for (let i = startIndex - tokenToFind.length; i >= 0; i--) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"prefer-template-literal.d.ts","sourceRoot":"","sources":["../../src/rules/prefer-template-literal.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"prefer-template-literal.d.ts","sourceRoot":"","sources":["../../src/rules/prefer-template-literal.ts"],"names":[],"mappings":"AAeA,QAAA,MAAM,SAAS,0BAA0B,CAAC;AAE1C,MAAM,MAAM,OAAO,GAAG,EAAE,CAAC;AACzB,MAAM,MAAM,UAAU,GAAG,OAAO,SAAS,CAAC;AAC1C,eAAO,MAAM,SAAS,4BAA4B,CAAC;;AA0FnD,wBA+IG;AAEH,eAAO,MAAM,mBAAmB;;CAG/B,CAAC"}
|
|
@@ -8,6 +8,71 @@ const literal_primitive_1 = require("../utils/literal-primitive");
|
|
|
8
8
|
const unwrap_parenthesized_expression_1 = require("../utils/unwrap-parenthesized-expression");
|
|
9
9
|
const messageId = 'preferTemplateLiteral';
|
|
10
10
|
exports.RULE_NAME = 'prefer-template-literal';
|
|
11
|
+
/**
|
|
12
|
+
* Check if this node is part of a larger Binary + chain.
|
|
13
|
+
* If so, we should skip and let the topmost node handle it.
|
|
14
|
+
*/
|
|
15
|
+
function isPartOfLargerBinaryChain(node) {
|
|
16
|
+
if (!('parent' in node))
|
|
17
|
+
return false;
|
|
18
|
+
const parent = node.parent;
|
|
19
|
+
// If parent is a Binary +, we're part of a larger chain
|
|
20
|
+
return parent instanceof bundled_angular_compiler_1.Binary && parent.operation === '+';
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Check if a Binary + chain contains at least one string literal anywhere.
|
|
24
|
+
* This recursively checks the entire tree.
|
|
25
|
+
*/
|
|
26
|
+
function chainContainsString(node) {
|
|
27
|
+
const unwrapped = (0, unwrap_parenthesized_expression_1.unwrapParenthesizedExpression)(node);
|
|
28
|
+
if ((0, literal_primitive_1.isStringLiteralPrimitive)(unwrapped) ||
|
|
29
|
+
unwrapped instanceof bundled_angular_compiler_1.TemplateLiteral) {
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
if (unwrapped instanceof bundled_angular_compiler_1.Binary && unwrapped.operation === '+') {
|
|
33
|
+
return (chainContainsString(unwrapped.left) ||
|
|
34
|
+
chainContainsString(unwrapped.right));
|
|
35
|
+
}
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Flatten a Binary + tree into a list of parts.
|
|
40
|
+
* This recursively collects all operands in a left-to-right order.
|
|
41
|
+
*/
|
|
42
|
+
function flattenBinaryConcat(node) {
|
|
43
|
+
const unwrapped = (0, unwrap_parenthesized_expression_1.unwrapParenthesizedExpression)(node);
|
|
44
|
+
if (unwrapped instanceof bundled_angular_compiler_1.Binary && unwrapped.operation === '+') {
|
|
45
|
+
// Recursively flatten both sides
|
|
46
|
+
return [
|
|
47
|
+
...flattenBinaryConcat(unwrapped.left),
|
|
48
|
+
...flattenBinaryConcat(unwrapped.right),
|
|
49
|
+
];
|
|
50
|
+
}
|
|
51
|
+
if (unwrapped instanceof bundled_angular_compiler_1.TemplateLiteral) {
|
|
52
|
+
// Flatten template literals by interleaving string elements and expressions
|
|
53
|
+
const parts = [];
|
|
54
|
+
for (let i = 0; i < unwrapped.elements.length; i++) {
|
|
55
|
+
// Add the string part from the element's text
|
|
56
|
+
if (unwrapped.elements[i].text) {
|
|
57
|
+
parts.push({ type: 'literal', value: unwrapped.elements[i].text });
|
|
58
|
+
}
|
|
59
|
+
// Add the expression part (if exists)
|
|
60
|
+
if (i < unwrapped.expressions.length) {
|
|
61
|
+
parts.push({ type: 'expression', node: unwrapped.expressions[i] });
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return parts;
|
|
65
|
+
}
|
|
66
|
+
if ((0, literal_primitive_1.isLiteralPrimitive)(unwrapped)) {
|
|
67
|
+
// Convert the literal to a string
|
|
68
|
+
const value = typeof unwrapped.value === 'string'
|
|
69
|
+
? unwrapped.value
|
|
70
|
+
: String(unwrapped.value);
|
|
71
|
+
return [{ type: 'literal', value }];
|
|
72
|
+
}
|
|
73
|
+
// Any other expression - use the unwrapped node (without parentheses)
|
|
74
|
+
return [{ type: 'expression', node: unwrapped }];
|
|
75
|
+
}
|
|
11
76
|
exports.default = (0, create_eslint_rule_1.createESLintRule)({
|
|
12
77
|
name: exports.RULE_NAME,
|
|
13
78
|
meta: {
|
|
@@ -27,38 +92,44 @@ exports.default = (0, create_eslint_rule_1.createESLintRule)({
|
|
|
27
92
|
const { sourceCode } = context;
|
|
28
93
|
return {
|
|
29
94
|
'Binary[operation="+"]'(node) {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
//
|
|
37
|
-
|
|
95
|
+
// Skip if this node is part of a larger Binary + chain
|
|
96
|
+
// Let the topmost node handle the entire chain
|
|
97
|
+
if (isPartOfLargerBinaryChain(node)) {
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
// Check if this Binary + chain contains at least one string
|
|
101
|
+
// This handles cases where the immediate operands aren't strings,
|
|
102
|
+
// but nested operands are (e.g., x.type + "" + y)
|
|
103
|
+
if (!chainContainsString(node)) {
|
|
38
104
|
return;
|
|
39
105
|
}
|
|
40
106
|
const { sourceSpan: { start, end }, } = node;
|
|
41
107
|
const parentIsTemplateLiteral = 'parent' in node && node.parent instanceof bundled_angular_compiler_1.TemplateLiteral;
|
|
108
|
+
// Flatten the entire concatenation chain
|
|
109
|
+
const parts = flattenBinaryConcat(node);
|
|
110
|
+
const allLiterals = parts.every((p) => p.type === 'literal');
|
|
42
111
|
function getQuote() {
|
|
43
112
|
if (parentIsTemplateLiteral) {
|
|
44
113
|
return '';
|
|
45
114
|
}
|
|
46
|
-
// If
|
|
47
|
-
if (!
|
|
115
|
+
// If there are any expression parts, we need backticks
|
|
116
|
+
if (!allLiterals) {
|
|
48
117
|
return '`';
|
|
49
118
|
}
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
119
|
+
// All parts are literals - try to preserve the original quote style
|
|
120
|
+
// Search the source for the first string literal quote
|
|
121
|
+
const sourceText = sourceCode.text.slice(start, end);
|
|
122
|
+
for (const char of sourceText) {
|
|
123
|
+
if (char === "'" || char === '"') {
|
|
124
|
+
return char;
|
|
55
125
|
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
return
|
|
126
|
+
if (char === '`') {
|
|
127
|
+
// If original had template literal, keep backticks
|
|
128
|
+
return '`';
|
|
59
129
|
}
|
|
60
130
|
}
|
|
61
|
-
|
|
131
|
+
// No string quotes found (all numbers/booleans/etc) - use single quote
|
|
132
|
+
return "'";
|
|
62
133
|
}
|
|
63
134
|
context.report({
|
|
64
135
|
loc: {
|
|
@@ -77,35 +148,25 @@ exports.default = (0, create_eslint_rule_1.createESLintRule)({
|
|
|
77
148
|
node.sourceSpan.start,
|
|
78
149
|
]));
|
|
79
150
|
}
|
|
80
|
-
//
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
else {
|
|
88
|
-
// Fix the left side - handle parenthesized expressions specially
|
|
89
|
-
if (originalLeft instanceof bundled_angular_compiler_1.ParenthesizedExpression) {
|
|
90
|
-
fixes.push(...getLeftSideFixesForParenthesized(fixer, left, originalLeft, quote));
|
|
91
|
-
}
|
|
92
|
-
else {
|
|
93
|
-
fixes.push(...getLeftSideFixes(fixer, left, quote));
|
|
94
|
-
}
|
|
95
|
-
// Remove the `+` sign (including surrounding whitespace)
|
|
96
|
-
fixes.push(fixer.removeRange([
|
|
97
|
-
originalLeft.sourceSpan.end,
|
|
98
|
-
originalRight.sourceSpan.start,
|
|
99
|
-
]));
|
|
100
|
-
// Fix the right side - handle parenthesized expressions specially
|
|
101
|
-
if (originalRight instanceof bundled_angular_compiler_1.ParenthesizedExpression) {
|
|
102
|
-
// For parenthesized expressions, we want to replace the whole thing including parens
|
|
103
|
-
fixes.push(...getRightSideFixesForParenthesized(fixer, right, originalRight, quote));
|
|
151
|
+
// Build the replacement string from all parts
|
|
152
|
+
const effectiveQuote = quote === '' ? '`' : quote;
|
|
153
|
+
let replacement = '';
|
|
154
|
+
for (const part of parts) {
|
|
155
|
+
if (part.type === 'literal') {
|
|
156
|
+
// Escape the quote character in the value
|
|
157
|
+
replacement += part.value.replaceAll(effectiveQuote, `\\${effectiveQuote}`);
|
|
104
158
|
}
|
|
105
159
|
else {
|
|
106
|
-
|
|
160
|
+
// Expression - wrap in ${}
|
|
161
|
+
const exprText = sourceCode.text.slice(part.node.sourceSpan.start, part.node.sourceSpan.end);
|
|
162
|
+
replacement += `\${${exprText}}`;
|
|
107
163
|
}
|
|
108
164
|
}
|
|
165
|
+
// Add quotes if not inside a template literal
|
|
166
|
+
if (!parentIsTemplateLiteral) {
|
|
167
|
+
replacement = `${quote}${replacement}${quote}`;
|
|
168
|
+
}
|
|
169
|
+
fixes.push(fixer.replaceTextRange([start, end], replacement));
|
|
109
170
|
// If the parent is a template literal, remove the `}` sign
|
|
110
171
|
if (parentIsTemplateLiteral) {
|
|
111
172
|
const templateInterpolationEndIndex = sourceCode.text.indexOf('}', node.sourceSpan.end);
|
|
@@ -121,104 +182,6 @@ exports.default = (0, create_eslint_rule_1.createESLintRule)({
|
|
|
121
182
|
};
|
|
122
183
|
},
|
|
123
184
|
});
|
|
124
|
-
function getLeftSideFixes(fixer, left, quote) {
|
|
125
|
-
const { start, end } = left.sourceSpan;
|
|
126
|
-
if (left instanceof bundled_angular_compiler_1.TemplateLiteral) {
|
|
127
|
-
// Remove the end ` sign from the left side
|
|
128
|
-
return [
|
|
129
|
-
fixer.replaceTextRange([start, start + 1], quote),
|
|
130
|
-
fixer.removeRange([end - 1, end]),
|
|
131
|
-
];
|
|
132
|
-
}
|
|
133
|
-
if ((0, literal_primitive_1.isLiteralPrimitive)(left)) {
|
|
134
|
-
// Transform left side to template literal
|
|
135
|
-
return [
|
|
136
|
-
fixer.replaceTextRange([start, end], quote === ''
|
|
137
|
-
? `${(0, literal_primitive_1.getLiteralPrimitiveStringValue)(left, '`')}`
|
|
138
|
-
: `${quote}${(0, literal_primitive_1.getLiteralPrimitiveStringValue)(left, quote)}`),
|
|
139
|
-
];
|
|
140
|
-
}
|
|
141
|
-
// Transform left side to template literal
|
|
142
|
-
return [
|
|
143
|
-
fixer.insertTextBeforeRange([start, end], `${quote}\${`),
|
|
144
|
-
fixer.insertTextAfterRange([start, end], '}'),
|
|
145
|
-
];
|
|
146
|
-
}
|
|
147
|
-
function getLeftSideFixesForParenthesized(fixer, innerExpression, parenthesizedExpression, quote) {
|
|
148
|
-
const parenthesizedStart = parenthesizedExpression.sourceSpan.start;
|
|
149
|
-
const parenthesizedEnd = parenthesizedExpression.sourceSpan.end;
|
|
150
|
-
const innerStart = innerExpression.sourceSpan.start;
|
|
151
|
-
const innerEnd = innerExpression.sourceSpan.end;
|
|
152
|
-
if (innerExpression instanceof bundled_angular_compiler_1.TemplateLiteral) {
|
|
153
|
-
// Remove the end ` sign from the inner expression and remove the parentheses
|
|
154
|
-
return [
|
|
155
|
-
fixer.replaceTextRange([parenthesizedStart, innerStart + 1], quote), // Replace opening paren and backtick with quote
|
|
156
|
-
fixer.removeRange([innerEnd - 1, parenthesizedEnd]), // Remove closing backtick and paren
|
|
157
|
-
];
|
|
158
|
-
}
|
|
159
|
-
if ((0, literal_primitive_1.isLiteralPrimitive)(innerExpression)) {
|
|
160
|
-
// Transform to template literal and remove parentheses
|
|
161
|
-
return [
|
|
162
|
-
fixer.replaceTextRange([parenthesizedStart, parenthesizedEnd], quote === ''
|
|
163
|
-
? `${(0, literal_primitive_1.getLiteralPrimitiveStringValue)(innerExpression, '`')}`
|
|
164
|
-
: `${quote}${(0, literal_primitive_1.getLiteralPrimitiveStringValue)(innerExpression, quote)}`),
|
|
165
|
-
];
|
|
166
|
-
}
|
|
167
|
-
// Transform parenthesized expression to template literal by removing parens and wrapping in ${}
|
|
168
|
-
return [
|
|
169
|
-
fixer.replaceTextRange([parenthesizedStart, innerStart], `${quote}\${`), // Replace opening paren with quote${
|
|
170
|
-
fixer.replaceTextRange([innerEnd, parenthesizedEnd], '}'), // Replace closing paren with }
|
|
171
|
-
];
|
|
172
|
-
}
|
|
173
|
-
function getRightSideFixes(fixer, right, quote) {
|
|
174
|
-
const { start, end } = right.sourceSpan;
|
|
175
|
-
if (right instanceof bundled_angular_compiler_1.TemplateLiteral) {
|
|
176
|
-
// Remove the start ` sign from the right side
|
|
177
|
-
return [
|
|
178
|
-
fixer.removeRange([start, start + 1]),
|
|
179
|
-
fixer.replaceTextRange([end - 1, end], quote),
|
|
180
|
-
];
|
|
181
|
-
}
|
|
182
|
-
if ((0, literal_primitive_1.isLiteralPrimitive)(right)) {
|
|
183
|
-
// Transform right side to template literal if it's a string
|
|
184
|
-
return [
|
|
185
|
-
fixer.replaceTextRange([start, end], quote === ''
|
|
186
|
-
? `${(0, literal_primitive_1.getLiteralPrimitiveStringValue)(right, '`')}`
|
|
187
|
-
: `${(0, literal_primitive_1.getLiteralPrimitiveStringValue)(right, quote)}${quote}`),
|
|
188
|
-
];
|
|
189
|
-
}
|
|
190
|
-
// Transform right side to template literal
|
|
191
|
-
return [
|
|
192
|
-
fixer.insertTextBeforeRange([start, end], '${'),
|
|
193
|
-
fixer.insertTextAfterRange([start, end], `}${quote}`),
|
|
194
|
-
];
|
|
195
|
-
}
|
|
196
|
-
function getRightSideFixesForParenthesized(fixer, innerExpression, parenthesizedExpression, quote) {
|
|
197
|
-
const parenthesizedStart = parenthesizedExpression.sourceSpan.start;
|
|
198
|
-
const parenthesizedEnd = parenthesizedExpression.sourceSpan.end;
|
|
199
|
-
const innerStart = innerExpression.sourceSpan.start;
|
|
200
|
-
const innerEnd = innerExpression.sourceSpan.end;
|
|
201
|
-
if (innerExpression instanceof bundled_angular_compiler_1.TemplateLiteral) {
|
|
202
|
-
// Remove the start ` sign from the inner expression and remove the parentheses
|
|
203
|
-
return [
|
|
204
|
-
fixer.removeRange([parenthesizedStart, innerStart + 1]), // Remove opening paren and backtick
|
|
205
|
-
fixer.replaceTextRange([innerEnd - 1, parenthesizedEnd], quote), // Replace closing backtick and paren with quote
|
|
206
|
-
];
|
|
207
|
-
}
|
|
208
|
-
if ((0, literal_primitive_1.isLiteralPrimitive)(innerExpression)) {
|
|
209
|
-
// Transform to template literal and remove parentheses
|
|
210
|
-
return [
|
|
211
|
-
fixer.replaceTextRange([parenthesizedStart, parenthesizedEnd], quote === ''
|
|
212
|
-
? `${(0, literal_primitive_1.getLiteralPrimitiveStringValue)(innerExpression, '`')}`
|
|
213
|
-
: `${(0, literal_primitive_1.getLiteralPrimitiveStringValue)(innerExpression, quote)}${quote}`),
|
|
214
|
-
];
|
|
215
|
-
}
|
|
216
|
-
// Transform parenthesized expression to template literal by removing parens and wrapping in ${}
|
|
217
|
-
return [
|
|
218
|
-
fixer.replaceTextRange([parenthesizedStart, innerStart], '${'), // Replace opening paren with ${
|
|
219
|
-
fixer.replaceTextRange([innerEnd, parenthesizedEnd], `}${quote}`), // Replace closing paren with }quote
|
|
220
|
-
];
|
|
221
|
-
}
|
|
222
185
|
exports.RULE_DOCS_EXTENSION = {
|
|
223
186
|
rationale: 'Template literals (backticks with ${} syntax) are more modern, readable, and maintainable than string concatenation with the + operator. String concatenation like "Hello " + name + "!" is harder to read and error-prone (easy to forget spaces or quotes) compared to the template literal `Hello ${name}!`. Template literals make string composition clearer, especially with multiple expressions. This is a widely accepted JavaScript/TypeScript best practice that should be followed in Angular templates for consistency. Angular templates have supported template literal syntax since v19.2. Using template literals throughout your codebase creates a consistent style and makes complex string building much more readable.',
|
|
224
187
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@angular-eslint/eslint-plugin-template",
|
|
3
|
-
"version": "21.0.1
|
|
3
|
+
"version": "21.0.1",
|
|
4
4
|
"description": "ESLint plugin for Angular Templates",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -20,19 +20,19 @@
|
|
|
20
20
|
"dependencies": {
|
|
21
21
|
"aria-query": "5.3.2",
|
|
22
22
|
"axobject-query": "4.1.0",
|
|
23
|
-
"@angular-eslint/bundled-angular-compiler": "21.0.1
|
|
24
|
-
"@angular-eslint/utils": "21.0.1
|
|
23
|
+
"@angular-eslint/bundled-angular-compiler": "21.0.1",
|
|
24
|
+
"@angular-eslint/utils": "21.0.1"
|
|
25
25
|
},
|
|
26
26
|
"devDependencies": {
|
|
27
27
|
"@types/aria-query": "5.0.4",
|
|
28
|
-
"@angular-eslint/test-utils": "21.0.1
|
|
28
|
+
"@angular-eslint/test-utils": "21.0.1"
|
|
29
29
|
},
|
|
30
30
|
"peerDependencies": {
|
|
31
31
|
"@typescript-eslint/types": "^7.11.0 || ^8.0.0",
|
|
32
32
|
"@typescript-eslint/utils": "^7.11.0 || ^8.0.0",
|
|
33
33
|
"eslint": "^8.57.0 || ^9.0.0",
|
|
34
34
|
"typescript": "*",
|
|
35
|
-
"@angular-eslint/template-parser": "21.0.1
|
|
35
|
+
"@angular-eslint/template-parser": "21.0.1"
|
|
36
36
|
},
|
|
37
37
|
"gitHead": "e2006e5e9c99e5a943d1a999e0efa5247d29ec24"
|
|
38
38
|
}
|