@angular-eslint/eslint-plugin-template 22.6.1-alpha.2 → 22.6.1-alpha.3
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 +1 -1
- package/dist/index.d.ts +6 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -4
- package/dist/inline-styles.d.ts +15 -0
- package/dist/inline-styles.d.ts.map +1 -0
- package/dist/inline-styles.js +335 -0
- package/dist/inline-templates.d.ts +40 -0
- package/dist/inline-templates.d.ts.map +1 -0
- package/dist/inline-templates.js +245 -0
- package/dist/processors.d.ts +19 -31
- package/dist/processors.d.ts.map +1 -1
- package/dist/processors.js +40 -258
- package/dist/rules/eqeqeq.d.ts.map +1 -1
- package/dist/rules/eqeqeq.js +7 -5
- package/dist/rules/i18n.js +1 -1
- package/dist/rules/no-distracting-elements.js +1 -1
- package/dist/rules/no-interpolation-in-attributes.d.ts.map +1 -1
- package/dist/rules/no-interpolation-in-attributes.js +1 -1
- package/dist/rules/prefer-at-else.d.ts.map +1 -1
- package/dist/rules/prefer-at-else.js +11 -1
- package/dist/rules/prefer-style-binding.d.ts +7 -2
- package/dist/rules/prefer-style-binding.d.ts.map +1 -1
- package/dist/rules/prefer-style-binding.js +196 -4
- package/dist/rules/prefer-template-literal.d.ts.map +1 -1
- package/dist/rules/prefer-template-literal.js +11 -2
- package/dist/rules/valid-aria.d.ts +1 -1
- package/dist/rules/valid-aria.d.ts.map +1 -1
- package/dist/rules/valid-aria.js +56 -17
- package/dist/utils/literal-primitive.d.ts +3 -1
- package/dist/utils/literal-primitive.d.ts.map +1 -1
- package/dist/utils/literal-primitive.js +12 -0
- package/package.json +5 -5
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.isFileLikelyToContainComponentDeclarations = isFileLikelyToContainComponentDeclarations;
|
|
7
|
+
exports.preprocessComponentFile = preprocessComponentFile;
|
|
8
|
+
exports.getComponentMetadata = getComponentMetadata;
|
|
9
|
+
exports.postprocessComponentFile = postprocessComponentFile;
|
|
10
|
+
const path_1 = require("path");
|
|
11
|
+
const typescript_1 = __importDefault(require("typescript"));
|
|
12
|
+
const rangeMap = new Map();
|
|
13
|
+
/**
|
|
14
|
+
* Because ultimately a user is in control of how and when this processor gets invoked,
|
|
15
|
+
* we can't fully protect them against doing more work than is necessary in all cases.
|
|
16
|
+
*
|
|
17
|
+
* Therefore, before we do a full parse of a TypeScript file to try and extract one or
|
|
18
|
+
* more Component declarations we want to do a really quick check for whether or not
|
|
19
|
+
* a file is likely to contain them.
|
|
20
|
+
*/
|
|
21
|
+
function isFileLikelyToContainComponentDeclarations(text, filename) {
|
|
22
|
+
/**
|
|
23
|
+
* Quickest possible heuristic is based on file extension suffix
|
|
24
|
+
*/
|
|
25
|
+
if ([
|
|
26
|
+
'.component.ts',
|
|
27
|
+
'.page.ts',
|
|
28
|
+
'.dialog.ts',
|
|
29
|
+
'.modal.ts',
|
|
30
|
+
'.popover.ts',
|
|
31
|
+
'.bottomsheet.ts',
|
|
32
|
+
'.snackbar.ts',
|
|
33
|
+
].some((likelySuffix) => filename.endsWith(likelySuffix))) {
|
|
34
|
+
return true;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Next quickest possible heuristic is the presence of the substring 'Component'
|
|
38
|
+
* and the substring '@angular/core' within the file contents
|
|
39
|
+
*/
|
|
40
|
+
if (text.includes('Component') && text.includes('@angular/core')) {
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
function preprocessComponentFile(text, filename,
|
|
46
|
+
/** Lets callers that already parsed the file avoid a second full parse */
|
|
47
|
+
parsedSourceFile) {
|
|
48
|
+
// This effectively instructs ESLint that there were no code blocks to extract for the current file
|
|
49
|
+
const noopResult = [text];
|
|
50
|
+
if (!isFileLikelyToContainComponentDeclarations(text, filename)) {
|
|
51
|
+
return noopResult;
|
|
52
|
+
}
|
|
53
|
+
try {
|
|
54
|
+
const sourceFile = parsedSourceFile ??
|
|
55
|
+
typescript_1.default.createSourceFile(filename, text, typescript_1.default.ScriptTarget.Latest,
|
|
56
|
+
/* setParentNodes */ true);
|
|
57
|
+
const result = [text];
|
|
58
|
+
let id = 0;
|
|
59
|
+
for (const metadata of getComponentMetadata(sourceFile)) {
|
|
60
|
+
/**
|
|
61
|
+
* Ignore Components which have external template files, they will be linted directly,
|
|
62
|
+
* and any that have inline templates which are malformed
|
|
63
|
+
*/
|
|
64
|
+
const templateProperty = metadata.properties.find((id) => id && id.name && id.name.getText() === 'template');
|
|
65
|
+
if (metadata.properties.find((id) => id && id.name && id.name.getText() === 'templateUrl') ||
|
|
66
|
+
!templateProperty) {
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
if (!typescript_1.default.isPropertyAssignment(templateProperty)) {
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
let templateText;
|
|
73
|
+
const templatePropertyInitializer = templateProperty.initializer;
|
|
74
|
+
if (typescript_1.default.isNoSubstitutionTemplateLiteral(templatePropertyInitializer)) {
|
|
75
|
+
templateText = templatePropertyInitializer.rawText;
|
|
76
|
+
}
|
|
77
|
+
if (typescript_1.default.isTemplateExpression(templatePropertyInitializer)) {
|
|
78
|
+
// The text includes the opening and closing
|
|
79
|
+
// backtick, so trim the first and last characters.
|
|
80
|
+
templateText = templatePropertyInitializer.getText().slice(1, -1);
|
|
81
|
+
}
|
|
82
|
+
if (typescript_1.default.isStringLiteral(templatePropertyInitializer)) {
|
|
83
|
+
templateText = templatePropertyInitializer.text;
|
|
84
|
+
}
|
|
85
|
+
// The template initializer is somehow not a string literal or a string template
|
|
86
|
+
if (!templateText) {
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
const baseFilename = (0, path_1.basename)(filename);
|
|
90
|
+
const inlineTemplateTmpFilename = `inline-template-${baseFilename}-${++id}.component.html`;
|
|
91
|
+
const start = templateProperty.initializer.getStart();
|
|
92
|
+
const end = templateProperty.initializer.getEnd();
|
|
93
|
+
rangeMap.set(inlineTemplateTmpFilename, {
|
|
94
|
+
sourceFilename: filename,
|
|
95
|
+
range: [start, end],
|
|
96
|
+
lineAndCharacter: {
|
|
97
|
+
start: sourceFile.getLineAndCharacterOfPosition(start),
|
|
98
|
+
end: sourceFile.getLineAndCharacterOfPosition(end),
|
|
99
|
+
},
|
|
100
|
+
});
|
|
101
|
+
/**
|
|
102
|
+
* We are ultimately returning an array containing both the original source,
|
|
103
|
+
* and a new fragment representing each of the inline HTML templates found.
|
|
104
|
+
* Each fragment must have an appropriate .html extension so that it can be
|
|
105
|
+
* linted using the right rules and plugins.
|
|
106
|
+
*
|
|
107
|
+
* The postprocessor will handle tying things back to the right position
|
|
108
|
+
* in the original file, so this temporary filename will never be visible
|
|
109
|
+
* to the end user.
|
|
110
|
+
*/
|
|
111
|
+
result.push({
|
|
112
|
+
text: templateText,
|
|
113
|
+
filename: inlineTemplateTmpFilename,
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
return result;
|
|
117
|
+
}
|
|
118
|
+
catch (err) {
|
|
119
|
+
console.log(err);
|
|
120
|
+
console.error('preprocess: ERROR could not parse @Component() metadata', filename);
|
|
121
|
+
return noopResult;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
function getComponentMetadata(sourceFile) {
|
|
125
|
+
const metadata = [];
|
|
126
|
+
for (const declaration of getClassDeclarationFromSourceFile(sourceFile)) {
|
|
127
|
+
for (const decorator of typescript_1.default.getDecorators(declaration) ?? []) {
|
|
128
|
+
const expression = decorator.expression;
|
|
129
|
+
if (typescript_1.default.isCallExpression(expression) &&
|
|
130
|
+
typescript_1.default.isIdentifier(expression.expression) &&
|
|
131
|
+
expression.expression.text === 'Component' &&
|
|
132
|
+
expression.arguments.length === 1 &&
|
|
133
|
+
typescript_1.default.isObjectLiteralExpression(expression.arguments[0])) {
|
|
134
|
+
metadata.push(expression.arguments[0]);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return metadata;
|
|
139
|
+
}
|
|
140
|
+
function getClassDeclarationFromSourceFile(sourceFile) {
|
|
141
|
+
const classDeclarations = [];
|
|
142
|
+
visit(sourceFile);
|
|
143
|
+
return classDeclarations;
|
|
144
|
+
function visit(node) {
|
|
145
|
+
if (typescript_1.default.isClassDeclaration(node)) {
|
|
146
|
+
classDeclarations.push(node);
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
// Class declarations are usually at the top-level, but there are
|
|
150
|
+
// some situations where they might be nested, such as in test files.
|
|
151
|
+
// If the node could have a class declaration somewhere in its
|
|
152
|
+
// descendant nodes, then we will recurse down into each child node.
|
|
153
|
+
// Keywords, tokens and trivia all come before `FirstNode`. They won't
|
|
154
|
+
// contain child nodes anyway, but we can skip them to save some time.
|
|
155
|
+
// Likewise, we can skip nodes that are part of JSDoc comments.
|
|
156
|
+
if (node.kind < typescript_1.default.SyntaxKind.FirstNode ||
|
|
157
|
+
node.kind > typescript_1.default.SyntaxKind.FirstJSDocNode) {
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
// Type nodes can be skipped.
|
|
161
|
+
if (node.kind >= typescript_1.default.SyntaxKind.TypePredicate &&
|
|
162
|
+
node.kind <= typescript_1.default.SyntaxKind.ImportType) {
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
// Some specific kinds of nodes can be skipped because
|
|
166
|
+
// we know that they cannot contain class declarations.
|
|
167
|
+
switch (node.kind) {
|
|
168
|
+
case typescript_1.default.SyntaxKind.InterfaceDeclaration:
|
|
169
|
+
case typescript_1.default.SyntaxKind.EnumDeclaration:
|
|
170
|
+
case typescript_1.default.SyntaxKind.ImportEqualsDeclaration:
|
|
171
|
+
case typescript_1.default.SyntaxKind.ImportDeclaration:
|
|
172
|
+
case typescript_1.default.SyntaxKind.ImportClause:
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
// For everything else, we'll play it safe
|
|
176
|
+
// and recurse down into the child nodes.
|
|
177
|
+
typescript_1.default.forEachChild(node, visit);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
function postprocessComponentFile(multiDimensionalMessages, filename) {
|
|
181
|
+
const messagesFromComponentSource = multiDimensionalMessages[0];
|
|
182
|
+
/**
|
|
183
|
+
* If the Component did not have one or more inline templates defined within it
|
|
184
|
+
* there will only be one item in the multiDimensionalMessages
|
|
185
|
+
*/
|
|
186
|
+
if (multiDimensionalMessages.length === 1) {
|
|
187
|
+
return messagesFromComponentSource;
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* There could be multiple inline templates found within the current file,
|
|
191
|
+
* so they are represented by all of the multiDimensionalMessages after the
|
|
192
|
+
* first one (which is the file itself)
|
|
193
|
+
*/
|
|
194
|
+
const messagesFromAllInlineTemplateHTML = multiDimensionalMessages.slice(1);
|
|
195
|
+
/**
|
|
196
|
+
* Adjust message location data to apply it back to the
|
|
197
|
+
* original file
|
|
198
|
+
*/
|
|
199
|
+
const res = [
|
|
200
|
+
...messagesFromComponentSource,
|
|
201
|
+
...messagesFromAllInlineTemplateHTML.flatMap((messagesFromInlineTemplateHTML, i) => {
|
|
202
|
+
const baseFilename = (0, path_1.basename)(filename);
|
|
203
|
+
const inlineTemplateTmpFilename = `inline-template-${baseFilename}-${i + 1}.component.html`;
|
|
204
|
+
const rangeData = rangeMap.get(inlineTemplateTmpFilename);
|
|
205
|
+
if (!rangeData) {
|
|
206
|
+
return [];
|
|
207
|
+
}
|
|
208
|
+
return messagesFromInlineTemplateHTML.map((message) => {
|
|
209
|
+
// The first line of the inline template starts at the column after
|
|
210
|
+
// the opening quote in the TypeScript file, so we need to adjust
|
|
211
|
+
// the message's column by that amount when the message starts on
|
|
212
|
+
// the first line. The character we recorded was the quote's column,
|
|
213
|
+
// so add one to get the column where the actual string starts.
|
|
214
|
+
if (message.line === 1) {
|
|
215
|
+
message.column += rangeData.lineAndCharacter.start.character + 1;
|
|
216
|
+
}
|
|
217
|
+
// The same thing applies to the end column
|
|
218
|
+
// if it also ends on the first line.
|
|
219
|
+
if (message.endLine === 1) {
|
|
220
|
+
message.endColumn += rangeData.lineAndCharacter.start.character + 1;
|
|
221
|
+
}
|
|
222
|
+
message.line += rangeData.lineAndCharacter.start.line;
|
|
223
|
+
message.endLine += rangeData.lineAndCharacter.start.line;
|
|
224
|
+
// The range defines the range of the value that initializes
|
|
225
|
+
// the `template` property, which includes the opening and
|
|
226
|
+
// closing quotes. Add one to move past the opening quote.
|
|
227
|
+
const startOffset = rangeData.range[0] + 1;
|
|
228
|
+
const adjustFixRange = (fix) => {
|
|
229
|
+
fix.range = [
|
|
230
|
+
startOffset + fix.range[0],
|
|
231
|
+
startOffset + fix.range[1],
|
|
232
|
+
];
|
|
233
|
+
};
|
|
234
|
+
if (message.fix) {
|
|
235
|
+
adjustFixRange(message.fix);
|
|
236
|
+
}
|
|
237
|
+
// Suggestions carry their own fixes, which are also
|
|
238
|
+
// relative to the inline template and need the same adjustment.
|
|
239
|
+
message.suggestions?.forEach((suggestion) => adjustFixRange(suggestion.fix));
|
|
240
|
+
return message;
|
|
241
|
+
});
|
|
242
|
+
}),
|
|
243
|
+
];
|
|
244
|
+
return res;
|
|
245
|
+
}
|
package/dist/processors.d.ts
CHANGED
|
@@ -1,41 +1,29 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
*/
|
|
9
|
-
export declare function isFileLikelyToContainComponentDeclarations(text: string, filename: string): boolean;
|
|
10
|
-
type PreprocessResult = (string | {
|
|
11
|
-
text: string;
|
|
12
|
-
filename: string;
|
|
13
|
-
})[];
|
|
14
|
-
export declare function preprocessComponentFile(text: string, filename: string): PreprocessResult;
|
|
15
|
-
export declare function postprocessComponentFile(multiDimensionalMessages: {
|
|
16
|
-
ruleId: string;
|
|
17
|
-
severity: number;
|
|
18
|
-
message: string;
|
|
19
|
-
line: number;
|
|
20
|
-
column: number;
|
|
21
|
-
nodeType: string;
|
|
22
|
-
messageId: string;
|
|
23
|
-
endLine: number;
|
|
24
|
-
endColumn: number;
|
|
25
|
-
fix?: {
|
|
26
|
-
range: number[];
|
|
27
|
-
text: string;
|
|
1
|
+
import { preprocessComponentFile, postprocessComponentFile } from './inline-templates';
|
|
2
|
+
export { isFileLikelyToContainComponentDeclarations, preprocessComponentFile, postprocessComponentFile, } from './inline-templates';
|
|
3
|
+
import type { InlineStyleLanguage } from './inline-styles';
|
|
4
|
+
export interface ProcessorOptions {
|
|
5
|
+
inlineTemplates?: boolean;
|
|
6
|
+
inlineStyles?: false | {
|
|
7
|
+
inlineStyleLanguage: InlineStyleLanguage;
|
|
28
8
|
};
|
|
29
|
-
}
|
|
30
|
-
declare const
|
|
9
|
+
}
|
|
10
|
+
declare const processors: {
|
|
31
11
|
'extract-inline-html': {
|
|
32
12
|
meta: {
|
|
33
13
|
name: string;
|
|
34
14
|
};
|
|
35
15
|
preprocess: typeof preprocessComponentFile;
|
|
36
16
|
postprocess: typeof postprocessComponentFile;
|
|
37
|
-
supportsAutofix:
|
|
17
|
+
supportsAutofix: true;
|
|
38
18
|
};
|
|
39
19
|
};
|
|
40
|
-
export
|
|
20
|
+
export declare function createProcessor({ inlineTemplates, inlineStyles, }?: ProcessorOptions): {
|
|
21
|
+
meta: {
|
|
22
|
+
name: string;
|
|
23
|
+
};
|
|
24
|
+
preprocess: typeof preprocessComponentFile;
|
|
25
|
+
postprocess: typeof postprocessComponentFile;
|
|
26
|
+
supportsAutofix: true;
|
|
27
|
+
};
|
|
28
|
+
export default processors;
|
|
41
29
|
//# sourceMappingURL=processors.d.ts.map
|
package/dist/processors.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"processors.d.ts","sourceRoot":"","sources":["../src/processors.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"processors.d.ts","sourceRoot":"","sources":["../src/processors.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,uBAAuB,EACvB,wBAAwB,EACzB,MAAM,oBAAoB,CAAC;AAK5B,OAAO,EACL,0CAA0C,EAC1C,uBAAuB,EACvB,wBAAwB,GACzB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AAE3D,MAAM,WAAW,gBAAgB;IAC/B,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,YAAY,CAAC,EAAE,KAAK,GAAG;QAAE,mBAAmB,EAAE,mBAAmB,CAAA;KAAE,CAAC;CACrE;AAED,QAAA,MAAM,UAAU;;;;;;;;;CASf,CAAC;AAOF,wBAAgB,eAAe,CAAC,EAC9B,eAAsB,EACtB,YAAoB,GACrB,GAAE,gBAAqB;;;;;;;EAsCvB;AAED,eAAe,UAAU,CAAC"}
|
package/dist/processors.js
CHANGED
|
@@ -1,267 +1,49 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.isFileLikelyToContainComponentDeclarations =
|
|
7
|
-
exports.
|
|
8
|
-
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
*
|
|
16
|
-
* Therefore, before we do a full parse of a TypeScript file to try and extract one or
|
|
17
|
-
* more Component declarations we want to do a really quick check for whether or not
|
|
18
|
-
* a file is likely to contain them.
|
|
19
|
-
*/
|
|
20
|
-
function isFileLikelyToContainComponentDeclarations(text, filename) {
|
|
21
|
-
/**
|
|
22
|
-
* Quickest possible heuristic is based on file extension suffix
|
|
23
|
-
*/
|
|
24
|
-
if ([
|
|
25
|
-
'.component.ts',
|
|
26
|
-
'.page.ts',
|
|
27
|
-
'.dialog.ts',
|
|
28
|
-
'.modal.ts',
|
|
29
|
-
'.popover.ts',
|
|
30
|
-
'.bottomsheet.ts',
|
|
31
|
-
'.snackbar.ts',
|
|
32
|
-
].some((likelySuffix) => filename.endsWith(likelySuffix))) {
|
|
33
|
-
return true;
|
|
34
|
-
}
|
|
35
|
-
/**
|
|
36
|
-
* Next quickest possible heuristic is the presence of the substring 'Component'
|
|
37
|
-
* and the substring '@angular/core' within the file contents
|
|
38
|
-
*/
|
|
39
|
-
if (text.includes('Component') && text.includes('@angular/core')) {
|
|
40
|
-
return true;
|
|
41
|
-
}
|
|
42
|
-
return false;
|
|
43
|
-
}
|
|
44
|
-
function preprocessComponentFile(text, filename) {
|
|
45
|
-
// This effectively instructs ESLint that there were no code blocks to extract for the current file
|
|
46
|
-
const noopResult = [text];
|
|
47
|
-
if (!isFileLikelyToContainComponentDeclarations(text, filename)) {
|
|
48
|
-
return noopResult;
|
|
49
|
-
}
|
|
50
|
-
try {
|
|
51
|
-
const sourceFile = typescript_1.default.createSourceFile(filename, text, typescript_1.default.ScriptTarget.Latest,
|
|
52
|
-
/* setParentNodes */ true);
|
|
53
|
-
const classDeclarations = getClassDeclarationFromSourceFile(sourceFile);
|
|
54
|
-
if (!classDeclarations || !classDeclarations.length) {
|
|
55
|
-
return noopResult;
|
|
56
|
-
}
|
|
57
|
-
/**
|
|
58
|
-
* Find all the Component decorators
|
|
59
|
-
*/
|
|
60
|
-
const componentDecoratorNodes = [];
|
|
61
|
-
for (const classDeclaration of classDeclarations) {
|
|
62
|
-
const classDecorators = typescript_1.default.getDecorators(classDeclaration);
|
|
63
|
-
if (!classDecorators) {
|
|
64
|
-
continue;
|
|
65
|
-
}
|
|
66
|
-
for (const decorator of classDecorators) {
|
|
67
|
-
if (typescript_1.default.isCallExpression(decorator.expression) &&
|
|
68
|
-
typescript_1.default.isIdentifier(decorator.expression.expression) &&
|
|
69
|
-
decorator.expression.expression.text === 'Component') {
|
|
70
|
-
componentDecoratorNodes.push(decorator);
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
/**
|
|
75
|
-
* Ignore malformed Component files
|
|
76
|
-
*/
|
|
77
|
-
if (!componentDecoratorNodes || !componentDecoratorNodes.length) {
|
|
78
|
-
return noopResult;
|
|
79
|
-
}
|
|
80
|
-
const result = [text];
|
|
81
|
-
let id = 0;
|
|
82
|
-
for (const componentDecoratorNode of componentDecoratorNodes) {
|
|
83
|
-
/**
|
|
84
|
-
* Ignore malformed component metadata
|
|
85
|
-
*/
|
|
86
|
-
if (!typescript_1.default.isDecorator(componentDecoratorNode) ||
|
|
87
|
-
!typescript_1.default.isCallExpression(componentDecoratorNode.expression) ||
|
|
88
|
-
componentDecoratorNode.expression.arguments.length !== 1) {
|
|
89
|
-
continue;
|
|
90
|
-
}
|
|
91
|
-
const metadata = componentDecoratorNode.expression.arguments[0];
|
|
92
|
-
if (!typescript_1.default.isObjectLiteralExpression(metadata)) {
|
|
93
|
-
continue;
|
|
94
|
-
}
|
|
95
|
-
/**
|
|
96
|
-
* Ignore Components which have external template files, they will be linted directly,
|
|
97
|
-
* and any that have inline templates which are malformed
|
|
98
|
-
*/
|
|
99
|
-
const templateProperty = metadata.properties.find((id) => id && id.name && id.name.getText() === 'template');
|
|
100
|
-
if (metadata.properties.find((id) => id && id.name && id.name.getText() === 'templateUrl') ||
|
|
101
|
-
!templateProperty) {
|
|
102
|
-
continue;
|
|
103
|
-
}
|
|
104
|
-
if (!typescript_1.default.isPropertyAssignment(templateProperty)) {
|
|
105
|
-
continue;
|
|
106
|
-
}
|
|
107
|
-
let templateText;
|
|
108
|
-
const templatePropertyInitializer = templateProperty.initializer;
|
|
109
|
-
if (typescript_1.default.isNoSubstitutionTemplateLiteral(templatePropertyInitializer)) {
|
|
110
|
-
templateText = templatePropertyInitializer.rawText;
|
|
111
|
-
}
|
|
112
|
-
if (typescript_1.default.isTemplateExpression(templatePropertyInitializer)) {
|
|
113
|
-
// The text includes the opening and closing
|
|
114
|
-
// backtick, so trim the first and last characters.
|
|
115
|
-
templateText = templatePropertyInitializer.getText().slice(1, -1);
|
|
116
|
-
}
|
|
117
|
-
if (typescript_1.default.isStringLiteral(templatePropertyInitializer)) {
|
|
118
|
-
templateText = templatePropertyInitializer.text;
|
|
119
|
-
}
|
|
120
|
-
// The template initializer is somehow not a string literal or a string template
|
|
121
|
-
if (!templateText) {
|
|
122
|
-
continue;
|
|
123
|
-
}
|
|
124
|
-
const baseFilename = (0, path_1.basename)(filename);
|
|
125
|
-
const inlineTemplateTmpFilename = `inline-template-${baseFilename}-${++id}.component.html`;
|
|
126
|
-
const start = templateProperty.initializer.getStart();
|
|
127
|
-
const end = templateProperty.initializer.getEnd();
|
|
128
|
-
rangeMap.set(inlineTemplateTmpFilename, {
|
|
129
|
-
range: [start, end],
|
|
130
|
-
lineAndCharacter: {
|
|
131
|
-
start: sourceFile.getLineAndCharacterOfPosition(start),
|
|
132
|
-
end: sourceFile.getLineAndCharacterOfPosition(end),
|
|
133
|
-
},
|
|
134
|
-
});
|
|
135
|
-
/**
|
|
136
|
-
* We are ultimately returning an array containing both the original source,
|
|
137
|
-
* and a new fragment representing each of the inline HTML templates found.
|
|
138
|
-
* Each fragment must have an appropriate .html extension so that it can be
|
|
139
|
-
* linted using the right rules and plugins.
|
|
140
|
-
*
|
|
141
|
-
* The postprocessor will handle tying things back to the right position
|
|
142
|
-
* in the original file, so this temporary filename will never be visible
|
|
143
|
-
* to the end user.
|
|
144
|
-
*/
|
|
145
|
-
result.push({
|
|
146
|
-
text: templateText,
|
|
147
|
-
filename: inlineTemplateTmpFilename,
|
|
148
|
-
});
|
|
149
|
-
}
|
|
150
|
-
return result;
|
|
151
|
-
}
|
|
152
|
-
catch (err) {
|
|
153
|
-
console.log(err);
|
|
154
|
-
console.error('preprocess: ERROR could not parse @Component() metadata', filename);
|
|
155
|
-
return noopResult;
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
function getClassDeclarationFromSourceFile(sourceFile) {
|
|
159
|
-
const classDeclarations = [];
|
|
160
|
-
visit(sourceFile);
|
|
161
|
-
return classDeclarations;
|
|
162
|
-
function visit(node) {
|
|
163
|
-
if (typescript_1.default.isClassDeclaration(node)) {
|
|
164
|
-
classDeclarations.push(node);
|
|
165
|
-
return;
|
|
166
|
-
}
|
|
167
|
-
// Class declarations are usually at the top-level, but there are
|
|
168
|
-
// some situations where they might be nested, such as in test files.
|
|
169
|
-
// If the node could have a class declaration somewhere in its
|
|
170
|
-
// descendant nodes, then we will recurse down into each child node.
|
|
171
|
-
// Keywords, tokens and trivia all come before `FirstNode`. They won't
|
|
172
|
-
// contain child nodes anyway, but we can skip them to save some time.
|
|
173
|
-
// Likewise, we can skip nodes that are part of JSDoc comments.
|
|
174
|
-
if (node.kind < typescript_1.default.SyntaxKind.FirstNode ||
|
|
175
|
-
node.kind > typescript_1.default.SyntaxKind.FirstJSDocNode) {
|
|
176
|
-
return;
|
|
177
|
-
}
|
|
178
|
-
// Type nodes can be skipped.
|
|
179
|
-
if (node.kind >= typescript_1.default.SyntaxKind.TypePredicate &&
|
|
180
|
-
node.kind <= typescript_1.default.SyntaxKind.ImportType) {
|
|
181
|
-
return;
|
|
182
|
-
}
|
|
183
|
-
// Some specific kinds of nodes can be skipped because
|
|
184
|
-
// we know that they cannot contain class declarations.
|
|
185
|
-
switch (node.kind) {
|
|
186
|
-
case typescript_1.default.SyntaxKind.InterfaceDeclaration:
|
|
187
|
-
case typescript_1.default.SyntaxKind.EnumDeclaration:
|
|
188
|
-
case typescript_1.default.SyntaxKind.ImportEqualsDeclaration:
|
|
189
|
-
case typescript_1.default.SyntaxKind.ImportDeclaration:
|
|
190
|
-
case typescript_1.default.SyntaxKind.ImportClause:
|
|
191
|
-
return;
|
|
192
|
-
}
|
|
193
|
-
// For everything else, we'll play it safe
|
|
194
|
-
// and recurse down into the child nodes.
|
|
195
|
-
typescript_1.default.forEachChild(node, visit);
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
function postprocessComponentFile(multiDimensionalMessages, filename) {
|
|
199
|
-
const messagesFromComponentSource = multiDimensionalMessages[0];
|
|
200
|
-
/**
|
|
201
|
-
* If the Component did not have one or more inline templates defined within it
|
|
202
|
-
* there will only be one item in the multiDimensionalMessages
|
|
203
|
-
*/
|
|
204
|
-
if (multiDimensionalMessages.length === 1) {
|
|
205
|
-
return messagesFromComponentSource;
|
|
206
|
-
}
|
|
207
|
-
/**
|
|
208
|
-
* There could be multiple inline templates found within the current file,
|
|
209
|
-
* so they are represented by all of the multiDimensionalMessages after the
|
|
210
|
-
* first one (which is the file itself)
|
|
211
|
-
*/
|
|
212
|
-
const messagesFromAllInlineTemplateHTML = multiDimensionalMessages.slice(1);
|
|
213
|
-
/**
|
|
214
|
-
* Adjust message location data to apply it back to the
|
|
215
|
-
* original file
|
|
216
|
-
*/
|
|
217
|
-
const res = [
|
|
218
|
-
...messagesFromComponentSource,
|
|
219
|
-
...messagesFromAllInlineTemplateHTML.flatMap((messagesFromInlineTemplateHTML, i) => {
|
|
220
|
-
const baseFilename = (0, path_1.basename)(filename);
|
|
221
|
-
const inlineTemplateTmpFilename = `inline-template-${baseFilename}-${i + 1}.component.html`;
|
|
222
|
-
const rangeData = rangeMap.get(inlineTemplateTmpFilename);
|
|
223
|
-
if (!rangeData) {
|
|
224
|
-
return [];
|
|
225
|
-
}
|
|
226
|
-
return messagesFromInlineTemplateHTML.map((message) => {
|
|
227
|
-
// The first line of the inline template starts at the column after
|
|
228
|
-
// the opening quote in the TypeScript file, so we need to adjust
|
|
229
|
-
// the message's column by that amount when the message starts on
|
|
230
|
-
// the first line. The character we recorded was the quote's column,
|
|
231
|
-
// so add one to get the column where the actual string starts.
|
|
232
|
-
if (message.line === 1) {
|
|
233
|
-
message.column += rangeData.lineAndCharacter.start.character + 1;
|
|
234
|
-
}
|
|
235
|
-
// The same thing applies to the end column
|
|
236
|
-
// if it also ends on the first line.
|
|
237
|
-
if (message.endLine === 1) {
|
|
238
|
-
message.endColumn += rangeData.lineAndCharacter.start.character + 1;
|
|
239
|
-
}
|
|
240
|
-
message.line += rangeData.lineAndCharacter.start.line;
|
|
241
|
-
message.endLine += rangeData.lineAndCharacter.start.line;
|
|
242
|
-
if (message.fix) {
|
|
243
|
-
// The range defines the range of the value that initializes
|
|
244
|
-
// the `template` property, which includes the opening and
|
|
245
|
-
// closing quotes. Add one to move past the opening quote.
|
|
246
|
-
const startOffset = rangeData.range[0] + 1;
|
|
247
|
-
message.fix.range = [
|
|
248
|
-
startOffset + message.fix.range[0],
|
|
249
|
-
startOffset + message.fix.range[1],
|
|
250
|
-
];
|
|
251
|
-
}
|
|
252
|
-
return message;
|
|
253
|
-
});
|
|
254
|
-
}),
|
|
255
|
-
];
|
|
256
|
-
return res;
|
|
257
|
-
}
|
|
258
|
-
exports.default = {
|
|
3
|
+
exports.postprocessComponentFile = exports.preprocessComponentFile = exports.isFileLikelyToContainComponentDeclarations = void 0;
|
|
4
|
+
exports.createProcessor = createProcessor;
|
|
5
|
+
const inline_templates_1 = require("./inline-templates");
|
|
6
|
+
const inline_styles_1 = require("./inline-styles");
|
|
7
|
+
var inline_templates_2 = require("./inline-templates");
|
|
8
|
+
Object.defineProperty(exports, "isFileLikelyToContainComponentDeclarations", { enumerable: true, get: function () { return inline_templates_2.isFileLikelyToContainComponentDeclarations; } });
|
|
9
|
+
Object.defineProperty(exports, "preprocessComponentFile", { enumerable: true, get: function () { return inline_templates_2.preprocessComponentFile; } });
|
|
10
|
+
Object.defineProperty(exports, "postprocessComponentFile", { enumerable: true, get: function () { return inline_templates_2.postprocessComponentFile; } });
|
|
11
|
+
const processors = {
|
|
259
12
|
'extract-inline-html': {
|
|
260
13
|
meta: {
|
|
261
14
|
name: 'extract-inline-html',
|
|
262
15
|
},
|
|
263
|
-
preprocess: preprocessComponentFile,
|
|
264
|
-
postprocess: postprocessComponentFile,
|
|
16
|
+
preprocess: inline_templates_1.preprocessComponentFile,
|
|
17
|
+
postprocess: inline_templates_1.postprocessComponentFile,
|
|
265
18
|
supportsAutofix: true,
|
|
266
19
|
},
|
|
267
20
|
};
|
|
21
|
+
const configuredProcessors = new Map();
|
|
22
|
+
function createProcessor({ inlineTemplates = true, inlineStyles = false, } = {}) {
|
|
23
|
+
if (typeof inlineTemplates !== 'boolean') {
|
|
24
|
+
throw new Error('inlineTemplates must be a boolean');
|
|
25
|
+
}
|
|
26
|
+
if (inlineStyles === false) {
|
|
27
|
+
if (!inlineTemplates) {
|
|
28
|
+
throw new Error('inlineTemplates and inlineStyles cannot both be disabled');
|
|
29
|
+
}
|
|
30
|
+
return processors['extract-inline-html'];
|
|
31
|
+
}
|
|
32
|
+
if (!inlineStyles ||
|
|
33
|
+
typeof inlineStyles !== 'object' ||
|
|
34
|
+
!inlineStyles.inlineStyleLanguage) {
|
|
35
|
+
throw new Error("inlineStyles requires inlineStyleLanguage (copy it from angular.json; Angular's default is 'css')");
|
|
36
|
+
}
|
|
37
|
+
const { inlineStyleLanguage } = inlineStyles;
|
|
38
|
+
if (!inline_styles_1.inlineStyleLanguages.includes(inlineStyleLanguage)) {
|
|
39
|
+
throw new Error(`createProcessor: unsupported inlineStyleLanguage "${inlineStyleLanguage}", expected one of: ${inline_styles_1.inlineStyleLanguages.join(', ')}`);
|
|
40
|
+
}
|
|
41
|
+
const name = `extract-inline[${inlineTemplates ? 'templates,' : ''}styles=${inlineStyleLanguage}]`;
|
|
42
|
+
let processor = configuredProcessors.get(name);
|
|
43
|
+
if (!processor) {
|
|
44
|
+
processor = (0, inline_styles_1.createInlineStylesProcessor)(inlineStyleLanguage, inlineTemplates);
|
|
45
|
+
configuredProcessors.set(name, processor);
|
|
46
|
+
}
|
|
47
|
+
return processor;
|
|
48
|
+
}
|
|
49
|
+
exports.default = processors;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"eqeqeq.d.ts","sourceRoot":"","sources":["../../src/rules/eqeqeq.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AAQzD,MAAM,MAAM,OAAO,GAAG,CAAC;IAAE,QAAQ,CAAC,oBAAoB,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC,CAAC;AACpE,MAAM,MAAM,UAAU,GAAG,QAAQ,GAAG,uBAAuB,CAAC;AAC5D,eAAO,MAAM,SAAS,WAAW,CAAC;;;;AAGlC,wBA4EG;
|
|
1
|
+
{"version":3,"file":"eqeqeq.d.ts","sourceRoot":"","sources":["../../src/rules/eqeqeq.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AAQzD,MAAM,MAAM,OAAO,GAAG,CAAC;IAAE,QAAQ,CAAC,oBAAoB,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC,CAAC;AACpE,MAAM,MAAM,UAAU,GAAG,QAAQ,GAAG,uBAAuB,CAAC;AAC5D,eAAO,MAAM,SAAS,WAAW,CAAC;;;;AAGlC,wBA4EG;AAgFH,eAAO,MAAM,mBAAmB;;CAG/B,CAAC"}
|
package/dist/rules/eqeqeq.js
CHANGED
|
@@ -79,12 +79,14 @@ function getSpanLength({ span: { start, end } }) {
|
|
|
79
79
|
}
|
|
80
80
|
const getFix = ({ node, right, end, sourceCode, fixer, }) => {
|
|
81
81
|
const result = (0, get_nearest_node_from_1.getNearestNodeFrom)(node, isASTWithSource);
|
|
82
|
-
if (!result?.source)
|
|
83
|
-
return null;
|
|
84
|
-
const { source, ast } = result;
|
|
85
82
|
let startOffset = 0;
|
|
86
|
-
|
|
87
|
-
|
|
83
|
+
if (result) {
|
|
84
|
+
const { source, ast } = result;
|
|
85
|
+
if (!source)
|
|
86
|
+
return null;
|
|
87
|
+
while (!isInterpolation(ast) && isLeadingTriviaChar(source[startOffset])) {
|
|
88
|
+
startOffset++;
|
|
89
|
+
}
|
|
88
90
|
}
|
|
89
91
|
const endRange = end - startOffset - getSpanLength(right) - 1;
|
|
90
92
|
let eqOffset = 0;
|
package/dist/rules/i18n.js
CHANGED
|
@@ -268,7 +268,7 @@ exports.default = (0, create_eslint_rule_1.createESLintRule)({
|
|
|
268
268
|
if (checkDuplicateId) {
|
|
269
269
|
for (const [customId, sourceSpans] of collectedCustomIds) {
|
|
270
270
|
if (sourceSpans.length <= 1) {
|
|
271
|
-
|
|
271
|
+
continue;
|
|
272
272
|
}
|
|
273
273
|
for (const sourceSpan of sourceSpans) {
|
|
274
274
|
const loc = parserServices.convertNodeSourceSpanToLoc(sourceSpan);
|
|
@@ -34,5 +34,5 @@ exports.default = (0, create_eslint_rule_1.createESLintRule)({
|
|
|
34
34
|
},
|
|
35
35
|
});
|
|
36
36
|
exports.RULE_DOCS_EXTENSION = {
|
|
37
|
-
rationale: "Elements like
|
|
37
|
+
rationale: "Elements like `<marquee>` and `<blink>` create distracting motion that can cause problems for users with attention disorders, vestibular disorders, or seizure disorders. Moving text is harder to read for everyone, including users with dyslexia or cognitive disabilities. These elements are also deprecated in HTML5 and not well-supported across browsers. WCAG guidelines require that moving, blinking, or scrolling content can be paused, stopped, or hidden by users. Instead of these elements, use modern CSS animations with appropriate controls, or simply use static content that doesn't distract from the main page content.",
|
|
38
38
|
};
|