wrec 0.30.1 → 0.30.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/scripts/lint.js +71 -33
package/package.json
CHANGED
package/scripts/lint.js
CHANGED
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
// - undefined context functions called in expressions
|
|
13
13
|
// - extra arguments passed to methods and context functions
|
|
14
14
|
// - incompatible method arguments in expressions
|
|
15
|
+
// - incompatible context function arguments in expressions
|
|
15
16
|
// - arithmetic type errors in expressions
|
|
16
17
|
// - invalid computed property references and calls to non-method members
|
|
17
18
|
// - invalid event handler references
|
|
@@ -27,6 +28,7 @@
|
|
|
27
28
|
// - invalid `useState` map entries
|
|
28
29
|
// - unsupported HTML attributes in templates
|
|
29
30
|
// - invalid HTML element nesting in templates
|
|
31
|
+
// - invalid ref attribute targets
|
|
30
32
|
// - duplicate ref attribute values
|
|
31
33
|
|
|
32
34
|
import fs from 'node:fs';
|
|
@@ -138,20 +140,20 @@ const SUPPORTED_EVENT_NAMES = new Set([
|
|
|
138
140
|
const WREC_REF_NAME = '__wrec';
|
|
139
141
|
const componentPropertyCache = new Map();
|
|
140
142
|
|
|
141
|
-
// Analyzes
|
|
143
|
+
// Analyzes code for invalid property access,
|
|
142
144
|
// method calls, and arithmetic usage.
|
|
143
|
-
function
|
|
144
|
-
|
|
145
|
+
function analyzeCodeNode(
|
|
146
|
+
codeNode,
|
|
145
147
|
checker,
|
|
146
148
|
classNode,
|
|
147
149
|
findings,
|
|
148
150
|
metadata
|
|
149
151
|
) {
|
|
150
|
-
if (metadata.eventHandler && ts.isIdentifier(
|
|
151
|
-
if (!metadata.classMethods.has(
|
|
152
|
+
if (metadata.eventHandler && ts.isIdentifier(codeNode)) {
|
|
153
|
+
if (!metadata.classMethods.has(codeNode.text)) {
|
|
152
154
|
uniquePush(
|
|
153
155
|
findings.invalidEventHandlers,
|
|
154
|
-
`"${
|
|
156
|
+
`"${codeNode.text}" is not a defined instance method`
|
|
155
157
|
);
|
|
156
158
|
}
|
|
157
159
|
}
|
|
@@ -173,7 +175,7 @@ function analyzeExpression(
|
|
|
173
175
|
|
|
174
176
|
if (ts.isCallExpression(node)) {
|
|
175
177
|
const callee = node.expression;
|
|
176
|
-
if (ts.isIdentifier(callee)) {
|
|
178
|
+
if (metadata.checkContextCalls && ts.isIdentifier(callee)) {
|
|
177
179
|
if (!metadata.contextKeys.has(callee.text)) {
|
|
178
180
|
const symbol = checker.getSymbolAtLocation(callee);
|
|
179
181
|
if (!symbol || requiresContextFunction(symbol, metadata.sourceFile)) {
|
|
@@ -281,7 +283,7 @@ function analyzeExpression(
|
|
|
281
283
|
ts.forEachChild(node, visit);
|
|
282
284
|
}
|
|
283
285
|
|
|
284
|
-
visit(
|
|
286
|
+
visit(codeNode);
|
|
285
287
|
}
|
|
286
288
|
|
|
287
289
|
// Builds a temporary source string used only for type-checking
|
|
@@ -295,7 +297,7 @@ function buildAugmentedSource(
|
|
|
295
297
|
classNode,
|
|
296
298
|
supportedProps,
|
|
297
299
|
contextKeys,
|
|
298
|
-
|
|
300
|
+
codeItems
|
|
299
301
|
) {
|
|
300
302
|
const propLines = [];
|
|
301
303
|
for (const [name, info] of supportedProps.entries()) {
|
|
@@ -306,17 +308,21 @@ function buildAugmentedSource(
|
|
|
306
308
|
? `const {${contextKeys.join(', ')}} = ${classNode.name.text}.context;`
|
|
307
309
|
: '';
|
|
308
310
|
|
|
309
|
-
const helperBlocks =
|
|
311
|
+
const helperBlocks = codeItems.map((item, index) => {
|
|
310
312
|
const targetType =
|
|
311
|
-
|
|
313
|
+
item.context === 'static'
|
|
312
314
|
? `typeof ${classNode.name.text}`
|
|
313
315
|
: `${classNode.name.text} & __WrecSupportedProps`;
|
|
314
|
-
const rewrittenText =
|
|
316
|
+
const rewrittenText = item.text.replace(/\bthis\b/g, WREC_REF_NAME);
|
|
317
|
+
const helperBody =
|
|
318
|
+
item.shape === 'block'
|
|
319
|
+
? rewrittenText
|
|
320
|
+
: `return (${rewrittenText});`;
|
|
315
321
|
return `
|
|
316
322
|
function __wrec_expr_${index}() {
|
|
317
323
|
const ${WREC_REF_NAME} = null as unknown as ${targetType};
|
|
318
|
-
${
|
|
319
|
-
|
|
324
|
+
${item.checkContextCalls ? contextLine : ''}
|
|
325
|
+
${helperBody}
|
|
320
326
|
}
|
|
321
327
|
`;
|
|
322
328
|
});
|
|
@@ -348,15 +354,14 @@ function collectClassMethods(classNode) {
|
|
|
348
354
|
}
|
|
349
355
|
|
|
350
356
|
// Finds the synthetic `__wrec_expr_*` helper functions that were added by
|
|
351
|
-
// `buildAugmentedSource` and
|
|
352
|
-
// This gives the linter a stable list of typed
|
|
353
|
-
// that line up with the original
|
|
354
|
-
|
|
355
|
-
function collectHelperExpressions(augmentedSourceFile) {
|
|
357
|
+
// `buildAugmentedSource` and returns their bodies in index order.
|
|
358
|
+
// This gives the linter a stable list of typed code nodes
|
|
359
|
+
// that line up with the original extracted snippets for later analysis.
|
|
360
|
+
function collectHelperCodeNodes(augmentedSourceFile) {
|
|
356
361
|
const helpers = [];
|
|
357
362
|
|
|
358
363
|
// Finds generated helper functions and
|
|
359
|
-
// stores their
|
|
364
|
+
// stores their bodies by index.
|
|
360
365
|
function visit(node) {
|
|
361
366
|
if (
|
|
362
367
|
ts.isFunctionDeclaration(node) &&
|
|
@@ -364,12 +369,7 @@ function collectHelperExpressions(augmentedSourceFile) {
|
|
|
364
369
|
) {
|
|
365
370
|
const match = node.name.text.match(/(\d+)$/);
|
|
366
371
|
const index = match ? Number(match[1]) : -1;
|
|
367
|
-
if (index >= 0 && node.body)
|
|
368
|
-
const statement = node.body.statements.find(ts.isReturnStatement);
|
|
369
|
-
if (statement?.expression) {
|
|
370
|
-
helpers[index] = statement.expression;
|
|
371
|
-
}
|
|
372
|
-
}
|
|
372
|
+
if (index >= 0 && node.body) helpers[index] = node.body;
|
|
373
373
|
}
|
|
374
374
|
ts.forEachChild(node, visit);
|
|
375
375
|
}
|
|
@@ -378,6 +378,37 @@ function collectHelperExpressions(augmentedSourceFile) {
|
|
|
378
378
|
return helpers;
|
|
379
379
|
}
|
|
380
380
|
|
|
381
|
+
// Collects analyzable code blocks from instance methods and accessors.
|
|
382
|
+
function collectMethodCodeItems(classNode) {
|
|
383
|
+
const codeItems = [];
|
|
384
|
+
|
|
385
|
+
for (const member of classNode.members) {
|
|
386
|
+
if (hasStaticModifier(member)) continue;
|
|
387
|
+
|
|
388
|
+
if (
|
|
389
|
+
ts.isMethodDeclaration(member) ||
|
|
390
|
+
ts.isGetAccessorDeclaration(member) ||
|
|
391
|
+
ts.isSetAccessorDeclaration(member)
|
|
392
|
+
) {
|
|
393
|
+
if (!member.body) continue;
|
|
394
|
+
if (!member.body.getText().includes('this.')) continue;
|
|
395
|
+
codeItems.push({
|
|
396
|
+
checkContextCalls: false,
|
|
397
|
+
context: 'instance',
|
|
398
|
+
eventHandler: false,
|
|
399
|
+
kind: 'method',
|
|
400
|
+
location: member.getSourceFile().getLineAndCharacterOfPosition(
|
|
401
|
+
member.name.getStart(member.getSourceFile())
|
|
402
|
+
),
|
|
403
|
+
shape: 'block',
|
|
404
|
+
text: member.body.getText()
|
|
405
|
+
});
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
return codeItems;
|
|
410
|
+
}
|
|
411
|
+
|
|
381
412
|
// Collects the property names declared in
|
|
382
413
|
// a component's static properties object.
|
|
383
414
|
function collectSupportedPropertyNames(classNode) {
|
|
@@ -1289,7 +1320,13 @@ export function lintSource(filePath, sourceText, options = {}) {
|
|
|
1289
1320
|
componentPropertyMaps,
|
|
1290
1321
|
supportedProps
|
|
1291
1322
|
);
|
|
1323
|
+
const methodCodeItems = collectMethodCodeItems(classNode);
|
|
1292
1324
|
const allExpressions = [...templateExprs, ...computedExprs];
|
|
1325
|
+
const allCodeItems = [...allExpressions, ...methodCodeItems].map(item => ({
|
|
1326
|
+
checkContextCalls: item.kind !== 'method',
|
|
1327
|
+
shape: 'shape' in item ? item.shape : 'expression',
|
|
1328
|
+
...item
|
|
1329
|
+
}));
|
|
1293
1330
|
|
|
1294
1331
|
if (allMethods.has('formAssociatedCallback') && !formAssociated) {
|
|
1295
1332
|
findings.missingFormAssociatedProperty.push(
|
|
@@ -1302,7 +1339,7 @@ export function lintSource(filePath, sourceText, options = {}) {
|
|
|
1302
1339
|
classNode,
|
|
1303
1340
|
supportedProps,
|
|
1304
1341
|
contextKeys,
|
|
1305
|
-
|
|
1342
|
+
allCodeItems
|
|
1306
1343
|
);
|
|
1307
1344
|
const augmentedProgram = createProgram(filePath, augmentedSource);
|
|
1308
1345
|
const augmentedSourceFile = augmentedProgram.getSourceFile(filePath);
|
|
@@ -1314,7 +1351,7 @@ export function lintSource(filePath, sourceText, options = {}) {
|
|
|
1314
1351
|
throw new Error('unable to find Wrec subclass after augmentation');
|
|
1315
1352
|
}
|
|
1316
1353
|
|
|
1317
|
-
const
|
|
1354
|
+
const helperCodeNodes = collectHelperCodeNodes(augmentedSourceFile);
|
|
1318
1355
|
|
|
1319
1356
|
validatePropertyConfigs(
|
|
1320
1357
|
checker,
|
|
@@ -1338,17 +1375,18 @@ export function lintSource(filePath, sourceText, options = {}) {
|
|
|
1338
1375
|
}
|
|
1339
1376
|
});
|
|
1340
1377
|
|
|
1341
|
-
|
|
1342
|
-
if (!
|
|
1343
|
-
|
|
1344
|
-
|
|
1378
|
+
helperCodeNodes.forEach((codeNode, index) => {
|
|
1379
|
+
if (!codeNode) return;
|
|
1380
|
+
analyzeCodeNode(
|
|
1381
|
+
codeNode,
|
|
1345
1382
|
augmentedChecker,
|
|
1346
1383
|
augmentedClassNode,
|
|
1347
1384
|
findings,
|
|
1348
1385
|
{
|
|
1349
1386
|
classMethods: allMethods,
|
|
1350
1387
|
contextKeys: new Set(contextKeys),
|
|
1351
|
-
|
|
1388
|
+
checkContextCalls: allCodeItems[index]?.checkContextCalls ?? true,
|
|
1389
|
+
eventHandler: allCodeItems[index]?.eventHandler ?? false,
|
|
1352
1390
|
sourceFile: augmentedSourceFile
|
|
1353
1391
|
}
|
|
1354
1392
|
);
|