wrec 0.30.2 → 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 +69 -33
package/package.json
CHANGED
package/scripts/lint.js
CHANGED
|
@@ -140,20 +140,20 @@ const SUPPORTED_EVENT_NAMES = new Set([
|
|
|
140
140
|
const WREC_REF_NAME = '__wrec';
|
|
141
141
|
const componentPropertyCache = new Map();
|
|
142
142
|
|
|
143
|
-
// Analyzes
|
|
143
|
+
// Analyzes code for invalid property access,
|
|
144
144
|
// method calls, and arithmetic usage.
|
|
145
|
-
function
|
|
146
|
-
|
|
145
|
+
function analyzeCodeNode(
|
|
146
|
+
codeNode,
|
|
147
147
|
checker,
|
|
148
148
|
classNode,
|
|
149
149
|
findings,
|
|
150
150
|
metadata
|
|
151
151
|
) {
|
|
152
|
-
if (metadata.eventHandler && ts.isIdentifier(
|
|
153
|
-
if (!metadata.classMethods.has(
|
|
152
|
+
if (metadata.eventHandler && ts.isIdentifier(codeNode)) {
|
|
153
|
+
if (!metadata.classMethods.has(codeNode.text)) {
|
|
154
154
|
uniquePush(
|
|
155
155
|
findings.invalidEventHandlers,
|
|
156
|
-
`"${
|
|
156
|
+
`"${codeNode.text}" is not a defined instance method`
|
|
157
157
|
);
|
|
158
158
|
}
|
|
159
159
|
}
|
|
@@ -175,7 +175,7 @@ function analyzeExpression(
|
|
|
175
175
|
|
|
176
176
|
if (ts.isCallExpression(node)) {
|
|
177
177
|
const callee = node.expression;
|
|
178
|
-
if (ts.isIdentifier(callee)) {
|
|
178
|
+
if (metadata.checkContextCalls && ts.isIdentifier(callee)) {
|
|
179
179
|
if (!metadata.contextKeys.has(callee.text)) {
|
|
180
180
|
const symbol = checker.getSymbolAtLocation(callee);
|
|
181
181
|
if (!symbol || requiresContextFunction(symbol, metadata.sourceFile)) {
|
|
@@ -283,7 +283,7 @@ function analyzeExpression(
|
|
|
283
283
|
ts.forEachChild(node, visit);
|
|
284
284
|
}
|
|
285
285
|
|
|
286
|
-
visit(
|
|
286
|
+
visit(codeNode);
|
|
287
287
|
}
|
|
288
288
|
|
|
289
289
|
// Builds a temporary source string used only for type-checking
|
|
@@ -297,7 +297,7 @@ function buildAugmentedSource(
|
|
|
297
297
|
classNode,
|
|
298
298
|
supportedProps,
|
|
299
299
|
contextKeys,
|
|
300
|
-
|
|
300
|
+
codeItems
|
|
301
301
|
) {
|
|
302
302
|
const propLines = [];
|
|
303
303
|
for (const [name, info] of supportedProps.entries()) {
|
|
@@ -308,17 +308,21 @@ function buildAugmentedSource(
|
|
|
308
308
|
? `const {${contextKeys.join(', ')}} = ${classNode.name.text}.context;`
|
|
309
309
|
: '';
|
|
310
310
|
|
|
311
|
-
const helperBlocks =
|
|
311
|
+
const helperBlocks = codeItems.map((item, index) => {
|
|
312
312
|
const targetType =
|
|
313
|
-
|
|
313
|
+
item.context === 'static'
|
|
314
314
|
? `typeof ${classNode.name.text}`
|
|
315
315
|
: `${classNode.name.text} & __WrecSupportedProps`;
|
|
316
|
-
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});`;
|
|
317
321
|
return `
|
|
318
322
|
function __wrec_expr_${index}() {
|
|
319
323
|
const ${WREC_REF_NAME} = null as unknown as ${targetType};
|
|
320
|
-
${
|
|
321
|
-
|
|
324
|
+
${item.checkContextCalls ? contextLine : ''}
|
|
325
|
+
${helperBody}
|
|
322
326
|
}
|
|
323
327
|
`;
|
|
324
328
|
});
|
|
@@ -350,15 +354,14 @@ function collectClassMethods(classNode) {
|
|
|
350
354
|
}
|
|
351
355
|
|
|
352
356
|
// Finds the synthetic `__wrec_expr_*` helper functions that were added by
|
|
353
|
-
// `buildAugmentedSource` and
|
|
354
|
-
// This gives the linter a stable list of typed
|
|
355
|
-
// that line up with the original
|
|
356
|
-
|
|
357
|
-
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) {
|
|
358
361
|
const helpers = [];
|
|
359
362
|
|
|
360
363
|
// Finds generated helper functions and
|
|
361
|
-
// stores their
|
|
364
|
+
// stores their bodies by index.
|
|
362
365
|
function visit(node) {
|
|
363
366
|
if (
|
|
364
367
|
ts.isFunctionDeclaration(node) &&
|
|
@@ -366,12 +369,7 @@ function collectHelperExpressions(augmentedSourceFile) {
|
|
|
366
369
|
) {
|
|
367
370
|
const match = node.name.text.match(/(\d+)$/);
|
|
368
371
|
const index = match ? Number(match[1]) : -1;
|
|
369
|
-
if (index >= 0 && node.body)
|
|
370
|
-
const statement = node.body.statements.find(ts.isReturnStatement);
|
|
371
|
-
if (statement?.expression) {
|
|
372
|
-
helpers[index] = statement.expression;
|
|
373
|
-
}
|
|
374
|
-
}
|
|
372
|
+
if (index >= 0 && node.body) helpers[index] = node.body;
|
|
375
373
|
}
|
|
376
374
|
ts.forEachChild(node, visit);
|
|
377
375
|
}
|
|
@@ -380,6 +378,37 @@ function collectHelperExpressions(augmentedSourceFile) {
|
|
|
380
378
|
return helpers;
|
|
381
379
|
}
|
|
382
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
|
+
|
|
383
412
|
// Collects the property names declared in
|
|
384
413
|
// a component's static properties object.
|
|
385
414
|
function collectSupportedPropertyNames(classNode) {
|
|
@@ -1291,7 +1320,13 @@ export function lintSource(filePath, sourceText, options = {}) {
|
|
|
1291
1320
|
componentPropertyMaps,
|
|
1292
1321
|
supportedProps
|
|
1293
1322
|
);
|
|
1323
|
+
const methodCodeItems = collectMethodCodeItems(classNode);
|
|
1294
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
|
+
}));
|
|
1295
1330
|
|
|
1296
1331
|
if (allMethods.has('formAssociatedCallback') && !formAssociated) {
|
|
1297
1332
|
findings.missingFormAssociatedProperty.push(
|
|
@@ -1304,7 +1339,7 @@ export function lintSource(filePath, sourceText, options = {}) {
|
|
|
1304
1339
|
classNode,
|
|
1305
1340
|
supportedProps,
|
|
1306
1341
|
contextKeys,
|
|
1307
|
-
|
|
1342
|
+
allCodeItems
|
|
1308
1343
|
);
|
|
1309
1344
|
const augmentedProgram = createProgram(filePath, augmentedSource);
|
|
1310
1345
|
const augmentedSourceFile = augmentedProgram.getSourceFile(filePath);
|
|
@@ -1316,7 +1351,7 @@ export function lintSource(filePath, sourceText, options = {}) {
|
|
|
1316
1351
|
throw new Error('unable to find Wrec subclass after augmentation');
|
|
1317
1352
|
}
|
|
1318
1353
|
|
|
1319
|
-
const
|
|
1354
|
+
const helperCodeNodes = collectHelperCodeNodes(augmentedSourceFile);
|
|
1320
1355
|
|
|
1321
1356
|
validatePropertyConfigs(
|
|
1322
1357
|
checker,
|
|
@@ -1340,17 +1375,18 @@ export function lintSource(filePath, sourceText, options = {}) {
|
|
|
1340
1375
|
}
|
|
1341
1376
|
});
|
|
1342
1377
|
|
|
1343
|
-
|
|
1344
|
-
if (!
|
|
1345
|
-
|
|
1346
|
-
|
|
1378
|
+
helperCodeNodes.forEach((codeNode, index) => {
|
|
1379
|
+
if (!codeNode) return;
|
|
1380
|
+
analyzeCodeNode(
|
|
1381
|
+
codeNode,
|
|
1347
1382
|
augmentedChecker,
|
|
1348
1383
|
augmentedClassNode,
|
|
1349
1384
|
findings,
|
|
1350
1385
|
{
|
|
1351
1386
|
classMethods: allMethods,
|
|
1352
1387
|
contextKeys: new Set(contextKeys),
|
|
1353
|
-
|
|
1388
|
+
checkContextCalls: allCodeItems[index]?.checkContextCalls ?? true,
|
|
1389
|
+
eventHandler: allCodeItems[index]?.eventHandler ?? false,
|
|
1354
1390
|
sourceFile: augmentedSourceFile
|
|
1355
1391
|
}
|
|
1356
1392
|
);
|