wrec 0.35.0 → 0.35.2
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 +91 -6
package/package.json
CHANGED
package/scripts/lint.js
CHANGED
|
@@ -451,6 +451,21 @@ function collectSupportedPropertyNames(classNode) {
|
|
|
451
451
|
return supportedProps;
|
|
452
452
|
}
|
|
453
453
|
|
|
454
|
+
// Collects declared TypeScript property types from a component class.
|
|
455
|
+
function collectDeclaredPropertyTypes(classNode) {
|
|
456
|
+
const declaredProps = new Map();
|
|
457
|
+
|
|
458
|
+
for (const member of classNode.members) {
|
|
459
|
+
if (!isDeclarePropertyDeclaration(member)) continue;
|
|
460
|
+
|
|
461
|
+
const propName = getMemberName(member);
|
|
462
|
+
if (!propName || !member.type) continue;
|
|
463
|
+
declaredProps.set(propName, member.type);
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
return declaredProps;
|
|
467
|
+
}
|
|
468
|
+
|
|
454
469
|
// Validates that useState mappings point at existing component properties.
|
|
455
470
|
function collectUseStateMapErrors(classNode, supportedProps, findings) {
|
|
456
471
|
// Walks the class body looking for useState calls with mapping objects.
|
|
@@ -815,6 +830,7 @@ function formatReport(
|
|
|
815
830
|
findings.duplicateProperties.length > 0 ||
|
|
816
831
|
findings.extraArguments.length > 0 ||
|
|
817
832
|
findings.incompatibleArguments.length > 0 ||
|
|
833
|
+
findings.incompatibleDeclareTypes.length > 0 ||
|
|
818
834
|
findings.invalidCheckedBindings.length > 0 ||
|
|
819
835
|
findings.invalidComputedProperties.length > 0 ||
|
|
820
836
|
findings.invalidDefaultValues.length > 0 ||
|
|
@@ -889,6 +905,13 @@ function formatReport(
|
|
|
889
905
|
});
|
|
890
906
|
}
|
|
891
907
|
|
|
908
|
+
if (findings.incompatibleDeclareTypes.length > 0) {
|
|
909
|
+
lines.push('incompatible declare types:');
|
|
910
|
+
findings.incompatibleDeclareTypes.forEach(message =>
|
|
911
|
+
lines.push(` ${message}`)
|
|
912
|
+
);
|
|
913
|
+
}
|
|
914
|
+
|
|
892
915
|
if (findings.invalidCheckedBindings.length > 0) {
|
|
893
916
|
lines.push('invalid checked bindings:');
|
|
894
917
|
findings.invalidCheckedBindings.forEach(message =>
|
|
@@ -1166,6 +1189,11 @@ function getPropertyTypeText(checker, sourceFile, expression) {
|
|
|
1166
1189
|
return checker.typeToString(type);
|
|
1167
1190
|
}
|
|
1168
1191
|
|
|
1192
|
+
// Gets the TypeScript type text for a declared property member.
|
|
1193
|
+
function getPropertyTypeTextFromNode(sourceFile, typeNode) {
|
|
1194
|
+
return typeNode.getText(sourceFile).trim();
|
|
1195
|
+
}
|
|
1196
|
+
|
|
1169
1197
|
// Returns an array of string literal values from a property when possible.
|
|
1170
1198
|
function getStringArrayLiteral(property) {
|
|
1171
1199
|
if (!property || !ts.isPropertyAssignment(property)) return undefined;
|
|
@@ -1275,6 +1303,17 @@ function isCallCallee(node) {
|
|
|
1275
1303
|
return ts.isCallExpression(node.parent) && node.parent.expression === node;
|
|
1276
1304
|
}
|
|
1277
1305
|
|
|
1306
|
+
// Returns whether a class member is a declared property declaration.
|
|
1307
|
+
function isDeclarePropertyDeclaration(member) {
|
|
1308
|
+
return (
|
|
1309
|
+
ts.isPropertyDeclaration(member) &&
|
|
1310
|
+
ts.canHaveModifiers(member) &&
|
|
1311
|
+
ts
|
|
1312
|
+
.getModifiers(member)
|
|
1313
|
+
?.some(mod => mod.kind === ts.SyntaxKind.DeclareKeyword)
|
|
1314
|
+
);
|
|
1315
|
+
}
|
|
1316
|
+
|
|
1278
1317
|
// Returns whether a reference refers to a getter method.
|
|
1279
1318
|
function isGetterReference(reference) {
|
|
1280
1319
|
return reference.startsWith(GETTER_PREFIX);
|
|
@@ -1362,12 +1401,14 @@ export function lintSource(filePath, sourceText, options = {}) {
|
|
|
1362
1401
|
propertyEntries,
|
|
1363
1402
|
reservedProperties
|
|
1364
1403
|
} = extractProperties(sourceFile, checker, classNode);
|
|
1404
|
+
const declaredPropertyTypes = collectDeclaredPropertyTypes(classNode);
|
|
1365
1405
|
const getterNames = collectGetterNames(classNode);
|
|
1366
1406
|
const allMethods = collectClassMethods(classNode);
|
|
1367
1407
|
const findings = {
|
|
1368
1408
|
duplicateProperties,
|
|
1369
1409
|
extraArguments: [],
|
|
1370
1410
|
incompatibleArguments: [],
|
|
1411
|
+
incompatibleDeclareTypes: [],
|
|
1371
1412
|
invalidCheckedBindings: [],
|
|
1372
1413
|
invalidComputedProperties: [],
|
|
1373
1414
|
invalidDefaultValues: [],
|
|
@@ -1430,6 +1471,8 @@ export function lintSource(filePath, sourceText, options = {}) {
|
|
|
1430
1471
|
|
|
1431
1472
|
validatePropertyConfigs(
|
|
1432
1473
|
checker,
|
|
1474
|
+
sourceFile,
|
|
1475
|
+
declaredPropertyTypes,
|
|
1433
1476
|
supportedProps,
|
|
1434
1477
|
propertyEntries,
|
|
1435
1478
|
getterNames,
|
|
@@ -1473,6 +1516,7 @@ export function lintSource(filePath, sourceText, options = {}) {
|
|
|
1473
1516
|
a.methodName.localeCompare(b.methodName) ||
|
|
1474
1517
|
a.parameterName.localeCompare(b.parameterName)
|
|
1475
1518
|
);
|
|
1519
|
+
findings.incompatibleDeclareTypes.sort();
|
|
1476
1520
|
findings.invalidCheckedBindings.sort();
|
|
1477
1521
|
findings.invalidComputedProperties.sort();
|
|
1478
1522
|
findings.invalidDefaultValues.sort();
|
|
@@ -1558,6 +1602,23 @@ function requiresContextFunction(symbol, sourceFile) {
|
|
|
1558
1602
|
});
|
|
1559
1603
|
}
|
|
1560
1604
|
|
|
1605
|
+
// Returns whether a type represents an object-like value other than an array.
|
|
1606
|
+
function isNonArrayObjectLikeType(checker, type) {
|
|
1607
|
+
if (type.isUnion()) {
|
|
1608
|
+
return type.types.every(member => isNonArrayObjectLikeType(checker, member));
|
|
1609
|
+
}
|
|
1610
|
+
|
|
1611
|
+
if (type.isIntersection()) {
|
|
1612
|
+
return type.types.every(member => isNonArrayObjectLikeType(checker, member));
|
|
1613
|
+
}
|
|
1614
|
+
|
|
1615
|
+
return (
|
|
1616
|
+
Boolean(type.flags & (ts.TypeFlags.Object | ts.TypeFlags.NonPrimitive)) &&
|
|
1617
|
+
!checker.isArrayType(type) &&
|
|
1618
|
+
!checker.isTupleType(type)
|
|
1619
|
+
);
|
|
1620
|
+
}
|
|
1621
|
+
|
|
1561
1622
|
// Resolves a relative import path to an existing source file.
|
|
1562
1623
|
function resolveImportPath(baseDir, importPath) {
|
|
1563
1624
|
if (!importPath.startsWith('.')) return undefined;
|
|
@@ -1578,6 +1639,22 @@ function toUserFacingExpression(text) {
|
|
|
1578
1639
|
return text.replaceAll(WREC_REF_NAME, 'this');
|
|
1579
1640
|
}
|
|
1580
1641
|
|
|
1642
|
+
// Returns whether a static property config type is compatible with a declare type.
|
|
1643
|
+
function typeExpressionMatchesDeclaredType(
|
|
1644
|
+
checker,
|
|
1645
|
+
typeExpression,
|
|
1646
|
+
declaredTypeNode
|
|
1647
|
+
) {
|
|
1648
|
+
const declaredType = checker.getTypeFromTypeNode(declaredTypeNode);
|
|
1649
|
+
|
|
1650
|
+
if (typeExpressionKind(typeExpression) === 'Object') {
|
|
1651
|
+
return isNonArrayObjectLikeType(checker, declaredType);
|
|
1652
|
+
}
|
|
1653
|
+
|
|
1654
|
+
const runtimeType = checker.getTypeAtLocation(typeExpression);
|
|
1655
|
+
return checker.isTypeAssignableTo(runtimeType, declaredType);
|
|
1656
|
+
}
|
|
1657
|
+
|
|
1581
1658
|
// Classifies a constructor-based type expression by its identifier name.
|
|
1582
1659
|
function typeExpressionKind(expression) {
|
|
1583
1660
|
if (!expression) return undefined;
|
|
@@ -1732,11 +1809,7 @@ function validateDefaultValue(checker, typeExpression, valueExpression) {
|
|
|
1732
1809
|
}
|
|
1733
1810
|
|
|
1734
1811
|
if (typeKind === 'Object') {
|
|
1735
|
-
|
|
1736
|
-
Boolean(valueType.flags & ts.TypeFlags.Object) &&
|
|
1737
|
-
!checker.isArrayType(valueType) &&
|
|
1738
|
-
!checker.isTupleType(valueType);
|
|
1739
|
-
if (!isObjectLike) {
|
|
1812
|
+
if (!isNonArrayObjectLikeType(checker, valueType)) {
|
|
1740
1813
|
return {typeName: 'Object', valueTypeName};
|
|
1741
1814
|
}
|
|
1742
1815
|
}
|
|
@@ -1865,6 +1938,8 @@ function validateHtmlNesting(node, findings) {
|
|
|
1865
1938
|
// Validates all configured component property metadata entries.
|
|
1866
1939
|
function validatePropertyConfigs(
|
|
1867
1940
|
checker,
|
|
1941
|
+
sourceFile,
|
|
1942
|
+
declaredPropertyTypes,
|
|
1868
1943
|
supportedProps,
|
|
1869
1944
|
propertyEntries,
|
|
1870
1945
|
getterNames,
|
|
@@ -1872,9 +1947,10 @@ function validatePropertyConfigs(
|
|
|
1872
1947
|
findings
|
|
1873
1948
|
) {
|
|
1874
1949
|
for (const {config, propName} of propertyEntries) {
|
|
1950
|
+
const computedProp = getObjectProperty(config, 'computed');
|
|
1951
|
+
const declaredTypeNode = declaredPropertyTypes.get(propName);
|
|
1875
1952
|
const typeProp = getObjectProperty(config, 'type');
|
|
1876
1953
|
const usedByProp = getObjectProperty(config, 'usedBy');
|
|
1877
|
-
const computedProp = getObjectProperty(config, 'computed');
|
|
1878
1954
|
const valueProp = getObjectProperty(config, 'value');
|
|
1879
1955
|
const valuesProp = getObjectProperty(config, 'values');
|
|
1880
1956
|
|
|
@@ -1894,6 +1970,15 @@ function validatePropertyConfigs(
|
|
|
1894
1970
|
`property "${propName}" type must be one of ` +
|
|
1895
1971
|
'Boolean, Number, String, Object, or Array'
|
|
1896
1972
|
);
|
|
1973
|
+
} else if (declaredTypeNode) {
|
|
1974
|
+
if (!typeExpressionMatchesDeclaredType(checker, typeExpression, declaredTypeNode)) {
|
|
1975
|
+
findings.incompatibleDeclareTypes.push(
|
|
1976
|
+
`property "${propName}" declare type ` +
|
|
1977
|
+
`"${getPropertyTypeTextFromNode(sourceFile, declaredTypeNode)}" ` +
|
|
1978
|
+
`is not compatible with static properties type ` +
|
|
1979
|
+
`"${getPropertyConfigTypeName(typeExpressionKind(typeExpression))}"`
|
|
1980
|
+
);
|
|
1981
|
+
}
|
|
1897
1982
|
}
|
|
1898
1983
|
|
|
1899
1984
|
if (usedByProp && ts.isPropertyAssignment(usedByProp)) {
|