@angular-eslint/eslint-plugin-template 19.1.1-alpha.0 → 19.1.1-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/processors.d.ts.map +1 -1
- package/dist/processors.js +41 -1
- package/dist/rules/prefer-self-closing-tags.d.ts.map +1 -1
- package/dist/rules/prefer-self-closing-tags.js +69 -38
- package/dist/rules/prefer-static-string-properties.d.ts.map +1 -1
- package/dist/rules/prefer-static-string-properties.js +1 -1
- package/package.json +5 -5
package/dist/processors.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"processors.d.ts","sourceRoot":"","sources":["../src/processors.ts"],"names":[],"mappings":"AAKA;;;;;;;GAOG;AACH,wBAAgB,0CAA0C,CACxD,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,GACf,OAAO,CA2BT;AAED,KAAK,gBAAgB,GAAG,CAAC,MAAM,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,EAAE,CAAC;AAExE,wBAAgB,uBAAuB,CACrC,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,GACf,gBAAgB,
|
|
1
|
+
{"version":3,"file":"processors.d.ts","sourceRoot":"","sources":["../src/processors.ts"],"names":[],"mappings":"AAKA;;;;;;;GAOG;AACH,wBAAgB,0CAA0C,CACxD,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,GACf,OAAO,CA2BT;AAED,KAAK,gBAAgB,GAAG,CAAC,MAAM,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,EAAE,CAAC;AAExE,wBAAgB,uBAAuB,CACrC,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,GACf,gBAAgB,CAmJlB;AAyDD,wBAAgB,wBAAwB,CACtC,wBAAwB,EAAE;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,GAAG,CAAC,EAAE;QACJ,KAAK,EAAE,MAAM,EAAE,CAAC;QAChB,IAAI,EAAE,MAAM,CAAC;KACd,CAAC;CACH,EAAE,EAAE,EACL,QAAQ,EAAE,MAAM,GACf,SAAS,OAAO,EAAE,CAmDpB;;;;;;;;;;;AAED,wBASE"}
|
package/dist/processors.js
CHANGED
|
@@ -50,7 +50,7 @@ function preprocessComponentFile(text, filename) {
|
|
|
50
50
|
try {
|
|
51
51
|
const sourceFile = typescript_1.default.createSourceFile(filename, text, typescript_1.default.ScriptTarget.Latest,
|
|
52
52
|
/* setParentNodes */ true);
|
|
53
|
-
const classDeclarations = sourceFile
|
|
53
|
+
const classDeclarations = getClassDeclarationFromSourceFile(sourceFile);
|
|
54
54
|
if (!classDeclarations || !classDeclarations.length) {
|
|
55
55
|
return noopResult;
|
|
56
56
|
}
|
|
@@ -153,6 +153,46 @@ function preprocessComponentFile(text, filename) {
|
|
|
153
153
|
return noopResult;
|
|
154
154
|
}
|
|
155
155
|
}
|
|
156
|
+
function getClassDeclarationFromSourceFile(sourceFile) {
|
|
157
|
+
const classDeclarations = [];
|
|
158
|
+
visit(sourceFile);
|
|
159
|
+
return classDeclarations;
|
|
160
|
+
function visit(node) {
|
|
161
|
+
if (typescript_1.default.isClassDeclaration(node)) {
|
|
162
|
+
classDeclarations.push(node);
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
// Class declarations are usually at the top-level, but there are
|
|
166
|
+
// some situations where they might be nested, such as in test files.
|
|
167
|
+
// If the node could have a class declaration somewhere in its
|
|
168
|
+
// descendant nodes, then we will recurse down into each child node.
|
|
169
|
+
// Keywords, tokens and trivia all come before `FirstNode`. They won't
|
|
170
|
+
// contain child nodes anyway, but we can skip them to save some time.
|
|
171
|
+
// Likewise, we can skip nodes that are part of JSDoc comments.
|
|
172
|
+
if (node.kind < typescript_1.default.SyntaxKind.FirstNode ||
|
|
173
|
+
node.kind > typescript_1.default.SyntaxKind.FirstJSDocNode) {
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
// Type nodes can be skipped.
|
|
177
|
+
if (node.kind >= typescript_1.default.SyntaxKind.TypePredicate &&
|
|
178
|
+
node.kind <= typescript_1.default.SyntaxKind.ImportType) {
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
// Some specific kinds of nodes can be skipped because
|
|
182
|
+
// we know that they cannot contain class declarations.
|
|
183
|
+
switch (node.kind) {
|
|
184
|
+
case typescript_1.default.SyntaxKind.InterfaceDeclaration:
|
|
185
|
+
case typescript_1.default.SyntaxKind.EnumDeclaration:
|
|
186
|
+
case typescript_1.default.SyntaxKind.ImportEqualsDeclaration:
|
|
187
|
+
case typescript_1.default.SyntaxKind.ImportDeclaration:
|
|
188
|
+
case typescript_1.default.SyntaxKind.ImportClause:
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
// For everything else, we'll play it safe
|
|
192
|
+
// and recurse down into the child nodes.
|
|
193
|
+
typescript_1.default.forEachChild(node, visit);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
156
196
|
function postprocessComponentFile(multiDimensionalMessages, filename) {
|
|
157
197
|
const messagesFromComponentSource = multiDimensionalMessages[0];
|
|
158
198
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"prefer-self-closing-tags.d.ts","sourceRoot":"","sources":["../../src/rules/prefer-self-closing-tags.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"prefer-self-closing-tags.d.ts","sourceRoot":"","sources":["../../src/rules/prefer-self-closing-tags.ts"],"names":[],"mappings":"AASA,MAAM,MAAM,OAAO,GAAG,EAAE,CAAC;AACzB,MAAM,MAAM,UAAU,GAAG,uBAAuB,CAAC;AACjD,eAAO,MAAM,SAAS,6BAA6B,CAAC;;AAEpD,wBAiJG"}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.RULE_NAME = void 0;
|
|
4
|
-
const bundled_angular_compiler_1 = require("@angular-eslint/bundled-angular-compiler");
|
|
5
4
|
const utils_1 = require("@angular-eslint/utils");
|
|
6
5
|
const create_eslint_rule_1 = require("../utils/create-eslint-rule");
|
|
7
6
|
const get_dom_elements_1 = require("../utils/get-dom-elements");
|
|
@@ -22,7 +21,7 @@ exports.default = (0, create_eslint_rule_1.createESLintRule)({
|
|
|
22
21
|
defaultOptions: [],
|
|
23
22
|
create(context) {
|
|
24
23
|
const parserServices = (0, utils_1.getTemplateParserServices)(context);
|
|
25
|
-
// angular 18
|
|
24
|
+
// angular 18 doesn't support self closing tags in index.html
|
|
26
25
|
if (/src[\\/]index\.html$/.test(context.physicalFilename)) {
|
|
27
26
|
// If it is, return an empty object to skip this rule
|
|
28
27
|
return {};
|
|
@@ -42,24 +41,31 @@ exports.default = (0, create_eslint_rule_1.createESLintRule)({
|
|
|
42
41
|
},
|
|
43
42
|
};
|
|
44
43
|
function processElementOrTemplateNode(node) {
|
|
45
|
-
const {
|
|
46
|
-
|
|
47
|
-
children.every((node) => {
|
|
48
|
-
// If the node is only whitespace, we can consider it empty.
|
|
49
|
-
// We need to look at the text from the source code, rather
|
|
50
|
-
// than the `TmplAstText.value` property. The `value` property
|
|
51
|
-
// contains the HTML-decoded value, so if the raw text contains
|
|
52
|
-
// ` `, that is decoded to a space, but we don't want to
|
|
53
|
-
// treat that as empty text.
|
|
54
|
-
return (node instanceof bundled_angular_compiler_1.TmplAstText &&
|
|
55
|
-
context.sourceCode.text
|
|
56
|
-
.slice(node.sourceSpan.start.offset, node.sourceSpan.end.offset)
|
|
57
|
-
.trim() === '');
|
|
58
|
-
});
|
|
59
|
-
const noCloseTag = !endSourceSpan ||
|
|
44
|
+
const { startSourceSpan, endSourceSpan } = node;
|
|
45
|
+
if (!endSourceSpan ||
|
|
60
46
|
(startSourceSpan.start.offset === endSourceSpan.start.offset &&
|
|
61
|
-
startSourceSpan.end.offset === endSourceSpan.end.offset)
|
|
62
|
-
|
|
47
|
+
startSourceSpan.end.offset === endSourceSpan.end.offset)) {
|
|
48
|
+
// No close tag.
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
// If the element only contains whitespace, we can consider it
|
|
52
|
+
// empty. We cannot use the children to reliably determine whether
|
|
53
|
+
// the element only contains whitespace because the template
|
|
54
|
+
// parser will HTML-decoded the text. For example, if the raw
|
|
55
|
+
// content contains ` `, that is decoded to a space, but
|
|
56
|
+
// we need to treat that as content that should be kept.
|
|
57
|
+
//
|
|
58
|
+
// Likewise, if the node only contains comments, those comments
|
|
59
|
+
// will not appear in the syntax tree, which results in the
|
|
60
|
+
// content appearing empty.
|
|
61
|
+
//
|
|
62
|
+
// So instead of using the syntax tree, we'll look at the
|
|
63
|
+
// source code and get the text that appears between the
|
|
64
|
+
// start element and the end element.
|
|
65
|
+
if (context.sourceCode.text
|
|
66
|
+
.slice(startSourceSpan.end.offset, endSourceSpan.start.offset)
|
|
67
|
+
.trim() !== '') {
|
|
68
|
+
// The element has content.
|
|
63
69
|
return;
|
|
64
70
|
}
|
|
65
71
|
// HTML tags always have more than two characters
|
|
@@ -75,25 +81,29 @@ exports.default = (0, create_eslint_rule_1.createESLintRule)({
|
|
|
75
81
|
function processContentNode(node) {
|
|
76
82
|
const { sourceSpan } = node;
|
|
77
83
|
const ngContentCloseTag = '</ng-content>';
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
84
|
+
const source = sourceSpan.toString();
|
|
85
|
+
if (source.endsWith(ngContentCloseTag)) {
|
|
86
|
+
// Content nodes don't have information about `startSourceSpan`
|
|
87
|
+
// and `endSourceSpan`, so we need to calculate where the inner
|
|
88
|
+
// HTML is ourselves. We know that the source ends with
|
|
89
|
+
// "</ng-content>", so we know where inner HTML ends.
|
|
90
|
+
// We just need to find where the inner HTML starts.
|
|
91
|
+
const startOfInnerHTML = findStartOfNgContentInnerHTML(source);
|
|
92
|
+
// If the start of the inner HTML is also where the close tag starts,
|
|
93
|
+
// then there is no inner HTML and we can avoid slicing the string.
|
|
94
|
+
if (startOfInnerHTML < source.length - ngContentCloseTag.length) {
|
|
95
|
+
if (source.slice(startOfInnerHTML, -ngContentCloseTag.length).trim()
|
|
96
|
+
.length > 0) {
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
86
99
|
}
|
|
87
|
-
|
|
88
|
-
//
|
|
100
|
+
// The source will always have at least "<ng-content"
|
|
101
|
+
// before the inner HTML, so two characters before
|
|
102
|
+
// the inner HTML will always be a valid index.
|
|
89
103
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
90
|
-
|
|
91
|
-
.toString()
|
|
92
|
-
.at(-2 - ngContentCloseTag.length - whiteSpaceContent.length);
|
|
104
|
+
const openingTagLastChar = source.at(startOfInnerHTML - 2);
|
|
93
105
|
const closingTagPrefix = getClosingTagPrefix(openingTagLastChar);
|
|
94
106
|
context.report({
|
|
95
|
-
// content nodes don't have information about startSourceSpan and endSourceSpan,
|
|
96
|
-
// so we need to calculate it by our own
|
|
97
107
|
loc: {
|
|
98
108
|
start: {
|
|
99
109
|
line: sourceSpan.end.line + 1,
|
|
@@ -106,10 +116,7 @@ exports.default = (0, create_eslint_rule_1.createESLintRule)({
|
|
|
106
116
|
},
|
|
107
117
|
messageId: 'preferSelfClosingTags',
|
|
108
118
|
fix: (fixer) => fixer.replaceTextRange([
|
|
109
|
-
sourceSpan.
|
|
110
|
-
ngContentCloseTag.length -
|
|
111
|
-
whiteSpaceContent.length -
|
|
112
|
-
1,
|
|
119
|
+
sourceSpan.start.offset + startOfInnerHTML - 1,
|
|
113
120
|
sourceSpan.end.offset,
|
|
114
121
|
], closingTagPrefix + '/>'),
|
|
115
122
|
});
|
|
@@ -120,6 +127,30 @@ exports.default = (0, create_eslint_rule_1.createESLintRule)({
|
|
|
120
127
|
function isContentNode(node) {
|
|
121
128
|
return 'name' in node && node.name === 'ng-content';
|
|
122
129
|
}
|
|
130
|
+
function findStartOfNgContentInnerHTML(html) {
|
|
131
|
+
let quote;
|
|
132
|
+
// The HTML will always start with at least "<ng-content",
|
|
133
|
+
// so we can skip over that part and start at index 11.
|
|
134
|
+
for (let i = 11; i < html.length; i++) {
|
|
135
|
+
const char = html.at(i);
|
|
136
|
+
if (quote !== undefined) {
|
|
137
|
+
if (quote === char) {
|
|
138
|
+
quote = undefined;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
else {
|
|
142
|
+
switch (char) {
|
|
143
|
+
case '>':
|
|
144
|
+
return i + 1;
|
|
145
|
+
case '"':
|
|
146
|
+
case "'":
|
|
147
|
+
quote = char;
|
|
148
|
+
break;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
return html.length;
|
|
153
|
+
}
|
|
123
154
|
function getClosingTagPrefix(openingTagLastChar) {
|
|
124
155
|
const hasOwnWhitespace = openingTagLastChar.trim() === '';
|
|
125
156
|
return hasOwnWhitespace ? '' : ' ';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"prefer-static-string-properties.d.ts","sourceRoot":"","sources":["../../src/rules/prefer-static-string-properties.ts"],"names":[],"mappings":"AAQA,MAAM,MAAM,OAAO,GAAG,EAAE,CAAC;AACzB,MAAM,MAAM,UAAU,GAAG,8BAA8B,CAAC;AACxD,eAAO,MAAM,SAAS,oCAAoC,CAAC;;AAE3D,
|
|
1
|
+
{"version":3,"file":"prefer-static-string-properties.d.ts","sourceRoot":"","sources":["../../src/rules/prefer-static-string-properties.ts"],"names":[],"mappings":"AAQA,MAAM,MAAM,OAAO,GAAG,EAAE,CAAC;AACzB,MAAM,MAAM,UAAU,GAAG,8BAA8B,CAAC;AACxD,eAAO,MAAM,SAAS,oCAAoC,CAAC;;AAE3D,wBAiDG"}
|
|
@@ -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
|
-
BoundAttribute({ name, sourceSpan, value }) {
|
|
25
|
+
['BoundAttribute.inputs']({ name, sourceSpan, value, }) {
|
|
26
26
|
if (value instanceof bundled_angular_compiler_1.ASTWithSource &&
|
|
27
27
|
value.ast instanceof bundled_angular_compiler_1.LiteralPrimitive &&
|
|
28
28
|
typeof value.ast.value === 'string') {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@angular-eslint/eslint-plugin-template",
|
|
3
|
-
"version": "19.1.1-alpha.
|
|
3
|
+
"version": "19.1.1-alpha.1",
|
|
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/bundled-angular-compiler": "19.1.1-alpha.
|
|
24
|
-
"@angular-eslint/utils": "19.1.1-alpha.
|
|
23
|
+
"@angular-eslint/bundled-angular-compiler": "19.1.1-alpha.1",
|
|
24
|
+
"@angular-eslint/utils": "19.1.1-alpha.1"
|
|
25
25
|
},
|
|
26
26
|
"devDependencies": {
|
|
27
27
|
"@types/aria-query": "5.0.4",
|
|
28
|
-
"@angular-eslint/template-parser": "19.1.1-alpha.
|
|
29
|
-
"@angular-eslint/test-utils": "19.1.1-alpha.
|
|
28
|
+
"@angular-eslint/template-parser": "19.1.1-alpha.1",
|
|
29
|
+
"@angular-eslint/test-utils": "19.1.1-alpha.1"
|
|
30
30
|
},
|
|
31
31
|
"peerDependencies": {
|
|
32
32
|
"@typescript-eslint/types": "^7.11.0 || ^8.0.0",
|