@angular-eslint/eslint-plugin-template 17.3.1-alpha.0 → 17.3.1-alpha.10
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/dist/rules/attributes-order.js +63 -24
- package/dist/rules/elements-content.js +1 -1
- package/dist/rules/eqeqeq.js +19 -14
- package/dist/rules/i18n.js +33 -27
- package/dist/rules/label-has-associated-control.js +3 -4
- package/dist/rules/mouse-events-have-key-events.js +5 -2
- package/dist/rules/no-duplicate-attributes.js +1 -1
- package/dist/rules/no-inline-styles.js +1 -1
- package/dist/rules/prefer-self-closing-tags.js +3 -3
- package/dist/rules/role-has-required-aria.js +3 -3
- package/dist/rules/use-track-by-function.js +8 -2
- package/dist/rules/valid-aria.js +1 -1
- package/dist/utils/get-attribute-value.js +2 -2
- package/dist/utils/get-dom-elements.js +1 -1
- package/dist/utils/get-original-attribute-name.js +1 -2
- package/dist/utils/is-interactive-element/get-interactive-element-ax-object-schemas.js +2 -1
- package/dist/utils/is-interactive-element/index.js +1 -1
- package/dist/utils/is-semantic-role-element.js +11 -16
- package/package.json +5 -5
|
@@ -65,7 +65,7 @@ exports.default = (0, create_eslint_rule_1.createESLintRule)({
|
|
|
65
65
|
},
|
|
66
66
|
end: {
|
|
67
67
|
line: loc.end.line,
|
|
68
|
-
column: loc.end.column + 1,
|
|
68
|
+
column: loc.end.column + (isValuelessStructuralDirective(attr) ? 0 : 1),
|
|
69
69
|
},
|
|
70
70
|
};
|
|
71
71
|
default:
|
|
@@ -74,7 +74,6 @@ exports.default = (0, create_eslint_rule_1.createESLintRule)({
|
|
|
74
74
|
}
|
|
75
75
|
return {
|
|
76
76
|
['Element$1, Template'](node) {
|
|
77
|
-
var _a;
|
|
78
77
|
if (isImplicitTemplate(node)) {
|
|
79
78
|
return;
|
|
80
79
|
}
|
|
@@ -96,7 +95,7 @@ exports.default = (0, create_eslint_rule_1.createESLintRule)({
|
|
|
96
95
|
let errorRange;
|
|
97
96
|
for (let i = 0; i < sortedAttributes.length; i++) {
|
|
98
97
|
if (sortedAttributes[i] !== expectedAttributes[i]) {
|
|
99
|
-
errorRange = [
|
|
98
|
+
errorRange = [errorRange?.[0] ?? i, i];
|
|
100
99
|
}
|
|
101
100
|
}
|
|
102
101
|
if (errorRange) {
|
|
@@ -149,12 +148,11 @@ function byLocation(one, other) {
|
|
|
149
148
|
}
|
|
150
149
|
function byOrder(order, alphabetical) {
|
|
151
150
|
return function (one, other) {
|
|
152
|
-
var _a, _b, _c, _d;
|
|
153
151
|
const orderComparison = getOrderIndex(one, order) - getOrderIndex(other, order);
|
|
154
152
|
if (alphabetical && orderComparison === 0) {
|
|
155
|
-
const oneName =
|
|
153
|
+
const oneName = one.keySpan?.details ?? one.name;
|
|
156
154
|
const oneNormalised = oneName.replace(/^i18n-/, '');
|
|
157
|
-
const otherName =
|
|
155
|
+
const otherName = other.keySpan?.details ?? other.name;
|
|
158
156
|
const otherNormalised = otherName.replace(/^i18n-/, '');
|
|
159
157
|
if (oneNormalised === otherNormalised) {
|
|
160
158
|
return /^i18n-/.test(oneName) ? 1 : -1;
|
|
@@ -168,33 +166,53 @@ function getOrderIndex(attr, order) {
|
|
|
168
166
|
return order.indexOf(attr.orderType);
|
|
169
167
|
}
|
|
170
168
|
function toAttributeBindingOrderType(attribute) {
|
|
171
|
-
return
|
|
169
|
+
return {
|
|
170
|
+
...attribute,
|
|
171
|
+
orderType: "ATTRIBUTE_BINDING" /* OrderType.AttributeBinding */,
|
|
172
|
+
};
|
|
172
173
|
}
|
|
173
174
|
function toInputBindingOrderType(input) {
|
|
174
|
-
return
|
|
175
|
+
return {
|
|
176
|
+
...input,
|
|
177
|
+
orderType: "INPUT_BINDING" /* OrderType.InputBinding */,
|
|
178
|
+
};
|
|
175
179
|
}
|
|
176
180
|
function toStructuralDirectiveOrderType(attributeOrInput) {
|
|
177
|
-
return
|
|
181
|
+
return {
|
|
182
|
+
...attributeOrInput,
|
|
183
|
+
orderType: "STRUCTURAL_DIRECTIVE" /* OrderType.StructuralDirective */,
|
|
184
|
+
};
|
|
178
185
|
}
|
|
179
186
|
function toOutputBindingOrderType(output) {
|
|
180
|
-
return
|
|
187
|
+
return {
|
|
188
|
+
...output,
|
|
189
|
+
orderType: "OUTPUT_BINDING" /* OrderType.OutputBinding */,
|
|
190
|
+
};
|
|
181
191
|
}
|
|
182
192
|
function toTwoWayBindingOrderType(output) {
|
|
183
|
-
return
|
|
193
|
+
return {
|
|
194
|
+
...output,
|
|
195
|
+
orderType: "TWO_WAY_BINDING" /* OrderType.TwoWayBinding */,
|
|
196
|
+
};
|
|
184
197
|
}
|
|
185
198
|
function toTemplateReferenceVariableOrderType(reference) {
|
|
186
|
-
return
|
|
199
|
+
return {
|
|
200
|
+
...reference,
|
|
201
|
+
orderType: "TEMPLATE_REFERENCE" /* OrderType.TemplateReferenceVariable */,
|
|
202
|
+
};
|
|
187
203
|
}
|
|
188
204
|
function isImplicitTemplate(node) {
|
|
189
205
|
return isTmplAstTemplate(node) && node.tagName !== 'ng-template';
|
|
190
206
|
}
|
|
191
207
|
function extractTemplateAttrs(node) {
|
|
192
|
-
var _a, _b, _c, _d, _e, _f;
|
|
193
208
|
if (isTmplAstTemplate(node)) {
|
|
194
209
|
return node.templateAttrs.map(toStructuralDirectiveOrderType).concat(node.variables.map((x) => {
|
|
195
|
-
return
|
|
210
|
+
return {
|
|
211
|
+
...toAttributeBindingOrderType(x),
|
|
196
212
|
// `let-` is excluded from the keySpan and name - add it back in
|
|
197
|
-
keySpan: new bundled_angular_compiler_1.ParseSourceSpan(x.keySpan.start.moveBy(-4), x.keySpan.end),
|
|
213
|
+
keySpan: new bundled_angular_compiler_1.ParseSourceSpan(x.keySpan.start.moveBy(-4), x.keySpan.end),
|
|
214
|
+
name: 'let-' + x.name,
|
|
215
|
+
};
|
|
198
216
|
}));
|
|
199
217
|
}
|
|
200
218
|
if (!isImplicitTemplate(node.parent)) {
|
|
@@ -207,22 +225,25 @@ function extractTemplateAttrs(node) {
|
|
|
207
225
|
* will parsed as two attributes (`ngFor` and `ngForOf`)
|
|
208
226
|
*/
|
|
209
227
|
const attrs = node.parent.templateAttrs.map(toStructuralDirectiveOrderType);
|
|
210
|
-
let keyEnd =
|
|
211
|
-
if (
|
|
228
|
+
let keyEnd = attrs[0].keySpan?.end;
|
|
229
|
+
if (keyEnd?.getContext(0, 0)?.after === '=') {
|
|
212
230
|
keyEnd = keyEnd.moveBy(1);
|
|
213
|
-
const apos =
|
|
231
|
+
const apos = keyEnd.getContext(0, 0)?.after;
|
|
214
232
|
if (apos === "'" || apos === '"') {
|
|
215
233
|
do {
|
|
216
234
|
keyEnd = keyEnd.moveBy(1);
|
|
217
|
-
} while (
|
|
235
|
+
} while (keyEnd.getContext(0, 0)?.after !== apos);
|
|
218
236
|
}
|
|
219
237
|
else {
|
|
220
|
-
while (!/[\s>]/.test(
|
|
238
|
+
while (!/[\s>]/.test(keyEnd.getContext(0, 0)?.after ?? '')) {
|
|
221
239
|
keyEnd = keyEnd.moveBy(1);
|
|
222
240
|
}
|
|
223
241
|
}
|
|
224
242
|
return [
|
|
225
|
-
|
|
243
|
+
{
|
|
244
|
+
...attrs[0],
|
|
245
|
+
sourceSpan: new bundled_angular_compiler_1.ParseSourceSpan(attrs[0].sourceSpan.start, keyEnd),
|
|
246
|
+
},
|
|
226
247
|
];
|
|
227
248
|
}
|
|
228
249
|
return [attrs[0]];
|
|
@@ -248,8 +269,7 @@ function isOnSameLocation(input, output) {
|
|
|
248
269
|
input.sourceSpan.end === output.sourceSpan.end);
|
|
249
270
|
}
|
|
250
271
|
function getMessageName(expected) {
|
|
251
|
-
|
|
252
|
-
const fullName = (_b = (_a = expected.keySpan) === null || _a === void 0 ? void 0 : _a.details) !== null && _b !== void 0 ? _b : expected.name;
|
|
272
|
+
const fullName = expected.keySpan?.details ?? expected.name;
|
|
253
273
|
switch (expected.orderType) {
|
|
254
274
|
case "STRUCTURAL_DIRECTIVE" /* OrderType.StructuralDirective */:
|
|
255
275
|
return `*${fullName}`;
|
|
@@ -265,6 +285,24 @@ function getMessageName(expected) {
|
|
|
265
285
|
return fullName;
|
|
266
286
|
}
|
|
267
287
|
}
|
|
288
|
+
function isValuelessStructuralDirective(attr) {
|
|
289
|
+
if (attr.orderType !== "STRUCTURAL_DIRECTIVE" /* OrderType.StructuralDirective */ || !attr.keySpan) {
|
|
290
|
+
return false;
|
|
291
|
+
}
|
|
292
|
+
const attrSpan = attr.sourceSpan;
|
|
293
|
+
const keySpan = attr.keySpan;
|
|
294
|
+
/**
|
|
295
|
+
* A valueless structural directive will have the same span as its key.
|
|
296
|
+
* TextAttribute[value=''] is not always a reliable selector, because
|
|
297
|
+
* a *structuralDirective with `let var = something` will have value = ''
|
|
298
|
+
*/
|
|
299
|
+
return (attrSpan.start.offset === keySpan.start.offset &&
|
|
300
|
+
attrSpan.start.line === keySpan.start.line &&
|
|
301
|
+
attrSpan.start.col === keySpan.start.col &&
|
|
302
|
+
attrSpan.end.offset === keySpan.end.offset &&
|
|
303
|
+
attrSpan.end.line === keySpan.end.line &&
|
|
304
|
+
attrSpan.end.col === keySpan.end.col);
|
|
305
|
+
}
|
|
268
306
|
function getStartPos(expected) {
|
|
269
307
|
switch (expected.orderType) {
|
|
270
308
|
case "STRUCTURAL_DIRECTIVE" /* OrderType.StructuralDirective */:
|
|
@@ -276,7 +314,8 @@ function getStartPos(expected) {
|
|
|
276
314
|
function getEndPos(expected) {
|
|
277
315
|
switch (expected.orderType) {
|
|
278
316
|
case "STRUCTURAL_DIRECTIVE" /* OrderType.StructuralDirective */:
|
|
279
|
-
return expected.sourceSpan.end.offset +
|
|
317
|
+
return (expected.sourceSpan.end.offset +
|
|
318
|
+
(isValuelessStructuralDirective(expected) ? 0 : 1));
|
|
280
319
|
default:
|
|
281
320
|
return expected.sourceSpan.end.offset;
|
|
282
321
|
}
|
|
@@ -50,7 +50,7 @@ exports.default = (0, create_eslint_rule_1.createESLintRule)({
|
|
|
50
50
|
const { attributes, inputs, name: element, sourceSpan } = node;
|
|
51
51
|
const safelistAttributes = new Set([
|
|
52
52
|
...DEFAULT_SAFELIST_ATTRIBUTES,
|
|
53
|
-
...(allowList
|
|
53
|
+
...(allowList ?? []),
|
|
54
54
|
]);
|
|
55
55
|
const hasAttributeSafelisted = [...attributes, ...inputs]
|
|
56
56
|
.map(({ name }) => name)
|
package/dist/rules/eqeqeq.js
CHANGED
|
@@ -48,22 +48,27 @@ exports.default = (0, create_eslint_rule_1.createESLintRule)({
|
|
|
48
48
|
actualOperation: operation,
|
|
49
49
|
expectedOperation: `${operation}=`,
|
|
50
50
|
};
|
|
51
|
-
context.report(
|
|
51
|
+
context.report({
|
|
52
|
+
loc: {
|
|
52
53
|
start: sourceCode.getLocFromIndex(start),
|
|
53
54
|
end: sourceCode.getLocFromIndex(end),
|
|
54
|
-
},
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
55
|
+
},
|
|
56
|
+
messageId: 'eqeqeq',
|
|
57
|
+
data,
|
|
58
|
+
...(isStringNonNumericValue(left) || isStringNonNumericValue(right)
|
|
59
|
+
? {
|
|
60
|
+
fix: (fixer) => getFix({ node, right, end, sourceCode, fixer }),
|
|
61
|
+
}
|
|
62
|
+
: {
|
|
63
|
+
suggest: [
|
|
64
|
+
{
|
|
65
|
+
messageId: 'suggestStrictEquality',
|
|
66
|
+
fix: (fixer) => getFix({ node, right, end, sourceCode, fixer }),
|
|
67
|
+
data,
|
|
68
|
+
},
|
|
69
|
+
],
|
|
70
|
+
}),
|
|
71
|
+
});
|
|
67
72
|
},
|
|
68
73
|
};
|
|
69
74
|
},
|
package/dist/rules/i18n.js
CHANGED
|
@@ -141,7 +141,7 @@ exports.default = (0, create_eslint_rule_1.createESLintRule)({
|
|
|
141
141
|
: DEFAULT_ALLOWED_BOUND_TEXT_PATTERN;
|
|
142
142
|
const allowedAttributes = new Set([
|
|
143
143
|
...DEFAULT_ALLOWED_ATTRIBUTES,
|
|
144
|
-
...(ignoreAttributes
|
|
144
|
+
...(ignoreAttributes ?? []),
|
|
145
145
|
]);
|
|
146
146
|
const allowedTags = new Set(ignoreTags);
|
|
147
147
|
const collectedCustomIds = new Map();
|
|
@@ -153,7 +153,6 @@ exports.default = (0, create_eslint_rule_1.createESLintRule)({
|
|
|
153
153
|
return parent;
|
|
154
154
|
}
|
|
155
155
|
function handleElementOrTemplate(node) {
|
|
156
|
-
var _a;
|
|
157
156
|
const { i18n, sourceSpan } = node;
|
|
158
157
|
const parent = getNextElementOrTemplateParent(node);
|
|
159
158
|
if (isTagAllowed(allowedTags, node) ||
|
|
@@ -171,7 +170,7 @@ exports.default = (0, create_eslint_rule_1.createESLintRule)({
|
|
|
171
170
|
});
|
|
172
171
|
}
|
|
173
172
|
else {
|
|
174
|
-
const sourceSpans =
|
|
173
|
+
const sourceSpans = collectedCustomIds.get(customId) ?? [];
|
|
175
174
|
collectedCustomIds.set(customId, [...sourceSpans, sourceSpan]);
|
|
176
175
|
}
|
|
177
176
|
}
|
|
@@ -189,11 +188,10 @@ exports.default = (0, create_eslint_rule_1.createESLintRule)({
|
|
|
189
188
|
}
|
|
190
189
|
}
|
|
191
190
|
function handleTextAttribute({ i18n, keySpan, name: attributeName, parent: { name: elementName }, sourceSpan, value, }) {
|
|
192
|
-
var _a;
|
|
193
191
|
if (allowedTags.has(elementName)) {
|
|
194
192
|
return;
|
|
195
193
|
}
|
|
196
|
-
const keyOrSourceSpanLoc = parserServices.convertNodeSourceSpanToLoc(keySpan
|
|
194
|
+
const keyOrSourceSpanLoc = parserServices.convertNodeSourceSpanToLoc(keySpan ?? sourceSpan);
|
|
197
195
|
if (i18n) {
|
|
198
196
|
const { customId, description } = i18n;
|
|
199
197
|
if (checkId) {
|
|
@@ -205,7 +203,7 @@ exports.default = (0, create_eslint_rule_1.createESLintRule)({
|
|
|
205
203
|
});
|
|
206
204
|
}
|
|
207
205
|
else {
|
|
208
|
-
const sourceSpans =
|
|
206
|
+
const sourceSpans = collectedCustomIds.get(customId) ?? [];
|
|
209
207
|
collectedCustomIds.set(customId, [...sourceSpans, sourceSpan]);
|
|
210
208
|
}
|
|
211
209
|
}
|
|
@@ -234,7 +232,6 @@ exports.default = (0, create_eslint_rule_1.createESLintRule)({
|
|
|
234
232
|
});
|
|
235
233
|
}
|
|
236
234
|
function handleBoundTextOrIcuOrText(node) {
|
|
237
|
-
var _a;
|
|
238
235
|
const { sourceSpan } = node;
|
|
239
236
|
const parent = getNextElementOrTemplateParent(node);
|
|
240
237
|
if ((isBoundText(node) &&
|
|
@@ -245,9 +242,13 @@ exports.default = (0, create_eslint_rule_1.createESLintRule)({
|
|
|
245
242
|
}
|
|
246
243
|
const loc = parserServices.convertNodeSourceSpanToLoc(sourceSpan);
|
|
247
244
|
const fix = (fixer) => getFixForIcuOrText(sourceCode, parserServices, fixer, loc, parent);
|
|
248
|
-
context.report(
|
|
249
|
-
|
|
250
|
-
|
|
245
|
+
context.report({
|
|
246
|
+
messageId: 'i18nAttributeOnIcuOrText',
|
|
247
|
+
loc,
|
|
248
|
+
...(parent?.children?.filter((child) => isElement(child) || isNgTemplate(child)).length
|
|
249
|
+
? { suggest: [{ messageId: 'suggestAddI18nAttribute', fix }] }
|
|
250
|
+
: { fix }),
|
|
251
|
+
});
|
|
251
252
|
}
|
|
252
253
|
function reportDuplicatedCustomIds() {
|
|
253
254
|
if (checkDuplicateId) {
|
|
@@ -267,24 +268,29 @@ exports.default = (0, create_eslint_rule_1.createESLintRule)({
|
|
|
267
268
|
}
|
|
268
269
|
collectedCustomIds.clear();
|
|
269
270
|
}
|
|
270
|
-
return
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
271
|
+
return {
|
|
272
|
+
...((checkId || requireDescription || requireMeaning) && {
|
|
273
|
+
':matches(Element$1, Template[tagName="ng-template"])[i18n]'(node) {
|
|
274
|
+
handleElementOrTemplate(node);
|
|
275
|
+
},
|
|
276
|
+
}),
|
|
277
|
+
...((checkAttributes ||
|
|
278
|
+
checkId ||
|
|
279
|
+
requireDescription ||
|
|
280
|
+
requireMeaning) && {
|
|
281
|
+
[`Element$1 > TextAttribute[value=${PL_PATTERN}]`](node) {
|
|
282
|
+
handleTextAttribute(node);
|
|
283
|
+
},
|
|
284
|
+
}),
|
|
285
|
+
...(checkText && {
|
|
286
|
+
[`BoundText, Icu$1, Text$3[value=${PL_PATTERN}]`](node) {
|
|
287
|
+
handleBoundTextOrIcuOrText(node);
|
|
288
|
+
},
|
|
289
|
+
}),
|
|
290
|
+
'Program:exit'() {
|
|
286
291
|
reportDuplicatedCustomIds();
|
|
287
|
-
}
|
|
292
|
+
},
|
|
293
|
+
};
|
|
288
294
|
},
|
|
289
295
|
});
|
|
290
296
|
function getFixForIcuOrTextWithParent(sourceCode, fixer, { start }, tagName) {
|
|
@@ -66,22 +66,21 @@ exports.default = (0, create_eslint_rule_1.createESLintRule)({
|
|
|
66
66
|
const parserServices = (0, utils_1.getTemplateParserServices)(context);
|
|
67
67
|
const allControlComponents = new Set([
|
|
68
68
|
...DEFAULT_CONTROL_COMPONENTS,
|
|
69
|
-
...(controlComponents
|
|
69
|
+
...(controlComponents ?? []),
|
|
70
70
|
]);
|
|
71
71
|
const allLabelComponents = [
|
|
72
72
|
...DEFAULT_LABEL_COMPONENTS,
|
|
73
|
-
...(labelComponents
|
|
73
|
+
...(labelComponents ?? []),
|
|
74
74
|
];
|
|
75
75
|
const labelSelectors = allLabelComponents.map(({ selector }) => selector);
|
|
76
76
|
const labelComponentsPattern = toPattern(labelSelectors);
|
|
77
77
|
return {
|
|
78
78
|
[`Element$1[name=${labelComponentsPattern}]`](node) {
|
|
79
|
-
var _a;
|
|
80
79
|
const element = allLabelComponents.find(({ selector }) => selector === node.name);
|
|
81
80
|
if (!element)
|
|
82
81
|
return;
|
|
83
82
|
const attributesInputs = new Set([...node.attributes, ...node.inputs].map(({ name }) => name));
|
|
84
|
-
const hasInput =
|
|
83
|
+
const hasInput = element.inputs?.some((input) => attributesInputs.has(input));
|
|
85
84
|
if (hasInput || hasControlComponentIn(allControlComponents, node)) {
|
|
86
85
|
return;
|
|
87
86
|
}
|
|
@@ -27,13 +27,16 @@ exports.default = (0, create_eslint_rule_1.createESLintRule)({
|
|
|
27
27
|
["blur" /* KeyEvents.Blur */, "mouseout" /* MouseEvents.MouseOut */],
|
|
28
28
|
["focus" /* KeyEvents.Focus */, "mouseover" /* MouseEvents.MouseOver */],
|
|
29
29
|
];
|
|
30
|
-
return eventPairs.reduce((accumulator, [keyEvent, mouseEvent]) => (
|
|
30
|
+
return eventPairs.reduce((accumulator, [keyEvent, mouseEvent]) => ({
|
|
31
|
+
...accumulator,
|
|
32
|
+
[`Element$1[name=${domElementsPattern}]:has(BoundEvent[name='${mouseEvent}']):not(:has(BoundEvent[name='${keyEvent}']))`]({ sourceSpan, }) {
|
|
31
33
|
const loc = parserServices.convertNodeSourceSpanToLoc(sourceSpan);
|
|
32
34
|
context.report({
|
|
33
35
|
loc,
|
|
34
36
|
messageId: 'mouseEventsHaveKeyEvents',
|
|
35
37
|
data: { keyEvent, mouseEvent },
|
|
36
38
|
});
|
|
37
|
-
}
|
|
39
|
+
},
|
|
40
|
+
}), {});
|
|
38
41
|
},
|
|
39
42
|
});
|
|
@@ -62,7 +62,7 @@ exports.default = (0, create_eslint_rule_1.createESLintRule)({
|
|
|
62
62
|
]);
|
|
63
63
|
if (allowStylePrecedenceDuplicates) {
|
|
64
64
|
const inputsIgnored = inputs.filter((input) => angularStylePrecedenceDuplicatesAllowed.includes((0, get_original_attribute_name_1.getOriginalAttributeName)(input)));
|
|
65
|
-
if (
|
|
65
|
+
if (inputsIgnored?.length > 0) {
|
|
66
66
|
const attributesIgnored = attributes.filter((attr) => angularStylePrecedenceDuplicatesAllowed.includes((0, get_original_attribute_name_1.getOriginalAttributeName)(attr)));
|
|
67
67
|
const inputsNotIgnored = inputs.filter((input) => !inputsIgnored.includes(input));
|
|
68
68
|
const attributesNotIgnored = attributes.filter((attr) => !attributesIgnored.includes(attr));
|
|
@@ -99,5 +99,5 @@ function isNgStyle(name) {
|
|
|
99
99
|
return name === 'ngStyle';
|
|
100
100
|
}
|
|
101
101
|
function isStyleBound(keySpan) {
|
|
102
|
-
return
|
|
102
|
+
return keySpan?.details ? keySpan.details.includes('style.') : false;
|
|
103
103
|
}
|
|
@@ -62,14 +62,14 @@ exports.default = (0, create_eslint_rule_1.createESLintRule)({
|
|
|
62
62
|
});
|
|
63
63
|
}
|
|
64
64
|
function processContentNode(node) {
|
|
65
|
-
var _a, _b;
|
|
66
65
|
const { sourceSpan } = node;
|
|
67
66
|
const ngContentCloseTag = '</ng-content>';
|
|
68
67
|
if (sourceSpan.toString().includes(ngContentCloseTag)) {
|
|
69
68
|
// content nodes can only contain whitespaces
|
|
70
|
-
const content =
|
|
69
|
+
const content = sourceSpan
|
|
71
70
|
.toString()
|
|
72
|
-
.match(/>(\s*)</m)
|
|
71
|
+
.match(/>(\s*)</m)
|
|
72
|
+
?.at(1) ?? '';
|
|
73
73
|
const openingTagLastChar =
|
|
74
74
|
// This is more than the minimum length of a ng-content element
|
|
75
75
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
@@ -32,7 +32,7 @@ exports.default = (0, create_eslint_rule_1.createESLintRule)({
|
|
|
32
32
|
const { attributes, inputs, name: element } = node.parent;
|
|
33
33
|
const props = [...attributes, ...inputs];
|
|
34
34
|
const roleDef = aria_query_1.roles.get(role);
|
|
35
|
-
const requiredProps = Object.keys(
|
|
35
|
+
const requiredProps = Object.keys(roleDef?.requiredProps || {});
|
|
36
36
|
if (!requiredProps.length)
|
|
37
37
|
return;
|
|
38
38
|
if ((0, is_semantic_role_element_1.isSemanticRoleElement)(element, role, props))
|
|
@@ -59,8 +59,8 @@ exports.default = (0, create_eslint_rule_1.createESLintRule)({
|
|
|
59
59
|
missingProps,
|
|
60
60
|
},
|
|
61
61
|
fix: (fixer) => fixer.removeRange([
|
|
62
|
-
|
|
63
|
-
sourceSpan
|
|
62
|
+
sourceSpan?.start.offset - 1,
|
|
63
|
+
sourceSpan?.end.offset,
|
|
64
64
|
]),
|
|
65
65
|
},
|
|
66
66
|
],
|
|
@@ -52,8 +52,14 @@ exports.default = (0, create_eslint_rule_1.createESLintRule)({
|
|
|
52
52
|
const { start } = parserServices.convertNodeSourceSpanToLoc(templateAttrs[0].sourceSpan);
|
|
53
53
|
const { end } = parserServices.convertNodeSourceSpanToLoc(templateAttrs[templateAttrs.length - 1].sourceSpan);
|
|
54
54
|
const loc = {
|
|
55
|
-
start:
|
|
56
|
-
|
|
55
|
+
start: {
|
|
56
|
+
...start,
|
|
57
|
+
column: start.column - 1,
|
|
58
|
+
},
|
|
59
|
+
end: {
|
|
60
|
+
...end,
|
|
61
|
+
column: end.column + 1,
|
|
62
|
+
},
|
|
57
63
|
};
|
|
58
64
|
context.report({
|
|
59
65
|
messageId: 'useTrackByFunction',
|
package/dist/rules/valid-aria.js
CHANGED
|
@@ -125,7 +125,7 @@ function isValidAriaPropertyValue({ allowundefined, type, values }, attributeVal
|
|
|
125
125
|
const parsedAttributeValue = isBooleanLike(attributeValue)
|
|
126
126
|
? JSON.parse(attributeValue)
|
|
127
127
|
: attributeValue;
|
|
128
|
-
return Boolean(values
|
|
128
|
+
return Boolean(values?.includes(parsedAttributeValue));
|
|
129
129
|
}
|
|
130
130
|
}
|
|
131
131
|
}
|
|
@@ -16,10 +16,10 @@ const get_original_attribute_name_1 = require("./get-original-attribute-name");
|
|
|
16
16
|
*/
|
|
17
17
|
function getAttributeValue({ attributes, inputs }, attributeName) {
|
|
18
18
|
const attributeOrInput = [...attributes, ...inputs].find((attrOrInput) => (0, get_original_attribute_name_1.getOriginalAttributeName)(attrOrInput) === attributeName);
|
|
19
|
-
if (typeof
|
|
19
|
+
if (typeof attributeOrInput?.value === 'string') {
|
|
20
20
|
return attributeOrInput.value;
|
|
21
21
|
}
|
|
22
|
-
if (!(
|
|
22
|
+
if (!(attributeOrInput?.value instanceof bundled_angular_compiler_1.ASTWithSource)) {
|
|
23
23
|
return null;
|
|
24
24
|
}
|
|
25
25
|
if (attributeOrInput.value.ast instanceof bundled_angular_compiler_1.LiteralArray) {
|
|
@@ -4,6 +4,6 @@ exports.getDomElements = void 0;
|
|
|
4
4
|
const aria_query_1 = require("aria-query");
|
|
5
5
|
let domElements = null;
|
|
6
6
|
function getDomElements() {
|
|
7
|
-
return domElements
|
|
7
|
+
return domElements ?? (domElements = new Set(aria_query_1.dom.keys()));
|
|
8
8
|
}
|
|
9
9
|
exports.getDomElements = getDomElements;
|
|
@@ -13,8 +13,7 @@ const bundled_angular_compiler_1 = require("@angular-eslint/bundled-angular-comp
|
|
|
13
13
|
* ```
|
|
14
14
|
*/
|
|
15
15
|
function getOriginalAttributeName(attribute) {
|
|
16
|
-
|
|
17
|
-
const { details } = (_a = attribute.keySpan) !== null && _a !== void 0 ? _a : {};
|
|
16
|
+
const { details } = attribute.keySpan ?? {};
|
|
18
17
|
if (!details) {
|
|
19
18
|
return attribute.name;
|
|
20
19
|
}
|
|
@@ -20,7 +20,8 @@ function getInteractiveElementAXObjectSchemas() {
|
|
|
20
20
|
return accumulator.concat([...AXObjectSet].every((role) => interactiveAXObjects.has(role))
|
|
21
21
|
? // summary element should not have required attributes
|
|
22
22
|
elementSchema.name === 'summary'
|
|
23
|
-
?
|
|
23
|
+
? { ...elementSchema, attributes: [] }
|
|
24
|
+
: elementSchema
|
|
24
25
|
: []);
|
|
25
26
|
}, []);
|
|
26
27
|
}
|
|
@@ -9,7 +9,7 @@ const get_interactive_element_role_schemas_1 = require("./get-interactive-elemen
|
|
|
9
9
|
const get_non_interactive_element_role_schemas_1 = require("./get-non-interactive-element-role-schemas");
|
|
10
10
|
function checkIsInteractiveElement(node) {
|
|
11
11
|
function elementSchemaMatcher({ attributes, name }) {
|
|
12
|
-
return node.name === name && (0, attributes_comparator_1.attributesComparator)(attributes
|
|
12
|
+
return node.name === name && (0, attributes_comparator_1.attributesComparator)(attributes ?? [], node);
|
|
13
13
|
}
|
|
14
14
|
// Check in elementRoles for inherent interactive role associations for
|
|
15
15
|
// this element.
|
|
@@ -9,7 +9,6 @@ let axRoles = null;
|
|
|
9
9
|
// initialize the `nonInteractiveElementRoleSchemas` until the function is called
|
|
10
10
|
// for the first time, so we will not take up the memory.
|
|
11
11
|
function isSemanticRoleElement(element, role, elementAttributes) {
|
|
12
|
-
var _a;
|
|
13
12
|
if (axElements === null || axRoles === null) {
|
|
14
13
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
15
14
|
const { AXObjectRoles, elementAXObjects } = require('axobject-query');
|
|
@@ -17,20 +16,16 @@ function isSemanticRoleElement(element, role, elementAttributes) {
|
|
|
17
16
|
axRoles = AXObjectRoles;
|
|
18
17
|
}
|
|
19
18
|
// elementAXObjects: HTML elements are mapped to their related AXConcepts concepts
|
|
20
|
-
return Array.from(
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
// AXObjectRoles: AXObjects are mapped to their related ARIA concepts
|
|
32
|
-
return (_a = axRoles === null || axRoles === void 0 ? void 0 : axRoles.get(roleName)) === null || _a === void 0 ? void 0 : _a.find((semanticRole) => semanticRole.name === role);
|
|
33
|
-
}));
|
|
34
|
-
});
|
|
19
|
+
return Array.from(axElements?.keys() ?? []).some((htmlElement) => htmlElement.name === element &&
|
|
20
|
+
(htmlElement.attributes ?? []).every((htmlElemAttr) =>
|
|
21
|
+
// match every axElement html attributes to given elementAttributes
|
|
22
|
+
elementAttributes.find((elemAttr) => htmlElemAttr.name === elemAttr.name &&
|
|
23
|
+
htmlElemAttr.value === elemAttr.value)) &&
|
|
24
|
+
// aria- properties are covered by the element's semantic role
|
|
25
|
+
axElements?.get(htmlElement)?.find((roleName) =>
|
|
26
|
+
// AXObjectRoles: AXObjects are mapped to their related ARIA concepts
|
|
27
|
+
axRoles
|
|
28
|
+
?.get(roleName)
|
|
29
|
+
?.find((semanticRole) => semanticRole.name === role)));
|
|
35
30
|
}
|
|
36
31
|
exports.isSemanticRoleElement = isSemanticRoleElement;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@angular-eslint/eslint-plugin-template",
|
|
3
|
-
"version": "17.3.1-alpha.
|
|
3
|
+
"version": "17.3.1-alpha.10",
|
|
4
4
|
"description": "ESLint plugin for Angular Templates",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -17,10 +17,10 @@
|
|
|
17
17
|
"LICENSE"
|
|
18
18
|
],
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"@angular-eslint/bundled-angular-compiler": "17.3.1-alpha.
|
|
21
|
-
"@angular-eslint/utils": "17.3.1-alpha.
|
|
22
|
-
"@typescript-eslint/type-utils": "7.
|
|
23
|
-
"@typescript-eslint/utils": "7.
|
|
20
|
+
"@angular-eslint/bundled-angular-compiler": "17.3.1-alpha.10",
|
|
21
|
+
"@angular-eslint/utils": "17.3.1-alpha.10",
|
|
22
|
+
"@typescript-eslint/type-utils": "7.7.0",
|
|
23
|
+
"@typescript-eslint/utils": "7.7.0",
|
|
24
24
|
"aria-query": "5.3.0",
|
|
25
25
|
"axobject-query": "4.0.0"
|
|
26
26
|
},
|