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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/scripts/lint.js +69 -33
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "wrec",
3
3
  "description": "a small library that greatly simplifies building web components",
4
4
  "author": "R. Mark Volkmann",
5
- "version": "0.30.2",
5
+ "version": "0.30.3",
6
6
  "license": "MIT",
7
7
  "repository": {
8
8
  "type": "git",
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 an expression for invalid property access,
143
+ // Analyzes code for invalid property access,
144
144
  // method calls, and arithmetic usage.
145
- function analyzeExpression(
146
- expressionNode,
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(expressionNode)) {
153
- if (!metadata.classMethods.has(expressionNode.text)) {
152
+ if (metadata.eventHandler && ts.isIdentifier(codeNode)) {
153
+ if (!metadata.classMethods.has(codeNode.text)) {
154
154
  uniquePush(
155
155
  findings.invalidEventHandlers,
156
- `"${expressionNode.text}" is not a defined instance method`
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(expressionNode);
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
- expressions
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 = expressions.map((expr, index) => {
311
+ const helperBlocks = codeItems.map((item, index) => {
312
312
  const targetType =
313
- expr.context === 'static'
313
+ item.context === 'static'
314
314
  ? `typeof ${classNode.name.text}`
315
315
  : `${classNode.name.text} & __WrecSupportedProps`;
316
- const rewrittenText = expr.text.replace(/\bthis\b/g, WREC_REF_NAME);
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
- ${expr.context === 'instance' ? contextLine : ''}
321
- return (${rewrittenText});
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 pulls out the expression each one returns.
354
- // This gives the linter a stable list of typed expression nodes
355
- // that line up with the original template and computed expressions
356
- // for later analysis.
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 return expressions by index.
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
- allExpressions
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 helperExpressions = collectHelperExpressions(augmentedSourceFile);
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
- helperExpressions.forEach((expressionNode, index) => {
1344
- if (!expressionNode) return;
1345
- analyzeExpression(
1346
- expressionNode,
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
- eventHandler: allExpressions[index]?.eventHandler ?? false,
1388
+ checkContextCalls: allCodeItems[index]?.checkContextCalls ?? true,
1389
+ eventHandler: allCodeItems[index]?.eventHandler ?? false,
1354
1390
  sourceFile: augmentedSourceFile
1355
1391
  }
1356
1392
  );