wrec 0.39.4 → 0.39.5

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 +84 -0
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "wrec",
3
3
  "description": "a library that greatly simplifies building web components",
4
4
  "author": "R. Mark Volkmann",
5
- "version": "0.39.4",
5
+ "version": "0.39.5",
6
6
  "license": "MIT",
7
7
  "repository": {
8
8
  "type": "git",
package/scripts/lint.js CHANGED
@@ -16,6 +16,7 @@
16
16
  // - incompatible declare property types
17
17
  // - arithmetic and other type errors in expressions
18
18
  // - invalid computed property references
19
+ // - computed property cycles
19
20
  // - invalid event handler references
20
21
  // - unsupported event names
21
22
  // - duplicate property names
@@ -370,6 +371,20 @@ function collectGetterNames(classNode) {
370
371
  return getters;
371
372
  }
372
373
 
374
+ // Collects computed-property dependencies on other computed properties.
375
+ function collectComputedDependencies(computedText, computedPropNames) {
376
+ const dependencies = new Set();
377
+
378
+ for (const match of computedText.matchAll(THIS_REF_RE)) {
379
+ const referencedName = match[1];
380
+ if (computedPropNames.has(referencedName)) {
381
+ dependencies.add(referencedName);
382
+ }
383
+ }
384
+
385
+ return [...dependencies].sort();
386
+ }
387
+
373
388
  // Finds the synthetic `__wrec_expr_*` helper functions that were added by
374
389
  // `buildAugmentedSource` and returns their bodies in index order.
375
390
  // This gives the linter a stable list of typed code nodes
@@ -1835,6 +1850,51 @@ function validateComputedProperty(
1835
1850
  }
1836
1851
  }
1837
1852
 
1853
+ // Validates that computed properties do not form dependency cycles.
1854
+ function validateComputedPropertyCycles(computedDependencies, findings) {
1855
+ const computedNames = [...computedDependencies.keys()].sort();
1856
+ const dependencyCountMap = new Map();
1857
+ const dependentsMap = new Map();
1858
+ const queue = [];
1859
+
1860
+ for (const computedName of computedNames) {
1861
+ const dependencies = computedDependencies.get(computedName) ?? [];
1862
+ dependencyCountMap.set(computedName, dependencies.length);
1863
+ if (dependencies.length === 0) queue.push(computedName);
1864
+
1865
+ for (const dependencyName of dependencies) {
1866
+ let dependents = dependentsMap.get(dependencyName);
1867
+ if (!dependents) {
1868
+ dependents = [];
1869
+ dependentsMap.set(dependencyName, dependents);
1870
+ }
1871
+ dependents.push(computedName);
1872
+ }
1873
+ }
1874
+
1875
+ const orderedNames = [];
1876
+ for (let index = 0; index < queue.length; index++) {
1877
+ const computedName = queue[index];
1878
+ orderedNames.push(computedName);
1879
+
1880
+ const dependents = (dependentsMap.get(computedName) ?? []).sort();
1881
+ for (const dependentName of dependents) {
1882
+ const nextCount = dependencyCountMap.get(dependentName) - 1;
1883
+ dependencyCountMap.set(dependentName, nextCount);
1884
+ if (nextCount === 0) queue.push(dependentName);
1885
+ }
1886
+ }
1887
+
1888
+ if (orderedNames.length === computedNames.length) return;
1889
+
1890
+ const cycleNames = computedNames.filter(
1891
+ computedName => dependencyCountMap.get(computedName) > 0
1892
+ );
1893
+ findings.invalidComputedProperties.push(
1894
+ `computed properties form a cycle: ${cycleNames.join(', ')}`
1895
+ );
1896
+ }
1897
+
1838
1898
  // Validates that a default value matches the declared property type.
1839
1899
  function validateDefaultValue(checker, typeExpression, valueExpression) {
1840
1900
  if (!typeExpression || !valueExpression) return undefined;
@@ -2022,6 +2082,21 @@ function validatePropertyConfigs(
2022
2082
  classMethods,
2023
2083
  findings
2024
2084
  ) {
2085
+ const computedDependencies = new Map();
2086
+ const computedPropNames = new Set();
2087
+
2088
+ for (const {config, propName} of propertyEntries) {
2089
+ const computedProp = getObjectProperty(config, 'computed');
2090
+ if (
2091
+ computedProp &&
2092
+ ts.isPropertyAssignment(computedProp) &&
2093
+ (ts.isStringLiteral(computedProp.initializer) ||
2094
+ ts.isNoSubstitutionTemplateLiteral(computedProp.initializer))
2095
+ ) {
2096
+ computedPropNames.add(propName);
2097
+ }
2098
+ }
2099
+
2025
2100
  for (const {config, propName} of propertyEntries) {
2026
2101
  const computedProp = getObjectProperty(config, 'computed');
2027
2102
  const declaredTypeNode = declaredPropertyTypes.get(propName);
@@ -2104,6 +2179,13 @@ function validatePropertyConfigs(
2104
2179
  (ts.isStringLiteral(computedProp.initializer) ||
2105
2180
  ts.isNoSubstitutionTemplateLiteral(computedProp.initializer))
2106
2181
  ) {
2182
+ computedDependencies.set(
2183
+ propName,
2184
+ collectComputedDependencies(
2185
+ computedProp.initializer.text,
2186
+ computedPropNames
2187
+ )
2188
+ );
2107
2189
  validateComputedProperty(
2108
2190
  propName,
2109
2191
  computedProp.initializer.text,
@@ -2150,6 +2232,8 @@ function validatePropertyConfigs(
2150
2232
  }
2151
2233
  }
2152
2234
  }
2235
+
2236
+ validateComputedPropertyCycles(computedDependencies, findings);
2153
2237
  }
2154
2238
 
2155
2239
  // Validates that a ref attribute targets a unique HTMLElement property.