@angular-eslint/eslint-plugin-template 20.3.1-alpha.14 → 20.3.1-alpha.15

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.
package/README.md CHANGED
@@ -63,6 +63,7 @@ Please see https://github.com/angular-eslint/angular-eslint for full usage instr
63
63
  | [`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. | | :wrench: | | |
64
64
  | [`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: | |
65
65
  | [`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: | |
66
+ | [`prefer-at-else`](https://github.com/angular-eslint/angular-eslint/blob/main/packages/eslint-plugin-template/docs/rules/prefer-at-else.md) | Prefer using `@else` instead of a second `@if` with the opposite condition to reduce code and make it easier to read. | | :wrench: | | |
66
67
  | [`prefer-at-empty`](https://github.com/angular-eslint/angular-eslint/blob/main/packages/eslint-plugin-template/docs/rules/prefer-at-empty.md) | Prefer using `@empty` with `@for` loops instead of a separate `@if` or `@else` block to reduce code and make it easier to read. | | :wrench: | | |
67
68
  | [`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: | | |
68
69
  | [`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. | | | | |
@@ -26,6 +26,7 @@
26
26
  "@angular-eslint/template/no-negated-async": "error",
27
27
  "@angular-eslint/template/no-nested-tags": "error",
28
28
  "@angular-eslint/template/no-positive-tabindex": "error",
29
+ "@angular-eslint/template/prefer-at-else": "error",
29
30
  "@angular-eslint/template/prefer-at-empty": "error",
30
31
  "@angular-eslint/template/prefer-contextual-for-variables": "error",
31
32
  "@angular-eslint/template/prefer-control-flow": "error",
package/dist/index.d.ts CHANGED
@@ -28,6 +28,7 @@ declare const _default: {
28
28
  "@angular-eslint/template/no-negated-async": string;
29
29
  "@angular-eslint/template/no-nested-tags": string;
30
30
  "@angular-eslint/template/no-positive-tabindex": string;
31
+ "@angular-eslint/template/prefer-at-else": string;
31
32
  "@angular-eslint/template/prefer-at-empty": string;
32
33
  "@angular-eslint/template/prefer-contextual-for-variables": string;
33
34
  "@angular-eslint/template/prefer-control-flow": string;
@@ -112,6 +113,7 @@ declare const _default: {
112
113
  "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>;
113
114
  "no-nested-tags": import("@typescript-eslint/utils/ts-eslint").RuleModule<"noNestedTags", [], import("./utils/create-eslint-rule").RuleDocs, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
114
115
  "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>;
116
+ "prefer-at-else": import("@typescript-eslint/utils/ts-eslint").RuleModule<"preferAtElse", [], import("./utils/create-eslint-rule").RuleDocs, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
115
117
  "prefer-at-empty": import("@typescript-eslint/utils/ts-eslint").RuleModule<"preferAtEmpty", [], import("./utils/create-eslint-rule").RuleDocs, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
116
118
  "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>;
117
119
  "prefer-control-flow": import("@typescript-eslint/utils/ts-eslint").RuleModule<"preferControlFlow", [], 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":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAqGA,kBA6CE"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAwGA,kBA8CE"}
package/dist/index.js CHANGED
@@ -65,6 +65,7 @@ const no_negated_async_1 = __importStar(require("./rules/no-negated-async"));
65
65
  const no_nested_tags_1 = __importStar(require("./rules/no-nested-tags"));
66
66
  const no_positive_tabindex_1 = __importStar(require("./rules/no-positive-tabindex"));
67
67
  const prefer_ngsrc_1 = __importStar(require("./rules/prefer-ngsrc"));
68
+ const prefer_at_else_1 = __importStar(require("./rules/prefer-at-else"));
68
69
  const prefer_at_empty_1 = __importStar(require("./rules/prefer-at-empty"));
69
70
  const prefer_contextual_for_variables_1 = __importStar(require("./rules/prefer-contextual-for-variables"));
70
71
  const prefer_control_flow_1 = __importStar(require("./rules/prefer-control-flow"));
@@ -108,6 +109,7 @@ module.exports = {
108
109
  [no_negated_async_1.RULE_NAME]: no_negated_async_1.default,
109
110
  [no_nested_tags_1.RULE_NAME]: no_nested_tags_1.default,
110
111
  [no_positive_tabindex_1.RULE_NAME]: no_positive_tabindex_1.default,
112
+ [prefer_at_else_1.RULE_NAME]: prefer_at_else_1.default,
111
113
  [prefer_at_empty_1.RULE_NAME]: prefer_at_empty_1.default,
112
114
  [prefer_contextual_for_variables_1.RULE_NAME]: prefer_contextual_for_variables_1.default,
113
115
  [prefer_control_flow_1.RULE_NAME]: prefer_control_flow_1.default,
@@ -0,0 +1,6 @@
1
+ export type Options = [];
2
+ export type MessageIds = 'preferAtElse';
3
+ export declare const RULE_NAME = "prefer-at-else";
4
+ declare const _default: import("@typescript-eslint/utils/ts-eslint").RuleModule<"preferAtElse", [], import("../utils/create-eslint-rule").RuleDocs, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
5
+ export default _default;
6
+ //# sourceMappingURL=prefer-at-else.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prefer-at-else.d.ts","sourceRoot":"","sources":["../../src/rules/prefer-at-else.ts"],"names":[],"mappings":"AAeA,MAAM,MAAM,OAAO,GAAG,EAAE,CAAC;AACzB,MAAM,MAAM,UAAU,GAAG,cAAc,CAAC;AACxC,eAAO,MAAM,SAAS,mBAAmB,CAAC;;AAe1C,wBA+HG"}
@@ -0,0 +1,192 @@
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
+ const ast_types_1 = require("../utils/ast-types");
9
+ const to_range_1 = require("../utils/to-range");
10
+ exports.RULE_NAME = 'prefer-at-else';
11
+ const OPPOSITE_OPERATORS = new Map([
12
+ ['', '!'],
13
+ ['!', ''],
14
+ ['<', '>='],
15
+ ['>', '<='],
16
+ ['<=', '>'],
17
+ ['>=', '<'],
18
+ ['==', '!='],
19
+ ['!=', '=='],
20
+ ['===', '!=='],
21
+ ['!==', '==='],
22
+ ]);
23
+ exports.default = (0, create_eslint_rule_1.createESLintRule)({
24
+ name: exports.RULE_NAME,
25
+ meta: {
26
+ type: 'suggestion',
27
+ fixable: 'code',
28
+ docs: {
29
+ description: 'Prefer using `@else` instead of a second `@if` with the opposite condition to reduce code and make it easier to read.',
30
+ },
31
+ schema: [],
32
+ messages: {
33
+ preferAtElse: 'Prefer using `@else` instead of a second `@if` clause.',
34
+ },
35
+ },
36
+ defaultOptions: [],
37
+ create(context) {
38
+ const parserServices = (0, utils_1.getTemplateParserServices)(context);
39
+ const previousNodeStack = [undefined];
40
+ function getFix(previous, current) {
41
+ const previousIf = previous.node.branches[0];
42
+ const currentIf = current.node.branches[0];
43
+ const currentElse = current.node.branches.at(1);
44
+ const previousElse = previous.node.branches.at(1);
45
+ // If the current `@if` block uses an alias, then
46
+ // we won't fix it because the alias won't exist
47
+ // in the `@else` block of the previous `@if` block.
48
+ if (currentIf.expressionAlias) {
49
+ return null;
50
+ }
51
+ return function* fix(fixer) {
52
+ if (!previousElse) {
53
+ // The previous `@if` block has no `@else` block,
54
+ // so we can turn the current `@if` block into one.
55
+ yield fixer.replaceTextRange([
56
+ currentIf.sourceSpan.start.offset,
57
+ currentIf.startSourceSpan.end.offset,
58
+ ], '@else {');
59
+ }
60
+ else {
61
+ // The previous `@if` block already has an `@else` block.
62
+ // Since the current `@if` block is the opposite of the previous
63
+ // `@if` block, the previous `@else` block and the current `@if`
64
+ // block would both be rendered. We can achieve the same result
65
+ // with a single block by putting the contents of the current
66
+ // `@if` block at the end of the previous `@else` block.
67
+ const ifContents = context.sourceCode.text.slice(currentIf.startSourceSpan.end.offset, currentIf.sourceSpan.end.offset - 1);
68
+ yield fixer.insertTextAfterRange((0, to_range_1.toZeroLengthRange)(previousElse.sourceSpan.end.offset - 1), ifContents);
69
+ yield fixer.removeRange((0, to_range_1.toRange)(currentIf.sourceSpan));
70
+ }
71
+ if (currentElse && currentIf.endSourceSpan) {
72
+ // The current node has an `@else` block. Since the current
73
+ // `@if` block is the opposite of the previous `@if` block,
74
+ // the `@else` block would be rendered when the previous
75
+ // `@if` is also rendered. We can achieve the same result
76
+ // by putting the contents of the current `@else` block
77
+ // at the end of the previous `@if` block.
78
+ const elseContents = context.sourceCode.text.slice(currentElse.startSourceSpan.end.offset, currentElse.sourceSpan.end.offset - 1);
79
+ yield fixer.insertTextAfterRange((0, to_range_1.toZeroLengthRange)(previousIf.sourceSpan.end.offset - 1), elseContents);
80
+ yield fixer.removeRange([
81
+ currentIf.endSourceSpan.end.offset,
82
+ currentElse.sourceSpan.end.offset,
83
+ ]);
84
+ }
85
+ };
86
+ }
87
+ return {
88
+ // We need to visit `@if` blocks, but we also
89
+ // need to know if there are any nodes immediately
90
+ // before them, so we need to visit all nodes.
91
+ '*'(node) {
92
+ const current = getIfNodeInfo(node);
93
+ if (current) {
94
+ const previous = previousNodeStack.at(-1);
95
+ if (previous && canCombine(previous, current)) {
96
+ context.report({
97
+ loc: parserServices.convertNodeSourceSpanToLoc(current.node.nameSpan),
98
+ messageId: 'preferAtElse',
99
+ fix: getFix(previous, current),
100
+ });
101
+ }
102
+ }
103
+ // Record this current node as the previous node so that
104
+ // we can get the info when we look at the next sibling.
105
+ previousNodeStack[previousNodeStack.length - 1] = current;
106
+ // We are about to visit the children of this node,
107
+ // so push a new "previous node info" onto the stack.
108
+ // The previous node of the first child is undefined.
109
+ previousNodeStack.push(undefined);
110
+ },
111
+ '*:exit'() {
112
+ // We've finished visiting the children of this node,
113
+ // so pop the "previous node info" off the stack.
114
+ previousNodeStack.pop();
115
+ },
116
+ };
117
+ },
118
+ });
119
+ function getIfNodeInfo(node) {
120
+ // We only care about `@if` blocks with one or two branches.
121
+ // Any more branches and it would have to contain an
122
+ // `@else if` branch, which we cannot handle.
123
+ if (node instanceof bundled_angular_compiler_1.TmplAstIfBlock &&
124
+ node.branches.length >= 1 &&
125
+ node.branches[0].expression instanceof bundled_angular_compiler_1.ASTWithSource &&
126
+ node.branches.length <= 2) {
127
+ // When there are two branches, the second
128
+ // branch cannot have an expression, otherwise it
129
+ // would be an `@else if` block, which we cannot
130
+ // combine with a previous or next `@if` block.
131
+ if (node.branches.length == 1 || !node.branches[1].expression) {
132
+ const ast = node.branches[0].expression.ast;
133
+ if (ast instanceof bundled_angular_compiler_1.Binary) {
134
+ return { node, lhs: ast.left, rhs: ast.right, operator: ast.operation };
135
+ }
136
+ if (ast instanceof bundled_angular_compiler_1.PrefixNot) {
137
+ return { node, lhs: ast.expression, rhs: undefined, operator: '!' };
138
+ }
139
+ return { node, lhs: ast, rhs: undefined, operator: '' };
140
+ }
141
+ }
142
+ return undefined;
143
+ }
144
+ function canCombine(previous, current) {
145
+ if (OPPOSITE_OPERATORS.get(previous.operator) === current.operator) {
146
+ if ((0, are_equivalent_asts_1.areEquivalentASTs)(previous.lhs, current.lhs)) {
147
+ if (previous.rhs === undefined && current.rhs === undefined) {
148
+ return true;
149
+ }
150
+ if (previous.rhs &&
151
+ current.rhs &&
152
+ (0, are_equivalent_asts_1.areEquivalentASTs)(previous.rhs, current.rhs)) {
153
+ return true;
154
+ }
155
+ }
156
+ }
157
+ // Arrays cannot have a length less than zero, so there is
158
+ // a special case we can look for. If the previous node
159
+ // was an "is empty" and the current node is "is not empty"
160
+ // (or vice versa), then we can consider them opposites.
161
+ if ((isEmptyLength(previous) && isNonEmptyLength(current)) ||
162
+ (isNonEmptyLength(previous) && isEmptyLength(current))) {
163
+ return true;
164
+ }
165
+ return false;
166
+ }
167
+ function isEmptyLength(node) {
168
+ if (node.rhs !== undefined) {
169
+ if (node.operator === '==' || node.operator === '===') {
170
+ if ((0, ast_types_1.isLengthRead)(node.lhs) && (0, ast_types_1.isZero)(node.rhs)) {
171
+ return true;
172
+ }
173
+ if ((0, ast_types_1.isZero)(node.lhs) && (0, ast_types_1.isLengthRead)(node.rhs)) {
174
+ return true;
175
+ }
176
+ }
177
+ }
178
+ return false;
179
+ }
180
+ function isNonEmptyLength(node) {
181
+ if (node.rhs !== undefined) {
182
+ // We don't need to check for the inequality operators because
183
+ // they would be handled by the standard "are opposite" check.
184
+ if ((0, ast_types_1.isLengthRead)(node.lhs) && node.operator === '>' && (0, ast_types_1.isZero)(node.rhs)) {
185
+ return true;
186
+ }
187
+ if ((0, ast_types_1.isZero)(node.lhs) && node.operator === '<' && (0, ast_types_1.isLengthRead)(node.rhs)) {
188
+ return true;
189
+ }
190
+ }
191
+ return false;
192
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"prefer-at-empty.d.ts","sourceRoot":"","sources":["../../src/rules/prefer-at-empty.ts"],"names":[],"mappings":"AAkBA,MAAM,MAAM,OAAO,GAAG,EAAE,CAAC;AACzB,MAAM,MAAM,UAAU,GAAG,eAAe,CAAC;AACzC,eAAO,MAAM,SAAS,oBAAoB,CAAC;;AAE3C,wBAwaG"}
1
+ {"version":3,"file":"prefer-at-empty.d.ts","sourceRoot":"","sources":["../../src/rules/prefer-at-empty.ts"],"names":[],"mappings":"AAiBA,MAAM,MAAM,OAAO,GAAG,EAAE,CAAC;AACzB,MAAM,MAAM,UAAU,GAAG,eAAe,CAAC;AACzC,eAAO,MAAM,SAAS,oBAAoB,CAAC;;AAE3C,wBAwaG"}
@@ -5,6 +5,8 @@ const bundled_angular_compiler_1 = require("@angular-eslint/bundled-angular-comp
5
5
  const utils_1 = require("@angular-eslint/utils");
6
6
  const create_eslint_rule_1 = require("../utils/create-eslint-rule");
7
7
  const are_equivalent_asts_1 = require("../utils/are-equivalent-asts");
8
+ const ast_types_1 = require("../utils/ast-types");
9
+ const to_range_1 = require("../utils/to-range");
8
10
  exports.RULE_NAME = 'prefer-at-empty';
9
11
  exports.default = (0, create_eslint_rule_1.createESLintRule)({
10
12
  name: exports.RULE_NAME,
@@ -64,7 +66,7 @@ exports.default = (0, create_eslint_rule_1.createESLintRule)({
64
66
  fix: branchEnd
65
67
  ? function* (fixer) {
66
68
  // Remove the entire `@if` block.
67
- yield fixer.removeRange(toRange(previous.node.sourceSpan));
69
+ yield fixer.removeRange((0, to_range_1.toRange)(previous.node.sourceSpan));
68
70
  if (forInfo.node.empty) {
69
71
  // There is already an `@empty` block. The contents of the
70
72
  // `@if` block and the contents of the `@empty` block would
@@ -83,7 +85,7 @@ exports.default = (0, create_eslint_rule_1.createESLintRule)({
83
85
  else {
84
86
  // Take the contents of the `@if` block and move
85
87
  // it into an `@empty` block after the `@for` block.
86
- yield fixer.insertTextAfterRange(toRange(forInfo.node.sourceSpan), ` @empty {${context.sourceCode.text.slice(branch.startSourceSpan.end.offset, branchEnd.end.offset)}`);
88
+ yield fixer.insertTextAfterRange((0, to_range_1.toRange)(forInfo.node.sourceSpan), ` @empty {${context.sourceCode.text.slice(branch.startSourceSpan.end.offset, branchEnd.end.offset)}`);
87
89
  }
88
90
  }
89
91
  : undefined,
@@ -115,14 +117,14 @@ exports.default = (0, create_eslint_rule_1.createESLintRule)({
115
117
  // the existing contents of the `@empty` block. This can
116
118
  // easily be achieved by removing the closing brace of the
117
119
  // `@empty` block and removing the `@if` statement.
118
- yield fixer.removeRange(toRange(previous.node.empty.endSourceSpan));
119
- yield fixer.removeRange(toRange(ifInfo.node.startSourceSpan));
120
+ yield fixer.removeRange((0, to_range_1.toRange)(previous.node.empty.endSourceSpan));
121
+ yield fixer.removeRange((0, to_range_1.toRange)(ifInfo.node.startSourceSpan));
120
122
  }
121
123
  else {
122
124
  // There is not already an `@empty` block, so
123
125
  // we can create one by replacing the entire
124
126
  // `@if (...) {` segment with `@empty {`.
125
- yield fixer.replaceTextRange(toRange(ifInfo.node.startSourceSpan), '@empty {');
127
+ yield fixer.replaceTextRange((0, to_range_1.toRange)(ifInfo.node.startSourceSpan), '@empty {');
126
128
  }
127
129
  },
128
130
  });
@@ -143,16 +145,16 @@ exports.default = (0, create_eslint_rule_1.createESLintRule)({
143
145
  fix: previousIfBlockEnd
144
146
  ? (fixer) => [
145
147
  // Remove the previous `@if` statement.
146
- fixer.removeRange(toRange(previous.node.startSourceSpan)),
148
+ fixer.removeRange((0, to_range_1.toRange)(previous.node.startSourceSpan)),
147
149
  // Remove the closing brace from the previous `@if` block.
148
- fixer.removeRange(toRange(previousIfBlockEnd)),
150
+ fixer.removeRange((0, to_range_1.toRange)(previousIfBlockEnd)),
149
151
  // Take the contents of the current `@if` block and move
150
152
  // it into the `@empty` block of the previous `@for` block.
151
- fixer.insertTextAfterRange(toRange(forBlock.sourceSpan), ` @empty {${context.sourceCode.text.slice(ifInfo.node.startSourceSpan.end.offset,
153
+ fixer.insertTextAfterRange((0, to_range_1.toRange)(forBlock.sourceSpan), ` @empty {${context.sourceCode.text.slice(ifInfo.node.startSourceSpan.end.offset,
152
154
  // The end offset includes the closing brace.
153
155
  ifInfo.node.sourceSpan.end.offset)}`),
154
156
  // Remove the entirety of the current `@if` block.
155
- fixer.removeRange(toRange(ifInfo.node.sourceSpan)),
157
+ fixer.removeRange((0, to_range_1.toRange)(ifInfo.node.sourceSpan)),
156
158
  ]
157
159
  : undefined,
158
160
  });
@@ -202,7 +204,7 @@ exports.default = (0, create_eslint_rule_1.createESLintRule)({
202
204
  // There isn't an existing `@empty` block, so we can create
203
205
  // one. We don't need to include a closing brace, because
204
206
  // we can reuse the one from the end of the @`if` block.
205
- yield fixer.insertTextAfterRange(toRange(forBlock.sourceSpan), ` @empty {${empty}`);
207
+ yield fixer.insertTextAfterRange((0, to_range_1.toRange)(forBlock.sourceSpan), ` @empty {${empty}`);
206
208
  }
207
209
  }
208
210
  : undefined,
@@ -223,12 +225,12 @@ exports.default = (0, create_eslint_rule_1.createESLintRule)({
223
225
  messageId: 'preferAtEmpty',
224
226
  fix: (fixer) => [
225
227
  // Remove the entire previous `@if` block.
226
- fixer.removeRange(toRange(previous.node.sourceSpan)),
228
+ fixer.removeRange((0, to_range_1.toRange)(previous.node.sourceSpan)),
227
229
  // Remove the current `@if` statement.
228
- fixer.removeRange(toRange(ifNotInfo.node.startSourceSpan)),
230
+ fixer.removeRange((0, to_range_1.toRange)(ifNotInfo.node.startSourceSpan)),
229
231
  // Take the contents of the previous `@if` block and move
230
232
  // it into the `@empty` block after the `@for` block.
231
- fixer.insertTextAfterRange(toRange(forBlock.sourceSpan), ` @empty {${context.sourceCode.text.slice(previous.node.startSourceSpan.end.offset,
233
+ fixer.insertTextAfterRange((0, to_range_1.toRange)(forBlock.sourceSpan), ` @empty {${context.sourceCode.text.slice(previous.node.startSourceSpan.end.offset,
232
234
  // The end offset is after the closing `}`, so we
233
235
  // need to subtract one to ensure it gets removed.
234
236
  previous.node.sourceSpan.end.offset - 1)}`),
@@ -257,10 +259,10 @@ exports.default = (0, create_eslint_rule_1.createESLintRule)({
257
259
  // block, the `@empty` block would never be rendered,
258
260
  // so we can remove it. We could try to replace it,
259
261
  // but it's easier to remove it and create a new one.
260
- yield fixer.removeRange(toRange(forBlock.empty.sourceSpan));
262
+ yield fixer.removeRange((0, to_range_1.toRange)(forBlock.empty.sourceSpan));
261
263
  }
262
264
  // Remove the entire `@if (...) {` segment.
263
- yield fixer.removeRange(toRange(info.node.startSourceSpan));
265
+ yield fixer.removeRange((0, to_range_1.toRange)(info.node.startSourceSpan));
264
266
  const elseBranch = info.node.branches[1];
265
267
  if (elseBranch.expression) {
266
268
  // The second branch is an `@else if` branch. We
@@ -273,7 +275,7 @@ exports.default = (0, create_eslint_rule_1.createESLintRule)({
273
275
  ifBranchEnd.end.offset - 1,
274
276
  elseBranch.nameSpan.end.offset,
275
277
  ], '@empty { @if ');
276
- yield fixer.insertTextAfterRange(toRange(ifEnd), '}');
278
+ yield fixer.insertTextAfterRange((0, to_range_1.toRange)(ifEnd), '}');
277
279
  }
278
280
  else {
279
281
  // The second branch is just an `@else` branch, so we
@@ -424,16 +426,16 @@ function getNotEmptyTestCollection(node) {
424
426
  if (node instanceof bundled_angular_compiler_1.ASTWithSource) {
425
427
  node = node.ast;
426
428
  }
427
- if (isLengthRead(node)) {
429
+ if ((0, ast_types_1.isLengthRead)(node)) {
428
430
  // @if (collection.length)
429
431
  return node.receiver;
430
432
  }
431
433
  if (node instanceof bundled_angular_compiler_1.Binary) {
432
- if (isLengthRead(node.left)) {
434
+ if ((0, ast_types_1.isLengthRead)(node.left)) {
433
435
  if (node.operation === '!==' ||
434
436
  node.operation === '>' ||
435
437
  node.operation === '!=') {
436
- if (isZero(node.right)) {
438
+ if ((0, ast_types_1.isZero)(node.right)) {
437
439
  // @if (collection.length !== 0)
438
440
  // @if (collection.length > 0)
439
441
  // @if (collection.length != 0)
@@ -441,11 +443,11 @@ function getNotEmptyTestCollection(node) {
441
443
  }
442
444
  }
443
445
  }
444
- else if (isZero(node.left)) {
446
+ else if ((0, ast_types_1.isZero)(node.left)) {
445
447
  if (node.operation === '!==' ||
446
448
  node.operation === '<' ||
447
449
  node.operation === '!=') {
448
- if (isLengthRead(node.right)) {
450
+ if ((0, ast_types_1.isLengthRead)(node.right)) {
449
451
  // @if (0 !== collection.length)
450
452
  // @if (0 < collection.length)
451
453
  // @if (0 != collection.length)
@@ -461,24 +463,24 @@ function getEmptyTestCollection(node) {
461
463
  node = node.ast;
462
464
  }
463
465
  if (node instanceof bundled_angular_compiler_1.PrefixNot) {
464
- if (isLengthRead(node.expression)) {
466
+ if ((0, ast_types_1.isLengthRead)(node.expression)) {
465
467
  // @if (!collection.length)
466
468
  return node.expression.receiver;
467
469
  }
468
470
  }
469
471
  else if (node instanceof bundled_angular_compiler_1.Binary) {
470
- if (isLengthRead(node.left)) {
472
+ if ((0, ast_types_1.isLengthRead)(node.left)) {
471
473
  if (node.operation === '===' || node.operation === '==') {
472
- if (isZero(node.right)) {
474
+ if ((0, ast_types_1.isZero)(node.right)) {
473
475
  // @if (collection.length === 0)
474
476
  // @if (collection.length == 0)
475
477
  return node.left.receiver;
476
478
  }
477
479
  }
478
480
  }
479
- else if (isZero(node.left)) {
481
+ else if ((0, ast_types_1.isZero)(node.left)) {
480
482
  if (node.operation === '===' || node.operation === '==') {
481
- if (isLengthRead(node.right)) {
483
+ if ((0, ast_types_1.isLengthRead)(node.right)) {
482
484
  // @if (0 === collection.length)
483
485
  // @if (0 == collection.length)
484
486
  return node.right.receiver;
@@ -488,12 +490,3 @@ function getEmptyTestCollection(node) {
488
490
  }
489
491
  return undefined;
490
492
  }
491
- function isLengthRead(node) {
492
- return node instanceof bundled_angular_compiler_1.PropertyRead && node.name === 'length';
493
- }
494
- function isZero(node) {
495
- return node instanceof bundled_angular_compiler_1.LiteralPrimitive && node.value === 0;
496
- }
497
- function toRange(span) {
498
- return [span.start.offset, span.end.offset];
499
- }
@@ -0,0 +1,4 @@
1
+ import { AST, PropertyRead } from '@angular-eslint/bundled-angular-compiler';
2
+ export declare function isLengthRead(node: AST): node is PropertyRead;
3
+ export declare function isZero(node: AST): boolean;
4
+ //# sourceMappingURL=ast-types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ast-types.d.ts","sourceRoot":"","sources":["../../src/utils/ast-types.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,GAAG,EAEH,YAAY,EACb,MAAM,0CAA0C,CAAC;AAElD,wBAAgB,YAAY,CAAC,IAAI,EAAE,GAAG,GAAG,IAAI,IAAI,YAAY,CAE5D;AAED,wBAAgB,MAAM,CAAC,IAAI,EAAE,GAAG,GAAG,OAAO,CAEzC"}
@@ -0,0 +1,11 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.isLengthRead = isLengthRead;
4
+ exports.isZero = isZero;
5
+ const bundled_angular_compiler_1 = require("@angular-eslint/bundled-angular-compiler");
6
+ function isLengthRead(node) {
7
+ return node instanceof bundled_angular_compiler_1.PropertyRead && node.name === 'length';
8
+ }
9
+ function isZero(node) {
10
+ return node instanceof bundled_angular_compiler_1.LiteralPrimitive && node.value === 0;
11
+ }
@@ -0,0 +1,4 @@
1
+ import { ParseSourceSpan } from '@angular-eslint/bundled-angular-compiler';
2
+ export declare function toRange(span: ParseSourceSpan): [number, number];
3
+ export declare function toZeroLengthRange(offset: number): [number, number];
4
+ //# sourceMappingURL=to-range.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"to-range.d.ts","sourceRoot":"","sources":["../../src/utils/to-range.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,0CAA0C,CAAC;AAE3E,wBAAgB,OAAO,CAAC,IAAI,EAAE,eAAe,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAE/D;AAED,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAElE"}
@@ -0,0 +1,10 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.toRange = toRange;
4
+ exports.toZeroLengthRange = toZeroLengthRange;
5
+ function toRange(span) {
6
+ return [span.start.offset, span.end.offset];
7
+ }
8
+ function toZeroLengthRange(offset) {
9
+ return [offset, offset];
10
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@angular-eslint/eslint-plugin-template",
3
- "version": "20.3.1-alpha.14",
3
+ "version": "20.3.1-alpha.15",
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": "20.3.1-alpha.14",
24
- "@angular-eslint/utils": "20.3.1-alpha.14"
23
+ "@angular-eslint/bundled-angular-compiler": "20.3.1-alpha.15",
24
+ "@angular-eslint/utils": "20.3.1-alpha.15"
25
25
  },
26
26
  "devDependencies": {
27
27
  "@types/aria-query": "5.0.4",
28
- "@angular-eslint/test-utils": "20.3.1-alpha.14"
28
+ "@angular-eslint/test-utils": "20.3.1-alpha.15"
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": "20.3.1-alpha.14"
35
+ "@angular-eslint/template-parser": "20.3.1-alpha.15"
36
36
  },
37
37
  "gitHead": "e2006e5e9c99e5a943d1a999e0efa5247d29ec24"
38
38
  }