@angular-eslint/eslint-plugin-template 19.2.2-alpha.4 → 19.2.2-alpha.6

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.
Files changed (33) hide show
  1. package/README.md +1 -0
  2. package/dist/configs/all.json +1 -0
  3. package/dist/index.d.ts +2 -0
  4. package/dist/index.d.ts.map +1 -1
  5. package/dist/index.js +2 -0
  6. package/dist/rules/alt-text.js +1 -1
  7. package/dist/rules/attributes-order.js +1 -1
  8. package/dist/rules/button-has-type.js +1 -1
  9. package/dist/rules/click-events-have-key-events.js +1 -1
  10. package/dist/rules/conditional-complexity.js +1 -1
  11. package/dist/rules/elements-content.js +1 -1
  12. package/dist/rules/i18n.js +3 -3
  13. package/dist/rules/interactive-supports-focus.js +1 -1
  14. package/dist/rules/label-has-associated-control.js +1 -1
  15. package/dist/rules/mouse-events-have-key-events.js +1 -1
  16. package/dist/rules/no-autofocus.js +1 -1
  17. package/dist/rules/no-distracting-elements.js +1 -1
  18. package/dist/rules/no-duplicate-attributes.js +1 -1
  19. package/dist/rules/no-inline-styles.js +1 -1
  20. package/dist/rules/no-interpolation-in-attributes.js +1 -1
  21. package/dist/rules/no-positive-tabindex.js +1 -1
  22. package/dist/rules/prefer-contextual-for-variables.d.ts +17 -0
  23. package/dist/rules/prefer-contextual-for-variables.d.ts.map +1 -0
  24. package/dist/rules/prefer-contextual-for-variables.js +546 -0
  25. package/dist/rules/prefer-ngsrc.js +1 -1
  26. package/dist/rules/prefer-self-closing-tags.js +1 -1
  27. package/dist/rules/role-has-required-aria.js +1 -1
  28. package/dist/rules/table-scope.js +1 -1
  29. package/dist/rules/valid-aria.js +1 -1
  30. package/dist/utils/are-equivalent-asts.d.ts +3 -0
  31. package/dist/utils/are-equivalent-asts.d.ts.map +1 -0
  32. package/dist/utils/are-equivalent-asts.js +115 -0
  33. package/package.json +5 -5
