@wcstack/lint 2.1.0 → 2.1.1
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/README.md +1 -1
- package/dist/cli.cjs +98 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -56,7 +56,7 @@ app.manifest.json:1:3 error wcs/manifest-broken Broken manifest JSON: ...
|
|
|
56
56
|
|
|
57
57
|
## Declaring a state contract (`stateSchema`)
|
|
58
58
|
|
|
59
|
-
Without a contract, a path the validator cannot resolve is only a **warning** (`wcs/binding-path-missing`): `count` may well exist at runtime even when the inline script cannot be read statically. Put an `application` sidecar next to (or above) the HTML and the same typo becomes an **error**:
|
|
59
|
+
Without a contract, a path the validator cannot resolve is only a **warning** (`wcs/binding-path-missing`): `count` may well exist at runtime even when the inline script cannot be read statically. Note that a list starting as `[]` does not need a contract just to name its row fields — the analyzer reads them from the row literals in the assignments that add or replace rows (`this.items = this.items.concat({ id, kind: "general" })`, `.toSpliced(i, n, { … })`, `.with(i, { … })`, `[...this.items, { … }]`); only a row passed as a variable (`concat(row)`) is invisible to it. Put an `application` sidecar next to (or above) the HTML and the same typo becomes an **error**:
|
|
60
60
|
|
|
61
61
|
```json
|
|
62
62
|
{
|
package/dist/cli.cjs
CHANGED
|
@@ -955,6 +955,7 @@ function analyzeStatePaths(scriptContent) {
|
|
|
955
955
|
for (const listKeyEntry of pendingListKeys) {
|
|
956
956
|
pushListKeyPaths(listKeyEntry, paths);
|
|
957
957
|
}
|
|
958
|
+
collectRowShapesFromAssignments(scriptContent, paths);
|
|
958
959
|
return paths;
|
|
959
960
|
}
|
|
960
961
|
function analyzeWatchEntries(scriptContent) {
|
|
@@ -1100,6 +1101,95 @@ function extractStringLiteralValue(value) {
|
|
|
1100
1101
|
const match = value.trim().match(/^["']([^"'\\]*)["']$/);
|
|
1101
1102
|
return match && match[1].length > 0 ? match[1] : null;
|
|
1102
1103
|
}
|
|
1104
|
+
var ROW_ASSIGN = new RegExp(
|
|
1105
|
+
String.raw`\bthis\s*(?:\.\s*([$\w]+)|\[\s*["']([^"']+)["']\s*\])\s*=(?![=>])\s*(?:(\[)|(?:[^;={}]|=>)*?\.\s*(?:concat|toSpliced|with)\s*(\())`,
|
|
1106
|
+
"gd"
|
|
1107
|
+
);
|
|
1108
|
+
function collectRowShapesFromAssignments(script, paths) {
|
|
1109
|
+
const scan = maskCommentsAndStrings(script);
|
|
1110
|
+
ROW_ASSIGN.lastIndex = 0;
|
|
1111
|
+
let match;
|
|
1112
|
+
while ((match = ROW_ASSIGN.exec(scan)) !== null) {
|
|
1113
|
+
const span = match.indices[1] ?? match.indices[2];
|
|
1114
|
+
const listPath = script.slice(span[0], span[1]);
|
|
1115
|
+
if (listPath.startsWith("$") || listPath.includes("*") || !hasPath(paths, `${listPath}.*`)) continue;
|
|
1116
|
+
const openIndex = match.index + match[0].length - 1;
|
|
1117
|
+
const isArrayLiteral2 = scan[openIndex] === "[";
|
|
1118
|
+
const inner = extractDelimitedContent(script, scan, openIndex, scan[openIndex], isArrayLiteral2 ? "]" : ")");
|
|
1119
|
+
for (const literal of collectRowLiterals(inner, isArrayLiteral2 ? 0 : 1)) {
|
|
1120
|
+
for (const field of extractRowLiteralFields(literal)) {
|
|
1121
|
+
pushRowFieldPaths(`${listPath}.*.${field.name}`, field, paths, 1);
|
|
1122
|
+
}
|
|
1123
|
+
}
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1126
|
+
function collectRowLiterals(elementList, arrayDepth) {
|
|
1127
|
+
const out = [];
|
|
1128
|
+
for (const element of splitTopLevelElements(elementList)) {
|
|
1129
|
+
if (element.startsWith("{")) {
|
|
1130
|
+
out.push(element);
|
|
1131
|
+
} else if (element.startsWith("[") && arrayDepth > 0) {
|
|
1132
|
+
const scan = maskCommentsAndStrings(element);
|
|
1133
|
+
out.push(...collectRowLiterals(extractDelimitedContent(element, scan, 0, "[", "]"), arrayDepth - 1));
|
|
1134
|
+
}
|
|
1135
|
+
}
|
|
1136
|
+
return out;
|
|
1137
|
+
}
|
|
1138
|
+
function extractRowLiteralFields(literal) {
|
|
1139
|
+
const content = extractObjectContent(literal);
|
|
1140
|
+
const fields = parseTopLevelProperties(content).filter((p) => p.kind === "data");
|
|
1141
|
+
for (const element of splitTopLevelElements(content)) {
|
|
1142
|
+
const shorthand = /^([$\w]+)$/.exec(element);
|
|
1143
|
+
if (shorthand && !fields.some((f) => f.name === shorthand[1])) {
|
|
1144
|
+
fields.push({ name: shorthand[1], kind: "data" });
|
|
1145
|
+
}
|
|
1146
|
+
}
|
|
1147
|
+
return fields;
|
|
1148
|
+
}
|
|
1149
|
+
function pushRowFieldPaths(path, prop, paths, depth) {
|
|
1150
|
+
if (!hasPath(paths, path)) paths.push(withHint({ path, kind: "data" }, prop.typeHint));
|
|
1151
|
+
if (!prop.value) return;
|
|
1152
|
+
if (isArrayLiteral(prop.value)) {
|
|
1153
|
+
if (!hasPath(paths, `${path}.*`)) paths.push({ path: `${path}.*`, kind: "list" });
|
|
1154
|
+
if (!hasPath(paths, `${path}.length`)) {
|
|
1155
|
+
paths.push({ path: `${path}.length`, kind: "data", typeHint: "number" });
|
|
1156
|
+
}
|
|
1157
|
+
if (depth >= MAX_OBJECT_NEST_DEPTH) return;
|
|
1158
|
+
for (const child of extractArrayElementDataProperties(prop.value)) {
|
|
1159
|
+
pushRowFieldPaths(`${path}.*.${child.name}`, child, paths, depth + 1);
|
|
1160
|
+
}
|
|
1161
|
+
return;
|
|
1162
|
+
}
|
|
1163
|
+
if (isObjectLiteral(prop.value)) {
|
|
1164
|
+
if (depth >= MAX_OBJECT_NEST_DEPTH) return;
|
|
1165
|
+
for (const child of parseTopLevelProperties(extractObjectContent(prop.value))) {
|
|
1166
|
+
if (child.kind !== "data") continue;
|
|
1167
|
+
pushRowFieldPaths(`${path}.${child.name}`, child, paths, depth + 1);
|
|
1168
|
+
}
|
|
1169
|
+
}
|
|
1170
|
+
}
|
|
1171
|
+
function hasPath(paths, path) {
|
|
1172
|
+
return paths.some((p) => p.path === path);
|
|
1173
|
+
}
|
|
1174
|
+
function splitTopLevelElements(text) {
|
|
1175
|
+
const scan = maskCommentsAndStrings(text);
|
|
1176
|
+
const out = [];
|
|
1177
|
+
let depth = 0;
|
|
1178
|
+
let start = 0;
|
|
1179
|
+
for (let i = 0; i < scan.length; i++) {
|
|
1180
|
+
const ch = scan[i];
|
|
1181
|
+
if (ch === "{" || ch === "[" || ch === "(") {
|
|
1182
|
+
depth++;
|
|
1183
|
+
} else if (ch === "}" || ch === "]" || ch === ")") {
|
|
1184
|
+
depth--;
|
|
1185
|
+
} else if (ch === "," && depth === 0) {
|
|
1186
|
+
out.push(text.slice(start, i));
|
|
1187
|
+
start = i + 1;
|
|
1188
|
+
}
|
|
1189
|
+
}
|
|
1190
|
+
out.push(text.slice(start));
|
|
1191
|
+
return out.map((e) => e.trim()).filter((e) => e.length > 0);
|
|
1192
|
+
}
|
|
1103
1193
|
function findStreamInitialProperty(entryValue) {
|
|
1104
1194
|
const defProps = parseTopLevelProperties(extractObjectContent(entryValue));
|
|
1105
1195
|
return defProps.find((p) => p.kind === "data" && p.name === "initial");
|
|
@@ -1321,9 +1411,12 @@ function extractFullValue(content, scan, startIndex) {
|
|
|
1321
1411
|
return content.slice(startIndex, i).trim();
|
|
1322
1412
|
}
|
|
1323
1413
|
function extractBracedContent(text, scan, openBraceIndex) {
|
|
1414
|
+
return extractDelimitedContent(text, scan, openBraceIndex, "{", "}");
|
|
1415
|
+
}
|
|
1416
|
+
function extractDelimitedContent(text, scan, openIndex, open, close) {
|
|
1324
1417
|
let depth = 0;
|
|
1325
1418
|
let inString = null;
|
|
1326
|
-
for (let i =
|
|
1419
|
+
for (let i = openIndex; i < scan.length; i++) {
|
|
1327
1420
|
const ch = scan[i];
|
|
1328
1421
|
if (inString) {
|
|
1329
1422
|
if (ch === inString && !isEscaped(scan, i)) {
|
|
@@ -1333,16 +1426,16 @@ function extractBracedContent(text, scan, openBraceIndex) {
|
|
|
1333
1426
|
}
|
|
1334
1427
|
if (ch === '"' || ch === "'" || ch === "`") {
|
|
1335
1428
|
inString = ch;
|
|
1336
|
-
} else if (ch ===
|
|
1429
|
+
} else if (ch === open) {
|
|
1337
1430
|
depth++;
|
|
1338
|
-
} else if (ch ===
|
|
1431
|
+
} else if (ch === close) {
|
|
1339
1432
|
depth--;
|
|
1340
1433
|
if (depth === 0) {
|
|
1341
|
-
return text.slice(
|
|
1434
|
+
return text.slice(openIndex + 1, i);
|
|
1342
1435
|
}
|
|
1343
1436
|
}
|
|
1344
1437
|
}
|
|
1345
|
-
return text.slice(
|
|
1438
|
+
return text.slice(openIndex + 1);
|
|
1346
1439
|
}
|
|
1347
1440
|
function isArrayLiteral(value) {
|
|
1348
1441
|
return value.trimStart().startsWith("[");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wcstack/lint",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.1",
|
|
4
4
|
"description": "Static-contract validator CLI (wcs-validate) for wcstack data-wcs bindings and wcstack.manifest.json sidecars. Thin npm wrapper around the wcstack-intellisense validator core.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|