wrec 0.40.3 → 0.42.0
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 +9 -15
- package/dist/{wrec-DHGadgxK.js → wrec-RkviIlbF.js} +17 -8
- package/dist/wrec-ssr.d.ts +1 -0
- package/dist/wrec-ssr.es.js +3 -3
- package/dist/wrec.d.ts +1 -0
- package/dist/wrec.es.js +1 -1
- package/package.json +39 -36
- package/scripts/ast-utils.js +20 -28
- package/scripts/declare.js +41 -62
- package/scripts/lint.js +609 -927
- package/scripts/scaffold.js +11 -13
- package/scripts/used-by.js +76 -119
package/scripts/lint.js
CHANGED
|
@@ -37,16 +37,16 @@
|
|
|
37
37
|
// - checkbox checked bindings that do not reference Boolean properties
|
|
38
38
|
// - radio checked bindings that do not reference String properties
|
|
39
39
|
|
|
40
|
-
import fs from
|
|
41
|
-
import path from
|
|
42
|
-
import ts from
|
|
43
|
-
import {parse} from
|
|
40
|
+
import fs from "node:fs";
|
|
41
|
+
import path from "node:path";
|
|
42
|
+
import ts from "typescript";
|
|
43
|
+
import { parse } from "node-html-parser";
|
|
44
44
|
import {
|
|
45
45
|
collectWrecClasses,
|
|
46
46
|
getMemberName,
|
|
47
47
|
getPropertyAssignmentNames,
|
|
48
|
-
hasStaticModifier
|
|
49
|
-
} from
|
|
48
|
+
hasStaticModifier,
|
|
49
|
+
} from "./ast-utils.js";
|
|
50
50
|
|
|
51
51
|
// Regular expressions
|
|
52
52
|
const CSS_PROPERTY_RE = /([a-zA-Z-]+)\s*:\s*([^;}]+)/g;
|
|
@@ -56,107 +56,95 @@ const REFS_TEST_RE = /this\.[a-zA-Z_$][\w$]*(\.[a-zA-Z_$][\w$]*)*/;
|
|
|
56
56
|
const THIS_CALL_RE = /this\.([A-Za-z_$][\w$]*)\s*\(/g;
|
|
57
57
|
const THIS_REF_RE = /this\.([A-Za-z_$][\w$]*)(\.[A-Za-z_$][\w$]*)*/g;
|
|
58
58
|
|
|
59
|
-
const GETTER_PREFIX =
|
|
59
|
+
const GETTER_PREFIX = "get ";
|
|
60
60
|
const HTML_ALLOWED_CHILDREN = new Map([
|
|
61
|
-
[
|
|
62
|
-
[
|
|
63
|
-
[
|
|
64
|
-
[
|
|
65
|
-
[
|
|
66
|
-
[
|
|
61
|
+
["select", new Set(["option"])],
|
|
62
|
+
["table", new Set(["tbody", "thead", "tr"])],
|
|
63
|
+
["tbody", new Set(["tr"])],
|
|
64
|
+
["thead", new Set(["tr"])],
|
|
65
|
+
["tr", new Set(["td", "th"])],
|
|
66
|
+
["ul", new Set(["li"])],
|
|
67
67
|
]);
|
|
68
68
|
const HTML_ALLOWED_PARENTS = new Map([
|
|
69
|
-
[
|
|
70
|
-
[
|
|
71
|
-
[
|
|
72
|
-
[
|
|
73
|
-
[
|
|
74
|
-
[
|
|
75
|
-
[
|
|
76
|
-
[
|
|
69
|
+
["legend", new Set(["fieldset"])],
|
|
70
|
+
["li", new Set(["ol", "ul"])],
|
|
71
|
+
["option", new Set(["select"])],
|
|
72
|
+
["tbody", new Set(["table"])],
|
|
73
|
+
["td", new Set(["tr"])],
|
|
74
|
+
["th", new Set(["tr"])],
|
|
75
|
+
["thead", new Set(["table"])],
|
|
76
|
+
["tr", new Set(["table", "tbody", "thead"])],
|
|
77
77
|
]);
|
|
78
78
|
const HTML_GLOBAL_ATTRIBUTES = new Set([
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
79
|
+
"aria-label",
|
|
80
|
+
"class",
|
|
81
|
+
"disabled",
|
|
82
|
+
"hidden",
|
|
83
|
+
"id",
|
|
84
|
+
"part",
|
|
85
|
+
"role",
|
|
86
|
+
"slot",
|
|
87
|
+
"style",
|
|
88
|
+
"tabindex",
|
|
89
|
+
"title",
|
|
90
90
|
]);
|
|
91
91
|
const HTML_TAG_ATTRIBUTES = new Map([
|
|
92
|
-
[
|
|
93
|
-
[
|
|
94
|
-
[
|
|
95
|
-
[
|
|
96
|
-
[
|
|
97
|
-
[
|
|
98
|
-
[
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
],
|
|
111
|
-
[
|
|
112
|
-
[
|
|
113
|
-
[
|
|
114
|
-
['option', new Set(['label', 'selected', 'value'])],
|
|
115
|
-
['p', new Set([])],
|
|
116
|
-
['select', new Set(['multiple', 'name', 'value'])],
|
|
117
|
-
['span', new Set([])],
|
|
118
|
-
['table', new Set([])],
|
|
119
|
-
['tbody', new Set([])],
|
|
120
|
-
['td', new Set(['colspan', 'rowspan'])],
|
|
121
|
-
['textarea', new Set(['name', 'placeholder', 'rows', 'value'])],
|
|
122
|
-
['th', new Set(['colspan', 'rowspan', 'scope'])],
|
|
123
|
-
['thead', new Set([])],
|
|
124
|
-
['tr', new Set([])],
|
|
125
|
-
['ul', new Set([])]
|
|
92
|
+
["a", new Set(["href", "rel", "target"])],
|
|
93
|
+
["button", new Set(["name", "type", "value"])],
|
|
94
|
+
["div", new Set([])],
|
|
95
|
+
["fieldset", new Set(["name"])],
|
|
96
|
+
["form", new Set(["action", "method", "name"])],
|
|
97
|
+
["img", new Set(["alt", "height", "src", "width"])],
|
|
98
|
+
["input", new Set(["checked", "max", "min", "name", "placeholder", "step", "type", "value"])],
|
|
99
|
+
["label", new Set(["for"])],
|
|
100
|
+
["legend", new Set([])],
|
|
101
|
+
["li", new Set(["value"])],
|
|
102
|
+
["option", new Set(["label", "selected", "value"])],
|
|
103
|
+
["p", new Set([])],
|
|
104
|
+
["select", new Set(["multiple", "name", "value"])],
|
|
105
|
+
["span", new Set([])],
|
|
106
|
+
["table", new Set([])],
|
|
107
|
+
["tbody", new Set([])],
|
|
108
|
+
["td", new Set(["colspan", "rowspan"])],
|
|
109
|
+
["textarea", new Set(["name", "placeholder", "rows", "value"])],
|
|
110
|
+
["th", new Set(["colspan", "rowspan", "scope"])],
|
|
111
|
+
["thead", new Set([])],
|
|
112
|
+
["tr", new Set([])],
|
|
113
|
+
["ul", new Set([])],
|
|
126
114
|
]);
|
|
127
|
-
const NATIVE_FORM_CONTROL_TAGS = new Set([
|
|
128
|
-
const PLACEHOLDER_PREFIX =
|
|
129
|
-
const RESERVED_PROPERTY_NAMES = new Set([
|
|
115
|
+
const NATIVE_FORM_CONTROL_TAGS = new Set(["input", "select", "textarea"]);
|
|
116
|
+
const PLACEHOLDER_PREFIX = "__WREC_PLACEHOLDER__";
|
|
117
|
+
const RESERVED_PROPERTY_NAMES = new Set(["class", "style"]);
|
|
130
118
|
const SUPPORTED_EVENT_NAMES = new Set([
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
119
|
+
"blur",
|
|
120
|
+
"change",
|
|
121
|
+
"click",
|
|
122
|
+
"dblclick",
|
|
123
|
+
"focus",
|
|
124
|
+
"focusin",
|
|
125
|
+
"focusout",
|
|
126
|
+
"input",
|
|
127
|
+
"keydown",
|
|
128
|
+
"keypress",
|
|
129
|
+
"keyup",
|
|
130
|
+
"mousedown",
|
|
131
|
+
"mouseenter",
|
|
132
|
+
"mouseleave",
|
|
133
|
+
"mousemove",
|
|
134
|
+
"mouseout",
|
|
135
|
+
"mouseover",
|
|
136
|
+
"mouseup",
|
|
137
|
+
"paste",
|
|
150
138
|
]);
|
|
151
139
|
const SUPPORTED_PROPERTY_TYPE_NAMES = new Set([
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
140
|
+
"Array",
|
|
141
|
+
"Boolean",
|
|
142
|
+
"HTMLElement",
|
|
143
|
+
"Number",
|
|
144
|
+
"Object",
|
|
145
|
+
"String",
|
|
158
146
|
]);
|
|
159
|
-
const WREC_REF_NAME =
|
|
147
|
+
const WREC_REF_NAME = "__wrec";
|
|
160
148
|
|
|
161
149
|
const componentPropertyCache = new Map();
|
|
162
150
|
|
|
@@ -169,10 +157,10 @@ function analyzeCodeNode(codeNode, checker, classNode, findings, metadata) {
|
|
|
169
157
|
findings.invalidEventHandlers,
|
|
170
158
|
metadata.location
|
|
171
159
|
? {
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
: `"${codeNode.text}" is not a defined instance method
|
|
160
|
+
location: metadata.location,
|
|
161
|
+
message: `"${codeNode.text}" is not a defined instance method`,
|
|
162
|
+
}
|
|
163
|
+
: `"${codeNode.text}" is not a defined instance method`,
|
|
176
164
|
);
|
|
177
165
|
}
|
|
178
166
|
}
|
|
@@ -187,12 +175,12 @@ function analyzeCodeNode(codeNode, checker, classNode, findings, metadata) {
|
|
|
187
175
|
if (isCallCallee(node)) {
|
|
188
176
|
uniquePush(
|
|
189
177
|
findings.undefinedMethods,
|
|
190
|
-
metadata.location ? {location: metadata.location, message: name} : name
|
|
178
|
+
metadata.location ? { location: metadata.location, message: name } : name,
|
|
191
179
|
);
|
|
192
180
|
} else {
|
|
193
181
|
uniquePush(
|
|
194
182
|
findings.undefinedProperties,
|
|
195
|
-
metadata.location ? {location: metadata.location, message: name} : name
|
|
183
|
+
metadata.location ? { location: metadata.location, message: name } : name,
|
|
196
184
|
);
|
|
197
185
|
}
|
|
198
186
|
}
|
|
@@ -207,33 +195,27 @@ function analyzeCodeNode(codeNode, checker, classNode, findings, metadata) {
|
|
|
207
195
|
uniquePush(
|
|
208
196
|
findings.undefinedContextFunctions,
|
|
209
197
|
metadata.location
|
|
210
|
-
? {location: metadata.location, message: callee.text}
|
|
211
|
-
: callee.text
|
|
198
|
+
? { location: metadata.location, message: callee.text }
|
|
199
|
+
: callee.text,
|
|
212
200
|
);
|
|
213
201
|
}
|
|
214
202
|
}
|
|
215
|
-
} else if (
|
|
216
|
-
ts.isPropertyAccessExpression(callee) &&
|
|
217
|
-
isWrecRooted(callee.expression)
|
|
218
|
-
) {
|
|
203
|
+
} else if (ts.isPropertyAccessExpression(callee) && isWrecRooted(callee.expression)) {
|
|
219
204
|
const ownerType = checker.getTypeAtLocation(callee.expression);
|
|
220
205
|
const symbol = ownerType.getProperty(callee.name.text);
|
|
221
206
|
if (!symbol) {
|
|
222
207
|
uniquePush(
|
|
223
208
|
findings.undefinedMethods,
|
|
224
209
|
metadata.location
|
|
225
|
-
? {location: metadata.location, message: callee.name.text}
|
|
226
|
-
: callee.name.text
|
|
210
|
+
? { location: metadata.location, message: callee.name.text }
|
|
211
|
+
: callee.name.text,
|
|
227
212
|
);
|
|
228
213
|
}
|
|
229
214
|
}
|
|
230
215
|
|
|
231
216
|
const signature =
|
|
232
217
|
checker.getResolvedSignature(node) ??
|
|
233
|
-
checker.getSignaturesOfType(
|
|
234
|
-
checker.getTypeAtLocation(callee),
|
|
235
|
-
ts.SignatureKind.Call
|
|
236
|
-
)[0];
|
|
218
|
+
checker.getSignaturesOfType(checker.getTypeAtLocation(callee), ts.SignatureKind.Call)[0];
|
|
237
219
|
|
|
238
220
|
if (signature) {
|
|
239
221
|
const parameters = signature.getParameters();
|
|
@@ -242,10 +224,7 @@ function analyzeCodeNode(codeNode, checker, classNode, findings, metadata) {
|
|
|
242
224
|
declaration &&
|
|
243
225
|
ts.isFunctionLike(declaration) &&
|
|
244
226
|
declaration.parameters.length > 0 &&
|
|
245
|
-
Boolean(
|
|
246
|
-
declaration.parameters[declaration.parameters.length - 1]
|
|
247
|
-
.dotDotDotToken
|
|
248
|
-
);
|
|
227
|
+
Boolean(declaration.parameters[declaration.parameters.length - 1].dotDotDotToken);
|
|
249
228
|
|
|
250
229
|
if (!isRest && node.arguments.length > parameters.length) {
|
|
251
230
|
node.arguments.slice(parameters.length).forEach((argument, index) => {
|
|
@@ -254,7 +233,7 @@ function analyzeCodeNode(codeNode, checker, classNode, findings, metadata) {
|
|
|
254
233
|
argumentIndex: parameters.length + index + 1,
|
|
255
234
|
location: metadata.location ?? null,
|
|
256
235
|
methodName: toUserFacingExpression(callee.getText()),
|
|
257
|
-
parameterCount: parameters.length
|
|
236
|
+
parameterCount: parameters.length,
|
|
258
237
|
});
|
|
259
238
|
});
|
|
260
239
|
}
|
|
@@ -262,9 +241,7 @@ function analyzeCodeNode(codeNode, checker, classNode, findings, metadata) {
|
|
|
262
241
|
node.arguments.forEach((argument, index) => {
|
|
263
242
|
let parameterSymbol = parameters[index];
|
|
264
243
|
let isRestArgument =
|
|
265
|
-
Boolean(isRest) &&
|
|
266
|
-
parameters.length > 0 &&
|
|
267
|
-
index >= parameters.length - 1;
|
|
244
|
+
Boolean(isRest) && parameters.length > 0 && index >= parameters.length - 1;
|
|
268
245
|
if (!parameterSymbol && isRest && parameters.length > 0) {
|
|
269
246
|
parameterSymbol = parameters[parameters.length - 1];
|
|
270
247
|
}
|
|
@@ -275,7 +252,7 @@ function analyzeCodeNode(codeNode, checker, classNode, findings, metadata) {
|
|
|
275
252
|
checker,
|
|
276
253
|
parameterSymbol,
|
|
277
254
|
declaration ?? classNode,
|
|
278
|
-
isRestArgument
|
|
255
|
+
isRestArgument,
|
|
279
256
|
);
|
|
280
257
|
if (!checker.isTypeAssignableTo(argumentType, parameterType)) {
|
|
281
258
|
findings.incompatibleArguments.push({
|
|
@@ -284,17 +261,14 @@ function analyzeCodeNode(codeNode, checker, classNode, findings, metadata) {
|
|
|
284
261
|
location: metadata.location ?? null,
|
|
285
262
|
methodName: toUserFacingExpression(callee.getText()),
|
|
286
263
|
parameterName: parameterSymbol.getName(),
|
|
287
|
-
parameterType: checker.typeToString(parameterType)
|
|
264
|
+
parameterType: checker.typeToString(parameterType),
|
|
288
265
|
});
|
|
289
266
|
}
|
|
290
267
|
});
|
|
291
268
|
}
|
|
292
269
|
}
|
|
293
270
|
|
|
294
|
-
if (
|
|
295
|
-
ts.isBinaryExpression(node) &&
|
|
296
|
-
isArithmeticOperator(node.operatorToken.kind)
|
|
297
|
-
) {
|
|
271
|
+
if (ts.isBinaryExpression(node) && isArithmeticOperator(node.operatorToken.kind)) {
|
|
298
272
|
const leftType = checker.getTypeAtLocation(node.left);
|
|
299
273
|
const rightType = checker.getTypeAtLocation(node.right);
|
|
300
274
|
|
|
@@ -305,7 +279,7 @@ function analyzeCodeNode(codeNode, checker, classNode, findings, metadata) {
|
|
|
305
279
|
message:
|
|
306
280
|
`left operand "${toUserFacingExpression(node.left.getText())}" ` +
|
|
307
281
|
`has type ${checker.typeToString(leftType)}, ` +
|
|
308
|
-
|
|
282
|
+
"but arithmetic operators require number",
|
|
309
283
|
});
|
|
310
284
|
}
|
|
311
285
|
|
|
@@ -316,7 +290,7 @@ function analyzeCodeNode(codeNode, checker, classNode, findings, metadata) {
|
|
|
316
290
|
message:
|
|
317
291
|
`right operand "${toUserFacingExpression(node.right.getText())}" ` +
|
|
318
292
|
`has type ${checker.typeToString(rightType)}, ` +
|
|
319
|
-
|
|
293
|
+
"but arithmetic operators require number",
|
|
320
294
|
});
|
|
321
295
|
}
|
|
322
296
|
}
|
|
@@ -333,34 +307,27 @@ function analyzeCodeNode(codeNode, checker, classNode, findings, metadata) {
|
|
|
333
307
|
// can be analyzed as if it were normal code. This gives TypeScript
|
|
334
308
|
// enough // context to understand available properties, methods, and
|
|
335
309
|
// context functions when the linter validates those expressions.
|
|
336
|
-
function buildAugmentedSource(
|
|
337
|
-
sourceFile,
|
|
338
|
-
classNode,
|
|
339
|
-
supportedProps,
|
|
340
|
-
contextKeys,
|
|
341
|
-
codeItems
|
|
342
|
-
) {
|
|
310
|
+
function buildAugmentedSource(sourceFile, classNode, supportedProps, contextKeys, codeItems) {
|
|
343
311
|
const propLines = [];
|
|
344
312
|
for (const [name, info] of supportedProps.entries()) {
|
|
345
313
|
propLines.push(` ${JSON.stringify(name)}: ${info.typeText};`);
|
|
346
314
|
}
|
|
347
315
|
|
|
348
316
|
const contextLine = contextKeys.length
|
|
349
|
-
? `const {${contextKeys.join(
|
|
350
|
-
:
|
|
317
|
+
? `const {${contextKeys.join(", ")}} = ${classNode.name.text}.context;`
|
|
318
|
+
: "";
|
|
351
319
|
|
|
352
320
|
const helperBlocks = codeItems.map((item, index) => {
|
|
353
321
|
const targetType =
|
|
354
|
-
item.context ===
|
|
322
|
+
item.context === "static"
|
|
355
323
|
? `typeof ${classNode.name.text}`
|
|
356
324
|
: `${classNode.name.text} & __WrecSupportedProps`;
|
|
357
325
|
const rewrittenText = item.text.replace(/\bthis\b/g, WREC_REF_NAME);
|
|
358
|
-
const helperBody =
|
|
359
|
-
item.shape === 'block' ? rewrittenText : `return (${rewrittenText});`;
|
|
326
|
+
const helperBody = item.shape === "block" ? rewrittenText : `return (${rewrittenText});`;
|
|
360
327
|
return `
|
|
361
328
|
function __wrec_expr_${index}() {
|
|
362
329
|
const ${WREC_REF_NAME} = null as unknown as ${targetType};
|
|
363
|
-
${item.checkContextCalls ? contextLine :
|
|
330
|
+
${item.checkContextCalls ? contextLine : ""}
|
|
364
331
|
${helperBody}
|
|
365
332
|
}
|
|
366
333
|
`;
|
|
@@ -368,11 +335,11 @@ function __wrec_expr_${index}() {
|
|
|
368
335
|
|
|
369
336
|
const propInterface = `
|
|
370
337
|
type __WrecSupportedProps = {
|
|
371
|
-
${propLines.join(
|
|
338
|
+
${propLines.join("\n")}
|
|
372
339
|
};
|
|
373
340
|
`;
|
|
374
341
|
|
|
375
|
-
return `${sourceFile.text}\n${propInterface}\n${helperBlocks.join(
|
|
342
|
+
return `${sourceFile.text}\n${propInterface}\n${helperBlocks.join("\n")}`;
|
|
376
343
|
}
|
|
377
344
|
|
|
378
345
|
// Collects all instance method and accessor names defined in a component class.
|
|
@@ -427,10 +394,7 @@ function collectHelperCodeNodes(augmentedSourceFile) {
|
|
|
427
394
|
// Finds generated helper functions and
|
|
428
395
|
// stores their bodies by index.
|
|
429
396
|
function visit(node) {
|
|
430
|
-
if (
|
|
431
|
-
ts.isFunctionDeclaration(node) &&
|
|
432
|
-
node.name?.text.startsWith('__wrec_expr_')
|
|
433
|
-
) {
|
|
397
|
+
if (ts.isFunctionDeclaration(node) && node.name?.text.startsWith("__wrec_expr_")) {
|
|
434
398
|
const match = node.name.text.match(/(\d+)$/);
|
|
435
399
|
const index = match ? Number(match[1]) : -1;
|
|
436
400
|
if (index >= 0 && node.body) helpers[index] = node.body;
|
|
@@ -455,19 +419,17 @@ function collectMethodCodeItems(classNode) {
|
|
|
455
419
|
ts.isSetAccessorDeclaration(member)
|
|
456
420
|
) {
|
|
457
421
|
if (!member.body) continue;
|
|
458
|
-
if (!member.body.getText().includes(
|
|
422
|
+
if (!member.body.getText().includes("this.")) continue;
|
|
459
423
|
codeItems.push({
|
|
460
424
|
checkContextCalls: false,
|
|
461
|
-
context:
|
|
425
|
+
context: "instance",
|
|
462
426
|
eventHandler: false,
|
|
463
|
-
kind:
|
|
427
|
+
kind: "method",
|
|
464
428
|
location: member
|
|
465
429
|
.getSourceFile()
|
|
466
|
-
.getLineAndCharacterOfPosition(
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
shape: 'block',
|
|
470
|
-
text: member.body.getText()
|
|
430
|
+
.getLineAndCharacterOfPosition(member.name.getStart(member.getSourceFile())),
|
|
431
|
+
shape: "block",
|
|
432
|
+
text: member.body.getText(),
|
|
471
433
|
});
|
|
472
434
|
}
|
|
473
435
|
}
|
|
@@ -486,7 +448,7 @@ function collectSupportedPropertyNames(classNode) {
|
|
|
486
448
|
|
|
487
449
|
const name = getMemberName(member);
|
|
488
450
|
if (
|
|
489
|
-
name !==
|
|
451
|
+
name !== "properties" ||
|
|
490
452
|
!member.initializer ||
|
|
491
453
|
!ts.isObjectLiteralExpression(member.initializer)
|
|
492
454
|
) {
|
|
@@ -524,7 +486,7 @@ function collectUseStateMapErrors(classNode, supportedProps, findings) {
|
|
|
524
486
|
ts.isCallExpression(node) &&
|
|
525
487
|
ts.isPropertyAccessExpression(node.expression) &&
|
|
526
488
|
ts.isThis(node.expression.expression) &&
|
|
527
|
-
node.expression.name.text ===
|
|
489
|
+
node.expression.name.text === "useState"
|
|
528
490
|
) {
|
|
529
491
|
const mapArg = node.arguments[1];
|
|
530
492
|
if (mapArg && ts.isObjectLiteralExpression(mapArg)) {
|
|
@@ -541,16 +503,12 @@ function collectUseStateMapErrors(classNode, supportedProps, findings) {
|
|
|
541
503
|
|
|
542
504
|
const componentProp = property.initializer.text;
|
|
543
505
|
if (!supportedProps.has(componentProp)) {
|
|
544
|
-
findings.invalidUseStateMaps.push(
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
`useState maps state property "${statePath}" to ` +
|
|
551
|
-
`missing component property "${componentProp}"`
|
|
552
|
-
}
|
|
553
|
-
);
|
|
506
|
+
findings.invalidUseStateMaps.push({
|
|
507
|
+
location: node.getSourceFile().getLineAndCharacterOfPosition(property.getStart()),
|
|
508
|
+
message:
|
|
509
|
+
`useState maps state property "${statePath}" to ` +
|
|
510
|
+
`missing component property "${componentProp}"`,
|
|
511
|
+
});
|
|
554
512
|
}
|
|
555
513
|
}
|
|
556
514
|
}
|
|
@@ -576,7 +534,7 @@ function createProgram(filePath, sourceText) {
|
|
|
576
534
|
skipLibCheck: true,
|
|
577
535
|
strict: true,
|
|
578
536
|
target: ts.ScriptTarget.ESNext,
|
|
579
|
-
useDefineForClassFields: true
|
|
537
|
+
useDefineForClassFields: true,
|
|
580
538
|
};
|
|
581
539
|
|
|
582
540
|
const host = {
|
|
@@ -585,35 +543,22 @@ function createProgram(filePath, sourceText) {
|
|
|
585
543
|
if (path.resolve(fileName) === filePath) return true;
|
|
586
544
|
return defaultHost.fileExists(fileName);
|
|
587
545
|
},
|
|
588
|
-
getSourceFile(
|
|
589
|
-
fileName,
|
|
590
|
-
languageVersion,
|
|
591
|
-
onError,
|
|
592
|
-
shouldCreateNewSourceFile
|
|
593
|
-
) {
|
|
546
|
+
getSourceFile(fileName, languageVersion, onError, shouldCreateNewSourceFile) {
|
|
594
547
|
if (path.resolve(fileName) === filePath) {
|
|
595
|
-
const kind = fileName.endsWith(
|
|
596
|
-
|
|
597
|
-
: ts.ScriptKind.JS;
|
|
598
|
-
return ts.createSourceFile(
|
|
599
|
-
fileName,
|
|
600
|
-
sourceText,
|
|
601
|
-
languageVersion,
|
|
602
|
-
true,
|
|
603
|
-
kind
|
|
604
|
-
);
|
|
548
|
+
const kind = fileName.endsWith(".ts") ? ts.ScriptKind.TS : ts.ScriptKind.JS;
|
|
549
|
+
return ts.createSourceFile(fileName, sourceText, languageVersion, true, kind);
|
|
605
550
|
}
|
|
606
551
|
return defaultHost.getSourceFile(
|
|
607
552
|
fileName,
|
|
608
553
|
languageVersion,
|
|
609
554
|
onError,
|
|
610
|
-
shouldCreateNewSourceFile
|
|
555
|
+
shouldCreateNewSourceFile,
|
|
611
556
|
);
|
|
612
557
|
},
|
|
613
558
|
readFile(fileName) {
|
|
614
559
|
if (path.resolve(fileName) === filePath) return sourceText;
|
|
615
560
|
return defaultHost.readFile(fileName);
|
|
616
|
-
}
|
|
561
|
+
},
|
|
617
562
|
};
|
|
618
563
|
|
|
619
564
|
return ts.createProgram([filePath], compilerOptions, host);
|
|
@@ -638,28 +583,19 @@ function extractProperties(sourceFile, checker, classNode) {
|
|
|
638
583
|
const name = getMemberName(member);
|
|
639
584
|
if (!name || !member.initializer) continue;
|
|
640
585
|
|
|
641
|
-
if (
|
|
642
|
-
name === 'context' &&
|
|
643
|
-
ts.isObjectLiteralExpression(member.initializer)
|
|
644
|
-
) {
|
|
586
|
+
if (name === "context" && ts.isObjectLiteralExpression(member.initializer)) {
|
|
645
587
|
contextKeys = member.initializer.properties
|
|
646
|
-
.map(property => getMemberName(property))
|
|
588
|
+
.map((property) => getMemberName(property))
|
|
647
589
|
.filter(Boolean);
|
|
648
590
|
continue;
|
|
649
591
|
}
|
|
650
592
|
|
|
651
|
-
if (
|
|
652
|
-
name === 'formAssociated' &&
|
|
653
|
-
member.initializer.kind === ts.SyntaxKind.TrueKeyword
|
|
654
|
-
) {
|
|
593
|
+
if (name === "formAssociated" && member.initializer.kind === ts.SyntaxKind.TrueKeyword) {
|
|
655
594
|
formAssociated = true;
|
|
656
595
|
continue;
|
|
657
596
|
}
|
|
658
597
|
|
|
659
|
-
if (
|
|
660
|
-
name !== 'properties' ||
|
|
661
|
-
!ts.isObjectLiteralExpression(member.initializer)
|
|
662
|
-
) {
|
|
598
|
+
if (name !== "properties" || !ts.isObjectLiteralExpression(member.initializer)) {
|
|
663
599
|
continue;
|
|
664
600
|
}
|
|
665
601
|
|
|
@@ -670,13 +606,13 @@ function extractProperties(sourceFile, checker, classNode) {
|
|
|
670
606
|
continue;
|
|
671
607
|
}
|
|
672
608
|
const propertyLocation = sourceFile.getLineAndCharacterOfPosition(
|
|
673
|
-
property.name.getStart(sourceFile)
|
|
609
|
+
property.name.getStart(sourceFile),
|
|
674
610
|
);
|
|
675
611
|
|
|
676
612
|
if (
|
|
677
613
|
supportedProps.has(propName) &&
|
|
678
|
-
!duplicateProperties.some(
|
|
679
|
-
|
|
614
|
+
!duplicateProperties.some((finding) =>
|
|
615
|
+
getLocatedFindingMessage(finding).startsWith(`"${propName}" `),
|
|
680
616
|
)
|
|
681
617
|
) {
|
|
682
618
|
duplicateProperties.push({
|
|
@@ -684,18 +620,16 @@ function extractProperties(sourceFile, checker, classNode) {
|
|
|
684
620
|
message:
|
|
685
621
|
`"${propName}" first declared at ` +
|
|
686
622
|
`${formatLocation(propertyLocations.get(propName))}, ` +
|
|
687
|
-
`duplicated at ${formatLocation(propertyLocation)}
|
|
623
|
+
`duplicated at ${formatLocation(propertyLocation)}`,
|
|
688
624
|
});
|
|
689
625
|
}
|
|
690
626
|
if (
|
|
691
627
|
RESERVED_PROPERTY_NAMES.has(propName) &&
|
|
692
|
-
!reservedProperties.some(
|
|
693
|
-
finding => getLocatedFindingMessage(finding) === propName
|
|
694
|
-
)
|
|
628
|
+
!reservedProperties.some((finding) => getLocatedFindingMessage(finding) === propName)
|
|
695
629
|
) {
|
|
696
630
|
reservedProperties.push({
|
|
697
631
|
location: propertyLocation,
|
|
698
|
-
message: propName
|
|
632
|
+
message: propName,
|
|
699
633
|
});
|
|
700
634
|
}
|
|
701
635
|
if (!propertyLocations.has(propName)) {
|
|
@@ -703,22 +637,18 @@ function extractProperties(sourceFile, checker, classNode) {
|
|
|
703
637
|
}
|
|
704
638
|
|
|
705
639
|
const config = property.initializer;
|
|
706
|
-
propertyEntries.push({config, propName, property});
|
|
707
|
-
const typeProp = getObjectProperty(config,
|
|
708
|
-
const computedProp = getObjectProperty(config,
|
|
640
|
+
propertyEntries.push({ config, propName, property });
|
|
641
|
+
const typeProp = getObjectProperty(config, "type");
|
|
642
|
+
const computedProp = getObjectProperty(config, "computed");
|
|
709
643
|
|
|
710
|
-
let typeText =
|
|
644
|
+
let typeText = "unknown";
|
|
711
645
|
let typeNode;
|
|
712
646
|
if (typeProp && ts.isPropertyAssignment(typeProp)) {
|
|
713
|
-
typeText = getPropertyTypeText(
|
|
714
|
-
checker,
|
|
715
|
-
sourceFile,
|
|
716
|
-
typeProp.initializer
|
|
717
|
-
);
|
|
647
|
+
typeText = getPropertyTypeText(checker, sourceFile, typeProp.initializer);
|
|
718
648
|
typeNode = typeNodeFromConstructorExpression(typeProp.initializer);
|
|
719
649
|
}
|
|
720
650
|
|
|
721
|
-
supportedProps.set(propName, {typeNode, typeText});
|
|
651
|
+
supportedProps.set(propName, { typeNode, typeText });
|
|
722
652
|
|
|
723
653
|
if (
|
|
724
654
|
computedProp &&
|
|
@@ -727,11 +657,11 @@ function extractProperties(sourceFile, checker, classNode) {
|
|
|
727
657
|
ts.isNoSubstitutionTemplateLiteral(computedProp.initializer))
|
|
728
658
|
) {
|
|
729
659
|
computedExprs.push({
|
|
730
|
-
kind:
|
|
660
|
+
kind: "computed",
|
|
731
661
|
text: computedProp.initializer.text.trim(),
|
|
732
662
|
location: sourceFile.getLineAndCharacterOfPosition(
|
|
733
|
-
computedProp.initializer.getStart(sourceFile)
|
|
734
|
-
)
|
|
663
|
+
computedProp.initializer.getStart(sourceFile),
|
|
664
|
+
),
|
|
735
665
|
});
|
|
736
666
|
}
|
|
737
667
|
}
|
|
@@ -744,17 +674,12 @@ function extractProperties(sourceFile, checker, classNode) {
|
|
|
744
674
|
duplicateProperties,
|
|
745
675
|
formAssociated,
|
|
746
676
|
propertyEntries,
|
|
747
|
-
reservedProperties
|
|
677
|
+
reservedProperties,
|
|
748
678
|
};
|
|
749
679
|
}
|
|
750
680
|
|
|
751
681
|
// Extracts analyzable expressions from static html and css templates.
|
|
752
|
-
function extractTemplateExpressions(
|
|
753
|
-
classNode,
|
|
754
|
-
findings,
|
|
755
|
-
componentPropertyMaps,
|
|
756
|
-
supportedProps
|
|
757
|
-
) {
|
|
682
|
+
function extractTemplateExpressions(classNode, findings, componentPropertyMaps, supportedProps) {
|
|
758
683
|
const expressions = [];
|
|
759
684
|
|
|
760
685
|
for (const member of classNode.members) {
|
|
@@ -762,66 +687,60 @@ function extractTemplateExpressions(
|
|
|
762
687
|
if (!ts.isPropertyDeclaration(member)) continue;
|
|
763
688
|
|
|
764
689
|
const name = getMemberName(member);
|
|
765
|
-
if ((name !==
|
|
690
|
+
if ((name !== "html" && name !== "css") || !member.initializer) continue;
|
|
766
691
|
if (!ts.isTaggedTemplateExpression(member.initializer)) continue;
|
|
767
692
|
|
|
768
693
|
const tag = member.initializer.tag.getText();
|
|
769
|
-
if (tag !==
|
|
694
|
+
if (tag !== "html" && tag !== "css") continue;
|
|
770
695
|
|
|
771
|
-
const {template} = member.initializer;
|
|
696
|
+
const { template } = member.initializer;
|
|
772
697
|
if (ts.isTemplateExpression(template)) {
|
|
773
698
|
for (const span of template.templateSpans) {
|
|
774
|
-
const trimmed = getExpressionText(
|
|
775
|
-
member.getSourceFile(),
|
|
776
|
-
span.expression
|
|
777
|
-
);
|
|
699
|
+
const trimmed = getExpressionText(member.getSourceFile(), span.expression);
|
|
778
700
|
if (trimmed) {
|
|
779
701
|
const location = span.expression
|
|
780
702
|
.getSourceFile()
|
|
781
703
|
.getLineAndCharacterOfPosition(span.expression.getStart());
|
|
782
704
|
expressions.push({
|
|
783
|
-
context:
|
|
705
|
+
context: "static",
|
|
784
706
|
eventHandler: false,
|
|
785
707
|
kind: tag,
|
|
786
708
|
text: trimmed,
|
|
787
|
-
location
|
|
709
|
+
location,
|
|
788
710
|
});
|
|
789
711
|
}
|
|
790
712
|
}
|
|
791
713
|
}
|
|
792
714
|
|
|
793
715
|
const rendered = getTemplateLiteralText(template);
|
|
794
|
-
const resolveLocation = createTemplateLocationResolver(
|
|
795
|
-
member.getSourceFile(),
|
|
796
|
-
template
|
|
797
|
-
);
|
|
716
|
+
const resolveLocation = createTemplateLocationResolver(member.getSourceFile(), template);
|
|
798
717
|
|
|
799
|
-
if (tag ===
|
|
718
|
+
if (tag === "css") {
|
|
800
719
|
CSS_PROPERTY_RE.lastIndex = 0;
|
|
801
720
|
while (true) {
|
|
802
721
|
const match = CSS_PROPERTY_RE.exec(rendered);
|
|
803
722
|
if (!match) break;
|
|
804
|
-
const rawValue = match[2] ??
|
|
723
|
+
const rawValue = match[2] ?? "";
|
|
805
724
|
const value = rawValue.trim();
|
|
806
725
|
if (value && REFS_TEST_RE.test(value)) {
|
|
807
726
|
const valueOffsetInMatch = match[0].lastIndexOf(rawValue);
|
|
808
727
|
const leadingWhitespace = rawValue.length - rawValue.trimStart().length;
|
|
809
728
|
const valueLocation = resolveLocation(
|
|
810
|
-
match.index + valueOffsetInMatch + leadingWhitespace
|
|
729
|
+
match.index + valueOffsetInMatch + leadingWhitespace,
|
|
811
730
|
);
|
|
812
731
|
expressions.push({
|
|
813
|
-
context:
|
|
732
|
+
context: "instance",
|
|
814
733
|
eventHandler: false,
|
|
815
|
-
kind:
|
|
734
|
+
kind: "css",
|
|
816
735
|
text: value,
|
|
817
|
-
location: valueLocation
|
|
736
|
+
location: valueLocation,
|
|
818
737
|
});
|
|
819
738
|
}
|
|
820
739
|
}
|
|
821
740
|
continue;
|
|
822
741
|
}
|
|
823
742
|
|
|
824
|
-
const root = parse(rendered, {comment: true});
|
|
743
|
+
const root = parse(rendered, { comment: true });
|
|
825
744
|
walkHtmlNode(
|
|
826
745
|
root,
|
|
827
746
|
expressions,
|
|
@@ -829,7 +748,7 @@ function extractTemplateExpressions(
|
|
|
829
748
|
componentPropertyMaps,
|
|
830
749
|
supportedProps,
|
|
831
750
|
new Set(),
|
|
832
|
-
resolveLocation
|
|
751
|
+
resolveLocation,
|
|
833
752
|
);
|
|
834
753
|
}
|
|
835
754
|
|
|
@@ -851,7 +770,7 @@ function findDefinedTagNames(sourceFile) {
|
|
|
851
770
|
if (
|
|
852
771
|
ts.isCallExpression(node) &&
|
|
853
772
|
ts.isPropertyAccessExpression(node.expression) &&
|
|
854
|
-
node.expression.name.text ===
|
|
773
|
+
node.expression.name.text === "define" &&
|
|
855
774
|
ts.isIdentifier(node.expression.expression) &&
|
|
856
775
|
node.arguments.length > 0 &&
|
|
857
776
|
ts.isStringLiteral(node.arguments[0])
|
|
@@ -868,9 +787,9 @@ function findDefinedTagNames(sourceFile) {
|
|
|
868
787
|
// Recursively finds Wrec component files under a directory
|
|
869
788
|
// and reports each match.
|
|
870
789
|
function findWrecFiles(rootDir, onMatch) {
|
|
871
|
-
const walk = currentDir => {
|
|
790
|
+
const walk = (currentDir) => {
|
|
872
791
|
const entries = fs
|
|
873
|
-
.readdirSync(currentDir, {withFileTypes: true})
|
|
792
|
+
.readdirSync(currentDir, { withFileTypes: true })
|
|
874
793
|
.sort((a, b) => a.name.localeCompare(b.name));
|
|
875
794
|
|
|
876
795
|
for (const entry of entries) {
|
|
@@ -882,7 +801,7 @@ function findWrecFiles(rootDir, onMatch) {
|
|
|
882
801
|
}
|
|
883
802
|
|
|
884
803
|
if (!entry.isFile()) continue;
|
|
885
|
-
if (!fullPath.endsWith(
|
|
804
|
+
if (!fullPath.endsWith(".js") && !fullPath.endsWith(".ts")) continue;
|
|
886
805
|
if (isWrecComponentFile(fullPath)) onMatch(fullPath);
|
|
887
806
|
}
|
|
888
807
|
};
|
|
@@ -898,7 +817,7 @@ function createTemplateLocationResolver(sourceFile, template) {
|
|
|
898
817
|
segments.push({
|
|
899
818
|
renderedEnd: template.text.length,
|
|
900
819
|
renderedStart: 0,
|
|
901
|
-
sourceStart: template.getStart(sourceFile) + 1
|
|
820
|
+
sourceStart: template.getStart(sourceFile) + 1,
|
|
902
821
|
});
|
|
903
822
|
} else {
|
|
904
823
|
let renderedStart = 0;
|
|
@@ -906,7 +825,7 @@ function createTemplateLocationResolver(sourceFile, template) {
|
|
|
906
825
|
segments.push({
|
|
907
826
|
renderedEnd: headText.length,
|
|
908
827
|
renderedStart,
|
|
909
|
-
sourceStart: template.head.getStart(sourceFile) + 1
|
|
828
|
+
sourceStart: template.head.getStart(sourceFile) + 1,
|
|
910
829
|
});
|
|
911
830
|
renderedStart += headText.length;
|
|
912
831
|
|
|
@@ -915,29 +834,27 @@ function createTemplateLocationResolver(sourceFile, template) {
|
|
|
915
834
|
segments.push({
|
|
916
835
|
renderedEnd: renderedStart + span.literal.text.length,
|
|
917
836
|
renderedStart,
|
|
918
|
-
sourceStart: span.literal.getStart(sourceFile) + 1
|
|
837
|
+
sourceStart: span.literal.getStart(sourceFile) + 1,
|
|
919
838
|
});
|
|
920
839
|
renderedStart += span.literal.text.length;
|
|
921
840
|
});
|
|
922
841
|
}
|
|
923
842
|
|
|
924
|
-
return offset => {
|
|
843
|
+
return (offset) => {
|
|
925
844
|
const segment =
|
|
926
845
|
segments.find(
|
|
927
|
-
candidate =>
|
|
928
|
-
offset >= candidate.renderedStart && offset <= candidate.renderedEnd
|
|
846
|
+
(candidate) => offset >= candidate.renderedStart && offset <= candidate.renderedEnd,
|
|
929
847
|
) ?? segments[segments.length - 1];
|
|
930
848
|
if (!segment) return null;
|
|
931
849
|
|
|
932
|
-
const sourceOffset =
|
|
933
|
-
segment.sourceStart + Math.max(0, offset - segment.renderedStart);
|
|
850
|
+
const sourceOffset = segment.sourceStart + Math.max(0, offset - segment.renderedStart);
|
|
934
851
|
return sourceFile.getLineAndCharacterOfPosition(sourceOffset);
|
|
935
852
|
};
|
|
936
853
|
}
|
|
937
854
|
|
|
938
855
|
// Formats an optional source location as line and column text.
|
|
939
856
|
function formatLocation(location) {
|
|
940
|
-
if (!location) return
|
|
857
|
+
if (!location) return "";
|
|
941
858
|
return `:${location.line + 1}:${location.character + 1}`;
|
|
942
859
|
}
|
|
943
860
|
|
|
@@ -961,13 +878,13 @@ function getHtmlAttributeLocation(node, attrName, resolveLocation) {
|
|
|
961
878
|
|
|
962
879
|
// Formats a lint finding that may optionally include a source location.
|
|
963
880
|
function formatMaybeLocatedFinding(finding) {
|
|
964
|
-
if (typeof finding ===
|
|
881
|
+
if (typeof finding === "string") return finding;
|
|
965
882
|
return `${formatLocation(finding.location)} ${finding.message}`.trim();
|
|
966
883
|
}
|
|
967
884
|
|
|
968
885
|
// Gets the message text from a lint finding that may include a location.
|
|
969
886
|
function getLocatedFindingMessage(finding) {
|
|
970
|
-
return typeof finding ===
|
|
887
|
+
return typeof finding === "string" ? finding : finding.message;
|
|
971
888
|
}
|
|
972
889
|
|
|
973
890
|
// Compares lint findings that may optionally include source locations.
|
|
@@ -976,19 +893,13 @@ function compareLocatedFindings(a, b) {
|
|
|
976
893
|
}
|
|
977
894
|
|
|
978
895
|
// Formats the collected lint findings into the command-line report output.
|
|
979
|
-
function formatReport(
|
|
980
|
-
filePath,
|
|
981
|
-
supportedProps,
|
|
982
|
-
allExpressions,
|
|
983
|
-
findings,
|
|
984
|
-
options = {}
|
|
985
|
-
) {
|
|
896
|
+
function formatReport(filePath, supportedProps, allExpressions, findings, options = {}) {
|
|
986
897
|
const {
|
|
987
898
|
fileLabel = filePath,
|
|
988
899
|
showFileHeader = true,
|
|
989
900
|
showDetailsForCleanFile = true,
|
|
990
901
|
showNoIssuesMessage = true,
|
|
991
|
-
verbose = false
|
|
902
|
+
verbose = false,
|
|
992
903
|
} = options;
|
|
993
904
|
const lines = [];
|
|
994
905
|
|
|
@@ -1022,224 +933,216 @@ function formatReport(
|
|
|
1022
933
|
if (showFileHeader) lines.push(`file: ${fileLabel}`);
|
|
1023
934
|
|
|
1024
935
|
if (verbose && (hasIssues || showDetailsForCleanFile)) {
|
|
1025
|
-
lines.push(
|
|
936
|
+
lines.push("properties:");
|
|
1026
937
|
if (supportedProps.size === 0) {
|
|
1027
|
-
lines.push(
|
|
938
|
+
lines.push(" none");
|
|
1028
939
|
} else {
|
|
1029
|
-
for (const [name, info] of [...supportedProps.entries()].sort(
|
|
1030
|
-
|
|
940
|
+
for (const [name, info] of [...supportedProps.entries()].sort(([a], [b]) =>
|
|
941
|
+
a.localeCompare(b),
|
|
1031
942
|
)) {
|
|
1032
943
|
lines.push(` ${name}: ${info.typeText}`);
|
|
1033
944
|
}
|
|
1034
945
|
}
|
|
1035
946
|
|
|
1036
|
-
lines.push(
|
|
947
|
+
lines.push("expressions:");
|
|
1037
948
|
if (allExpressions.length === 0) {
|
|
1038
|
-
lines.push(
|
|
949
|
+
lines.push(" none");
|
|
1039
950
|
} else {
|
|
1040
|
-
allExpressions.forEach(expr => {
|
|
1041
|
-
lines.push(
|
|
1042
|
-
` [${expr.kind}]${formatLocation(expr.location)} ${expr.text}`
|
|
1043
|
-
);
|
|
951
|
+
allExpressions.forEach((expr) => {
|
|
952
|
+
lines.push(` [${expr.kind}]${formatLocation(expr.location)} ${expr.text}`);
|
|
1044
953
|
});
|
|
1045
954
|
}
|
|
1046
955
|
}
|
|
1047
956
|
|
|
1048
957
|
if (findings.duplicateProperties.length > 0) {
|
|
1049
|
-
lines.push(
|
|
1050
|
-
findings.duplicateProperties.forEach(finding =>
|
|
1051
|
-
lines.push(` ${formatMaybeLocatedFinding(finding)}`)
|
|
958
|
+
lines.push("duplicate properties:");
|
|
959
|
+
findings.duplicateProperties.forEach((finding) =>
|
|
960
|
+
lines.push(` ${formatMaybeLocatedFinding(finding)}`),
|
|
1052
961
|
);
|
|
1053
962
|
}
|
|
1054
963
|
|
|
1055
964
|
if (findings.extraArguments.length > 0) {
|
|
1056
|
-
lines.push(
|
|
1057
|
-
findings.extraArguments.forEach(finding => {
|
|
1058
|
-
const locationPrefix = finding.location
|
|
1059
|
-
? `${formatLocation(finding.location)} `
|
|
1060
|
-
: '';
|
|
965
|
+
lines.push("extra arguments:");
|
|
966
|
+
findings.extraArguments.forEach((finding) => {
|
|
967
|
+
const locationPrefix = finding.location ? `${formatLocation(finding.location)} ` : "";
|
|
1061
968
|
lines.push(
|
|
1062
969
|
` ${locationPrefix}${finding.methodName}: argument ${finding.argumentIndex} ` +
|
|
1063
970
|
`"${finding.argument}" exceeds the ` +
|
|
1064
|
-
`${finding.parameterCount}-parameter signature
|
|
971
|
+
`${finding.parameterCount}-parameter signature`,
|
|
1065
972
|
);
|
|
1066
973
|
});
|
|
1067
974
|
}
|
|
1068
975
|
|
|
1069
976
|
if (findings.incompatibleArguments.length > 0) {
|
|
1070
|
-
lines.push(
|
|
1071
|
-
findings.incompatibleArguments.forEach(finding => {
|
|
1072
|
-
const locationPrefix = finding.location
|
|
1073
|
-
? `${formatLocation(finding.location)} `
|
|
1074
|
-
: '';
|
|
977
|
+
lines.push("incompatible arguments:");
|
|
978
|
+
findings.incompatibleArguments.forEach((finding) => {
|
|
979
|
+
const locationPrefix = finding.location ? `${formatLocation(finding.location)} ` : "";
|
|
1075
980
|
lines.push(
|
|
1076
981
|
` ${locationPrefix}${finding.methodName}: argument "${finding.argument}" ` +
|
|
1077
982
|
`has type ${finding.argumentType}, but parameter ` +
|
|
1078
|
-
`"${finding.parameterName}" expects ${finding.parameterType}
|
|
983
|
+
`"${finding.parameterName}" expects ${finding.parameterType}`,
|
|
1079
984
|
);
|
|
1080
985
|
});
|
|
1081
986
|
}
|
|
1082
987
|
|
|
1083
988
|
if (findings.incompatibleDeclareTypes.length > 0) {
|
|
1084
|
-
lines.push(
|
|
1085
|
-
findings.incompatibleDeclareTypes.forEach(finding =>
|
|
1086
|
-
lines.push(` ${formatMaybeLocatedFinding(finding)}`)
|
|
989
|
+
lines.push("incompatible declare types:");
|
|
990
|
+
findings.incompatibleDeclareTypes.forEach((finding) =>
|
|
991
|
+
lines.push(` ${formatMaybeLocatedFinding(finding)}`),
|
|
1087
992
|
);
|
|
1088
993
|
}
|
|
1089
994
|
|
|
1090
995
|
if (findings.invalidCheckedBindings.length > 0) {
|
|
1091
|
-
lines.push(
|
|
1092
|
-
findings.invalidCheckedBindings.forEach(finding =>
|
|
1093
|
-
lines.push(` ${formatMaybeLocatedFinding(finding)}`)
|
|
996
|
+
lines.push("invalid checked bindings:");
|
|
997
|
+
findings.invalidCheckedBindings.forEach((finding) =>
|
|
998
|
+
lines.push(` ${formatMaybeLocatedFinding(finding)}`),
|
|
1094
999
|
);
|
|
1095
1000
|
}
|
|
1096
1001
|
|
|
1097
1002
|
if (findings.invalidComputedProperties.length > 0) {
|
|
1098
|
-
lines.push(
|
|
1099
|
-
findings.invalidComputedProperties.forEach(finding =>
|
|
1100
|
-
lines.push(` ${formatMaybeLocatedFinding(finding)}`)
|
|
1003
|
+
lines.push("invalid computed properties:");
|
|
1004
|
+
findings.invalidComputedProperties.forEach((finding) =>
|
|
1005
|
+
lines.push(` ${formatMaybeLocatedFinding(finding)}`),
|
|
1101
1006
|
);
|
|
1102
1007
|
}
|
|
1103
1008
|
|
|
1104
1009
|
if (findings.invalidDefaultValues.length > 0) {
|
|
1105
|
-
lines.push(
|
|
1106
|
-
findings.invalidDefaultValues.forEach(finding =>
|
|
1107
|
-
lines.push(` ${formatMaybeLocatedFinding(finding)}`)
|
|
1010
|
+
lines.push("invalid default values:");
|
|
1011
|
+
findings.invalidDefaultValues.forEach((finding) =>
|
|
1012
|
+
lines.push(` ${formatMaybeLocatedFinding(finding)}`),
|
|
1108
1013
|
);
|
|
1109
1014
|
}
|
|
1110
1015
|
|
|
1111
1016
|
if (findings.invalidEventHandlers.length > 0) {
|
|
1112
|
-
lines.push(
|
|
1113
|
-
findings.invalidEventHandlers.forEach(finding =>
|
|
1114
|
-
lines.push(` ${formatMaybeLocatedFinding(finding)}`)
|
|
1017
|
+
lines.push("invalid event handler references:");
|
|
1018
|
+
findings.invalidEventHandlers.forEach((finding) =>
|
|
1019
|
+
lines.push(` ${formatMaybeLocatedFinding(finding)}`),
|
|
1115
1020
|
);
|
|
1116
1021
|
}
|
|
1117
1022
|
|
|
1118
1023
|
if (findings.invalidFormAssocValues.length > 0) {
|
|
1119
|
-
lines.push(
|
|
1120
|
-
findings.invalidFormAssocValues.forEach(finding =>
|
|
1121
|
-
lines.push(` ${formatMaybeLocatedFinding(finding)}`)
|
|
1024
|
+
lines.push("invalid form-assoc values:");
|
|
1025
|
+
findings.invalidFormAssocValues.forEach((finding) =>
|
|
1026
|
+
lines.push(` ${formatMaybeLocatedFinding(finding)}`),
|
|
1122
1027
|
);
|
|
1123
1028
|
}
|
|
1124
1029
|
|
|
1125
1030
|
if (findings.invalidHtmlNesting.length > 0) {
|
|
1126
|
-
lines.push(
|
|
1127
|
-
findings.invalidHtmlNesting.forEach(finding =>
|
|
1128
|
-
lines.push(` ${formatMaybeLocatedFinding(finding)}`)
|
|
1031
|
+
lines.push("invalid html nesting:");
|
|
1032
|
+
findings.invalidHtmlNesting.forEach((finding) =>
|
|
1033
|
+
lines.push(` ${formatMaybeLocatedFinding(finding)}`),
|
|
1129
1034
|
);
|
|
1130
1035
|
}
|
|
1131
1036
|
|
|
1132
1037
|
if (findings.invalidRefAttributes.length > 0) {
|
|
1133
|
-
lines.push(
|
|
1134
|
-
findings.invalidRefAttributes.forEach(finding =>
|
|
1135
|
-
lines.push(` ${formatMaybeLocatedFinding(finding)}`)
|
|
1038
|
+
lines.push("invalid ref attributes:");
|
|
1039
|
+
findings.invalidRefAttributes.forEach((finding) =>
|
|
1040
|
+
lines.push(` ${formatMaybeLocatedFinding(finding)}`),
|
|
1136
1041
|
);
|
|
1137
1042
|
}
|
|
1138
1043
|
|
|
1139
1044
|
if (findings.invalidTypeProperties.length > 0) {
|
|
1140
|
-
lines.push(
|
|
1141
|
-
findings.invalidTypeProperties.forEach(finding =>
|
|
1142
|
-
lines.push(` ${formatMaybeLocatedFinding(finding)}`)
|
|
1045
|
+
lines.push("invalid type properties:");
|
|
1046
|
+
findings.invalidTypeProperties.forEach((finding) =>
|
|
1047
|
+
lines.push(` ${formatMaybeLocatedFinding(finding)}`),
|
|
1143
1048
|
);
|
|
1144
1049
|
}
|
|
1145
1050
|
|
|
1146
1051
|
if (findings.invalidUsedByReferences.length > 0) {
|
|
1147
|
-
lines.push(
|
|
1148
|
-
findings.invalidUsedByReferences.forEach(finding =>
|
|
1149
|
-
lines.push(` ${formatMaybeLocatedFinding(finding)}`)
|
|
1052
|
+
lines.push("invalid usedBy references:");
|
|
1053
|
+
findings.invalidUsedByReferences.forEach((finding) =>
|
|
1054
|
+
lines.push(` ${formatMaybeLocatedFinding(finding)}`),
|
|
1150
1055
|
);
|
|
1151
1056
|
}
|
|
1152
1057
|
|
|
1153
1058
|
if (findings.invalidUseStateMaps.length > 0) {
|
|
1154
|
-
lines.push(
|
|
1155
|
-
findings.invalidUseStateMaps.forEach(finding =>
|
|
1156
|
-
lines.push(` ${formatMaybeLocatedFinding(finding)}`)
|
|
1059
|
+
lines.push("invalid useState map entries:");
|
|
1060
|
+
findings.invalidUseStateMaps.forEach((finding) =>
|
|
1061
|
+
lines.push(` ${formatMaybeLocatedFinding(finding)}`),
|
|
1157
1062
|
);
|
|
1158
1063
|
}
|
|
1159
1064
|
|
|
1160
1065
|
if (findings.invalidValueBindings.length > 0) {
|
|
1161
|
-
lines.push(
|
|
1162
|
-
findings.invalidValueBindings.forEach(finding =>
|
|
1163
|
-
lines.push(` ${formatMaybeLocatedFinding(finding)}`)
|
|
1066
|
+
lines.push("invalid value bindings:");
|
|
1067
|
+
findings.invalidValueBindings.forEach((finding) =>
|
|
1068
|
+
lines.push(` ${formatMaybeLocatedFinding(finding)}`),
|
|
1164
1069
|
);
|
|
1165
1070
|
}
|
|
1166
1071
|
|
|
1167
1072
|
if (findings.invalidValuesConfigurations.length > 0) {
|
|
1168
|
-
lines.push(
|
|
1169
|
-
findings.invalidValuesConfigurations.forEach(finding =>
|
|
1170
|
-
lines.push(` ${formatMaybeLocatedFinding(finding)}`)
|
|
1073
|
+
lines.push("invalid values configurations:");
|
|
1074
|
+
findings.invalidValuesConfigurations.forEach((finding) =>
|
|
1075
|
+
lines.push(` ${formatMaybeLocatedFinding(finding)}`),
|
|
1171
1076
|
);
|
|
1172
1077
|
}
|
|
1173
1078
|
|
|
1174
1079
|
if (findings.missingRequiredMembers.length > 0) {
|
|
1175
|
-
lines.push(
|
|
1176
|
-
findings.missingRequiredMembers.forEach(finding =>
|
|
1177
|
-
lines.push(` ${formatMaybeLocatedFinding(finding)}`)
|
|
1080
|
+
lines.push("missing required members:");
|
|
1081
|
+
findings.missingRequiredMembers.forEach((finding) =>
|
|
1082
|
+
lines.push(` ${formatMaybeLocatedFinding(finding)}`),
|
|
1178
1083
|
);
|
|
1179
1084
|
}
|
|
1180
1085
|
|
|
1181
1086
|
if (findings.missingTypeProperties.length > 0) {
|
|
1182
|
-
lines.push(
|
|
1183
|
-
findings.missingTypeProperties.forEach(finding =>
|
|
1184
|
-
lines.push(` ${formatMaybeLocatedFinding(finding)}`)
|
|
1087
|
+
lines.push("missing type properties:");
|
|
1088
|
+
findings.missingTypeProperties.forEach((finding) =>
|
|
1089
|
+
lines.push(` ${formatMaybeLocatedFinding(finding)}`),
|
|
1185
1090
|
);
|
|
1186
1091
|
}
|
|
1187
1092
|
|
|
1188
1093
|
if (findings.reservedProperties.length > 0) {
|
|
1189
|
-
lines.push(
|
|
1190
|
-
findings.reservedProperties.forEach(finding =>
|
|
1191
|
-
lines.push(` ${formatMaybeLocatedFinding(finding)}`)
|
|
1094
|
+
lines.push("reserved property names:");
|
|
1095
|
+
findings.reservedProperties.forEach((finding) =>
|
|
1096
|
+
lines.push(` ${formatMaybeLocatedFinding(finding)}`),
|
|
1192
1097
|
);
|
|
1193
1098
|
}
|
|
1194
1099
|
|
|
1195
1100
|
if (findings.typeErrors.length > 0) {
|
|
1196
|
-
lines.push(
|
|
1197
|
-
findings.typeErrors.forEach(finding => {
|
|
1198
|
-
const locationPrefix = finding.location
|
|
1199
|
-
? `${formatLocation(finding.location)} `
|
|
1200
|
-
: '';
|
|
1101
|
+
lines.push("type errors:");
|
|
1102
|
+
findings.typeErrors.forEach((finding) => {
|
|
1103
|
+
const locationPrefix = finding.location ? `${formatLocation(finding.location)} ` : "";
|
|
1201
1104
|
lines.push(` ${locationPrefix}${finding.expression}: ${finding.message}`);
|
|
1202
1105
|
});
|
|
1203
1106
|
}
|
|
1204
1107
|
|
|
1205
1108
|
if (findings.undefinedContextFunctions.length > 0) {
|
|
1206
|
-
lines.push(
|
|
1207
|
-
findings.undefinedContextFunctions.forEach(finding =>
|
|
1208
|
-
lines.push(` ${formatMaybeLocatedFinding(finding)}`)
|
|
1109
|
+
lines.push("undefined context functions:");
|
|
1110
|
+
findings.undefinedContextFunctions.forEach((finding) =>
|
|
1111
|
+
lines.push(` ${formatMaybeLocatedFinding(finding)}`),
|
|
1209
1112
|
);
|
|
1210
1113
|
}
|
|
1211
1114
|
|
|
1212
1115
|
if (findings.undefinedMethods.length > 0) {
|
|
1213
|
-
lines.push(
|
|
1214
|
-
findings.undefinedMethods.forEach(finding =>
|
|
1215
|
-
lines.push(` ${formatMaybeLocatedFinding(finding)}`)
|
|
1116
|
+
lines.push("undefined methods:");
|
|
1117
|
+
findings.undefinedMethods.forEach((finding) =>
|
|
1118
|
+
lines.push(` ${formatMaybeLocatedFinding(finding)}`),
|
|
1216
1119
|
);
|
|
1217
1120
|
}
|
|
1218
1121
|
|
|
1219
1122
|
if (findings.undefinedProperties.length > 0) {
|
|
1220
|
-
lines.push(
|
|
1221
|
-
findings.undefinedProperties.forEach(finding =>
|
|
1222
|
-
lines.push(` ${formatMaybeLocatedFinding(finding)}`)
|
|
1123
|
+
lines.push("undefined properties:");
|
|
1124
|
+
findings.undefinedProperties.forEach((finding) =>
|
|
1125
|
+
lines.push(` ${formatMaybeLocatedFinding(finding)}`),
|
|
1223
1126
|
);
|
|
1224
1127
|
}
|
|
1225
1128
|
|
|
1226
1129
|
if (findings.unsupportedEventNames.length > 0) {
|
|
1227
|
-
lines.push(
|
|
1228
|
-
findings.unsupportedEventNames.forEach(finding =>
|
|
1229
|
-
lines.push(` ${formatMaybeLocatedFinding(finding)}`)
|
|
1130
|
+
lines.push("unsupported event names:");
|
|
1131
|
+
findings.unsupportedEventNames.forEach((finding) =>
|
|
1132
|
+
lines.push(` ${formatMaybeLocatedFinding(finding)}`),
|
|
1230
1133
|
);
|
|
1231
1134
|
}
|
|
1232
1135
|
|
|
1233
1136
|
if (findings.unsupportedHtmlAttributes.length > 0) {
|
|
1234
|
-
lines.push(
|
|
1235
|
-
findings.unsupportedHtmlAttributes.forEach(finding =>
|
|
1236
|
-
lines.push(` ${formatMaybeLocatedFinding(finding)}`)
|
|
1137
|
+
lines.push("unsupported html attributes:");
|
|
1138
|
+
findings.unsupportedHtmlAttributes.forEach((finding) =>
|
|
1139
|
+
lines.push(` ${formatMaybeLocatedFinding(finding)}`),
|
|
1237
1140
|
);
|
|
1238
1141
|
}
|
|
1239
1142
|
|
|
1240
|
-
if (!hasIssues && showNoIssuesMessage) lines.push(
|
|
1143
|
+
if (!hasIssues && showNoIssuesMessage) lines.push("no issues found");
|
|
1241
1144
|
|
|
1242
|
-
return `${lines.join(
|
|
1145
|
+
return `${lines.join("\n")}\n`;
|
|
1243
1146
|
}
|
|
1244
1147
|
|
|
1245
1148
|
// Builds a map from tag names to supported properties
|
|
@@ -1252,40 +1155,24 @@ function getComponentPropertyMaps(filePath, sourceText, seen = new Set()) {
|
|
|
1252
1155
|
if (seen.has(resolved)) return new Map();
|
|
1253
1156
|
seen.add(resolved);
|
|
1254
1157
|
|
|
1255
|
-
const text = sourceText ?? fs.readFileSync(resolved,
|
|
1256
|
-
const scriptKind = resolved.endsWith(
|
|
1257
|
-
|
|
1258
|
-
: ts.ScriptKind.JS;
|
|
1259
|
-
const sourceFile = ts.createSourceFile(
|
|
1260
|
-
resolved,
|
|
1261
|
-
text,
|
|
1262
|
-
ts.ScriptTarget.ESNext,
|
|
1263
|
-
true,
|
|
1264
|
-
scriptKind
|
|
1265
|
-
);
|
|
1158
|
+
const text = sourceText ?? fs.readFileSync(resolved, "utf8");
|
|
1159
|
+
const scriptKind = resolved.endsWith(".ts") ? ts.ScriptKind.TS : ts.ScriptKind.JS;
|
|
1160
|
+
const sourceFile = ts.createSourceFile(resolved, text, ts.ScriptTarget.ESNext, true, scriptKind);
|
|
1266
1161
|
const tagNames = findDefinedTagNames(sourceFile);
|
|
1267
1162
|
const propertyMaps = new Map();
|
|
1268
1163
|
|
|
1269
1164
|
for (const classNode of collectWrecClasses(sourceFile)) {
|
|
1270
|
-
const tagName = classNode.name
|
|
1271
|
-
? tagNames.get(classNode.name.text)
|
|
1272
|
-
: undefined;
|
|
1165
|
+
const tagName = classNode.name ? tagNames.get(classNode.name.text) : undefined;
|
|
1273
1166
|
if (!tagName) continue;
|
|
1274
1167
|
propertyMaps.set(tagName, collectSupportedPropertyNames(classNode));
|
|
1275
1168
|
}
|
|
1276
1169
|
|
|
1277
1170
|
for (const statement of sourceFile.statements) {
|
|
1278
|
-
if (
|
|
1279
|
-
!ts.isImportDeclaration(statement) ||
|
|
1280
|
-
!ts.isStringLiteral(statement.moduleSpecifier)
|
|
1281
|
-
) {
|
|
1171
|
+
if (!ts.isImportDeclaration(statement) || !ts.isStringLiteral(statement.moduleSpecifier)) {
|
|
1282
1172
|
continue;
|
|
1283
1173
|
}
|
|
1284
1174
|
|
|
1285
|
-
const importPath = resolveImportPath(
|
|
1286
|
-
path.dirname(resolved),
|
|
1287
|
-
statement.moduleSpecifier.text
|
|
1288
|
-
);
|
|
1175
|
+
const importPath = resolveImportPath(path.dirname(resolved), statement.moduleSpecifier.text);
|
|
1289
1176
|
if (!importPath) continue;
|
|
1290
1177
|
|
|
1291
1178
|
const importedMaps = getComponentPropertyMaps(importPath, undefined, seen);
|
|
@@ -1323,22 +1210,19 @@ function getGetterName(reference) {
|
|
|
1323
1210
|
// Returns a lowercased HTML tag name for a parsed HTML node.
|
|
1324
1211
|
function getHtmlTagName(node) {
|
|
1325
1212
|
const tagName = node.rawTagName || node.tagName;
|
|
1326
|
-
return typeof tagName ===
|
|
1213
|
+
return typeof tagName === "string" ? tagName.toLowerCase() : "";
|
|
1327
1214
|
}
|
|
1328
1215
|
|
|
1329
1216
|
// Returns the literal input type attribute value when one is present.
|
|
1330
1217
|
function getInputType(node) {
|
|
1331
|
-
const type = node.getAttribute(
|
|
1332
|
-
return typeof type ===
|
|
1218
|
+
const type = node.getAttribute("type");
|
|
1219
|
+
return typeof type === "string" ? type.toLowerCase() : undefined;
|
|
1333
1220
|
}
|
|
1334
1221
|
|
|
1335
1222
|
// Gets an object-literal property with the given key.
|
|
1336
1223
|
function getObjectProperty(objectLiteral, key) {
|
|
1337
1224
|
for (const property of objectLiteral.properties) {
|
|
1338
|
-
if (
|
|
1339
|
-
!ts.isPropertyAssignment(property) &&
|
|
1340
|
-
!ts.isShorthandPropertyAssignment(property)
|
|
1341
|
-
) {
|
|
1225
|
+
if (!ts.isPropertyAssignment(property) && !ts.isShorthandPropertyAssignment(property)) {
|
|
1342
1226
|
continue;
|
|
1343
1227
|
}
|
|
1344
1228
|
const name = getMemberName(property);
|
|
@@ -1349,15 +1233,9 @@ function getObjectProperty(objectLiteral, key) {
|
|
|
1349
1233
|
// Resolves the effective parameter type,
|
|
1350
1234
|
// including element types for rest parameters.
|
|
1351
1235
|
function getParameterType(checker, parameterSymbol, location, isRestArgument) {
|
|
1352
|
-
const parameterType = checker.getTypeOfSymbolAtLocation(
|
|
1353
|
-
parameterSymbol,
|
|
1354
|
-
location
|
|
1355
|
-
);
|
|
1236
|
+
const parameterType = checker.getTypeOfSymbolAtLocation(parameterSymbol, location);
|
|
1356
1237
|
if (!isRestArgument) return parameterType;
|
|
1357
|
-
if (
|
|
1358
|
-
!checker.isArrayType(parameterType) &&
|
|
1359
|
-
!checker.isTupleType(parameterType)
|
|
1360
|
-
) {
|
|
1238
|
+
if (!checker.isArrayType(parameterType) && !checker.isTupleType(parameterType)) {
|
|
1361
1239
|
return parameterType;
|
|
1362
1240
|
}
|
|
1363
1241
|
|
|
@@ -1368,16 +1246,16 @@ function getParameterType(checker, parameterSymbol, location, isRestArgument) {
|
|
|
1368
1246
|
// Returns the Wrec property config type name for a normalized type string.
|
|
1369
1247
|
function getPropertyConfigTypeName(typeName) {
|
|
1370
1248
|
switch (typeName) {
|
|
1371
|
-
case
|
|
1372
|
-
return
|
|
1373
|
-
case
|
|
1374
|
-
return
|
|
1375
|
-
case
|
|
1376
|
-
return
|
|
1377
|
-
case
|
|
1378
|
-
return
|
|
1379
|
-
case
|
|
1380
|
-
return
|
|
1249
|
+
case "array":
|
|
1250
|
+
return "Array";
|
|
1251
|
+
case "boolean":
|
|
1252
|
+
return "Boolean";
|
|
1253
|
+
case "number":
|
|
1254
|
+
return "Number";
|
|
1255
|
+
case "object":
|
|
1256
|
+
return "Object";
|
|
1257
|
+
case "string":
|
|
1258
|
+
return "String";
|
|
1381
1259
|
default:
|
|
1382
1260
|
return typeName;
|
|
1383
1261
|
}
|
|
@@ -1413,10 +1291,7 @@ function getStringArrayLiteral(property) {
|
|
|
1413
1291
|
|
|
1414
1292
|
const values = [];
|
|
1415
1293
|
for (const element of property.initializer.elements) {
|
|
1416
|
-
if (
|
|
1417
|
-
!ts.isStringLiteral(element) &&
|
|
1418
|
-
!ts.isNoSubstitutionTemplateLiteral(element)
|
|
1419
|
-
) {
|
|
1294
|
+
if (!ts.isStringLiteral(element) && !ts.isNoSubstitutionTemplateLiteral(element)) {
|
|
1420
1295
|
return undefined;
|
|
1421
1296
|
}
|
|
1422
1297
|
values.push(element.text);
|
|
@@ -1428,22 +1303,19 @@ function getStringArrayLiteral(property) {
|
|
|
1428
1303
|
function getValuesConfigurationError(property) {
|
|
1429
1304
|
if (!property || !ts.isPropertyAssignment(property)) return undefined;
|
|
1430
1305
|
|
|
1431
|
-
const {initializer} = property;
|
|
1306
|
+
const { initializer } = property;
|
|
1432
1307
|
if (!ts.isArrayLiteralExpression(initializer)) {
|
|
1433
|
-
return
|
|
1308
|
+
return "values must be a literal array of strings";
|
|
1434
1309
|
}
|
|
1435
1310
|
|
|
1436
1311
|
if (initializer.elements.length === 0) {
|
|
1437
|
-
return
|
|
1312
|
+
return "values must not be empty";
|
|
1438
1313
|
}
|
|
1439
1314
|
|
|
1440
1315
|
const seenValues = new Set();
|
|
1441
1316
|
for (const element of initializer.elements) {
|
|
1442
|
-
if (
|
|
1443
|
-
|
|
1444
|
-
!ts.isNoSubstitutionTemplateLiteral(element)
|
|
1445
|
-
) {
|
|
1446
|
-
return 'values must contain only string literals';
|
|
1317
|
+
if (!ts.isStringLiteral(element) && !ts.isNoSubstitutionTemplateLiteral(element)) {
|
|
1318
|
+
return "values must contain only string literals";
|
|
1447
1319
|
}
|
|
1448
1320
|
|
|
1449
1321
|
if (seenValues.has(element.text)) {
|
|
@@ -1494,29 +1366,23 @@ function getTypeSyntaxText(sourceFile, expression) {
|
|
|
1494
1366
|
|
|
1495
1367
|
if (ts.isIdentifier(expression)) {
|
|
1496
1368
|
switch (expression.text) {
|
|
1497
|
-
case
|
|
1498
|
-
return
|
|
1499
|
-
case
|
|
1500
|
-
return
|
|
1501
|
-
case
|
|
1502
|
-
return
|
|
1503
|
-
case
|
|
1504
|
-
return
|
|
1505
|
-
case
|
|
1506
|
-
return
|
|
1369
|
+
case "String":
|
|
1370
|
+
return "string";
|
|
1371
|
+
case "Number":
|
|
1372
|
+
return "number";
|
|
1373
|
+
case "Boolean":
|
|
1374
|
+
return "boolean";
|
|
1375
|
+
case "Array":
|
|
1376
|
+
return "unknown[]";
|
|
1377
|
+
case "Object":
|
|
1378
|
+
return "object";
|
|
1507
1379
|
default:
|
|
1508
1380
|
return expression.getText(sourceFile);
|
|
1509
1381
|
}
|
|
1510
1382
|
}
|
|
1511
1383
|
|
|
1512
|
-
if (
|
|
1513
|
-
|
|
1514
|
-
ts.isIdentifier(expression.expression)
|
|
1515
|
-
) {
|
|
1516
|
-
if (
|
|
1517
|
-
expression.expression.text === 'Array' &&
|
|
1518
|
-
expression.typeArguments?.length === 1
|
|
1519
|
-
) {
|
|
1384
|
+
if (ts.isCallExpression(expression) && ts.isIdentifier(expression.expression)) {
|
|
1385
|
+
if (expression.expression.text === "Array" && expression.typeArguments?.length === 1) {
|
|
1520
1386
|
return `${expression.typeArguments[0].getText(sourceFile)}[]`;
|
|
1521
1387
|
}
|
|
1522
1388
|
}
|
|
@@ -1533,7 +1399,7 @@ function hasStaticHtmlDefinition(classNode) {
|
|
|
1533
1399
|
for (const member of classNode.members) {
|
|
1534
1400
|
if (!hasStaticModifier(member)) continue;
|
|
1535
1401
|
if (!ts.isPropertyDeclaration(member)) continue;
|
|
1536
|
-
if (getMemberName(member) ===
|
|
1402
|
+
if (getMemberName(member) === "html") return true;
|
|
1537
1403
|
}
|
|
1538
1404
|
return false;
|
|
1539
1405
|
}
|
|
@@ -1559,9 +1425,7 @@ function isDeclarePropertyDeclaration(member) {
|
|
|
1559
1425
|
return (
|
|
1560
1426
|
ts.isPropertyDeclaration(member) &&
|
|
1561
1427
|
ts.canHaveModifiers(member) &&
|
|
1562
|
-
ts
|
|
1563
|
-
.getModifiers(member)
|
|
1564
|
-
?.some(mod => mod.kind === ts.SyntaxKind.DeclareKeyword)
|
|
1428
|
+
ts.getModifiers(member)?.some((mod) => mod.kind === ts.SyntaxKind.DeclareKeyword)
|
|
1565
1429
|
);
|
|
1566
1430
|
}
|
|
1567
1431
|
|
|
@@ -1588,15 +1452,11 @@ function isNativeFormControl(node) {
|
|
|
1588
1452
|
// Returns whether a type represents an object-like value other than an array.
|
|
1589
1453
|
function isNonArrayObjectLikeType(checker, type) {
|
|
1590
1454
|
if (type.isUnion()) {
|
|
1591
|
-
return type.types.every(member =>
|
|
1592
|
-
isNonArrayObjectLikeType(checker, member)
|
|
1593
|
-
);
|
|
1455
|
+
return type.types.every((member) => isNonArrayObjectLikeType(checker, member));
|
|
1594
1456
|
}
|
|
1595
1457
|
|
|
1596
1458
|
if (type.isIntersection()) {
|
|
1597
|
-
return type.types.every(member =>
|
|
1598
|
-
isNonArrayObjectLikeType(checker, member)
|
|
1599
|
-
);
|
|
1459
|
+
return type.types.every((member) => isNonArrayObjectLikeType(checker, member));
|
|
1600
1460
|
}
|
|
1601
1461
|
|
|
1602
1462
|
return (
|
|
@@ -1609,7 +1469,7 @@ function isNonArrayObjectLikeType(checker, type) {
|
|
|
1609
1469
|
// Returns whether a type is fully numeric or any-like for arithmetic checks.
|
|
1610
1470
|
function isNumericLikeType(type) {
|
|
1611
1471
|
const parts = type.isUnion() ? type.types : [type];
|
|
1612
|
-
return parts.every(part => {
|
|
1472
|
+
return parts.every((part) => {
|
|
1613
1473
|
const flags = part.flags;
|
|
1614
1474
|
return Boolean(
|
|
1615
1475
|
flags &
|
|
@@ -1617,23 +1477,21 @@ function isNumericLikeType(type) {
|
|
|
1617
1477
|
ts.TypeFlags.NumberLiteral |
|
|
1618
1478
|
ts.TypeFlags.BigInt |
|
|
1619
1479
|
ts.TypeFlags.BigIntLiteral |
|
|
1620
|
-
ts.TypeFlags.Any)
|
|
1480
|
+
ts.TypeFlags.Any),
|
|
1621
1481
|
);
|
|
1622
1482
|
});
|
|
1623
1483
|
}
|
|
1624
1484
|
|
|
1625
1485
|
// Returns whether a file defines at least one Wrec component class.
|
|
1626
1486
|
function isWrecComponentFile(filePath) {
|
|
1627
|
-
const sourceText = fs.readFileSync(filePath,
|
|
1628
|
-
const scriptKind = filePath.endsWith(
|
|
1629
|
-
? ts.ScriptKind.TS
|
|
1630
|
-
: ts.ScriptKind.JS;
|
|
1487
|
+
const sourceText = fs.readFileSync(filePath, "utf8");
|
|
1488
|
+
const scriptKind = filePath.endsWith(".ts") ? ts.ScriptKind.TS : ts.ScriptKind.JS;
|
|
1631
1489
|
const sourceFile = ts.createSourceFile(
|
|
1632
1490
|
filePath,
|
|
1633
1491
|
sourceText,
|
|
1634
1492
|
ts.ScriptTarget.ESNext,
|
|
1635
1493
|
true,
|
|
1636
|
-
scriptKind
|
|
1494
|
+
scriptKind,
|
|
1637
1495
|
);
|
|
1638
1496
|
return collectWrecClasses(sourceFile).length > 0;
|
|
1639
1497
|
}
|
|
@@ -1643,10 +1501,7 @@ function isWrecRooted(expression) {
|
|
|
1643
1501
|
if (ts.isIdentifier(expression) && expression.text === WREC_REF_NAME) {
|
|
1644
1502
|
return true;
|
|
1645
1503
|
}
|
|
1646
|
-
if (
|
|
1647
|
-
ts.isPropertyAccessExpression(expression) ||
|
|
1648
|
-
ts.isElementAccessExpression(expression)
|
|
1649
|
-
) {
|
|
1504
|
+
if (ts.isPropertyAccessExpression(expression) || ts.isElementAccessExpression(expression)) {
|
|
1650
1505
|
return isWrecRooted(expression.expression);
|
|
1651
1506
|
}
|
|
1652
1507
|
return false;
|
|
@@ -1655,7 +1510,7 @@ function isWrecRooted(expression) {
|
|
|
1655
1510
|
// Lints a component file by path after resolving and reading it.
|
|
1656
1511
|
export function lintFile(filePath, options = {}) {
|
|
1657
1512
|
const resolved = validateFilePath(filePath);
|
|
1658
|
-
return lintSource(resolved, fs.readFileSync(resolved,
|
|
1513
|
+
return lintSource(resolved, fs.readFileSync(resolved, "utf8"), options);
|
|
1659
1514
|
}
|
|
1660
1515
|
|
|
1661
1516
|
// Lints provided component source text and returns a formatted report.
|
|
@@ -1666,7 +1521,7 @@ export function lintSource(filePath, sourceText, options = {}) {
|
|
|
1666
1521
|
|
|
1667
1522
|
const checker = baseProgram.getTypeChecker();
|
|
1668
1523
|
const classNode = collectWrecClasses(sourceFile)[0];
|
|
1669
|
-
if (!classNode) throw new Error(
|
|
1524
|
+
if (!classNode) throw new Error("file must define a subclass of Wrec");
|
|
1670
1525
|
const componentPropertyMaps = getComponentPropertyMaps(filePath, sourceText);
|
|
1671
1526
|
|
|
1672
1527
|
const {
|
|
@@ -1676,7 +1531,7 @@ export function lintSource(filePath, sourceText, options = {}) {
|
|
|
1676
1531
|
duplicateProperties,
|
|
1677
1532
|
formAssociated,
|
|
1678
1533
|
propertyEntries,
|
|
1679
|
-
reservedProperties
|
|
1534
|
+
reservedProperties,
|
|
1680
1535
|
} = extractProperties(sourceFile, checker, classNode);
|
|
1681
1536
|
const declaredPropertyTypes = collectDeclaredPropertyTypes(classNode);
|
|
1682
1537
|
const getterNames = collectGetterNames(classNode);
|
|
@@ -1706,48 +1561,44 @@ export function lintSource(filePath, sourceText, options = {}) {
|
|
|
1706
1561
|
undefinedMethods: [],
|
|
1707
1562
|
undefinedProperties: [],
|
|
1708
1563
|
unsupportedEventNames: [],
|
|
1709
|
-
unsupportedHtmlAttributes: []
|
|
1564
|
+
unsupportedHtmlAttributes: [],
|
|
1710
1565
|
};
|
|
1711
1566
|
const templateExprs = extractTemplateExpressions(
|
|
1712
1567
|
classNode,
|
|
1713
1568
|
findings,
|
|
1714
1569
|
componentPropertyMaps,
|
|
1715
|
-
supportedProps
|
|
1570
|
+
supportedProps,
|
|
1716
1571
|
);
|
|
1717
1572
|
const methodCodeItems = collectMethodCodeItems(classNode);
|
|
1718
1573
|
const allExpressions = [...templateExprs, ...computedExprs];
|
|
1719
|
-
const allCodeItems = [...allExpressions, ...methodCodeItems].map(item => ({
|
|
1720
|
-
checkContextCalls: item.kind !==
|
|
1721
|
-
shape:
|
|
1722
|
-
...item
|
|
1574
|
+
const allCodeItems = [...allExpressions, ...methodCodeItems].map((item) => ({
|
|
1575
|
+
checkContextCalls: item.kind !== "method",
|
|
1576
|
+
shape: "shape" in item ? item.shape : "expression",
|
|
1577
|
+
...item,
|
|
1723
1578
|
}));
|
|
1724
1579
|
|
|
1725
|
-
if (allMethods.has(
|
|
1580
|
+
if (allMethods.has("formAssociatedCallback") && !formAssociated) {
|
|
1726
1581
|
const callbackMember = classNode.members.find(
|
|
1727
|
-
member =>
|
|
1728
|
-
ts.isMethodDeclaration(member) &&
|
|
1729
|
-
getMemberName(member) === 'formAssociatedCallback'
|
|
1582
|
+
(member) =>
|
|
1583
|
+
ts.isMethodDeclaration(member) && getMemberName(member) === "formAssociatedCallback",
|
|
1730
1584
|
);
|
|
1731
|
-
findings.missingRequiredMembers.push(
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
? callbackMember
|
|
1585
|
+
findings.missingRequiredMembers.push({
|
|
1586
|
+
location: callbackMember
|
|
1587
|
+
? callbackMember
|
|
1735
1588
|
.getSourceFile()
|
|
1736
1589
|
.getLineAndCharacterOfPosition(
|
|
1737
|
-
callbackMember.name.getStart(callbackMember.getSourceFile())
|
|
1590
|
+
callbackMember.name.getStart(callbackMember.getSourceFile()),
|
|
1738
1591
|
)
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
}
|
|
1743
|
-
);
|
|
1592
|
+
: null,
|
|
1593
|
+
message: "formAssociatedCallback is defined, but static formAssociated is not true",
|
|
1594
|
+
});
|
|
1744
1595
|
}
|
|
1745
1596
|
if (!hasStaticHtmlDefinition(classNode)) {
|
|
1746
1597
|
findings.missingRequiredMembers.push({
|
|
1747
1598
|
location: sourceFile.getLineAndCharacterOfPosition(
|
|
1748
|
-
classNode.name?.getStart(sourceFile) ?? classNode.getStart(sourceFile)
|
|
1599
|
+
classNode.name?.getStart(sourceFile) ?? classNode.getStart(sourceFile),
|
|
1749
1600
|
),
|
|
1750
|
-
message:
|
|
1601
|
+
message: "static html property must be defined",
|
|
1751
1602
|
});
|
|
1752
1603
|
}
|
|
1753
1604
|
|
|
@@ -1756,7 +1607,7 @@ export function lintSource(filePath, sourceText, options = {}) {
|
|
|
1756
1607
|
classNode,
|
|
1757
1608
|
supportedProps,
|
|
1758
1609
|
contextKeys,
|
|
1759
|
-
allCodeItems
|
|
1610
|
+
allCodeItems,
|
|
1760
1611
|
);
|
|
1761
1612
|
const augmentedProgram = createProgram(filePath, augmentedSource);
|
|
1762
1613
|
const augmentedSourceFile = augmentedProgram.getSourceFile(filePath);
|
|
@@ -1765,7 +1616,7 @@ export function lintSource(filePath, sourceText, options = {}) {
|
|
|
1765
1616
|
const augmentedChecker = augmentedProgram.getTypeChecker();
|
|
1766
1617
|
const augmentedClassNode = collectWrecClasses(augmentedSourceFile)[0];
|
|
1767
1618
|
if (!augmentedClassNode) {
|
|
1768
|
-
throw new Error(
|
|
1619
|
+
throw new Error("unable to find Wrec subclass after augmentation");
|
|
1769
1620
|
}
|
|
1770
1621
|
|
|
1771
1622
|
const helperCodeNodes = collectHelperCodeNodes(augmentedSourceFile);
|
|
@@ -1778,24 +1629,20 @@ export function lintSource(filePath, sourceText, options = {}) {
|
|
|
1778
1629
|
propertyEntries,
|
|
1779
1630
|
getterNames,
|
|
1780
1631
|
allMethods,
|
|
1781
|
-
findings
|
|
1632
|
+
findings,
|
|
1782
1633
|
);
|
|
1783
1634
|
collectUseStateMapErrors(classNode, supportedProps, findings);
|
|
1784
1635
|
|
|
1785
|
-
allExpressions.forEach(expr => {
|
|
1786
|
-
if (
|
|
1787
|
-
expr.eventHandler &&
|
|
1788
|
-
IDENTIFIER_RE.test(expr.text) &&
|
|
1789
|
-
!allMethods.has(expr.text)
|
|
1790
|
-
) {
|
|
1636
|
+
allExpressions.forEach((expr) => {
|
|
1637
|
+
if (expr.eventHandler && IDENTIFIER_RE.test(expr.text) && !allMethods.has(expr.text)) {
|
|
1791
1638
|
uniquePush(
|
|
1792
1639
|
findings.invalidEventHandlers,
|
|
1793
1640
|
expr.location
|
|
1794
1641
|
? {
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
: `"${expr.text}" is not a defined instance method
|
|
1642
|
+
location: expr.location,
|
|
1643
|
+
message: `"${expr.text}" is not a defined instance method`,
|
|
1644
|
+
}
|
|
1645
|
+
: `"${expr.text}" is not a defined instance method`,
|
|
1799
1646
|
);
|
|
1800
1647
|
}
|
|
1801
1648
|
});
|
|
@@ -1808,20 +1655,17 @@ export function lintSource(filePath, sourceText, options = {}) {
|
|
|
1808
1655
|
checkContextCalls: allCodeItems[index]?.checkContextCalls ?? true,
|
|
1809
1656
|
eventHandler: allCodeItems[index]?.eventHandler ?? false,
|
|
1810
1657
|
location: allCodeItems[index]?.location ?? null,
|
|
1811
|
-
sourceFile: augmentedSourceFile
|
|
1658
|
+
sourceFile: augmentedSourceFile,
|
|
1812
1659
|
});
|
|
1813
1660
|
});
|
|
1814
1661
|
|
|
1815
1662
|
findings.duplicateProperties.sort(compareLocatedFindings);
|
|
1816
1663
|
findings.extraArguments.sort(
|
|
1817
|
-
(a, b) =>
|
|
1818
|
-
a.methodName.localeCompare(b.methodName) ||
|
|
1819
|
-
a.argumentIndex - b.argumentIndex
|
|
1664
|
+
(a, b) => a.methodName.localeCompare(b.methodName) || a.argumentIndex - b.argumentIndex,
|
|
1820
1665
|
);
|
|
1821
1666
|
findings.incompatibleArguments.sort(
|
|
1822
1667
|
(a, b) =>
|
|
1823
|
-
a.methodName.localeCompare(b.methodName) ||
|
|
1824
|
-
a.parameterName.localeCompare(b.parameterName)
|
|
1668
|
+
a.methodName.localeCompare(b.methodName) || a.parameterName.localeCompare(b.parameterName),
|
|
1825
1669
|
);
|
|
1826
1670
|
findings.incompatibleDeclareTypes.sort(compareLocatedFindings);
|
|
1827
1671
|
findings.invalidCheckedBindings.sort(compareLocatedFindings);
|
|
@@ -1846,30 +1690,22 @@ export function lintSource(filePath, sourceText, options = {}) {
|
|
|
1846
1690
|
findings.unsupportedEventNames.sort(compareLocatedFindings);
|
|
1847
1691
|
findings.unsupportedHtmlAttributes.sort(compareLocatedFindings);
|
|
1848
1692
|
|
|
1849
|
-
return formatReport(
|
|
1850
|
-
filePath,
|
|
1851
|
-
supportedProps,
|
|
1852
|
-
allExpressions,
|
|
1853
|
-
findings,
|
|
1854
|
-
options
|
|
1855
|
-
);
|
|
1693
|
+
return formatReport(filePath, supportedProps, allExpressions, findings, options);
|
|
1856
1694
|
}
|
|
1857
1695
|
|
|
1858
1696
|
// Runs the command-line interface for the linter.
|
|
1859
1697
|
function main() {
|
|
1860
1698
|
const args = process.argv.slice(2);
|
|
1861
|
-
const unknownFlags = args.filter(
|
|
1862
|
-
arg => arg.startsWith('--') && arg !== '--verbose'
|
|
1863
|
-
);
|
|
1699
|
+
const unknownFlags = args.filter((arg) => arg.startsWith("--") && arg !== "--verbose");
|
|
1864
1700
|
if (unknownFlags.length > 0) {
|
|
1865
1701
|
fail(`unknown option: ${unknownFlags[0]}`);
|
|
1866
1702
|
}
|
|
1867
1703
|
|
|
1868
|
-
const verbose = args.includes(
|
|
1869
|
-
const positionalArgs = args.filter(arg => arg !==
|
|
1704
|
+
const verbose = args.includes("--verbose");
|
|
1705
|
+
const positionalArgs = args.filter((arg) => arg !== "--verbose");
|
|
1870
1706
|
|
|
1871
1707
|
if (positionalArgs.length > 1) {
|
|
1872
|
-
fail(
|
|
1708
|
+
fail("usage: node scripts/wrec-lint.js [--verbose] {file-path}");
|
|
1873
1709
|
}
|
|
1874
1710
|
|
|
1875
1711
|
const [filePath] = positionalArgs;
|
|
@@ -1878,24 +1714,23 @@ function main() {
|
|
|
1878
1714
|
process.stdout.write(
|
|
1879
1715
|
lintFile(validateFilePath(filePath), {
|
|
1880
1716
|
showFileHeader: false,
|
|
1881
|
-
verbose
|
|
1882
|
-
})
|
|
1717
|
+
verbose,
|
|
1718
|
+
}),
|
|
1883
1719
|
);
|
|
1884
1720
|
return;
|
|
1885
1721
|
}
|
|
1886
1722
|
|
|
1887
1723
|
const rootDir = process.cwd();
|
|
1888
1724
|
let previousHadIssues = false;
|
|
1889
|
-
findWrecFiles(rootDir, matchedFile => {
|
|
1725
|
+
findWrecFiles(rootDir, (matchedFile) => {
|
|
1890
1726
|
const report = lintFile(matchedFile, {
|
|
1891
|
-
fileLabel:
|
|
1892
|
-
path.relative(rootDir, matchedFile) || path.basename(matchedFile),
|
|
1727
|
+
fileLabel: path.relative(rootDir, matchedFile) || path.basename(matchedFile),
|
|
1893
1728
|
showDetailsForCleanFile: false,
|
|
1894
1729
|
showNoIssuesMessage: false,
|
|
1895
|
-
verbose
|
|
1730
|
+
verbose,
|
|
1896
1731
|
});
|
|
1897
|
-
const currentHasIssues = report.trim().includes(
|
|
1898
|
-
if (previousHadIssues) process.stdout.write(
|
|
1732
|
+
const currentHasIssues = report.trim().includes("\n");
|
|
1733
|
+
if (previousHadIssues) process.stdout.write("\n");
|
|
1899
1734
|
process.stdout.write(report);
|
|
1900
1735
|
previousHadIssues = currentHasIssues;
|
|
1901
1736
|
});
|
|
@@ -1904,7 +1739,7 @@ function main() {
|
|
|
1904
1739
|
// Determines whether a symbol should be treated as a required context function.
|
|
1905
1740
|
function requiresContextFunction(symbol, sourceFile) {
|
|
1906
1741
|
const declarations = symbol.declarations ?? [];
|
|
1907
|
-
return declarations.some(declaration => {
|
|
1742
|
+
return declarations.some((declaration) => {
|
|
1908
1743
|
if (isImportLikeDeclaration(declaration)) return true;
|
|
1909
1744
|
return declaration.getSourceFile() === sourceFile;
|
|
1910
1745
|
});
|
|
@@ -1912,66 +1747,58 @@ function requiresContextFunction(symbol, sourceFile) {
|
|
|
1912
1747
|
|
|
1913
1748
|
// Resolves a relative import path to an existing source file.
|
|
1914
1749
|
function resolveImportPath(baseDir, importPath) {
|
|
1915
|
-
if (!importPath.startsWith(
|
|
1750
|
+
if (!importPath.startsWith(".")) return undefined;
|
|
1916
1751
|
|
|
1917
1752
|
const candidates = [
|
|
1918
1753
|
path.resolve(baseDir, importPath),
|
|
1919
1754
|
path.resolve(baseDir, `${importPath}.js`),
|
|
1920
1755
|
path.resolve(baseDir, `${importPath}.ts`),
|
|
1921
|
-
path.resolve(baseDir, importPath,
|
|
1922
|
-
path.resolve(baseDir, importPath,
|
|
1756
|
+
path.resolve(baseDir, importPath, "index.js"),
|
|
1757
|
+
path.resolve(baseDir, importPath, "index.ts"),
|
|
1923
1758
|
];
|
|
1924
1759
|
|
|
1925
|
-
return candidates.find(candidate => fs.existsSync(candidate));
|
|
1760
|
+
return candidates.find((candidate) => fs.existsSync(candidate));
|
|
1926
1761
|
}
|
|
1927
1762
|
|
|
1928
1763
|
// Rewrites synthetic receiver references back to this for reporting.
|
|
1929
1764
|
function toUserFacingExpression(text) {
|
|
1930
|
-
return text.replaceAll(WREC_REF_NAME,
|
|
1765
|
+
return text.replaceAll(WREC_REF_NAME, "this");
|
|
1931
1766
|
}
|
|
1932
1767
|
|
|
1933
1768
|
// Returns whether a static property config type is compatible with a declare type.
|
|
1934
|
-
function typeExpressionMatchesDeclaredType(
|
|
1935
|
-
checker,
|
|
1936
|
-
typeExpression,
|
|
1937
|
-
declaredTypeNode
|
|
1938
|
-
) {
|
|
1769
|
+
function typeExpressionMatchesDeclaredType(checker, typeExpression, declaredTypeNode) {
|
|
1939
1770
|
const typeKind = typeExpressionKind(typeExpression);
|
|
1940
1771
|
const declaredType = checker.getTypeFromTypeNode(declaredTypeNode);
|
|
1941
1772
|
|
|
1942
|
-
if (typeKind ===
|
|
1773
|
+
if (typeKind === "Boolean") {
|
|
1943
1774
|
return checker.isTypeAssignableTo(checker.getBooleanType(), declaredType);
|
|
1944
1775
|
}
|
|
1945
1776
|
|
|
1946
|
-
if (typeKind ===
|
|
1777
|
+
if (typeKind === "Number") {
|
|
1947
1778
|
return checker.isTypeAssignableTo(checker.getNumberType(), declaredType);
|
|
1948
1779
|
}
|
|
1949
1780
|
|
|
1950
|
-
if (typeKind ===
|
|
1781
|
+
if (typeKind === "String") {
|
|
1951
1782
|
return checker.isTypeAssignableTo(checker.getStringType(), declaredType);
|
|
1952
1783
|
}
|
|
1953
1784
|
|
|
1954
|
-
if (typeKind ===
|
|
1785
|
+
if (typeKind === "Object") {
|
|
1955
1786
|
return isNonArrayObjectLikeType(checker, declaredType);
|
|
1956
1787
|
}
|
|
1957
1788
|
|
|
1958
|
-
if (typeKind ===
|
|
1959
|
-
const declaredTypes = declaredType.isUnion()
|
|
1960
|
-
? declaredType.types
|
|
1961
|
-
: [declaredType];
|
|
1789
|
+
if (typeKind === "Array") {
|
|
1790
|
+
const declaredTypes = declaredType.isUnion() ? declaredType.types : [declaredType];
|
|
1962
1791
|
return declaredTypes.every(
|
|
1963
|
-
type =>
|
|
1792
|
+
(type) =>
|
|
1964
1793
|
Boolean(type.flags & (ts.TypeFlags.Any | ts.TypeFlags.Unknown)) ||
|
|
1965
1794
|
checker.isArrayType(type) ||
|
|
1966
|
-
checker.isTupleType(type)
|
|
1795
|
+
checker.isTupleType(type),
|
|
1967
1796
|
);
|
|
1968
1797
|
}
|
|
1969
1798
|
|
|
1970
|
-
if (typeKind ===
|
|
1799
|
+
if (typeKind === "HTMLElement") {
|
|
1971
1800
|
const elementType = getConstructedType(checker, typeExpression);
|
|
1972
|
-
return elementType
|
|
1973
|
-
? checker.isTypeAssignableTo(declaredType, elementType)
|
|
1974
|
-
: false;
|
|
1801
|
+
return elementType ? checker.isTypeAssignableTo(declaredType, elementType) : false;
|
|
1975
1802
|
}
|
|
1976
1803
|
|
|
1977
1804
|
const runtimeType = checker.getTypeAtLocation(typeExpression);
|
|
@@ -1989,25 +1816,22 @@ function typeExpressionKind(expression) {
|
|
|
1989
1816
|
function typeNodeFromConstructorExpression(expression) {
|
|
1990
1817
|
if (ts.isIdentifier(expression)) {
|
|
1991
1818
|
switch (expression.text) {
|
|
1992
|
-
case
|
|
1819
|
+
case "String":
|
|
1993
1820
|
return ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword);
|
|
1994
|
-
case
|
|
1821
|
+
case "Number":
|
|
1995
1822
|
return ts.factory.createKeywordTypeNode(ts.SyntaxKind.NumberKeyword);
|
|
1996
|
-
case
|
|
1823
|
+
case "Boolean":
|
|
1997
1824
|
return ts.factory.createKeywordTypeNode(ts.SyntaxKind.BooleanKeyword);
|
|
1998
|
-
case
|
|
1825
|
+
case "Object":
|
|
1999
1826
|
return ts.factory.createKeywordTypeNode(ts.SyntaxKind.ObjectKeyword);
|
|
2000
1827
|
default:
|
|
2001
1828
|
return ts.factory.createTypeReferenceNode(expression.text);
|
|
2002
1829
|
}
|
|
2003
1830
|
}
|
|
2004
1831
|
|
|
2005
|
-
if (
|
|
2006
|
-
ts.isCallExpression(expression) &&
|
|
2007
|
-
ts.isIdentifier(expression.expression)
|
|
2008
|
-
) {
|
|
1832
|
+
if (ts.isCallExpression(expression) && ts.isIdentifier(expression.expression)) {
|
|
2009
1833
|
const name = expression.expression.text;
|
|
2010
|
-
if (name ===
|
|
1834
|
+
if (name === "Array" && expression.typeArguments?.length === 1) {
|
|
2011
1835
|
return ts.factory.createArrayTypeNode(expression.typeArguments[0]);
|
|
2012
1836
|
}
|
|
2013
1837
|
}
|
|
@@ -2022,9 +1846,7 @@ function typeNodeFromConstructorExpression(expression) {
|
|
|
2022
1846
|
// Pushes a value into an array only if it is not already present.
|
|
2023
1847
|
function uniquePush(array, value) {
|
|
2024
1848
|
const valueText = getLocatedFindingMessage(value);
|
|
2025
|
-
if (
|
|
2026
|
-
!array.some(existing => getLocatedFindingMessage(existing) === valueText)
|
|
2027
|
-
) {
|
|
1849
|
+
if (!array.some((existing) => getLocatedFindingMessage(existing) === valueText)) {
|
|
2028
1850
|
array.push(value);
|
|
2029
1851
|
}
|
|
2030
1852
|
}
|
|
@@ -2036,15 +1858,15 @@ function validateCheckedBinding(
|
|
|
2036
1858
|
attrValue,
|
|
2037
1859
|
findings,
|
|
2038
1860
|
supportedProps,
|
|
2039
|
-
resolveLocation
|
|
1861
|
+
resolveLocation,
|
|
2040
1862
|
) {
|
|
2041
|
-
if (getHtmlTagName(node) !==
|
|
1863
|
+
if (getHtmlTagName(node) !== "input") return;
|
|
2042
1864
|
|
|
2043
|
-
const [baseAttrName] = attrName.split(
|
|
2044
|
-
if (baseAttrName !==
|
|
1865
|
+
const [baseAttrName] = attrName.split(":");
|
|
1866
|
+
if (baseAttrName !== "checked") return;
|
|
2045
1867
|
|
|
2046
1868
|
const inputType = getInputType(node);
|
|
2047
|
-
if (inputType !==
|
|
1869
|
+
if (inputType !== "checkbox" && inputType !== "radio") return;
|
|
2048
1870
|
|
|
2049
1871
|
const propName = getPropertyNameInAttribute(attrValue);
|
|
2050
1872
|
if (!propName) return;
|
|
@@ -2052,19 +1874,17 @@ function validateCheckedBinding(
|
|
|
2052
1874
|
const propInfo = supportedProps.get(propName);
|
|
2053
1875
|
if (!propInfo) return;
|
|
2054
1876
|
|
|
2055
|
-
const expectedType = inputType ===
|
|
1877
|
+
const expectedType = inputType === "checkbox" ? "boolean" : "string";
|
|
2056
1878
|
if (propInfo.typeText === expectedType) return;
|
|
2057
1879
|
|
|
2058
1880
|
const expectedTypeName = getPropertyConfigTypeName(expectedType);
|
|
2059
1881
|
|
|
2060
|
-
findings.invalidCheckedBindings.push(
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
}
|
|
2067
|
-
);
|
|
1882
|
+
findings.invalidCheckedBindings.push({
|
|
1883
|
+
location: getHtmlAttributeLocation(node, attrName, resolveLocation),
|
|
1884
|
+
message:
|
|
1885
|
+
`input type="${inputType}" attribute "${attrName}" refers to ` +
|
|
1886
|
+
`property "${propName}" whose type is not ${expectedTypeName}`,
|
|
1887
|
+
});
|
|
2068
1888
|
}
|
|
2069
1889
|
|
|
2070
1890
|
// Validates value bindings on native form controls.
|
|
@@ -2074,10 +1894,10 @@ function validateValueBinding(
|
|
|
2074
1894
|
attrValue,
|
|
2075
1895
|
findings,
|
|
2076
1896
|
supportedProps,
|
|
2077
|
-
resolveLocation
|
|
1897
|
+
resolveLocation,
|
|
2078
1898
|
) {
|
|
2079
|
-
const [baseAttrName] = attrName.split(
|
|
2080
|
-
if (baseAttrName !==
|
|
1899
|
+
const [baseAttrName] = attrName.split(":");
|
|
1900
|
+
if (baseAttrName !== "value") return;
|
|
2081
1901
|
if (!isNativeFormControl(node)) return;
|
|
2082
1902
|
|
|
2083
1903
|
const propName = getPropertyNameInAttribute(attrValue);
|
|
@@ -2086,18 +1906,16 @@ function validateValueBinding(
|
|
|
2086
1906
|
const propInfo = supportedProps.get(propName);
|
|
2087
1907
|
if (!propInfo) return;
|
|
2088
1908
|
|
|
2089
|
-
if (propInfo.typeText ===
|
|
1909
|
+
if (propInfo.typeText === "number" || propInfo.typeText === "string") {
|
|
2090
1910
|
return;
|
|
2091
1911
|
}
|
|
2092
1912
|
|
|
2093
|
-
findings.invalidValueBindings.push(
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
}
|
|
2100
|
-
);
|
|
1913
|
+
findings.invalidValueBindings.push({
|
|
1914
|
+
location: getHtmlAttributeLocation(node, attrName, resolveLocation),
|
|
1915
|
+
message:
|
|
1916
|
+
`${getHtmlTagName(node)} attribute "${attrName}" refers to property ` +
|
|
1917
|
+
`"${propName}" whose type is not String or Number`,
|
|
1918
|
+
});
|
|
2101
1919
|
}
|
|
2102
1920
|
|
|
2103
1921
|
// Validates computed property references and method calls.
|
|
@@ -2107,46 +1925,33 @@ function validateComputedProperty(
|
|
|
2107
1925
|
supportedProps,
|
|
2108
1926
|
classMethods,
|
|
2109
1927
|
findings,
|
|
2110
|
-
location
|
|
1928
|
+
location,
|
|
2111
1929
|
) {
|
|
2112
1930
|
for (const match of computedText.matchAll(THIS_REF_RE)) {
|
|
2113
1931
|
const referencedName = match[1];
|
|
2114
|
-
if (
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
location,
|
|
2121
|
-
message:
|
|
2122
|
-
`property "${propName}" computed references ` +
|
|
2123
|
-
`missing property "${referencedName}"`
|
|
2124
|
-
}
|
|
2125
|
-
);
|
|
1932
|
+
if (!supportedProps.has(referencedName) && !classMethods.has(referencedName)) {
|
|
1933
|
+
findings.invalidComputedProperties.push({
|
|
1934
|
+
location,
|
|
1935
|
+
message:
|
|
1936
|
+
`property "${propName}" computed references ` + `missing property "${referencedName}"`,
|
|
1937
|
+
});
|
|
2126
1938
|
}
|
|
2127
1939
|
}
|
|
2128
1940
|
|
|
2129
1941
|
for (const match of computedText.matchAll(THIS_CALL_RE)) {
|
|
2130
1942
|
const methodName = match[1];
|
|
2131
1943
|
if (!classMethods.has(methodName)) {
|
|
2132
|
-
findings.invalidComputedProperties.push(
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
`non-method instance member "${methodName}"`
|
|
2138
|
-
}
|
|
2139
|
-
);
|
|
1944
|
+
findings.invalidComputedProperties.push({
|
|
1945
|
+
location,
|
|
1946
|
+
message:
|
|
1947
|
+
`property "${propName}" computed calls ` + `non-method instance member "${methodName}"`,
|
|
1948
|
+
});
|
|
2140
1949
|
}
|
|
2141
1950
|
}
|
|
2142
1951
|
}
|
|
2143
1952
|
|
|
2144
1953
|
// Validates that computed properties do not form dependency cycles.
|
|
2145
|
-
function validateComputedPropertyCycles(
|
|
2146
|
-
computedDependencies,
|
|
2147
|
-
computedLocations,
|
|
2148
|
-
findings
|
|
2149
|
-
) {
|
|
1954
|
+
function validateComputedPropertyCycles(computedDependencies, computedLocations, findings) {
|
|
2150
1955
|
const computedNames = [...computedDependencies.keys()].sort();
|
|
2151
1956
|
const dependencyCountMap = new Map();
|
|
2152
1957
|
const dependentsMap = new Map();
|
|
@@ -2183,20 +1988,16 @@ function validateComputedPropertyCycles(
|
|
|
2183
1988
|
if (orderedNames.length === computedNames.length) return;
|
|
2184
1989
|
|
|
2185
1990
|
const cycleNames = computedNames.filter(
|
|
2186
|
-
computedName => dependencyCountMap.get(computedName) > 0
|
|
1991
|
+
(computedName) => dependencyCountMap.get(computedName) > 0,
|
|
2187
1992
|
);
|
|
2188
1993
|
const cycleLocation = cycleNames
|
|
2189
|
-
.map(name => computedLocations.get(name))
|
|
1994
|
+
.map((name) => computedLocations.get(name))
|
|
2190
1995
|
.filter(Boolean)
|
|
2191
|
-
.sort(
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
location: cycleLocation ?? null,
|
|
2197
|
-
message: `computed properties form a cycle: ${cycleNames.join(', ')}`
|
|
2198
|
-
}
|
|
2199
|
-
);
|
|
1996
|
+
.sort((a, b) => a.line - b.line || a.character - b.character)[0];
|
|
1997
|
+
findings.invalidComputedProperties.push({
|
|
1998
|
+
location: cycleLocation ?? null,
|
|
1999
|
+
message: `computed properties form a cycle: ${cycleNames.join(", ")}`,
|
|
2000
|
+
});
|
|
2200
2001
|
}
|
|
2201
2002
|
|
|
2202
2003
|
// Validates that a default value matches the declared property type.
|
|
@@ -2207,50 +2008,44 @@ function validateDefaultValue(checker, typeExpression, valueExpression) {
|
|
|
2207
2008
|
const valueType = checker.getTypeAtLocation(valueExpression);
|
|
2208
2009
|
const valueTypeName = checker.typeToString(valueType);
|
|
2209
2010
|
|
|
2210
|
-
if (typeKind ===
|
|
2211
|
-
if (
|
|
2212
|
-
|
|
2213
|
-
) {
|
|
2214
|
-
return {typeName: 'String', valueTypeName};
|
|
2011
|
+
if (typeKind === "String") {
|
|
2012
|
+
if (!(valueType.flags & (ts.TypeFlags.String | ts.TypeFlags.StringLiteral))) {
|
|
2013
|
+
return { typeName: "String", valueTypeName };
|
|
2215
2014
|
}
|
|
2216
2015
|
return undefined;
|
|
2217
2016
|
}
|
|
2218
2017
|
|
|
2219
|
-
if (typeKind ===
|
|
2220
|
-
if (
|
|
2221
|
-
|
|
2222
|
-
) {
|
|
2223
|
-
return {typeName: 'Number', valueTypeName};
|
|
2018
|
+
if (typeKind === "Number") {
|
|
2019
|
+
if (!(valueType.flags & (ts.TypeFlags.Number | ts.TypeFlags.NumberLiteral))) {
|
|
2020
|
+
return { typeName: "Number", valueTypeName };
|
|
2224
2021
|
}
|
|
2225
2022
|
return undefined;
|
|
2226
2023
|
}
|
|
2227
2024
|
|
|
2228
|
-
if (typeKind ===
|
|
2229
|
-
if (
|
|
2230
|
-
|
|
2231
|
-
) {
|
|
2232
|
-
return {typeName: 'Boolean', valueTypeName};
|
|
2025
|
+
if (typeKind === "Boolean") {
|
|
2026
|
+
if (!(valueType.flags & (ts.TypeFlags.Boolean | ts.TypeFlags.BooleanLiteral))) {
|
|
2027
|
+
return { typeName: "Boolean", valueTypeName };
|
|
2233
2028
|
}
|
|
2234
2029
|
return undefined;
|
|
2235
2030
|
}
|
|
2236
2031
|
|
|
2237
|
-
if (typeKind ===
|
|
2032
|
+
if (typeKind === "Array") {
|
|
2238
2033
|
if (!checker.isArrayType(valueType) && !checker.isTupleType(valueType)) {
|
|
2239
|
-
return {typeName:
|
|
2034
|
+
return { typeName: "Array", valueTypeName };
|
|
2240
2035
|
}
|
|
2241
2036
|
return undefined;
|
|
2242
2037
|
}
|
|
2243
2038
|
|
|
2244
|
-
if (typeKind ===
|
|
2039
|
+
if (typeKind === "Object") {
|
|
2245
2040
|
if (!isNonArrayObjectLikeType(checker, valueType)) {
|
|
2246
|
-
return {typeName:
|
|
2041
|
+
return { typeName: "Object", valueTypeName };
|
|
2247
2042
|
}
|
|
2248
2043
|
}
|
|
2249
2044
|
|
|
2250
|
-
if (typeKind ===
|
|
2045
|
+
if (typeKind === "HTMLElement") {
|
|
2251
2046
|
const elementType = getConstructedType(checker, typeExpression);
|
|
2252
2047
|
if (!elementType || !checker.isTypeAssignableTo(valueType, elementType)) {
|
|
2253
|
-
return {typeName:
|
|
2048
|
+
return { typeName: "HTMLElement", valueTypeName };
|
|
2254
2049
|
}
|
|
2255
2050
|
}
|
|
2256
2051
|
|
|
@@ -2261,8 +2056,8 @@ function validateDefaultValue(checker, typeExpression, valueExpression) {
|
|
|
2261
2056
|
function validateFilePath(filePath) {
|
|
2262
2057
|
const resolved = path.resolve(filePath);
|
|
2263
2058
|
const ext = path.extname(resolved);
|
|
2264
|
-
if (ext !==
|
|
2265
|
-
throw new Error(
|
|
2059
|
+
if (ext !== ".js" && ext !== ".ts") {
|
|
2060
|
+
throw new Error("argument must be a path to a .js or .ts file");
|
|
2266
2061
|
}
|
|
2267
2062
|
if (!fs.existsSync(resolved)) {
|
|
2268
2063
|
throw new Error(`file not found: ${resolved}`);
|
|
@@ -2271,30 +2066,20 @@ function validateFilePath(filePath) {
|
|
|
2271
2066
|
}
|
|
2272
2067
|
|
|
2273
2068
|
// Validates the syntax of a form-assoc attribute value.
|
|
2274
|
-
function validateFormAssocAttribute(
|
|
2275
|
-
|
|
2276
|
-
attrName,
|
|
2277
|
-
attrValue,
|
|
2278
|
-
findings,
|
|
2279
|
-
resolveLocation
|
|
2280
|
-
) {
|
|
2281
|
-
if (attrName !== 'form-assoc') return;
|
|
2069
|
+
function validateFormAssocAttribute(node, attrName, attrValue, findings, resolveLocation) {
|
|
2070
|
+
if (attrName !== "form-assoc") return;
|
|
2282
2071
|
|
|
2283
|
-
const pairs = attrValue.split(
|
|
2072
|
+
const pairs = attrValue.split(",");
|
|
2284
2073
|
for (const pair of pairs) {
|
|
2285
2074
|
const trimmed = pair.trim();
|
|
2286
|
-
const [propName, fieldName, ...rest] = trimmed
|
|
2287
|
-
.split(':')
|
|
2288
|
-
.map(part => part.trim());
|
|
2075
|
+
const [propName, fieldName, ...rest] = trimmed.split(":").map((part) => part.trim());
|
|
2289
2076
|
if (!trimmed || rest.length > 0 || !propName || !fieldName) {
|
|
2290
|
-
findings.invalidFormAssocValues.push(
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
}
|
|
2297
|
-
);
|
|
2077
|
+
findings.invalidFormAssocValues.push({
|
|
2078
|
+
location: getHtmlAttributeLocation(node, attrName, resolveLocation),
|
|
2079
|
+
message:
|
|
2080
|
+
`form-assoc="${attrValue}" is invalid; expected ` +
|
|
2081
|
+
'"property:field" or a comma-separated list of them',
|
|
2082
|
+
});
|
|
2298
2083
|
return;
|
|
2299
2084
|
}
|
|
2300
2085
|
}
|
|
@@ -2307,79 +2092,66 @@ function validateFormAssocPropertyMappings(
|
|
|
2307
2092
|
attrValue,
|
|
2308
2093
|
findings,
|
|
2309
2094
|
componentPropertyMaps,
|
|
2310
|
-
resolveLocation
|
|
2095
|
+
resolveLocation,
|
|
2311
2096
|
) {
|
|
2312
|
-
if (attrName !==
|
|
2313
|
-
const tagName = (node.rawTagName || node.tagName ||
|
|
2097
|
+
if (attrName !== "form-assoc") return;
|
|
2098
|
+
const tagName = (node.rawTagName || node.tagName || "").toLowerCase();
|
|
2314
2099
|
const supportedProps = componentPropertyMaps.get(tagName);
|
|
2315
2100
|
if (!supportedProps) return;
|
|
2316
2101
|
|
|
2317
|
-
const pairs = attrValue.split(
|
|
2102
|
+
const pairs = attrValue.split(",");
|
|
2318
2103
|
for (const pair of pairs) {
|
|
2319
|
-
const [propName] = pair.split(
|
|
2104
|
+
const [propName] = pair.split(":").map((part) => part.trim());
|
|
2320
2105
|
if (!propName) continue;
|
|
2321
2106
|
if (!supportedProps.has(propName)) {
|
|
2322
|
-
findings.invalidFormAssocValues.push(
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
`missing component property "${propName}"`
|
|
2328
|
-
}
|
|
2329
|
-
);
|
|
2107
|
+
findings.invalidFormAssocValues.push({
|
|
2108
|
+
location: getHtmlAttributeLocation(node, attrName, resolveLocation),
|
|
2109
|
+
message:
|
|
2110
|
+
`form-assoc="${attrValue}" refers to ` + `missing component property "${propName}"`,
|
|
2111
|
+
});
|
|
2330
2112
|
}
|
|
2331
2113
|
}
|
|
2332
2114
|
}
|
|
2333
2115
|
|
|
2334
2116
|
// Validates that an HTML attribute is supported for the current element.
|
|
2335
2117
|
function validateHtmlAttribute(node, attrName, findings, resolveLocation) {
|
|
2336
|
-
if (attrName.startsWith(
|
|
2337
|
-
if (attrName.startsWith(
|
|
2338
|
-
if (attrName ===
|
|
2339
|
-
if (attrName ===
|
|
2118
|
+
if (attrName.startsWith("aria-") || attrName.startsWith("data-")) return;
|
|
2119
|
+
if (attrName.startsWith("on")) return;
|
|
2120
|
+
if (attrName === "form-assoc") return;
|
|
2121
|
+
if (attrName === "ref") return;
|
|
2340
2122
|
|
|
2341
|
-
const [baseAttrName] = attrName.split(
|
|
2123
|
+
const [baseAttrName] = attrName.split(":");
|
|
2342
2124
|
if (HTML_GLOBAL_ATTRIBUTES.has(baseAttrName)) return;
|
|
2343
2125
|
|
|
2344
|
-
const tagName = (node.rawTagName || node.tagName ||
|
|
2345
|
-
if (!tagName || tagName.includes(
|
|
2126
|
+
const tagName = (node.rawTagName || node.tagName || "").toLowerCase();
|
|
2127
|
+
if (!tagName || tagName.includes("-")) return;
|
|
2346
2128
|
|
|
2347
2129
|
const supported = getSupportedHtmlAttributes(tagName);
|
|
2348
2130
|
if (!supported) return;
|
|
2349
2131
|
if (supported.has(baseAttrName)) return;
|
|
2350
2132
|
|
|
2351
|
-
findings.unsupportedHtmlAttributes.push(
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
}
|
|
2356
|
-
);
|
|
2133
|
+
findings.unsupportedHtmlAttributes.push({
|
|
2134
|
+
location: getHtmlAttributeLocation(node, attrName, resolveLocation),
|
|
2135
|
+
message: `${tagName} attribute "${attrName}" is not supported`,
|
|
2136
|
+
});
|
|
2357
2137
|
}
|
|
2358
2138
|
|
|
2359
2139
|
// Validates required parent-child relationships for supported HTML tags.
|
|
2360
2140
|
function validateHtmlNesting(node, findings, resolveLocation) {
|
|
2361
2141
|
const tagName = getHtmlTagName(node);
|
|
2362
|
-
if (!tagName || tagName.includes(
|
|
2142
|
+
if (!tagName || tagName.includes("-")) return;
|
|
2363
2143
|
|
|
2364
2144
|
const parentNode = node.parentNode?.nodeType === 1 ? node.parentNode : null;
|
|
2365
|
-
const parentTagName = parentNode ? getHtmlTagName(parentNode) :
|
|
2145
|
+
const parentTagName = parentNode ? getHtmlTagName(parentNode) : "";
|
|
2366
2146
|
const allowedParents = HTML_ALLOWED_PARENTS.get(tagName);
|
|
2367
|
-
if (
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
{
|
|
2376
|
-
location: getHtmlNodeLocation(node, resolveLocation),
|
|
2377
|
-
message:
|
|
2378
|
-
`<${tagName}> must be nested inside ${[...allowedParents]
|
|
2379
|
-
.map(name => `<${name}>`)
|
|
2380
|
-
.join(' or ')}, but parent is ${parentDescription}`
|
|
2381
|
-
}
|
|
2382
|
-
);
|
|
2147
|
+
if (allowedParents && (!parentTagName || !allowedParents.has(parentTagName))) {
|
|
2148
|
+
const parentDescription = parentTagName ? `<${parentTagName}>` : "the document root";
|
|
2149
|
+
findings.invalidHtmlNesting.push({
|
|
2150
|
+
location: getHtmlNodeLocation(node, resolveLocation),
|
|
2151
|
+
message: `<${tagName}> must be nested inside ${[...allowedParents]
|
|
2152
|
+
.map((name) => `<${name}>`)
|
|
2153
|
+
.join(" or ")}, but parent is ${parentDescription}`,
|
|
2154
|
+
});
|
|
2383
2155
|
}
|
|
2384
2156
|
|
|
2385
2157
|
const allowedChildren = HTML_ALLOWED_CHILDREN.get(tagName);
|
|
@@ -2388,15 +2160,13 @@ function validateHtmlNesting(node, findings, resolveLocation) {
|
|
|
2388
2160
|
for (const child of node.childNodes ?? []) {
|
|
2389
2161
|
if (child.nodeType !== 1) continue;
|
|
2390
2162
|
const childTagName = getHtmlTagName(child);
|
|
2391
|
-
if (!childTagName || childTagName.includes(
|
|
2163
|
+
if (!childTagName || childTagName.includes("-")) continue;
|
|
2392
2164
|
if (allowedChildren.has(childTagName)) continue;
|
|
2393
2165
|
|
|
2394
|
-
findings.invalidHtmlNesting.push(
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
}
|
|
2399
|
-
);
|
|
2166
|
+
findings.invalidHtmlNesting.push({
|
|
2167
|
+
location: getHtmlNodeLocation(child, resolveLocation),
|
|
2168
|
+
message: `<${childTagName}> is not allowed directly inside <${tagName}>`,
|
|
2169
|
+
});
|
|
2400
2170
|
}
|
|
2401
2171
|
}
|
|
2402
2172
|
|
|
@@ -2409,14 +2179,14 @@ function validatePropertyConfigs(
|
|
|
2409
2179
|
propertyEntries,
|
|
2410
2180
|
getterNames,
|
|
2411
2181
|
classMethods,
|
|
2412
|
-
findings
|
|
2182
|
+
findings,
|
|
2413
2183
|
) {
|
|
2414
2184
|
const computedDependencies = new Map();
|
|
2415
2185
|
const computedLocations = new Map();
|
|
2416
2186
|
const computedPropNames = new Set();
|
|
2417
2187
|
|
|
2418
|
-
for (const {config, propName} of propertyEntries) {
|
|
2419
|
-
const computedProp = getObjectProperty(config,
|
|
2188
|
+
for (const { config, propName } of propertyEntries) {
|
|
2189
|
+
const computedProp = getObjectProperty(config, "computed");
|
|
2420
2190
|
if (
|
|
2421
2191
|
computedProp &&
|
|
2422
2192
|
ts.isPropertyAssignment(computedProp) &&
|
|
@@ -2425,81 +2195,59 @@ function validatePropertyConfigs(
|
|
|
2425
2195
|
) {
|
|
2426
2196
|
computedLocations.set(
|
|
2427
2197
|
propName,
|
|
2428
|
-
sourceFile.getLineAndCharacterOfPosition(
|
|
2429
|
-
computedProp.initializer.getStart(sourceFile)
|
|
2430
|
-
)
|
|
2198
|
+
sourceFile.getLineAndCharacterOfPosition(computedProp.initializer.getStart(sourceFile)),
|
|
2431
2199
|
);
|
|
2432
2200
|
computedPropNames.add(propName);
|
|
2433
2201
|
}
|
|
2434
2202
|
}
|
|
2435
2203
|
|
|
2436
|
-
for (const {config, propName, property} of propertyEntries) {
|
|
2437
|
-
const computedProp = getObjectProperty(config,
|
|
2204
|
+
for (const { config, propName, property } of propertyEntries) {
|
|
2205
|
+
const computedProp = getObjectProperty(config, "computed");
|
|
2438
2206
|
const declaredTypeNode = declaredPropertyTypes.get(propName);
|
|
2439
|
-
const typeProp = getObjectProperty(config,
|
|
2440
|
-
const usedByProp = getObjectProperty(config,
|
|
2441
|
-
const valueProp = getObjectProperty(config,
|
|
2442
|
-
const valuesProp = getObjectProperty(config,
|
|
2207
|
+
const typeProp = getObjectProperty(config, "type");
|
|
2208
|
+
const usedByProp = getObjectProperty(config, "usedBy");
|
|
2209
|
+
const valueProp = getObjectProperty(config, "value");
|
|
2210
|
+
const valuesProp = getObjectProperty(config, "values");
|
|
2443
2211
|
const valuesConfigError = getValuesConfigurationError(valuesProp);
|
|
2444
2212
|
const propertyLocation = sourceFile.getLineAndCharacterOfPosition(
|
|
2445
|
-
property.name.getStart(sourceFile)
|
|
2213
|
+
property.name.getStart(sourceFile),
|
|
2446
2214
|
);
|
|
2447
2215
|
|
|
2448
2216
|
const typeExpression =
|
|
2449
|
-
typeProp && ts.isPropertyAssignment(typeProp)
|
|
2450
|
-
? typeProp.initializer
|
|
2451
|
-
: undefined;
|
|
2217
|
+
typeProp && ts.isPropertyAssignment(typeProp) ? typeProp.initializer : undefined;
|
|
2452
2218
|
|
|
2453
2219
|
if (!typeExpression) {
|
|
2454
|
-
findings.missingTypeProperties.push(
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
}
|
|
2459
|
-
);
|
|
2460
|
-
} else if (
|
|
2461
|
-
SUPPORTED_PROPERTY_TYPE_NAMES.has(
|
|
2462
|
-
getPropertyTypeGenericBaseName(sourceFile, typeExpression)
|
|
2463
|
-
)
|
|
2464
|
-
) {
|
|
2465
|
-
findings.invalidTypeProperties.push(
|
|
2466
|
-
{
|
|
2467
|
-
location: propertyLocation,
|
|
2468
|
-
message:
|
|
2469
|
-
`property "${propName}" type cannot use generic syntax like ` +
|
|
2470
|
-
`"${typeExpression.getText(sourceFile).trim()}"; use ` +
|
|
2471
|
-
`"${getPropertyTypeGenericBaseName(sourceFile, typeExpression)}" instead`
|
|
2472
|
-
}
|
|
2473
|
-
);
|
|
2220
|
+
findings.missingTypeProperties.push({
|
|
2221
|
+
location: propertyLocation,
|
|
2222
|
+
message: `property "${propName}" does not specify a type`,
|
|
2223
|
+
});
|
|
2474
2224
|
} else if (
|
|
2475
|
-
|
|
2225
|
+
SUPPORTED_PROPERTY_TYPE_NAMES.has(getPropertyTypeGenericBaseName(sourceFile, typeExpression))
|
|
2476
2226
|
) {
|
|
2477
|
-
findings.invalidTypeProperties.push(
|
|
2478
|
-
|
|
2227
|
+
findings.invalidTypeProperties.push({
|
|
2228
|
+
location: propertyLocation,
|
|
2229
|
+
message:
|
|
2230
|
+
`property "${propName}" type cannot use generic syntax like ` +
|
|
2231
|
+
`"${typeExpression.getText(sourceFile).trim()}"; use ` +
|
|
2232
|
+
`"${getPropertyTypeGenericBaseName(sourceFile, typeExpression)}" instead`,
|
|
2233
|
+
});
|
|
2234
|
+
} else if (!SUPPORTED_PROPERTY_TYPE_NAMES.has(typeExpressionKind(typeExpression))) {
|
|
2235
|
+
findings.invalidTypeProperties.push({
|
|
2236
|
+
location: propertyLocation,
|
|
2237
|
+
message:
|
|
2238
|
+
`property "${propName}" type must be one of ` +
|
|
2239
|
+
"Boolean, Number, String, Object, Array, or HTMLElement",
|
|
2240
|
+
});
|
|
2241
|
+
} else if (declaredTypeNode) {
|
|
2242
|
+
if (!typeExpressionMatchesDeclaredType(checker, typeExpression, declaredTypeNode)) {
|
|
2243
|
+
findings.incompatibleDeclareTypes.push({
|
|
2479
2244
|
location: propertyLocation,
|
|
2480
2245
|
message:
|
|
2481
|
-
`property "${propName}" type
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
if (
|
|
2487
|
-
!typeExpressionMatchesDeclaredType(
|
|
2488
|
-
checker,
|
|
2489
|
-
typeExpression,
|
|
2490
|
-
declaredTypeNode
|
|
2491
|
-
)
|
|
2492
|
-
) {
|
|
2493
|
-
findings.incompatibleDeclareTypes.push(
|
|
2494
|
-
{
|
|
2495
|
-
location: propertyLocation,
|
|
2496
|
-
message:
|
|
2497
|
-
`property "${propName}" declare type ` +
|
|
2498
|
-
`"${getPropertyTypeTextFromNode(sourceFile, declaredTypeNode)}" ` +
|
|
2499
|
-
`is not compatible with static properties type ` +
|
|
2500
|
-
`"${getPropertyConfigTypeName(typeExpressionKind(typeExpression))}"`
|
|
2501
|
-
}
|
|
2502
|
-
);
|
|
2246
|
+
`property "${propName}" declare type ` +
|
|
2247
|
+
`"${getPropertyTypeTextFromNode(sourceFile, declaredTypeNode)}" ` +
|
|
2248
|
+
`is not compatible with static properties type ` +
|
|
2249
|
+
`"${getPropertyConfigTypeName(typeExpressionKind(typeExpression))}"`,
|
|
2250
|
+
});
|
|
2503
2251
|
}
|
|
2504
2252
|
}
|
|
2505
2253
|
|
|
@@ -2511,26 +2259,20 @@ function validatePropertyConfigs(
|
|
|
2511
2259
|
if (isGetterReference(methodName)) {
|
|
2512
2260
|
const getterName = getGetterName(methodName);
|
|
2513
2261
|
if (getterNames.has(getterName)) continue;
|
|
2514
|
-
findings.invalidUsedByReferences.push(
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
`missing getter "${getterName}"`
|
|
2520
|
-
}
|
|
2521
|
-
);
|
|
2262
|
+
findings.invalidUsedByReferences.push({
|
|
2263
|
+
location: propertyLocation,
|
|
2264
|
+
message:
|
|
2265
|
+
`property "${propName}" usedBy references ` + `missing getter "${getterName}"`,
|
|
2266
|
+
});
|
|
2522
2267
|
continue;
|
|
2523
2268
|
}
|
|
2524
2269
|
|
|
2525
2270
|
if (!classMethods.has(methodName)) {
|
|
2526
|
-
findings.invalidUsedByReferences.push(
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
`missing method "${methodName}"`
|
|
2532
|
-
}
|
|
2533
|
-
);
|
|
2271
|
+
findings.invalidUsedByReferences.push({
|
|
2272
|
+
location: propertyLocation,
|
|
2273
|
+
message:
|
|
2274
|
+
`property "${propName}" usedBy references ` + `missing method "${methodName}"`,
|
|
2275
|
+
});
|
|
2534
2276
|
}
|
|
2535
2277
|
}
|
|
2536
2278
|
}
|
|
@@ -2544,10 +2286,7 @@ function validatePropertyConfigs(
|
|
|
2544
2286
|
) {
|
|
2545
2287
|
computedDependencies.set(
|
|
2546
2288
|
propName,
|
|
2547
|
-
collectComputedDependencies(
|
|
2548
|
-
computedProp.initializer.text,
|
|
2549
|
-
computedPropNames
|
|
2550
|
-
)
|
|
2289
|
+
collectComputedDependencies(computedProp.initializer.text, computedPropNames),
|
|
2551
2290
|
);
|
|
2552
2291
|
validateComputedProperty(
|
|
2553
2292
|
propName,
|
|
@@ -2555,28 +2294,24 @@ function validatePropertyConfigs(
|
|
|
2555
2294
|
supportedProps,
|
|
2556
2295
|
classMethods,
|
|
2557
2296
|
findings,
|
|
2558
|
-
computedLocations.get(propName) ?? propertyLocation
|
|
2297
|
+
computedLocations.get(propName) ?? propertyLocation,
|
|
2559
2298
|
);
|
|
2560
2299
|
}
|
|
2561
2300
|
|
|
2562
2301
|
if (valuesConfigError) {
|
|
2563
|
-
findings.invalidValuesConfigurations.push(
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
}
|
|
2568
|
-
);
|
|
2302
|
+
findings.invalidValuesConfigurations.push({
|
|
2303
|
+
location: propertyLocation,
|
|
2304
|
+
message: `property "${propName}" ${valuesConfigError}`,
|
|
2305
|
+
});
|
|
2569
2306
|
}
|
|
2570
2307
|
|
|
2571
2308
|
const values = getStringArrayLiteral(valuesProp);
|
|
2572
2309
|
if (values) {
|
|
2573
|
-
if (typeExpressionKind(typeExpression) !==
|
|
2574
|
-
findings.invalidValuesConfigurations.push(
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
}
|
|
2579
|
-
);
|
|
2310
|
+
if (typeExpressionKind(typeExpression) !== "String") {
|
|
2311
|
+
findings.invalidValuesConfigurations.push({
|
|
2312
|
+
location: propertyLocation,
|
|
2313
|
+
message: `property "${propName}" uses values, but its type is not String`,
|
|
2314
|
+
});
|
|
2580
2315
|
}
|
|
2581
2316
|
|
|
2582
2317
|
if (
|
|
@@ -2586,52 +2321,34 @@ function validatePropertyConfigs(
|
|
|
2586
2321
|
ts.isNoSubstitutionTemplateLiteral(valueProp.initializer)) &&
|
|
2587
2322
|
!values.includes(valueProp.initializer.text)
|
|
2588
2323
|
) {
|
|
2589
|
-
findings.invalidDefaultValues.push(
|
|
2590
|
-
|
|
2591
|
-
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
}
|
|
2596
|
-
);
|
|
2324
|
+
findings.invalidDefaultValues.push({
|
|
2325
|
+
location: propertyLocation,
|
|
2326
|
+
message:
|
|
2327
|
+
`property "${propName}" default value ` +
|
|
2328
|
+
`"${valueProp.initializer.text}" is not in values`,
|
|
2329
|
+
});
|
|
2597
2330
|
}
|
|
2598
2331
|
}
|
|
2599
2332
|
|
|
2600
2333
|
if (valueProp && ts.isPropertyAssignment(valueProp)) {
|
|
2601
|
-
const mismatch = validateDefaultValue(
|
|
2602
|
-
checker,
|
|
2603
|
-
typeExpression,
|
|
2604
|
-
valueProp.initializer
|
|
2605
|
-
);
|
|
2334
|
+
const mismatch = validateDefaultValue(checker, typeExpression, valueProp.initializer);
|
|
2606
2335
|
if (mismatch) {
|
|
2607
|
-
findings.invalidDefaultValues.push(
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
}
|
|
2615
|
-
);
|
|
2336
|
+
findings.invalidDefaultValues.push({
|
|
2337
|
+
location: propertyLocation,
|
|
2338
|
+
message:
|
|
2339
|
+
`property "${propName}" default value ` +
|
|
2340
|
+
`has type ${mismatch.valueTypeName}, ` +
|
|
2341
|
+
`but declared type is ${mismatch.typeName}`,
|
|
2342
|
+
});
|
|
2616
2343
|
}
|
|
2617
2344
|
}
|
|
2618
2345
|
}
|
|
2619
2346
|
|
|
2620
|
-
validateComputedPropertyCycles(
|
|
2621
|
-
computedDependencies,
|
|
2622
|
-
computedLocations,
|
|
2623
|
-
findings
|
|
2624
|
-
);
|
|
2347
|
+
validateComputedPropertyCycles(computedDependencies, computedLocations, findings);
|
|
2625
2348
|
}
|
|
2626
2349
|
|
|
2627
2350
|
// Validates that a ref attribute targets a unique HTMLElement property.
|
|
2628
|
-
function validateRefAttribute(
|
|
2629
|
-
attrValue,
|
|
2630
|
-
supportedProps,
|
|
2631
|
-
findings,
|
|
2632
|
-
seenRefProps,
|
|
2633
|
-
location
|
|
2634
|
-
) {
|
|
2351
|
+
function validateRefAttribute(attrValue, supportedProps, findings, seenRefProps, location) {
|
|
2635
2352
|
if (!attrValue) return;
|
|
2636
2353
|
|
|
2637
2354
|
const propName = attrValue.trim();
|
|
@@ -2639,36 +2356,27 @@ function validateRefAttribute(
|
|
|
2639
2356
|
|
|
2640
2357
|
const propInfo = supportedProps.get(propName);
|
|
2641
2358
|
if (!propInfo) {
|
|
2642
|
-
findings.invalidRefAttributes.push(
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
}
|
|
2647
|
-
);
|
|
2359
|
+
findings.invalidRefAttributes.push({
|
|
2360
|
+
location,
|
|
2361
|
+
message: `ref="${attrValue}" refers to missing property "${propName}"`,
|
|
2362
|
+
});
|
|
2648
2363
|
return;
|
|
2649
2364
|
}
|
|
2650
2365
|
|
|
2651
|
-
if (propInfo.typeText !==
|
|
2652
|
-
findings.invalidRefAttributes.push(
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
'whose type is not HTMLElement'
|
|
2658
|
-
}
|
|
2659
|
-
);
|
|
2366
|
+
if (propInfo.typeText !== "HTMLElement") {
|
|
2367
|
+
findings.invalidRefAttributes.push({
|
|
2368
|
+
location,
|
|
2369
|
+
message:
|
|
2370
|
+
`ref="${attrValue}" refers to property "${propName}" ` + "whose type is not HTMLElement",
|
|
2371
|
+
});
|
|
2660
2372
|
return;
|
|
2661
2373
|
}
|
|
2662
2374
|
|
|
2663
2375
|
if (seenRefProps.has(propName)) {
|
|
2664
|
-
findings.invalidRefAttributes.push(
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
`ref="${attrValue}" is a duplicate reference ` +
|
|
2669
|
-
`to the property "${propName}"`
|
|
2670
|
-
}
|
|
2671
|
-
);
|
|
2376
|
+
findings.invalidRefAttributes.push({
|
|
2377
|
+
location,
|
|
2378
|
+
message: `ref="${attrValue}" is a duplicate reference ` + `to the property "${propName}"`,
|
|
2379
|
+
});
|
|
2672
2380
|
return;
|
|
2673
2381
|
}
|
|
2674
2382
|
|
|
@@ -2677,19 +2385,16 @@ function validateRefAttribute(
|
|
|
2677
2385
|
|
|
2678
2386
|
// Validates event names used in value-binding attributes.
|
|
2679
2387
|
function validateValueBindingEvent(node, attrName, findings, resolveLocation) {
|
|
2680
|
-
const [realAttrName, eventName] = attrName.split(
|
|
2681
|
-
if (realAttrName !==
|
|
2388
|
+
const [realAttrName, eventName] = attrName.split(":");
|
|
2389
|
+
if (realAttrName !== "value" || !eventName) return;
|
|
2682
2390
|
if (SUPPORTED_EVENT_NAMES.has(eventName)) return;
|
|
2683
2391
|
|
|
2684
|
-
const tagName = node.rawTagName || node.tagName ||
|
|
2685
|
-
findings.unsupportedEventNames.push(
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
`an unsupported event name "${eventName}"`
|
|
2691
|
-
}
|
|
2692
|
-
);
|
|
2392
|
+
const tagName = node.rawTagName || node.tagName || "element";
|
|
2393
|
+
findings.unsupportedEventNames.push({
|
|
2394
|
+
location: getHtmlAttributeLocation(node, attrName, resolveLocation),
|
|
2395
|
+
message:
|
|
2396
|
+
`${tagName} attribute "${attrName}" refers to ` + `an unsupported event name "${eventName}"`,
|
|
2397
|
+
});
|
|
2693
2398
|
}
|
|
2694
2399
|
|
|
2695
2400
|
// Walks parsed HTML nodes to extract expressions and apply HTML validations.
|
|
@@ -2700,82 +2405,59 @@ function walkHtmlNode(
|
|
|
2700
2405
|
componentPropertyMaps,
|
|
2701
2406
|
supportedProps,
|
|
2702
2407
|
seenRefProps,
|
|
2703
|
-
resolveLocation
|
|
2408
|
+
resolveLocation,
|
|
2704
2409
|
) {
|
|
2705
2410
|
if (node.nodeType === 1) {
|
|
2706
2411
|
validateHtmlNesting(node, findings, resolveLocation);
|
|
2707
2412
|
|
|
2708
2413
|
for (const [attrName, attrValue] of Object.entries(node.attributes)) {
|
|
2709
2414
|
if (!attrValue) continue;
|
|
2710
|
-
validateFormAssocAttribute(
|
|
2711
|
-
node,
|
|
2712
|
-
attrName,
|
|
2713
|
-
attrValue,
|
|
2714
|
-
findings,
|
|
2715
|
-
resolveLocation
|
|
2716
|
-
);
|
|
2415
|
+
validateFormAssocAttribute(node, attrName, attrValue, findings, resolveLocation);
|
|
2717
2416
|
validateFormAssocPropertyMappings(
|
|
2718
2417
|
node,
|
|
2719
2418
|
attrName,
|
|
2720
2419
|
attrValue,
|
|
2721
2420
|
findings,
|
|
2722
2421
|
componentPropertyMaps,
|
|
2723
|
-
resolveLocation
|
|
2724
|
-
);
|
|
2725
|
-
validateCheckedBinding(
|
|
2726
|
-
node,
|
|
2727
|
-
attrName,
|
|
2728
|
-
attrValue,
|
|
2729
|
-
findings,
|
|
2730
|
-
supportedProps,
|
|
2731
|
-
resolveLocation
|
|
2422
|
+
resolveLocation,
|
|
2732
2423
|
);
|
|
2424
|
+
validateCheckedBinding(node, attrName, attrValue, findings, supportedProps, resolveLocation);
|
|
2733
2425
|
validateHtmlAttribute(node, attrName, findings, resolveLocation);
|
|
2734
|
-
validateValueBinding(
|
|
2735
|
-
node,
|
|
2736
|
-
attrName,
|
|
2737
|
-
attrValue,
|
|
2738
|
-
findings,
|
|
2739
|
-
supportedProps,
|
|
2740
|
-
resolveLocation
|
|
2741
|
-
);
|
|
2426
|
+
validateValueBinding(node, attrName, attrValue, findings, supportedProps, resolveLocation);
|
|
2742
2427
|
validateValueBindingEvent(node, attrName, findings, resolveLocation);
|
|
2743
|
-
if (attrName ===
|
|
2428
|
+
if (attrName === "ref") {
|
|
2744
2429
|
validateRefAttribute(
|
|
2745
2430
|
attrValue,
|
|
2746
2431
|
supportedProps,
|
|
2747
2432
|
findings,
|
|
2748
2433
|
seenRefProps,
|
|
2749
|
-
getHtmlAttributeLocation(node, attrName, resolveLocation)
|
|
2434
|
+
getHtmlAttributeLocation(node, attrName, resolveLocation),
|
|
2750
2435
|
);
|
|
2751
2436
|
}
|
|
2752
2437
|
if (
|
|
2753
2438
|
REFS_TEST_RE.test(attrValue) ||
|
|
2754
|
-
(attrName.startsWith(
|
|
2439
|
+
(attrName.startsWith("on") && IDENTIFIER_RE.test(attrValue))
|
|
2755
2440
|
) {
|
|
2756
2441
|
expressions.push({
|
|
2757
|
-
context:
|
|
2758
|
-
eventHandler: attrName.startsWith(
|
|
2759
|
-
kind:
|
|
2442
|
+
context: "instance",
|
|
2443
|
+
eventHandler: attrName.startsWith("on"),
|
|
2444
|
+
kind: "html",
|
|
2760
2445
|
text: attrValue.trim(),
|
|
2761
|
-
location: getHtmlAttributeLocation(node, attrName, resolveLocation)
|
|
2446
|
+
location: getHtmlAttributeLocation(node, attrName, resolveLocation),
|
|
2762
2447
|
});
|
|
2763
2448
|
}
|
|
2764
2449
|
}
|
|
2765
2450
|
}
|
|
2766
2451
|
|
|
2767
|
-
if (
|
|
2768
|
-
(node.nodeType === 3 || node.nodeType === 8) &&
|
|
2769
|
-
typeof node.rawText === 'string'
|
|
2770
|
-
) {
|
|
2452
|
+
if ((node.nodeType === 3 || node.nodeType === 8) && typeof node.rawText === "string") {
|
|
2771
2453
|
const text = node.rawText.trim();
|
|
2772
2454
|
if (text && REFS_TEST_RE.test(text)) {
|
|
2773
2455
|
expressions.push({
|
|
2774
|
-
context:
|
|
2456
|
+
context: "instance",
|
|
2775
2457
|
eventHandler: false,
|
|
2776
|
-
kind:
|
|
2458
|
+
kind: "html",
|
|
2777
2459
|
text,
|
|
2778
|
-
location: getHtmlNodeLocation(node, resolveLocation)
|
|
2460
|
+
location: getHtmlNodeLocation(node, resolveLocation),
|
|
2779
2461
|
});
|
|
2780
2462
|
}
|
|
2781
2463
|
}
|
|
@@ -2788,7 +2470,7 @@ function walkHtmlNode(
|
|
|
2788
2470
|
componentPropertyMaps,
|
|
2789
2471
|
supportedProps,
|
|
2790
2472
|
seenRefProps,
|
|
2791
|
-
resolveLocation
|
|
2473
|
+
resolveLocation,
|
|
2792
2474
|
);
|
|
2793
2475
|
}
|
|
2794
2476
|
}
|