package/README.md CHANGED
@@ -61,6 +61,7 @@ Please see https://github.com/angular-eslint/angular-eslint for full usage instr
61
61
  | [`no-interpolation-in-attributes`](https://github.com/angular-eslint/angular-eslint/blob/main/packages/eslint-plugin-template/docs/rules/no-interpolation-in-attributes.md) | Ensures that property-binding is used instead of interpolation in attributes. | | | | |
62
62
  | [`no-negated-async`](https://github.com/angular-eslint/angular-eslint/blob/main/packages/eslint-plugin-template/docs/rules/no-negated-async.md) | Ensures that async pipe results, as well as values used with the async pipe, are not negated | :white_check_mark: | | :bulb: | |
63
63
  | [`no-positive-tabindex`](https://github.com/angular-eslint/angular-eslint/blob/main/packages/eslint-plugin-template/docs/rules/no-positive-tabindex.md) | Ensures that the `tabindex` attribute is not positive | | | :bulb: | |
64
+ | [`prefer-contextual-for-variables`](https://github.com/angular-eslint/angular-eslint/blob/main/packages/eslint-plugin-template/docs/rules/prefer-contextual-for-variables.md) | Ensures that contextual variables are used in @for blocks where possible instead of aliasing them. | | :wrench: | | |
64
65
  | [`prefer-control-flow`](https://github.com/angular-eslint/angular-eslint/blob/main/packages/eslint-plugin-template/docs/rules/prefer-control-flow.md) | Ensures that the built-in control flow is used. | | | | |
65
66
  | [`prefer-ngsrc`](https://github.com/angular-eslint/angular-eslint/blob/main/packages/eslint-plugin-template/docs/rules/prefer-ngsrc.md) | Ensures ngSrc is used instead of src for img elements | | | | |
66
67
  | [`role-has-required-aria`](https://github.com/angular-eslint/angular-eslint/blob/main/packages/eslint-plugin-template/docs/rules/role-has-required-aria.md) | [Accessibility] Ensures elements with ARIA roles have all required properties for that role. | | | :bulb: | :accessibility: |
@@ -24,6 +24,7 @@
24
24
  "@angular-eslint/template/no-interpolation-in-attributes": "error",
25
25
  "@angular-eslint/template/no-negated-async": "error",
26
26
  "@angular-eslint/template/no-positive-tabindex": "error",
27
+ "@angular-eslint/template/prefer-contextual-for-variables": "error",
27
28
  "@angular-eslint/template/prefer-control-flow": "error",
28
29
  "@angular-eslint/template/prefer-ngsrc": "error",
29
30
  "@angular-eslint/template/prefer-self-closing-tags": "error",
package/dist/index.d.ts CHANGED
@@ -26,6 +26,7 @@ declare const _default: {
26
26
  "@angular-eslint/template/no-interpolation-in-attributes": string;
27
27
  "@angular-eslint/template/no-negated-async": string;
28
28
  "@angular-eslint/template/no-positive-tabindex": string;
29
+ "@angular-eslint/template/prefer-contextual-for-variables": string;
29
30
  "@angular-eslint/template/prefer-control-flow": string;
30
31
  "@angular-eslint/template/prefer-ngsrc": string;
31
32
  "@angular-eslint/template/prefer-self-closing-tags": string;
@@ -105,6 +106,7 @@ declare const _default: {
105
106
  "no-interpolation-in-attributes": import("@typescript-eslint/utils/ts-eslint").RuleModule<"noInterpolationInAttributes", [], import("./utils/create-eslint-rule").RuleDocs, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
106
107
  "no-negated-async": import("@typescript-eslint/utils/ts-eslint").RuleModule<import("./rules/no-negated-async").MessageIds, [], import("./utils/create-eslint-rule").RuleDocs, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
107
108
  "no-positive-tabindex": import("@typescript-eslint/utils/ts-eslint").RuleModule<import("./rules/no-positive-tabindex").MessageIds, [], import("./utils/create-eslint-rule").RuleDocs, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
109
+ "prefer-contextual-for-variables": import("@typescript-eslint/utils/ts-eslint").RuleModule<import("./rules/prefer-contextual-for-variables").MessageIds, import("./rules/prefer-contextual-for-variables").Options, import("./utils/create-eslint-rule").RuleDocs, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
108
110
  "prefer-control-flow": import("@typescript-eslint/utils/ts-eslint").RuleModule<"preferControlFlow", [], import("./utils/create-eslint-rule").RuleDocs, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
109
111
  "prefer-self-closing-tags": import("@typescript-eslint/utils/ts-eslint").RuleModule<"preferSelfClosingTags", [], import("./utils/create-eslint-rule").RuleDocs, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
110
112
  "prefer-static-string-properties": import("@typescript-eslint/utils/ts-eslint").RuleModule<"preferStaticStringProperties", [], import("./utils/create-eslint-rule").RuleDocs, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAsFA,kBAwCE"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAyFA,kBAyCE"}
package/dist/index.js CHANGED
@@ -53,6 +53,7 @@ const no_interpolation_in_attributes_1 = __importStar(require("./rules/no-interp
53
53
  const no_negated_async_1 = __importStar(require("./rules/no-negated-async"));
54
54
  const no_positive_tabindex_1 = __importStar(require("./rules/no-positive-tabindex"));
55
55
  const prefer_ngsrc_1 = __importStar(require("./rules/prefer-ngsrc"));
56
+ const prefer_contextual_for_variables_1 = __importStar(require("./rules/prefer-contextual-for-variables"));
56
57
  const prefer_control_flow_1 = __importStar(require("./rules/prefer-control-flow"));
57
58
  const prefer_self_closing_tags_1 = __importStar(require("./rules/prefer-self-closing-tags"));
58
59
  const prefer_static_string_properties_1 = __importStar(require("./rules/prefer-static-string-properties"));
@@ -91,6 +92,7 @@ module.exports = {
91
92
  [no_interpolation_in_attributes_1.RULE_NAME]: no_interpolation_in_attributes_1.default,
92
93
  [no_negated_async_1.RULE_NAME]: no_negated_async_1.default,
93
94
  [no_positive_tabindex_1.RULE_NAME]: no_positive_tabindex_1.default,
95
+ [prefer_contextual_for_variables_1.RULE_NAME]: prefer_contextual_for_variables_1.default,
94
96
  [prefer_control_flow_1.RULE_NAME]: prefer_control_flow_1.default,
95
97
  [prefer_self_closing_tags_1.RULE_NAME]: prefer_self_closing_tags_1.default,
96
98
  [prefer_static_string_properties_1.RULE_NAME]: prefer_static_string_properties_1.default,
@@ -21,7 +21,7 @@ exports.default = (0, create_eslint_rule_1.createESLintRule)({
21
21
  create(context) {
22
22
  const parserServices = (0, utils_1.getTemplateParserServices)(context);
23
23
  return {
24
- 'Element$1[name=/^(img|area|object|input)$/]'(node) {
24
+ 'Element[name=/^(img|area|object|input)$/]'(node) {
25
25
  const isValid = isValidNode(node);
26
26
  if (!isValid) {
27
27
  const loc = parserServices.convertElementSourceSpanToLoc(context, node);
@@ -67,7 +67,7 @@ exports.default = (0, create_eslint_rule_1.createESLintRule)({
67
67
  return adjustLocation(parserServices.convertNodeSourceSpanToLoc(attr.sourceSpan), 'location', attr);
68
68
  }
69
69
  return {
70
- ['Element$1, Template'](node) {
70
+ ['Element, Template'](node) {
71
71
  if (isImplicitTemplate(node)) {
72
72
  return;
73
73
  }
@@ -42,7 +42,7 @@ exports.default = (0, create_eslint_rule_1.createESLintRule)({
42
42
  create(context, [{ ignoreWithDirectives }]) {
43
43
  const parserServices = (0, utils_1.getTemplateParserServices)(context);
44
44
  return {
45
- [`Element$1[name=button]`](element) {
45
+ [`Element[name=button]`](element) {
46
46
  if (!isTypeAttributePresentInElement(element)) {
47
47
  if (!isIgnored(ignoreWithDirectives, element)) {
48
48
  context.report({
@@ -23,7 +23,7 @@ exports.default = (0, create_eslint_rule_1.createESLintRule)({
23
23
  defaultOptions: [],
24
24
  create(context) {
25
25
  return {
26
- Element$1(node) {
26
+ Element(node) {
27
27
  if (!(0, get_dom_elements_1.getDomElements)().has(node.name)) {
28
28
  return;
29
29
  }
@@ -53,7 +53,7 @@ exports.default = (0, create_eslint_rule_1.createESLintRule)({
53
53
  data: { maxComplexity, totalComplexity },
54
54
  });
55
55
  },
56
- Interpolation$1({ expressions }) {
56
+ Interpolation({ expressions }) {
57
57
  for (const expression of expressions) {
58
58
  const totalComplexity = getTotalComplexity(expression);
59
59
  if (totalComplexity <= maxComplexity) {
@@ -44,7 +44,7 @@ exports.default = (0, create_eslint_rule_1.createESLintRule)({
44
44
  create(context, [{ allowList }]) {
45
45
  const parserServices = (0, utils_1.getTemplateParserServices)(context);
46
46
  return {
47
- 'Element$1[name=/^(a|button|h1|h2|h3|h4|h5|h6)$/][children.length=0]'(node) {
47
+ 'Element[name=/^(a|button|h1|h2|h3|h4|h5|h6)$/][children.length=0]'(node) {
48
48
  if ((0, is_hidden_from_screen_reader_1.isHiddenFromScreenReader)(node))
49
49
  return;
50
50
  const { attributes, inputs, name: element, sourceSpan } = node;
@@ -287,7 +287,7 @@ exports.default = (0, create_eslint_rule_1.createESLintRule)({
287
287
  checkId ||
288
288
  requireDescription ||
289
289
  requireMeaning) && {
290
- [`:matches(Element$1, Template[tagName="ng-template"])${allowMarkupInContent ? '[i18n]' : ''}`](node) {
290
+ [`:matches(Element, Template[tagName="ng-template"])${allowMarkupInContent ? '[i18n]' : ''}`](node) {
291
291
  handleElementOrTemplate(node);
292
292
  },
293
293
  }),
@@ -295,12 +295,12 @@ exports.default = (0, create_eslint_rule_1.createESLintRule)({
295
295
  checkId ||
296
296
  requireDescription ||
297
297
  requireMeaning) && {
298
- [`Element$1 > TextAttribute[value=${PL_PATTERN}]`](node) {
298
+ [`Element > TextAttribute[value=${PL_PATTERN}]`](node) {
299
299
  handleTextAttribute(node);
300
300
  },
301
301
  }),
302
302
  ...(checkText && {
303
- [`BoundText, Icu$1, Text$3[value=${PL_PATTERN}]`](node) {
303
+ [`BoundText, Icu, Text[value=${PL_PATTERN}]`](node) {
304
304
  handleBoundTextOrIcuOrText(node);
305
305
  },
306
306
  }),
@@ -38,7 +38,7 @@ exports.default = (0, create_eslint_rule_1.createESLintRule)({
38
38
  defaultOptions: [{ allowList: DEFAULT_ALLOW_LIST }],
39
39
  create(context, [{ allowList }]) {
40
40
  return {
41
- Element$1(node) {
41
+ Element(node) {
42
42
  const elementType = node.name;
43
43
  if (!(0, get_dom_elements_1.getDomElements)().has(elementType)) {
44
44
  return;
@@ -77,7 +77,7 @@ exports.default = (0, create_eslint_rule_1.createESLintRule)({
77
77
  let inputItems = [];
78
78
  let labelItems = [];
79
79
  return {
80
- [`Element$1`](node) {
80
+ [`Element`](node) {
81
81
  if (allControlComponents.has(node.name)) {
82
82
  inputItems.push(node);
83
83
  }
@@ -39,7 +39,7 @@ exports.default = (0, create_eslint_rule_1.createESLintRule)({
39
39
  ];
40
40
  return eventPairs.reduce((accumulator, [keyEvent, mouseEvent]) => ({
41
41
  ...accumulator,
42
- [`Element$1[name=${domElementsPattern}]:has(BoundEvent[name='${mouseEvent}']):not(:has(BoundEvent[name='${keyEvent}']))`]({ sourceSpan, }) {
42
+ [`Element[name=${domElementsPattern}]:has(BoundEvent[name='${mouseEvent}']):not(:has(BoundEvent[name='${keyEvent}']))`]({ sourceSpan, }) {
43
43
  const loc = parserServices.convertNodeSourceSpanToLoc(sourceSpan);
44
44
  context.report({
45
45
  loc,
@@ -24,7 +24,7 @@ exports.default = (0, create_eslint_rule_1.createESLintRule)({
24
24
  const parserServices = (0, utils_1.getTemplateParserServices)(context);
25
25
  const elementNamePattern = (0, to_pattern_1.toPattern)([...(0, get_dom_elements_1.getDomElements)()]);
26
26
  return {
27
- [`Element$1[name=${elementNamePattern}] > :matches(BoundAttribute, TextAttribute)[name="autofocus"]`]({ sourceSpan, }) {
27
+ [`Element[name=${elementNamePattern}] > :matches(BoundAttribute, TextAttribute)[name="autofocus"]`]({ sourceSpan, }) {
28
28
  const loc = parserServices.convertNodeSourceSpanToLoc(sourceSpan);
29
29
  context.report({
30
30
  loc,
@@ -21,7 +21,7 @@ exports.default = (0, create_eslint_rule_1.createESLintRule)({
21
21
  create(context) {
22
22
  const parserServices = (0, utils_1.getTemplateParserServices)(context);
23
23
  return {
24
- 'Element$1[name=/^(blink|marquee)$/]'({ name: element, sourceSpan, }) {
24
+ 'Element[name=/^(blink|marquee)$/]'({ name: element, sourceSpan, }) {
25
25
  const loc = parserServices.convertNodeSourceSpanToLoc(sourceSpan);
26
26
  context.report({
27
27
  loc,
@@ -52,7 +52,7 @@ exports.default = (0, create_eslint_rule_1.createESLintRule)({
52
52
  create(context, [{ allowTwoWayDataBinding, allowStylePrecedenceDuplicates, ignore }]) {
53
53
  const parserServices = (0, utils_1.getTemplateParserServices)(context);
54
54
  return {
55
- Element$1({ inputs, outputs, attributes }) {
55
+ Element({ inputs, outputs, attributes }) {
56
56
  // According to the Angular documentation (https://angular.dev/guide/templates/class-binding#styling-precedence)
57
57
  // Angular merges both attributes which means their combined use can be seen as valid
58
58
  const angularStylePrecedenceDuplicatesAllowed = ['class', 'style'];
@@ -39,7 +39,7 @@ exports.default = (0, create_eslint_rule_1.createESLintRule)({
39
39
  create(context, [{ allowNgStyle, allowBindToStyle }]) {
40
40
  const parserServices = (0, utils_1.getTemplateParserServices)(context);
41
41
  return {
42
- Element$1(node) {
42
+ Element(node) {
43
43
  let isInvalid = false;
44
44
  if (!allowNgStyle && !allowBindToStyle) {
45
45
  isInvalid =
@@ -19,7 +19,7 @@ exports.default = (0, create_eslint_rule_1.createESLintRule)({
19
19
  create(context) {
20
20
  const sourceCode = context.sourceCode;
21
21
  return {
22
- ['BoundAttribute Interpolation$1'](interpolation) {
22
+ ['BoundAttribute Interpolation'](interpolation) {
23
23
  const { sourceSpan: { start, end }, } = interpolation;
24
24
  context.report({
25
25
  loc: {
@@ -25,7 +25,7 @@ exports.default = (0, create_eslint_rule_1.createESLintRule)({
25
25
  const parserServices = (0, utils_1.getTemplateParserServices)(context);
26
26
  const elementNamePattern = (0, to_pattern_1.toPattern)([...(0, get_dom_elements_1.getDomElements)()]);
27
27
  return {
28
- [`Element$1[name=${elementNamePattern}] > BoundAttribute[name="tabindex"][value.ast.value>0], TextAttribute[name="tabindex"][value>0]`]({ valueSpan, }) {
28
+ [`Element[name=${elementNamePattern}] > BoundAttribute[name="tabindex"][value.ast.value>0], TextAttribute[name="tabindex"][value>0]`]({ valueSpan, }) {
29
29
  const loc = parserServices.convertNodeSourceSpanToLoc(valueSpan);
30
30
  context.report({
31
31
  loc,
@@ -0,0 +1,17 @@
1
+ export type Options = [
2
+ {
3
+ readonly allowedAliases?: {
4
+ $count?: readonly string[];
5
+ $index?: readonly string[];
6
+ $first?: readonly string[];
7
+ $last?: readonly string[];
8
+ $even?: readonly string[];
9
+ $odd?: readonly string[];
10
+ };
11
+ }
12
+ ];
13
+ export type MessageIds = 'preferContextualVariable' | 'preferCount' | 'preferFirst' | 'preferLast' | 'preferEven' | 'preferOdd';
14
+ export declare const RULE_NAME = "prefer-contextual-for-variables";
15
+ declare const _default: import("@typescript-eslint/utils/ts-eslint").RuleModule<MessageIds, Options, import("../utils/create-eslint-rule").RuleDocs, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
16
+ export default _default;
17
+ //# sourceMappingURL=prefer-contextual-for-variables.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prefer-contextual-for-variables.d.ts","sourceRoot":"","sources":["../../src/rules/prefer-contextual-for-variables.ts"],"names":[],"mappings":"AAmBA,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,wBAiaG"}
@@ -0,0 +1,546 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.RULE_NAME = void 0;
4
+ const bundled_angular_compiler_1 = require("@angular-eslint/bundled-angular-compiler");
5
+ const utils_1 = require("@angular-eslint/utils");
6
+ const create_eslint_rule_1 = require("../utils/create-eslint-rule");
7
+ const are_equivalent_asts_1 = require("../utils/are-equivalent-asts");
8
+ exports.RULE_NAME = 'prefer-contextual-for-variables';
9
+ const DEFAULT_OPTIONS = {
10
+ allowedAliases: {
11
+ $count: [],
12
+ $index: [],
13
+ $first: [],
14
+ $last: [],
15
+ $even: [],
16
+ $odd: [],
17
+ },
18
+ };
19
+ const EQUALITY_OPERATORS = ['===', '=='];
20
+ const INEQUALITY_OPERATORS = ['!==', '!='];
21
+ const LOGICAL_OPERATORS = ['&&', '||'];
22
+ exports.default = (0, create_eslint_rule_1.createESLintRule)({
23
+ name: exports.RULE_NAME,
24
+ meta: {
25
+ type: 'suggestion',
26
+ docs: {
27
+ description: 'Ensures that contextual variables are used in @for blocks where possible instead of aliasing them.',
28
+ },
29
+ fixable: 'code',
30
+ schema: [
31
+ {
32
+ type: 'object',
33
+ properties: {
34
+ allowedAliases: {
35
+ type: 'object',
36
+ properties: {
37
+ $count: {
38
+ type: 'array',
39
+ items: { type: 'string' },
40
+ description: 'Aliases for $count that are allowed to be used.',
41
+ },
42
+ $index: {
43
+ type: 'array',
44
+ items: { type: 'string' },
45
+ description: 'Aliases for $index that are allowed to be used.',
46
+ },
47
+ $first: {
48
+ type: 'array',
49
+ items: { type: 'string' },
50
+ description: 'Aliases for $first that are allowed to be used.',
51
+ },
52
+ $last: {
53
+ type: 'array',
54
+ items: { type: 'string' },
55
+ description: 'Aliases for $last that are allowed to be used.',
56
+ },
57
+ $even: {
58
+ type: 'array',
59
+ items: { type: 'string' },
60
+ description: 'Aliases for $even that are allowed to be used.',
61
+ },
62
+ $odd: {
63
+ type: 'array',
64
+ items: { type: 'string' },
65
+ description: 'Aliases for $odd that are allowed to be used.',
66
+ },
67
+ },
68
+ },
69
+ },
70
+ additionalProperties: false,
71
+ },
72
+ ],
73
+ messages: {
74
+ preferContextualVariable: "Use the '{{name}}' contextual variable instead of aliasing it.",
75
+ preferCount: "Use '$count' instead of '{{ expression }}'.",
76
+ preferFirst: "Use '$first' instead of '{{ expression }}'.",
77
+ preferLast: "Use '$last' instead of '{{ expression }}'.",
78
+ preferEven: "Use '$even' instead of '{{ expression }}'.",
79
+ preferOdd: "Use '$odd' instead of '{{ expression }}'.",
80
+ },
81
+ },
82
+ defaultOptions: [DEFAULT_OPTIONS],
83
+ create(context, [{ allowedAliases }]) {
84
+ const parserServices = (0, utils_1.getTemplateParserServices)(context);
85
+ const forLoops = [];
86
+ function reportSimplifications(messageId, forLoop) {
87
+ const simplifications = forLoop.simplifications?.[messageId];
88
+ if (!simplifications) {
89
+ return;
90
+ }
91
+ const sourceCode = context.sourceCode;
92
+ for (const simplification of simplifications) {
93
+ context.report({
94
+ messageId,
95
+ loc: {
96
+ start: sourceCode.getLocFromIndex(simplification.range[0]),
97
+ end: sourceCode.getLocFromIndex(simplification.range[1]),
98
+ },
99
+ data: {
100
+ expression: context.sourceCode.text.slice(simplification.range[0], simplification.range[1]),
101
+ },
102
+ fix: (fixer) => fixer.replaceTextRange(simplification.range, simplification.replacement),
103
+ });
104
+ }
105
+ }
106
+ return {
107
+ ForLoopBlock(node) {
108
+ // We need to know if there are nested for loops before we
109
+ // can report any problems. When there are nested for loops,
110
+ // aliasing will be required to access the outer contextual
111
+ // variables from within the inner loop, so we won't report
112
+ // any problems when there are nested for loops.
113
+ const nested = forLoops.length > 0;
114
+ if (nested) {
115
+ forLoops[forLoops.length - 1].canAlias = true;
116
+ }
117
+ // All contextual variables are defined, but
118
+ // only aliased variables have a value span.
119
+ const variables = node.contextVariables.filter((x) => x.valueSpan);
120
+ forLoops.push({
121
+ canAlias: nested,
122
+ source: node.expression.ast,
123
+ variables,
124
+ references:
125
+ // Don't bother creating a map of variable
126
+ // references if there are no variables to track.
127
+ variables.length > 0
128
+ ? new Map(variables.map((variable) => [variable.name, []]))
129
+ : undefined,
130
+ });
131
+ },
132
+ 'ForLoopBlock:exit'() {
133
+ const forLoop = forLoops.pop();
134
+ if (!forLoop) {
135
+ return;
136
+ }
137
+ if (!forLoop.canAlias) {
138
+ const problems = [];
139
+ for (const [index, variable] of forLoop.variables.entries()) {
140
+ const allowed = getAllowedAliases(allowedAliases, variable.value);
141
+ if (allowed === undefined || !allowed.includes(variable.name)) {
142
+ problems.push({
143
+ index,
144
+ variable,
145
+ loc: parserServices.convertNodeSourceSpanToLoc(variable.sourceSpan),
146
+ });
147
+ }
148
+ }
149
+ for (const problem of problems) {
150
+ context.report({
151
+ messageId: 'preferContextualVariable',
152
+ loc: problem.loc,
153
+ data: { name: problem.variable.value },
154
+ fix: function* (fixer) {
155
+ yield fixer.removeRange(getVariableRangeToRemove(problem, context.sourceCode, forLoop.variables.length));
156
+ // Replace any references to the alias
157
+ // with the contextual variable name.
158
+ const references = forLoop.references?.get(problem.variable.name);
159
+ if (references) {
160
+ for (const reference of references) {
161
+ yield fixer.replaceTextRange(reference, problem.variable.value);
162
+ }
163
+ }
164
+ },
165
+ });
166
+ }
167
+ }
168
+ if (forLoop.simplifications) {
169
+ reportSimplifications('preferCount', forLoop);
170
+ reportSimplifications('preferFirst', forLoop);
171
+ reportSimplifications('preferLast', forLoop);
172
+ reportSimplifications('preferEven', forLoop);
173
+ reportSimplifications('preferOdd', forLoop);
174
+ }
175
+ },
176
+ PropertyRead(node) {
177
+ // Get the information for the inner-most for loop (which will be
178
+ // the last one in the array) so that we can record the usage of
179
+ // aliases and expressions using contextual variables that can be
180
+ // simplified. We only need the inner-most for loop because we
181
+ // don't remove aliases when there are nested for loops (meaning
182
+ // we don't need to record alias usage for the outer for loop), and
183
+ // any contextual variables will only reference the inner most loop.
184
+ const forLoop = forLoops.at(-1);
185
+ if (!forLoop) {
186
+ return;
187
+ }
188
+ // Record any references to aliased variables so
189
+ // that we can replace them if we remove the alias.
190
+ forLoop.references
191
+ ?.get(node.name)
192
+ ?.push([node.sourceSpan.start, node.sourceSpan.end]);
193
+ // If the `length` property is being read from the same
194
+ // value that was used as the source of the for loop, then
195
+ // we can simplify that to just use the `$count` variable.
196
+ if (node.name === 'length' &&
197
+ (0, are_equivalent_asts_1.areEquivalentASTs)(node.receiver, forLoop.source)) {
198
+ recordSimplification(node, forLoop, 'preferCount', '$count');
199
+ }
200
+ },
201
+ Binary(node) {
202
+ const forLoop = forLoops.at(-1);
203
+ if (!forLoop) {
204
+ return;
205
+ }
206
+ if (isIndex(node.left)) {
207
+ if (isZero(node.right)) {
208
+ if (EQUALITY_OPERATORS.includes(node.operation)) {
209
+ // `$index === 0` can be simplified to `$first`.
210
+ recordSimplification(node, forLoop, 'preferFirst', '$first');
211
+ }
212
+ else if (INEQUALITY_OPERATORS.includes(node.operation) ||
213
+ node.operation === '>') {
214
+ // `$index !== 0` or `$index > 0` can be simplified to `!$first`.
215
+ recordSimplification(node, forLoop, 'preferFirst', '!$first');
216
+ }
217
+ }
218
+ else if (isCountMinusOne(node.right)) {
219
+ if (EQUALITY_OPERATORS.includes(node.operation)) {
220
+ // `$index === ($count - 1)` can be simplified to `$last`.
221
+ recordSimplification(node, forLoop, 'preferLast', '$last');
222
+ }
223
+ else if (INEQUALITY_OPERATORS.includes(node.operation) ||
224
+ node.operation === '<') {
225
+ // `$index !== ($count - 1)` or `$index < ($count - 1)`
226
+ // can be simplified to `!$last`.
227
+ recordSimplification(node, forLoop, 'preferLast', '!$last');
228
+ }
229
+ }
230
+ }
231
+ else if (isZero(node.left)) {
232
+ if (isIndex(node.right)) {
233
+ if (EQUALITY_OPERATORS.includes(node.operation)) {
234
+ // `0 === $index` can be simplified to `$first`.
235
+ recordSimplification(node, forLoop, 'preferFirst', '$first');
236
+ }
237
+ else if (INEQUALITY_OPERATORS.includes(node.operation) ||
238
+ node.operation === '<') {
239
+ // `0 !== $index` or `0 < $index` can be simplified to `!$first`.
240
+ recordSimplification(node, forLoop, 'preferFirst', '!$first');
241
+ }
242
+ }
243
+ else if (isIndexModTwo(node.right)) {
244
+ if (EQUALITY_OPERATORS.includes(node.operation)) {
245
+ // `0 == ($index % 2)` can be simplified to `$even`.
246
+ recordSimplification(node, forLoop, 'preferEven', '$even');
247
+ }
248
+ else if (INEQUALITY_OPERATORS.includes(node.operation) ||
249
+ node.operation === '<') {
250
+ // `0 !== ($index % 2)` or `0 < ($index % 2)`
251
+ // can be simplified to `$odd`.
252
+ recordSimplification(node, forLoop, 'preferOdd', '$odd');
253
+ }
254
+ }
255
+ }
256
+ else if (isOne(node.left)) {
257
+ if (isIndexModTwo(node.right)) {
258
+ if (EQUALITY_OPERATORS.includes(node.operation)) {
259
+ // `1 === ($index % 2)` can be simplified to `$odd`.
260
+ recordSimplification(node, forLoop, 'preferOdd', '$odd');
261
+ }
262
+ else if (INEQUALITY_OPERATORS.includes(node.operation) ||
263
+ node.operation === '>') {
264
+ // `1 !== ($index % 2)` or `1 > ($index % 2)`
265
+ // can be simplified to `$even`.
266
+ recordSimplification(node, forLoop, 'preferEven', '$even');
267
+ }
268
+ }
269
+ }
270
+ else if (isCount(node.left)) {
271
+ if (isIndexPlusOne(node.right)) {
272
+ if (EQUALITY_OPERATORS.includes(node.operation)) {
273
+ // `$count === ($index + 1)` can be simplified to `$last`.
274
+ recordSimplification(node, forLoop, 'preferLast', '$last');
275
+ }
276
+ else if (INEQUALITY_OPERATORS.includes(node.operation) ||
277
+ node.operation === '>') {
278
+ // `$count !== ($index + 1)` or `$count > ($index + 1)`
279
+ // can be simplified to `!$last`.
280
+ recordSimplification(node, forLoop, 'preferLast', '!$last');
281
+ }
282
+ }
283
+ }
284
+ else if (isIndexPlusOne(node.left)) {
285
+ if (isCount(node.right)) {
286
+ if (EQUALITY_OPERATORS.includes(node.operation)) {
287
+ // `($index + 1) === $count` can be simplified to `$last`.
288
+ recordSimplification(node, forLoop, 'preferLast', '$last');
289
+ }
290
+ else if (INEQUALITY_OPERATORS.includes(node.operation) ||
291
+ node.operation === '<') {
292
+ // `($index + 1) !== $count` or `($index + 1) < $count`
293
+ // can be simplified to `!$last`.
294
+ recordSimplification(node, forLoop, 'preferLast', '!$last');
295
+ }
296
+ }
297
+ }
298
+ else if (isCountMinusOne(node.left)) {
299
+ if (isIndex(node.right)) {
300
+ if (EQUALITY_OPERATORS.includes(node.operation)) {
301
+ // `($count - 1) === $index` can be simplified to `$last`.
302
+ recordSimplification(node, forLoop, 'preferLast', '$last');
303
+ }
304
+ else if (INEQUALITY_OPERATORS.includes(node.operation) ||
305
+ node.operation === '>') {
306
+ // `($count - 1) !== $index` or `($count - 1) > $index`
307
+ // can be simplified to `!$last`.
308
+ recordSimplification(node, forLoop, 'preferLast', '!$last');
309
+ }
310
+ }
311
+ }
312
+ else if (isIndexModTwo(node.left)) {
313
+ if (isZero(node.right)) {
314
+ if (EQUALITY_OPERATORS.includes(node.operation)) {
315
+ // `($index % 2) === 0` can be simplified to `$even`.
316
+ recordSimplification(node, forLoop, 'preferEven', '$even');
317
+ }
318
+ else if (INEQUALITY_OPERATORS.includes(node.operation) ||
319
+ node.operation === '>') {
320
+ // `($index % 2) !== 0` or `($index % 2) > 0`
321
+ // can be simplified to `$odd`.
322
+ recordSimplification(node, forLoop, 'preferOdd', '$odd');
323
+ }
324
+ }
325
+ else if (isOne(node.right)) {
326
+ if (EQUALITY_OPERATORS.includes(node.operation)) {
327
+ // `($index % 2) === 1` can be simplified to `$odd`.
328
+ recordSimplification(node, forLoop, 'preferOdd', '$odd');
329
+ }
330
+ else if (INEQUALITY_OPERATORS.includes(node.operation) ||
331
+ node.operation === '<') {
332
+ // `($index % 2) !== 1` or `($index % 2) < 1`
333
+ // can be simplified to `$even`.
334
+ recordSimplification(node, forLoop, 'preferEven', '$even');
335
+ }
336
+ }
337
+ else if (LOGICAL_OPERATORS.includes(node.operation)) {
338
+ // `$index % 2` can be used to test if `$index` is odd, but it
339
+ // results in a number, so we can only simplify it when it is
340
+ // being used as a truthy value. Because it's on the left-hand
341
+ // side of a logical binary expression, we can simplify it.
342
+ recordSimplification(node.left, forLoop, 'preferOdd', '$odd');
343
+ }
344
+ }
345
+ if (isIndexModTwo(node.right) &&
346
+ LOGICAL_OPERATORS.includes(node.operation)) {
347
+ // As we did with the left-hand side above, when `$index % 2`
348
+ // is used as a truthy value on the right-hand side
349
+ // of a logical binary expression, we can simplify it.
350
+ recordSimplification(node.right, forLoop, 'preferOdd', '$odd');
351
+ }
352
+ },
353
+ PrefixNot(node) {
354
+ const forLoop = forLoops.at(-1);
355
+ if (!forLoop) {
356
+ return;
357
+ }
358
+ if (isOdd(node.expression) || isIndexModTwo(node.expression)) {
359
+ // `!$odd` or `!($index % 2)` can be simplified to `$even`.
360
+ recordSimplification(node, forLoop, 'preferEven', '$even');
361
+ }
362
+ else if (isEven(node.expression)) {
363
+ // `!$even` can be simplified to `$odd`.
364
+ recordSimplification(node, forLoop, 'preferOdd', '$odd');
365
+ }
366
+ },
367
+ Conditional(node) {
368
+ const forLoop = forLoops.at(-1);
369
+ if (!forLoop) {
370
+ return;
371
+ }
372
+ // If the condition is `$index % 2`, then it's being
373
+ // used as a truthy value and we can simplify it.
374
+ if (isIndexModTwo(node.condition)) {
375
+ recordSimplification(node.condition, forLoop, 'preferOdd', '$odd');
376
+ }
377
+ },
378
+ IfBlockBranch(node) {
379
+ const forLoop = forLoops.at(-1);
380
+ if (!forLoop) {
381
+ return;
382
+ }
383
+ // If the expression is `$index % 2`, then it's being
384
+ // used as a truthy value and we can simplify it.
385
+ if (node.expression) {
386
+ let expression = node.expression;
387
+ if (expression instanceof bundled_angular_compiler_1.ASTWithSource) {
388
+ expression = expression.ast;
389
+ }
390
+ if (isIndexModTwo(expression)) {
391
+ recordSimplification(expression, forLoop, 'preferOdd', '$odd');
392
+ }
393
+ }
394
+ },
395
+ };
396
+ },
397
+ });
398
+ function getAllowedAliases(allowedAliases, variableName) {
399
+ if (allowedAliases && variableName in allowedAliases) {
400
+ return allowedAliases[variableName];
401
+ }
402
+ return undefined;
403
+ }
404
+ function getVariableRangeToRemove(problem, sourceCode, variableCount) {
405
+ let start = problem.variable.sourceSpan.start.offset;
406
+ let end = problem.variable.sourceSpan.end.offset;
407
+ if (variableCount === 1) {
408
+ // There's only one variable defined, so we
409
+ // want to remove the `let` keyword as well.
410
+ const letIndex = getStartOfPreviousToken('let', start, sourceCode);
411
+ if (letIndex !== undefined) {
412
+ // We also want to remove the preceding semicolon.
413
+ start = getStartOfPreviousToken(';', letIndex, sourceCode) ?? letIndex;
414
+ }
415
+ }
416
+ else if (problem.index === 0) {
417
+ // There are multiple variables, but we're removing
418
+ // the first one. We need to keep the `let` keyword, but
419
+ // remove the trailing comma and any whitespace after it.
420
+ const commaIndex = getStartOfNextToken(',', end, sourceCode);
421
+ if (commaIndex !== undefined) {
422
+ // The range to remove is end-exclusive, so we
423
+ // need to add one to remove the comma.
424
+ end = getIndexOfNextNonWhitespace(commaIndex + 1, sourceCode);
425
+ }
426
+ }
427
+ else {
428
+ // There is a variable before this one, so we
429
+ // need to remove the preceding comma as well.
430
+ start = getStartOfPreviousToken(',', start, sourceCode) ?? start;
431
+ }
432
+ return [start, end];
433
+ }
434
+ function getStartOfPreviousToken(tokenToFind, startIndex, sourceCode) {
435
+ const text = sourceCode.text;
436
+ for (let i = startIndex - tokenToFind.length; i >= 0; i--) {
437
+ if (text.slice(i, i + tokenToFind.length) === tokenToFind) {
438
+ return i;
439
+ }
440
+ }
441
+ return undefined;
442
+ }
443
+ function getStartOfNextToken(tokenToFind, startIndex, sourceCode) {
444
+ const text = sourceCode.text;
445
+ for (let i = startIndex; i < text.length; i++) {
446
+ if (text.slice(i, i + tokenToFind.length) === tokenToFind) {
447
+ return i;
448
+ }
449
+ }
450
+ return undefined;
451
+ }
452
+ function getIndexOfNextNonWhitespace(startIndex, sourceCode) {
453
+ const text = sourceCode.text;
454
+ let index = startIndex;
455
+ while (index < text.length) {
456
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
457
+ if (!/\s/.test(text.at(index))) {
458
+ return index;
459
+ }
460
+ index++;
461
+ }
462
+ return text.length;
463
+ }
464
+ function recordSimplification(node, forLoop, type, replacement) {
465
+ // Most of the time we won't find anything to simplify because
466
+ // we would have simplified everything on the previous passes,
467
+ // so we delay-create this to reduce memory allocations.
468
+ if (!forLoop.simplifications) {
469
+ forLoop.simplifications = {};
470
+ }
471
+ let nodes = forLoop.simplifications[type];
472
+ if (!nodes) {
473
+ nodes = [];
474
+ forLoop.simplifications[type] = nodes;
475
+ }
476
+ nodes.push({
477
+ range: [node.sourceSpan.start, node.sourceSpan.end],
478
+ replacement,
479
+ });
480
+ }
481
+ function isIndex(node) {
482
+ return isContextualVariable(node, '$index');
483
+ }
484
+ function isIndexPlusOne(node) {
485
+ if (node instanceof bundled_angular_compiler_1.Binary) {
486
+ if (node.operation === '+') {
487
+ if (isIndex(node.left)) {
488
+ return isOne(node.right);
489
+ }
490
+ else {
491
+ return isIndex(node.right) && isOne(node.left);
492
+ }
493
+ }
494
+ }
495
+ return false;
496
+ }
497
+ function isIndexModTwo(node) {
498
+ return (node instanceof bundled_angular_compiler_1.Binary &&
499
+ node.operation === '%' &&
500
+ isIndex(node.left) &&
501
+ isTwo(node.right));
502
+ }
503
+ function isCount(node) {
504
+ return isContextualVariable(node, '$count');
505
+ }
506
+ function isCountMinusOne(node) {
507
+ if (node instanceof bundled_angular_compiler_1.Binary) {
508
+ if (node.operation === '-') {
509
+ if (isCount(node.left)) {
510
+ return isOne(node.right);
511
+ }
512
+ else {
513
+ return isCount(node.right) && isOne(node.left);
514
+ }
515
+ }
516
+ }
517
+ return false;
518
+ }
519
+ function isEven(node) {
520
+ return isContextualVariable(node, '$even');
521
+ }
522
+ function isOdd(node) {
523
+ return isContextualVariable(node, '$odd');
524
+ }
525
+ function isContextualVariable(node, name) {
526
+ return (node instanceof bundled_angular_compiler_1.PropertyRead &&
527
+ node.name === name &&
528
+ // The contextual variable must be accessed implicitly.
529
+ // That is, `this.$index` is not a contextual variable.
530
+ // Note that `ThisReceiver` extends `ImplicitReceiver`, so we
531
+ // need to check that the receiver is exactly an `ImplicitReceiver`
532
+ // and not just an instance of `ImplicitReceiver`.
533
+ node.receiver.constructor === bundled_angular_compiler_1.ImplicitReceiver);
534
+ }
535
+ function isZero(node) {
536
+ return isLiteralNumber(node, 0);
537
+ }
538
+ function isOne(node) {
539
+ return isLiteralNumber(node, 1);
540
+ }
541
+ function isTwo(node) {
542
+ return isLiteralNumber(node, 2);
543
+ }
544
+ function isLiteralNumber(node, value) {
545
+ return node instanceof bundled_angular_compiler_1.LiteralPrimitive && node.value === value;
546
+ }
@@ -22,7 +22,7 @@ exports.default = (0, create_eslint_rule_1.createESLintRule)({
22
22
  create(context) {
23
23
  const parserServices = (0, utils_1.getTemplateParserServices)(context);
24
24
  return {
25
- 'Element$1[name=img]'(element) {
25
+ 'Element[name=img]'(element) {
26
26
  const ngSrcAttribute = hasNgSrcAttribute(element);
27
27
  const srcAttribute = hasNormalSrcAttribute(element);
28
28
  if (!srcAttribute ||
@@ -28,7 +28,7 @@ exports.default = (0, create_eslint_rule_1.createESLintRule)({
28
28
  return {};
29
29
  }
30
30
  return {
31
- 'Element$1, Template, Content'(node) {
31
+ 'Element, Template, Content'(node) {
32
32
  if (isContentNode(node)) {
33
33
  processContentNode(node);
34
34
  }
@@ -27,7 +27,7 @@ exports.default = (0, create_eslint_rule_1.createESLintRule)({
27
27
  const parserServices = (0, utils_1.getTemplateParserServices)(context);
28
28
  const elementNamePattern = (0, to_pattern_1.toPattern)([...(0, get_dom_elements_1.getDomElements)()]);
29
29
  return {
30
- [`Element$1[name=${elementNamePattern}] > TextAttribute[name='role']`](node) {
30
+ [`Element[name=${elementNamePattern}] > TextAttribute[name='role']`](node) {
31
31
  const { value: role, sourceSpan } = node;
32
32
  const { attributes, inputs, name: element } = node.parent;
33
33
  const props = [...attributes, ...inputs];
@@ -24,7 +24,7 @@ exports.default = (0, create_eslint_rule_1.createESLintRule)({
24
24
  const parserServices = (0, utils_1.getTemplateParserServices)(context);
25
25
  const domElementsPattern = (0, to_pattern_1.toPattern)([...(0, get_dom_elements_1.getDomElements)()].filter((domElement) => domElement !== 'th'));
26
26
  return {
27
- [`Element$1[name=${domElementsPattern}] > :matches(BoundAttribute, TextAttribute)[name='scope']`]({ sourceSpan, }) {
27
+ [`Element[name=${domElementsPattern}] > :matches(BoundAttribute, TextAttribute)[name='scope']`]({ sourceSpan, }) {
28
28
  const loc = parserServices.convertNodeSourceSpanToLoc(sourceSpan);
29
29
  context.report({
30
30
  loc,
@@ -28,7 +28,7 @@ exports.default = (0, create_eslint_rule_1.createESLintRule)({
28
28
  const parserServices = (0, utils_1.getTemplateParserServices)(context);
29
29
  const elementNamePattern = (0, to_pattern_1.toPattern)([...(0, get_dom_elements_1.getDomElements)()]);
30
30
  return {
31
- [`Element$1[name=${elementNamePattern}] > :matches(BoundAttribute, TextAttribute)[name=/^aria-.+/]`](node) {
31
+ [`Element[name=${elementNamePattern}] > :matches(BoundAttribute, TextAttribute)[name=/^aria-.+/]`](node) {
32
32
  const { name: attribute, sourceSpan } = node;
33
33
  const ariaPropertyDefinition = aria_query_1.aria.get(attribute);
34
34
  const loc = parserServices.convertNodeSourceSpanToLoc(sourceSpan);
@@ -0,0 +1,3 @@
1
+ import { AST } from '@angular-eslint/bundled-angular-compiler';
2
+ export declare function areEquivalentASTs(a: AST, b: AST): boolean;
3
+ //# sourceMappingURL=are-equivalent-asts.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"are-equivalent-asts.d.ts","sourceRoot":"","sources":["../../src/utils/are-equivalent-asts.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,GAAG,EAuBJ,MAAM,0CAA0C,CAAC;AAElD,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,GAAG,GAAG,OAAO,CA4JzD"}
@@ -0,0 +1,115 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.areEquivalentASTs = areEquivalentASTs;
4
+ const bundled_angular_compiler_1 = require("@angular-eslint/bundled-angular-compiler");
5
+ function areEquivalentASTs(a, b) {
6
+ // An `ImplicitReceiver` is equivalent to a `ThisReceiver` because
7
+ // `this.foo` and `foo` mean the same thing. A `ThisReceiver` extends
8
+ // `ImplicitReceiver` so before we check if the two ASTs are the same
9
+ // type, we can check if they are both some sort of `ImplicitReceiver`.
10
+ if (a instanceof bundled_angular_compiler_1.ImplicitReceiver) {
11
+ return b instanceof bundled_angular_compiler_1.ImplicitReceiver;
12
+ }
13
+ // Bail out if the two ASTs are not the same type.
14
+ if (a.constructor !== b.constructor) {
15
+ return false;
16
+ }
17
+ // Check reads and calls first, because
18
+ // they are probably the most common type.
19
+ if (a instanceof bundled_angular_compiler_1.PropertyRead && b instanceof bundled_angular_compiler_1.PropertyRead) {
20
+ return a.name === b.name && areEquivalentASTs(a.receiver, b.receiver);
21
+ }
22
+ if (a instanceof bundled_angular_compiler_1.SafePropertyRead && b instanceof bundled_angular_compiler_1.SafePropertyRead) {
23
+ return a.name === b.name && areEquivalentASTs(a.receiver, b.receiver);
24
+ }
25
+ if (a instanceof bundled_angular_compiler_1.Call && b instanceof bundled_angular_compiler_1.Call) {
26
+ return (areEquivalentASTArrays(a.args, b.args) &&
27
+ areEquivalentASTs(a.receiver, b.receiver));
28
+ }
29
+ if (a instanceof bundled_angular_compiler_1.SafeCall && b instanceof bundled_angular_compiler_1.SafeCall) {
30
+ return (areEquivalentASTArrays(a.args, b.args) &&
31
+ areEquivalentASTs(a.receiver, b.receiver));
32
+ }
33
+ if (a instanceof bundled_angular_compiler_1.KeyedRead && b instanceof bundled_angular_compiler_1.KeyedRead) {
34
+ return (areEquivalentASTs(a.key, b.key) &&
35
+ areEquivalentASTs(a.receiver, b.receiver));
36
+ }
37
+ if (a instanceof bundled_angular_compiler_1.SafeKeyedRead && b instanceof bundled_angular_compiler_1.SafeKeyedRead) {
38
+ return (areEquivalentASTs(a.key, b.key) &&
39
+ areEquivalentASTs(a.receiver, b.receiver));
40
+ }
41
+ if (a instanceof bundled_angular_compiler_1.NonNullAssert && b instanceof bundled_angular_compiler_1.NonNullAssert) {
42
+ return areEquivalentASTs(a.expression, b.expression);
43
+ }
44
+ // Expressions used as conditions can come next.
45
+ if (a instanceof bundled_angular_compiler_1.PrefixNot && b instanceof bundled_angular_compiler_1.PrefixNot) {
46
+ return areEquivalentASTs(a.expression, b.expression);
47
+ }
48
+ // Unary extends Binary, so we need to check `Unary`
49
+ // first, otherwise we will treat it as a `Binary`.
50
+ if (a instanceof bundled_angular_compiler_1.Unary && b instanceof bundled_angular_compiler_1.Unary) {
51
+ return a.operator === b.operator && areEquivalentASTs(a.expr, b.expr);
52
+ }
53
+ if (a instanceof bundled_angular_compiler_1.Binary && b instanceof bundled_angular_compiler_1.Binary) {
54
+ return (a.operation === b.operation &&
55
+ areEquivalentASTs(a.left, b.left) &&
56
+ areEquivalentASTs(a.right, b.right));
57
+ }
58
+ if (a instanceof bundled_angular_compiler_1.Conditional && b instanceof bundled_angular_compiler_1.Conditional) {
59
+ return (areEquivalentASTs(a.condition, b.condition) &&
60
+ areEquivalentASTs(a.trueExp, b.trueExp) &&
61
+ areEquivalentASTs(a.falseExp, b.falseExp));
62
+ }
63
+ // Literals can be checked next.
64
+ if (a instanceof bundled_angular_compiler_1.LiteralPrimitive && b instanceof bundled_angular_compiler_1.LiteralPrimitive) {
65
+ return a.value === b.value;
66
+ }
67
+ if (a instanceof bundled_angular_compiler_1.LiteralArray && b instanceof bundled_angular_compiler_1.LiteralArray) {
68
+ return areEquivalentASTArrays(a.expressions, b.expressions);
69
+ }
70
+ if (a instanceof bundled_angular_compiler_1.LiteralMap && b instanceof bundled_angular_compiler_1.LiteralMap) {
71
+ return (a.keys.length === b.keys.length &&
72
+ // Only check that the keys are equivalent. We don't need to check
73
+ // the `quoted` property because a quoted key with the same value as
74
+ // an unquoted key is the same key. Likewise, the `isShorthandInitialized`
75
+ // property doesn't affect the name of the key.
76
+ a.keys.every((aKey, index) => aKey.key === b.keys[index].key) &&
77
+ areEquivalentASTArrays(a.values, b.values));
78
+ }
79
+ // Pipes and interpolations are next.
80
+ if (a instanceof bundled_angular_compiler_1.BindingPipe && b instanceof bundled_angular_compiler_1.BindingPipe) {
81
+ return (a.name === b.name &&
82
+ areEquivalentASTs(a.exp, b.exp) &&
83
+ areEquivalentASTArrays(a.args, b.args));
84
+ }
85
+ if (a instanceof bundled_angular_compiler_1.Interpolation && b instanceof bundled_angular_compiler_1.Interpolation) {
86
+ return (a.strings.length === b.strings.length &&
87
+ a.strings.every((aString, index) => aString === b.strings[index]) &&
88
+ areEquivalentASTArrays(a.expressions, b.expressions));
89
+ }
90
+ // Miscellaneous things and writes can be checked next.
91
+ if (a instanceof bundled_angular_compiler_1.ASTWithSource && b instanceof bundled_angular_compiler_1.ASTWithSource) {
92
+ return areEquivalentASTs(a.ast, b.ast);
93
+ }
94
+ if (a instanceof bundled_angular_compiler_1.Chain && b instanceof bundled_angular_compiler_1.Chain) {
95
+ return areEquivalentASTArrays(a.expressions, b.expressions);
96
+ }
97
+ if (a instanceof bundled_angular_compiler_1.PropertyWrite && b instanceof bundled_angular_compiler_1.PropertyWrite) {
98
+ return (a.name === b.name &&
99
+ areEquivalentASTs(a.receiver, b.receiver) &&
100
+ areEquivalentASTs(a.value, b.value));
101
+ }
102
+ if (a instanceof bundled_angular_compiler_1.KeyedWrite && b instanceof bundled_angular_compiler_1.KeyedWrite) {
103
+ return (areEquivalentASTs(a.key, b.key) &&
104
+ areEquivalentASTs(a.receiver, b.receiver) &&
105
+ areEquivalentASTs(a.value, b.value));
106
+ }
107
+ if (a instanceof bundled_angular_compiler_1.TypeofExpression && b instanceof bundled_angular_compiler_1.TypeofExpression) {
108
+ return areEquivalentASTs(a.expression, b.expression);
109
+ }
110
+ return false;
111
+ }
112
+ function areEquivalentASTArrays(a, b) {
113
+ return (a.length === b.length &&
114
+ a.every((aElement, index) => areEquivalentASTs(aElement, b[index])));
115
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@angular-eslint/eslint-plugin-template",
3
- "version": "19.2.2-alpha.4",
3
+ "version": "19.2.2-alpha.6",
4
4
  "description": "ESLint plugin for Angular Templates",
5
5
  "license": "MIT",
6
6
  "main": "dist/index.js",
@@ -20,13 +20,13 @@
20
20
  "dependencies": {
21
21
  "aria-query": "5.3.2",
22
22
  "axobject-query": "4.1.0",
23
- "@angular-eslint/utils": "19.2.2-alpha.4",
24
- "@angular-eslint/bundled-angular-compiler": "19.2.2-alpha.4"
23
+ "@angular-eslint/bundled-angular-compiler": "19.2.2-alpha.6",
24
+ "@angular-eslint/utils": "19.2.2-alpha.6"
25
25
  },
26
26
  "devDependencies": {
27
27
  "@types/aria-query": "5.0.4",
28
- "@angular-eslint/template-parser": "19.2.2-alpha.4",
29
- "@angular-eslint/test-utils": "19.2.2-alpha.4"
28
+ "@angular-eslint/template-parser": "19.2.2-alpha.6",
29
+ "@angular-eslint/test-utils": "19.2.2-alpha.6"
30
30
  },
31
31
  "peerDependencies": {
32
32
  "@typescript-eslint/types": "^7.11.0 || ^8.0.0",