@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;AAyOH,eAAO,MAAM,mBAAmB;;CAG/B,CAAC"}
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 (variableCount === 1) {
410
- // There's only one variable defined, so we
411
- // want to remove the `let` keyword as well.
412
- const letIndex = getStartOfPreviousToken('let', start, sourceCode);
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, but we're removing
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":"AAkBA,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;;AAEnD,wBAsKG;AAkJH,eAAO,MAAM,mBAAmB;;CAG/B,CAAC"}
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
- const originalLeft = node.left;
31
- const originalRight = node.right;
32
- const left = (0, unwrap_parenthesized_expression_1.unwrapParenthesizedExpression)(originalLeft);
33
- const right = (0, unwrap_parenthesized_expression_1.unwrapParenthesizedExpression)(originalRight);
34
- const isLeftString = (0, literal_primitive_1.isStringLiteralPrimitive)(left) || left instanceof bundled_angular_compiler_1.TemplateLiteral;
35
- const isRightString = (0, literal_primitive_1.isStringLiteralPrimitive)(right) || right instanceof bundled_angular_compiler_1.TemplateLiteral;
36
- // If both sides are not strings, we don't report anything
37
- if (!isLeftString && !isRightString) {
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 either side is not a literal primitive, we need to use backticks for interpolation
47
- if (!(0, literal_primitive_1.isLiteralPrimitive)(left) || !(0, literal_primitive_1.isLiteralPrimitive)(right)) {
115
+ // If there are any expression parts, we need backticks
116
+ if (!allLiterals) {
48
117
  return '`';
49
118
  }
50
- if (left instanceof bundled_angular_compiler_1.LiteralPrimitive &&
51
- right instanceof bundled_angular_compiler_1.LiteralPrimitive) {
52
- const leftValue = sourceCode.text.at(left.sourceSpan.start);
53
- if (leftValue === "'" || leftValue === '"') {
54
- return leftValue;
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
- const rightValue = sourceCode.text.at(right.sourceSpan.start);
57
- if (rightValue === "'" || rightValue === '"') {
58
- return rightValue;
126
+ if (char === '`') {
127
+ // If original had template literal, keep backticks
128
+ return '`';
59
129
  }
60
130
  }
61
- return '`';
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
- // If both sides are literals, we remove the `+` sign, escape if necessary and concatenate them
81
- if (left instanceof bundled_angular_compiler_1.LiteralPrimitive &&
82
- right instanceof bundled_angular_compiler_1.LiteralPrimitive) {
83
- fixes.push(fixer.replaceTextRange([start, end], parentIsTemplateLiteral
84
- ? `${(0, literal_primitive_1.getLiteralPrimitiveStringValue)(left, '`')}${(0, literal_primitive_1.getLiteralPrimitiveStringValue)(right, '`')}`
85
- : `${quote}${(0, literal_primitive_1.getLiteralPrimitiveStringValue)(left, quote)}${(0, literal_primitive_1.getLiteralPrimitiveStringValue)(right, quote)}${quote}`));
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
- fixes.push(...getRightSideFixes(fixer, right, quote));
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-alpha.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-alpha.1",
24
- "@angular-eslint/utils": "21.0.1-alpha.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-alpha.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-alpha.1"
35
+ "@angular-eslint/template-parser": "21.0.1"
36
36
  },
37
37
  "gitHead": "e2006e5e9c99e5a943d1a999e0efa5247d29ec24"
38
38
  }