@angular-eslint/eslint-plugin-template 19.1.1-alpha.0 → 19.1.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.
@@ -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,CAqJlB;AAED,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"}
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"}
@@ -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.statements.filter((s) => typescript_1.default.isClassDeclaration(s));
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":"AAUA,MAAM,MAAM,OAAO,GAAG,EAAE,CAAC;AACzB,MAAM,MAAM,UAAU,GAAG,uBAAuB,CAAC;AACjD,eAAO,MAAM,SAAS,6BAA6B,CAAC;;AAEpD,wBAuIG"}
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 doesnt support self closing tags in index.html
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 { children, startSourceSpan, endSourceSpan } = node;
46
- const noContent = !children.length ||
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
- // `&nbsp;`, 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
- if (!noContent || noCloseTag) {
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 `&nbsp;`, 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
- if (sourceSpan.toString().includes(ngContentCloseTag)) {
79
- const whiteSpaceContent = sourceSpan
80
- .toString()
81
- .match(/<ng-content[^>]*>(\s*)<\/ng-content>/m)
82
- ?.at(1);
83
- const hasContent = typeof whiteSpaceContent === 'undefined';
84
- if (hasContent) {
85
- return;
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
- const openingTagLastChar =
88
- // This is more than the minimum length of a ng-content element
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
- sourceSpan
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.end.offset -
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,wBA6CG"}
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,wBA0DG"}
@@ -22,8 +22,14 @@ 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 }) {
26
- if (value instanceof bundled_angular_compiler_1.ASTWithSource &&
25
+ ['BoundAttribute.inputs']({ name, sourceSpan, keySpan, value, }) {
26
+ // Exclude @xxx (Animation) and xx.color
27
+ // When attribute start with "*", keySpan details is null so *ngSwitchCase is excluded
28
+ const isBindingProperty = keySpan?.details &&
29
+ !keySpan.details.includes('@') &&
30
+ !keySpan.details.includes('.');
31
+ if (isBindingProperty &&
32
+ value instanceof bundled_angular_compiler_1.ASTWithSource &&
27
33
  value.ast instanceof bundled_angular_compiler_1.LiteralPrimitive &&
28
34
  typeof value.ast.value === 'string') {
29
35
  // If the string literal is quoted with a double quote,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@angular-eslint/eslint-plugin-template",
3
- "version": "19.1.1-alpha.0",
3
+ "version": "19.1.1-alpha.10",
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.0",
24
- "@angular-eslint/utils": "19.1.1-alpha.0"
23
+ "@angular-eslint/bundled-angular-compiler": "19.1.1-alpha.10",
24
+ "@angular-eslint/utils": "19.1.1-alpha.10"
25
25
  },
26
26
  "devDependencies": {
27
27
  "@types/aria-query": "5.0.4",
28
- "@angular-eslint/template-parser": "19.1.1-alpha.0",
29
- "@angular-eslint/test-utils": "19.1.1-alpha.0"
28
+ "@angular-eslint/template-parser": "19.1.1-alpha.10",
29
+ "@angular-eslint/test-utils": "19.1.1-alpha.10"
30
30
  },
31
31
  "peerDependencies": {
32
32
  "@typescript-eslint/types": "^7.11.0 || ^8.0.0",