wrec 0.24.6 → 0.24.7

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 +112 -47
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.24.6",
5
+ "version": "0.24.7",
6
6
  "license": "MIT",
7
7
  "repository": {
8
8
  "type": "git",
package/scripts/lint.js CHANGED
@@ -1,24 +1,24 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- // This linter checks Wrec components for:
4
- // - duplicate property names
5
- // - invalid computed property references and non-method calls
6
- // - invalid default values
7
- // - invalid form-assoc values
8
- // - invalid event handler references
9
- // - invalid useState map entries
10
- // - invalid usedBy references
11
- // - invalid values configurations
12
- // - missing formAssociated property
13
- // - missing type properties in property configurations
14
- // - reserved property names
15
- // - undefined context functions called in expressions
16
- // - undefined instance methods called in expressions
3
+ // This linter checks Wrec components for these issues:
17
4
  // - undefined properties accessed in expressions
18
- // - incompatible method arguments
19
- // - unsupported HTML attributes in html templates
20
- // - unsupported event names
5
+ // - undefined instance methods called in expressions
6
+ // - undefined context functions called in expressions
7
+ // - incompatible method arguments in expressions
21
8
  // - arithmetic type errors in expressions
9
+ // - invalid computed property references and calls to non-method members
10
+ // - invalid event handler references
11
+ // - unsupported event names
12
+ // - duplicate property names
13
+ // - reserved property names
14
+ // - missing `type` in property configurations
15
+ // - invalid default values
16
+ // - invalid `values` configurations
17
+ // - invalid `usedBy` references
18
+ // - missing `formAssociated` when `formAssociatedCallback` is defined
19
+ // - invalid `form-assoc` values
20
+ // - invalid `useState` map entries
21
+ // - unsupported HTML attributes in templates
22
22
 
23
23
  import fs from 'node:fs';
24
24
  import path from 'node:path';
@@ -49,7 +49,19 @@ const HTML_TAG_ATTRIBUTES = new Map([
49
49
  ['fieldset', new Set(['name'])],
50
50
  ['form', new Set(['action', 'method', 'name'])],
51
51
  ['img', new Set(['alt', 'height', 'src', 'width'])],
52
- ['input', new Set(['checked', 'max', 'min', 'name', 'placeholder', 'step', 'type', 'value'])],
52
+ [
53
+ 'input',
54
+ new Set([
55
+ 'checked',
56
+ 'max',
57
+ 'min',
58
+ 'name',
59
+ 'placeholder',
60
+ 'step',
61
+ 'type',
62
+ 'value'
63
+ ])
64
+ ],
53
65
  ['label', new Set(['for'])],
54
66
  ['legend', new Set([])],
55
67
  ['li', new Set(['value'])],
@@ -486,7 +498,10 @@ function extractProperties(sourceFile, checker, classNode) {
486
498
  continue;
487
499
  }
488
500
 
489
- if (supportedProps.has(propName) && !duplicateProperties.includes(propName)) {
501
+ if (
502
+ supportedProps.has(propName) &&
503
+ !duplicateProperties.includes(propName)
504
+ ) {
490
505
  duplicateProperties.push(propName);
491
506
  }
492
507
  if (
@@ -542,7 +557,11 @@ function extractProperties(sourceFile, checker, classNode) {
542
557
  };
543
558
  }
544
559
 
545
- function extractTemplateExpressions(classNode, findings, componentPropertyMaps) {
560
+ function extractTemplateExpressions(
561
+ classNode,
562
+ findings,
563
+ componentPropertyMaps
564
+ ) {
546
565
  const expressions = [];
547
566
 
548
567
  for (const member of classNode.members) {
@@ -645,7 +664,8 @@ function formatReport(
645
664
  fileLabel = filePath,
646
665
  showFileHeader = true,
647
666
  showDetailsForCleanFile = true,
648
- showNoIssuesMessage = true
667
+ showNoIssuesMessage = true,
668
+ verbose = false
649
669
  } = options;
650
670
  const lines = [];
651
671
 
@@ -671,13 +691,13 @@ function formatReport(
671
691
 
672
692
  if (showFileHeader) lines.push(`file: ${fileLabel}`);
673
693
 
674
- if (hasIssues || showDetailsForCleanFile) {
694
+ if (verbose && (hasIssues || showDetailsForCleanFile)) {
675
695
  lines.push('properties:');
676
696
  if (supportedProps.size === 0) {
677
697
  lines.push(' none');
678
698
  } else {
679
- for (const [name, info] of [...supportedProps.entries()].sort(([a], [b]) =>
680
- a.localeCompare(b)
699
+ for (const [name, info] of [...supportedProps.entries()].sort(
700
+ ([a], [b]) => a.localeCompare(b)
681
701
  )) {
682
702
  lines.push(` ${name}: ${info.typeText}`);
683
703
  }
@@ -688,7 +708,9 @@ function formatReport(
688
708
  lines.push(' none');
689
709
  } else {
690
710
  allExpressions.forEach(expr => {
691
- lines.push(` [${expr.kind}]${formatLocation(expr.location)} ${expr.text}`);
711
+ lines.push(
712
+ ` [${expr.kind}]${formatLocation(expr.location)} ${expr.text}`
713
+ );
692
714
  });
693
715
  }
694
716
  }
@@ -726,12 +748,16 @@ function formatReport(
726
748
 
727
749
  if (findings.invalidDefaultValues.length > 0) {
728
750
  lines.push('invalid default values:');
729
- findings.invalidDefaultValues.forEach(message => lines.push(` ${message}`));
751
+ findings.invalidDefaultValues.forEach(message =>
752
+ lines.push(` ${message}`)
753
+ );
730
754
  }
731
755
 
732
756
  if (findings.invalidFormAssocValues.length > 0) {
733
757
  lines.push('invalid form-assoc values:');
734
- findings.invalidFormAssocValues.forEach(message => lines.push(` ${message}`));
758
+ findings.invalidFormAssocValues.forEach(message =>
759
+ lines.push(` ${message}`)
760
+ );
735
761
  }
736
762
 
737
763
  if (findings.missingFormAssociatedProperty.length > 0) {
@@ -743,7 +769,9 @@ function formatReport(
743
769
 
744
770
  if (findings.missingTypeProperties.length > 0) {
745
771
  lines.push('missing type properties:');
746
- findings.missingTypeProperties.forEach(message => lines.push(` ${message}`));
772
+ findings.missingTypeProperties.forEach(message =>
773
+ lines.push(` ${message}`)
774
+ );
747
775
  }
748
776
 
749
777
  if (findings.undefinedProperties.length > 0) {
@@ -763,7 +791,9 @@ function formatReport(
763
791
 
764
792
  if (findings.invalidEventHandlers.length > 0) {
765
793
  lines.push('invalid event handler references:');
766
- findings.invalidEventHandlers.forEach(message => lines.push(` ${message}`));
794
+ findings.invalidEventHandlers.forEach(message =>
795
+ lines.push(` ${message}`)
796
+ );
767
797
  }
768
798
 
769
799
  if (findings.invalidUseStateMaps.length > 0) {
@@ -796,7 +826,9 @@ function formatReport(
796
826
 
797
827
  if (findings.unsupportedEventNames.length > 0) {
798
828
  lines.push('unsupported event names:');
799
- findings.unsupportedEventNames.forEach(message => lines.push(` ${message}`));
829
+ findings.unsupportedEventNames.forEach(message =>
830
+ lines.push(` ${message}`)
831
+ );
800
832
  }
801
833
 
802
834
  if (!hasIssues && showNoIssuesMessage) lines.push('no issues found');
@@ -822,7 +854,9 @@ function getComponentPropertyMaps(filePath, sourceText, seen = new Set()) {
822
854
  const propertyMaps = new Map();
823
855
 
824
856
  for (const classNode of collectWrecClasses(sourceFile)) {
825
- const tagName = classNode.name ? tagNames.get(classNode.name.text) : undefined;
857
+ const tagName = classNode.name
858
+ ? tagNames.get(classNode.name.text)
859
+ : undefined;
826
860
  if (!tagName) continue;
827
861
  const {supportedProps} = extractProperties(sourceFile, checker, classNode);
828
862
  propertyMaps.set(tagName, new Set(supportedProps.keys()));
@@ -927,7 +961,10 @@ function getStringArrayLiteral(property) {
927
961
 
928
962
  const values = [];
929
963
  for (const element of property.initializer.elements) {
930
- if (!ts.isStringLiteral(element) && !ts.isNoSubstitutionTemplateLiteral(element)) {
964
+ if (
965
+ !ts.isStringLiteral(element) &&
966
+ !ts.isNoSubstitutionTemplateLiteral(element)
967
+ ) {
931
968
  return undefined;
932
969
  }
933
970
  values.push(element.text);
@@ -949,7 +986,10 @@ function getParameterType(checker, parameterSymbol, location, isRestArgument) {
949
986
  location
950
987
  );
951
988
  if (!isRestArgument) return parameterType;
952
- if (!checker.isArrayType(parameterType) && !checker.isTupleType(parameterType)) {
989
+ if (
990
+ !checker.isArrayType(parameterType) &&
991
+ !checker.isTupleType(parameterType)
992
+ ) {
953
993
  return parameterType;
954
994
  }
955
995
 
@@ -1251,7 +1291,13 @@ export function lintSource(filePath, sourceText, options = {}) {
1251
1291
  findings.unsupportedHtmlAttributes.sort();
1252
1292
  findings.unsupportedEventNames.sort();
1253
1293
 
1254
- return formatReport(filePath, supportedProps, allExpressions, findings, options);
1294
+ return formatReport(
1295
+ filePath,
1296
+ supportedProps,
1297
+ allExpressions,
1298
+ findings,
1299
+ options
1300
+ );
1255
1301
  }
1256
1302
 
1257
1303
  export function lintFile(filePath, options = {}) {
@@ -1260,15 +1306,21 @@ export function lintFile(filePath, options = {}) {
1260
1306
  }
1261
1307
 
1262
1308
  function main() {
1263
- const [, , filePath, ...rest] = process.argv;
1264
- if (rest.length > 0) {
1309
+ const args = process.argv.slice(2);
1310
+ const verbose = args.includes('--verbose');
1311
+ const positionalArgs = args.filter(arg => arg !== '--verbose');
1312
+
1313
+ if (positionalArgs.length > 1) {
1265
1314
  fail('usage: node scripts/wrec-lint.js [file.js|file.ts]');
1266
1315
  }
1267
1316
 
1317
+ const [filePath] = positionalArgs;
1318
+
1268
1319
  if (filePath) {
1269
1320
  process.stdout.write(
1270
1321
  lintFile(validateFilePath(filePath), {
1271
- showFileHeader: false
1322
+ showFileHeader: false,
1323
+ verbose
1272
1324
  })
1273
1325
  );
1274
1326
  return;
@@ -1278,9 +1330,11 @@ function main() {
1278
1330
  let previousHadIssues = false;
1279
1331
  findWrecFiles(rootDir, matchedFile => {
1280
1332
  const report = lintFile(matchedFile, {
1281
- fileLabel: path.relative(rootDir, matchedFile) || path.basename(matchedFile),
1333
+ fileLabel:
1334
+ path.relative(rootDir, matchedFile) || path.basename(matchedFile),
1282
1335
  showDetailsForCleanFile: false,
1283
- showNoIssuesMessage: false
1336
+ showNoIssuesMessage: false,
1337
+ verbose
1284
1338
  });
1285
1339
  const currentHasIssues = report.trim().includes('\n');
1286
1340
  if (previousHadIssues) process.stdout.write('\n');
@@ -1308,7 +1362,10 @@ function validateComputedProperty(
1308
1362
  ) {
1309
1363
  for (const match of computedText.matchAll(THIS_REF_RE)) {
1310
1364
  const referencedName = match[1];
1311
- if (!supportedProps.has(referencedName) && !classMethods.has(referencedName)) {
1365
+ if (
1366
+ !supportedProps.has(referencedName) &&
1367
+ !classMethods.has(referencedName)
1368
+ ) {
1312
1369
  findings.invalidComputedProperties.push(
1313
1370
  `property "${propName}" computed references missing property "${referencedName}"`
1314
1371
  );
@@ -1334,21 +1391,27 @@ function validateDefaultValue(checker, typeExpression, valueExpression) {
1334
1391
  const valueTypeName = checker.typeToString(valueType);
1335
1392
 
1336
1393
  if (typeKind === 'String') {
1337
- if (!(valueType.flags & (ts.TypeFlags.String | ts.TypeFlags.StringLiteral))) {
1394
+ if (
1395
+ !(valueType.flags & (ts.TypeFlags.String | ts.TypeFlags.StringLiteral))
1396
+ ) {
1338
1397
  return {typeName: 'string', valueTypeName};
1339
1398
  }
1340
1399
  return undefined;
1341
1400
  }
1342
1401
 
1343
1402
  if (typeKind === 'Number') {
1344
- if (!(valueType.flags & (ts.TypeFlags.Number | ts.TypeFlags.NumberLiteral))) {
1403
+ if (
1404
+ !(valueType.flags & (ts.TypeFlags.Number | ts.TypeFlags.NumberLiteral))
1405
+ ) {
1345
1406
  return {typeName: 'number', valueTypeName};
1346
1407
  }
1347
1408
  return undefined;
1348
1409
  }
1349
1410
 
1350
1411
  if (typeKind === 'Boolean') {
1351
- if (!(valueType.flags & (ts.TypeFlags.Boolean | ts.TypeFlags.BooleanLiteral))) {
1412
+ if (
1413
+ !(valueType.flags & (ts.TypeFlags.Boolean | ts.TypeFlags.BooleanLiteral))
1414
+ ) {
1352
1415
  return {typeName: 'boolean', valueTypeName};
1353
1416
  }
1354
1417
  return undefined;
@@ -1401,7 +1464,9 @@ function validatePropertyConfigs(
1401
1464
  const valuesProp = getObjectProperty(config, 'values');
1402
1465
 
1403
1466
  const typeExpression =
1404
- typeProp && ts.isPropertyAssignment(typeProp) ? typeProp.initializer : undefined;
1467
+ typeProp && ts.isPropertyAssignment(typeProp)
1468
+ ? typeProp.initializer
1469
+ : undefined;
1405
1470
 
1406
1471
  if (!typeExpression) {
1407
1472
  findings.missingTypeProperties.push(
@@ -1517,9 +1582,9 @@ function validateFormAssocAttribute(attrName, attrValue, findings) {
1517
1582
  const pairs = attrValue.split(',');
1518
1583
  for (const pair of pairs) {
1519
1584
  const trimmed = pair.trim();
1520
- const [propName, fieldName, ...rest] = trimmed.split(':').map(part =>
1521
- part.trim()
1522
- );
1585
+ const [propName, fieldName, ...rest] = trimmed
1586
+ .split(':')
1587
+ .map(part => part.trim());
1523
1588
  if (!trimmed || rest.length > 0 || !propName || !fieldName) {
1524
1589
  findings.invalidFormAssocValues.push(
1525
1590
  `form-assoc="${attrValue}" is invalid; expected "property:field" or a comma-separated list of them`