@wcstack/typescript 4.0.0 → 4.0.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/dist/index.esm.js +1 -1
- package/dist/schema-core.cjs +288 -2
- package/dist/wcs-schema.mjs +1 -1
- package/dist/wcs-tsc.mjs +1 -1
- package/package.json +1 -1
package/dist/index.esm.js
CHANGED
package/dist/schema-core.cjs
CHANGED
|
@@ -1309,6 +1309,10 @@ var SENTENCES = {
|
|
|
1309
1309
|
121
|
|
1310
1310
|
/* ForNoFilters */
|
|
1311
1311
|
]: (t) => `"${t}": "for:" takes no filters \u2014 a row is "<path>.<index>", so the rows of a filtered list would name other elements. Declare a getter that returns the filtered list and loop over it ("for: <getter>").`,
|
|
1312
|
+
[
|
|
1313
|
+
122
|
|
1314
|
+
/* NamespaceNeedsName */
|
|
1315
|
+
]: (t, ns) => ns === "class" ? `"${t}": "class:" needs a class name \u2014 "class.NAME:" toggles one class (a bare "class:" would set an element property named "class", which does nothing). To set the whole class attribute, bind "className:".` : `"${t}": "attr:" needs an attribute name \u2014 "attr.NAME:" sets one attribute (a bare "attr:" would set an element property named "attr", which does nothing).`,
|
|
1312
1316
|
[
|
|
1313
1317
|
119
|
|
1314
1318
|
/* EmptySegment */
|
|
@@ -1573,6 +1577,9 @@ function parseBindTextsForElement(bindText) {
|
|
|
1573
1577
|
bindingType: keyword === SPREAD_PROP ? "spread" : keyword
|
|
1574
1578
|
};
|
|
1575
1579
|
}
|
|
1580
|
+
if (keyword === CLASS_NAMESPACE || keyword === ATTR_NAMESPACE) {
|
|
1581
|
+
raise(122, [bindText2, keyword]);
|
|
1582
|
+
}
|
|
1576
1583
|
const stateResult = parseStatePart(statePart);
|
|
1577
1584
|
const propResult = parsePropPart(propPart);
|
|
1578
1585
|
const [head] = propResult.propSegments;
|
|
@@ -2870,6 +2877,7 @@ function analyzeStatePaths(scriptContent) {
|
|
|
2870
2877
|
}
|
|
2871
2878
|
}
|
|
2872
2879
|
collectRowShapesFromAssignments(scriptContent, paths);
|
|
2880
|
+
collectGetterResultShapes(topLevelProps, effectiveDescriptors, paths);
|
|
2873
2881
|
return paths;
|
|
2874
2882
|
}
|
|
2875
2883
|
function specFromRecursionValue(prop) {
|
|
@@ -3253,6 +3261,284 @@ function pushRowFieldPaths(path, prop, paths, depth) {
|
|
|
3253
3261
|
}
|
|
3254
3262
|
}
|
|
3255
3263
|
}
|
|
3264
|
+
var ROW_PRESERVING_METHODS = /* @__PURE__ */ new Set([
|
|
3265
|
+
"filter",
|
|
3266
|
+
"toSorted",
|
|
3267
|
+
"toReversed",
|
|
3268
|
+
"slice",
|
|
3269
|
+
"toSpliced",
|
|
3270
|
+
"concat",
|
|
3271
|
+
"with",
|
|
3272
|
+
"sort",
|
|
3273
|
+
"reverse"
|
|
3274
|
+
]);
|
|
3275
|
+
var ROW_PICKING_METHODS = /* @__PURE__ */ new Set(["find", "findLast", "at"]);
|
|
3276
|
+
var MAX_GETTER_SHAPE_PASSES = 4;
|
|
3277
|
+
function collectGetterResultShapes(props, effective, paths) {
|
|
3278
|
+
const getters = /* @__PURE__ */ new Map();
|
|
3279
|
+
for (const prop of props) {
|
|
3280
|
+
if (prop.kind !== "getter" || prop.accessor !== "get" || prop.value === void 0) continue;
|
|
3281
|
+
if (prop.name.startsWith("$") || hasRecursionWildcard(prop.name) || effective.get(prop.name) === "data") continue;
|
|
3282
|
+
getters.set(prop.name, prop);
|
|
3283
|
+
}
|
|
3284
|
+
for (let pass = 0; pass < MAX_GETTER_SHAPE_PASSES; pass++) {
|
|
3285
|
+
const before = paths.length;
|
|
3286
|
+
for (const [name, prop] of getters) {
|
|
3287
|
+
const shape = getterResultShape(prop.value, paths);
|
|
3288
|
+
if (shape !== null) applyResultShape(name, shape, paths);
|
|
3289
|
+
}
|
|
3290
|
+
if (paths.length === before) break;
|
|
3291
|
+
}
|
|
3292
|
+
}
|
|
3293
|
+
function getterResultShape(body, paths) {
|
|
3294
|
+
const scan = maskCommentsAndStrings(body);
|
|
3295
|
+
const aliases = /* @__PURE__ */ new Map();
|
|
3296
|
+
for (const m of scan.matchAll(/\b(?:const|let|var)\s+([$\w]+)\s*=(?![=>])/g)) {
|
|
3297
|
+
const name = m[1];
|
|
3298
|
+
if (aliases.has(name)) continue;
|
|
3299
|
+
const start = m.index + m[0].length;
|
|
3300
|
+
const shape = expressionShape(readExpression(body, scan, start), paths, aliases);
|
|
3301
|
+
if (shape !== null) aliases.set(name, shape);
|
|
3302
|
+
}
|
|
3303
|
+
const shapes = [];
|
|
3304
|
+
for (const m of scan.matchAll(/\breturn\b/g)) {
|
|
3305
|
+
const start = m.index + m[0].length;
|
|
3306
|
+
const shape = expressionShape(readExpression(body, scan, start), paths, aliases);
|
|
3307
|
+
if (shape !== null) shapes.push(shape);
|
|
3308
|
+
}
|
|
3309
|
+
return shapes.length === 0 ? null : mergeShapes(shapes);
|
|
3310
|
+
}
|
|
3311
|
+
function readExpression(text3, scan, start) {
|
|
3312
|
+
let depth = 0;
|
|
3313
|
+
let i = start;
|
|
3314
|
+
for (; i < scan.length; i++) {
|
|
3315
|
+
const ch = scan[i];
|
|
3316
|
+
if (ch === "(" || ch === "[" || ch === "{") depth++;
|
|
3317
|
+
else if (ch === ")" || ch === "]" || ch === "}") {
|
|
3318
|
+
if (depth === 0) break;
|
|
3319
|
+
depth--;
|
|
3320
|
+
} else if (depth === 0 && (ch === ";" || ch === ",")) break;
|
|
3321
|
+
else if (depth === 0 && ch === "\n") {
|
|
3322
|
+
const before = scan.slice(start, i).trimEnd();
|
|
3323
|
+
const after = scan.slice(i + 1).trimStart();
|
|
3324
|
+
if (before === "") continue;
|
|
3325
|
+
if (!/[.?:|&+\-*/=(,]$/.test(before) && !/^[.?:|&]/.test(after)) break;
|
|
3326
|
+
}
|
|
3327
|
+
}
|
|
3328
|
+
return text3.slice(start, i).trim();
|
|
3329
|
+
}
|
|
3330
|
+
function expressionShape(text3, paths, aliases) {
|
|
3331
|
+
const expr = stripWrappingParens(text3.trim());
|
|
3332
|
+
if (expr === "") return null;
|
|
3333
|
+
const scan = maskCommentsAndStrings(expr);
|
|
3334
|
+
const ternary = splitTernary(scan);
|
|
3335
|
+
if (ternary !== null) {
|
|
3336
|
+
const branches = [expr.slice(ternary[0] + 1, ternary[1]), expr.slice(ternary[1] + 1)].map((part) => expressionShape(part, paths, aliases)).filter((s) => s !== null);
|
|
3337
|
+
return branches.length === 0 ? null : mergeShapes(branches);
|
|
3338
|
+
}
|
|
3339
|
+
const operands = splitTopLevel(scan, /\|\||\?\?/g);
|
|
3340
|
+
if (operands.length > 1) {
|
|
3341
|
+
const sides = operands.map(([s, e]) => expressionShape(expr.slice(s, e), paths, aliases)).filter((s) => s !== null);
|
|
3342
|
+
return sides.length === 0 ? null : mergeShapes(sides);
|
|
3343
|
+
}
|
|
3344
|
+
if (scan.startsWith("{")) {
|
|
3345
|
+
if (extractDelimitedContent(expr, scan, 0, "{", "}").length !== expr.length - 2) return null;
|
|
3346
|
+
return { isList: false, mirrors: [], fields: extractRowLiteralFields(expr).map((field) => ({ to: "", field })) };
|
|
3347
|
+
}
|
|
3348
|
+
let shape;
|
|
3349
|
+
let rest;
|
|
3350
|
+
if (scan.startsWith("[")) {
|
|
3351
|
+
const inner = extractDelimitedContent(expr, scan, 0, "[", "]");
|
|
3352
|
+
rest = inner.length + 2;
|
|
3353
|
+
const spread = /^\s*\.\.\.([\s\S]+)$/.exec(inner);
|
|
3354
|
+
if (spread === null || splitTopLevelElements(inner).length !== 1) return null;
|
|
3355
|
+
const source = expressionShape(spread[1], paths, aliases);
|
|
3356
|
+
if (source === null || !source.isList) return null;
|
|
3357
|
+
shape = source;
|
|
3358
|
+
} else {
|
|
3359
|
+
const base = readBase(expr, scan, aliases, paths);
|
|
3360
|
+
if (base === null) return null;
|
|
3361
|
+
({ shape, rest } = base);
|
|
3362
|
+
}
|
|
3363
|
+
return applyChain(expr, scan, rest, shape);
|
|
3364
|
+
}
|
|
3365
|
+
function readBase(expr, scan, aliases, paths) {
|
|
3366
|
+
const alias = /^([$\w]+)/.exec(scan);
|
|
3367
|
+
if (alias === null) return null;
|
|
3368
|
+
if (alias[1] !== "this") {
|
|
3369
|
+
const shape = aliases.get(alias[1]);
|
|
3370
|
+
return shape === void 0 ? null : { shape, rest: alias[1].length };
|
|
3371
|
+
}
|
|
3372
|
+
const segments = [];
|
|
3373
|
+
let i = 4;
|
|
3374
|
+
for (; ; ) {
|
|
3375
|
+
const dot = /^\s*\??\.\s*([$\w]+)/.exec(scan.slice(i));
|
|
3376
|
+
if (dot !== null) {
|
|
3377
|
+
if (/^\s*\(/.test(scan.slice(i + dot[0].length))) break;
|
|
3378
|
+
segments.push(dot[1]);
|
|
3379
|
+
i += dot[0].length;
|
|
3380
|
+
continue;
|
|
3381
|
+
}
|
|
3382
|
+
const bracket = /^\s*\[\s*(["'])/.exec(scan.slice(i));
|
|
3383
|
+
if (bracket !== null) {
|
|
3384
|
+
const open = i + bracket[0].length - 1;
|
|
3385
|
+
const close = scan.indexOf("]", open);
|
|
3386
|
+
const literal2 = /^(["'])([^"']*)\1\s*$/.exec(expr.slice(open, close));
|
|
3387
|
+
if (close === -1 || literal2 === null) return null;
|
|
3388
|
+
segments.push(literal2[2]);
|
|
3389
|
+
i = close + 1;
|
|
3390
|
+
continue;
|
|
3391
|
+
}
|
|
3392
|
+
break;
|
|
3393
|
+
}
|
|
3394
|
+
const path = segments.join(".");
|
|
3395
|
+
if (path === "" || path.startsWith("$")) return null;
|
|
3396
|
+
return {
|
|
3397
|
+
shape: { isList: hasPath(paths, `${path}.*`), mirrors: [{ from: path, to: "" }], fields: [] },
|
|
3398
|
+
rest: i
|
|
3399
|
+
};
|
|
3400
|
+
}
|
|
3401
|
+
function applyChain(expr, scan, start, initial) {
|
|
3402
|
+
let shape = initial;
|
|
3403
|
+
let i = start;
|
|
3404
|
+
while (scan.slice(i).trim() !== "") {
|
|
3405
|
+
const call = /^\s*\??\.\s*([$\w]+)\s*\(/.exec(scan.slice(i));
|
|
3406
|
+
if (call === null) return null;
|
|
3407
|
+
const open = i + call[0].length - 1;
|
|
3408
|
+
const args = extractDelimitedContent(expr, scan, open, "(", ")");
|
|
3409
|
+
i = open + args.length + 2;
|
|
3410
|
+
const method = call[1];
|
|
3411
|
+
if (ROW_PRESERVING_METHODS.has(method)) {
|
|
3412
|
+
if (!shape.isList) return null;
|
|
3413
|
+
} else if (ROW_PICKING_METHODS.has(method)) {
|
|
3414
|
+
if (!shape.isList) return null;
|
|
3415
|
+
shape = pickRow(shape);
|
|
3416
|
+
} else if (method === "map") {
|
|
3417
|
+
if (!shape.isList) return null;
|
|
3418
|
+
const mapped = mapRows(args, shape);
|
|
3419
|
+
if (mapped === null) return null;
|
|
3420
|
+
shape = mapped;
|
|
3421
|
+
} else {
|
|
3422
|
+
return null;
|
|
3423
|
+
}
|
|
3424
|
+
}
|
|
3425
|
+
return shape;
|
|
3426
|
+
}
|
|
3427
|
+
function pickRow(shape) {
|
|
3428
|
+
return {
|
|
3429
|
+
isList: false,
|
|
3430
|
+
mirrors: shape.mirrors.map((m) => m.to === "" ? { from: `${m.from}.*`, to: "" } : { from: m.from, to: "" }),
|
|
3431
|
+
fields: shape.fields.filter((f) => f.to === ".*").map((f) => ({ to: "", field: f.field }))
|
|
3432
|
+
};
|
|
3433
|
+
}
|
|
3434
|
+
function mapRows(args, shape) {
|
|
3435
|
+
const [callback] = splitTopLevelElements(args);
|
|
3436
|
+
if (callback === void 0) return null;
|
|
3437
|
+
const head = /^(?:async\s+)?(?:function\b[^(]*\(\s*([$\w]+)[^)]*\)|\(\s*([$\w]+)[^)]*\)|([$\w]+))\s*(=>)?\s*/.exec(callback);
|
|
3438
|
+
if (head === null) return null;
|
|
3439
|
+
const param = head[1] ?? head[2] ?? head[3];
|
|
3440
|
+
const body = callback.slice(head[0].length);
|
|
3441
|
+
let literal2 = null;
|
|
3442
|
+
const bodyScan = maskCommentsAndStrings(body);
|
|
3443
|
+
if (bodyScan.startsWith("(")) {
|
|
3444
|
+
const inner = stripWrappingParens(body).trim();
|
|
3445
|
+
if (inner.startsWith("{")) literal2 = inner;
|
|
3446
|
+
} else if (bodyScan.startsWith("{")) {
|
|
3447
|
+
const block = extractDelimitedContent(body, bodyScan, 0, "{", "}");
|
|
3448
|
+
const blockScan = maskCommentsAndStrings(block);
|
|
3449
|
+
const ret = /\breturn\s*\{/.exec(blockScan);
|
|
3450
|
+
if (ret !== null) {
|
|
3451
|
+
const open = ret.index + ret[0].length - 1;
|
|
3452
|
+
literal2 = `{${extractDelimitedContent(block, blockScan, open, "{", "}")}}`;
|
|
3453
|
+
}
|
|
3454
|
+
}
|
|
3455
|
+
if (literal2 === null) return null;
|
|
3456
|
+
const content = extractObjectContent(literal2);
|
|
3457
|
+
const spreadsRow = splitTopLevelElements(content).some((e) => e === `...${param}`);
|
|
3458
|
+
return {
|
|
3459
|
+
isList: true,
|
|
3460
|
+
mirrors: spreadsRow ? shape.mirrors.map((m) => m.to === "" ? { from: `${m.from}.*`, to: ".*" } : m) : [],
|
|
3461
|
+
fields: [
|
|
3462
|
+
...spreadsRow ? shape.fields.filter((f) => f.to === ".*") : [],
|
|
3463
|
+
...extractRowLiteralFields(literal2).map((field) => ({ to: ".*", field }))
|
|
3464
|
+
]
|
|
3465
|
+
};
|
|
3466
|
+
}
|
|
3467
|
+
function applyResultShape(getter, shape, paths) {
|
|
3468
|
+
if (shape.isList) {
|
|
3469
|
+
if (!hasPath(paths, `${getter}.*`)) paths.push({ path: `${getter}.*`, kind: "list" });
|
|
3470
|
+
if (!hasPath(paths, `${getter}.length`)) paths.push({ path: `${getter}.length`, kind: "data", typeHint: "number" });
|
|
3471
|
+
}
|
|
3472
|
+
for (const { from, to } of shape.mirrors) {
|
|
3473
|
+
if (from === getter || from.startsWith(`${getter}.`)) continue;
|
|
3474
|
+
const sources = paths.filter((p) => p.path.startsWith(`${from}.`) && (p.kind === "data" || p.kind === "list"));
|
|
3475
|
+
for (const source of sources) {
|
|
3476
|
+
const target = `${getter}${to}${source.path.slice(from.length)}`;
|
|
3477
|
+
if (!hasPath(paths, target)) paths.push(withHint({ path: target, kind: source.kind }, source.typeHint));
|
|
3478
|
+
}
|
|
3479
|
+
}
|
|
3480
|
+
for (const { to, field } of shape.fields) {
|
|
3481
|
+
pushRowFieldPaths(`${getter}${to}.${field.name}`, field, paths, 1);
|
|
3482
|
+
}
|
|
3483
|
+
}
|
|
3484
|
+
function mergeShapes(shapes) {
|
|
3485
|
+
return {
|
|
3486
|
+
isList: shapes.some((s) => s.isList),
|
|
3487
|
+
mirrors: shapes.flatMap((s) => s.mirrors),
|
|
3488
|
+
fields: shapes.flatMap((s) => s.fields)
|
|
3489
|
+
};
|
|
3490
|
+
}
|
|
3491
|
+
function stripWrappingParens(text3) {
|
|
3492
|
+
let expr = text3.trim();
|
|
3493
|
+
for (; ; ) {
|
|
3494
|
+
if (!expr.startsWith("(")) return expr;
|
|
3495
|
+
const scan = maskCommentsAndStrings(expr);
|
|
3496
|
+
const inner = extractDelimitedContent(expr, scan, 0, "(", ")");
|
|
3497
|
+
if (inner.length !== expr.length - 2) return expr;
|
|
3498
|
+
expr = inner.trim();
|
|
3499
|
+
}
|
|
3500
|
+
}
|
|
3501
|
+
function splitTernary(scan) {
|
|
3502
|
+
let depth = 0;
|
|
3503
|
+
let question = -1;
|
|
3504
|
+
let nested = 0;
|
|
3505
|
+
for (let i = 0; i < scan.length; i++) {
|
|
3506
|
+
const ch = scan[i];
|
|
3507
|
+
if (ch === "(" || ch === "[" || ch === "{") depth++;
|
|
3508
|
+
else if (ch === ")" || ch === "]" || ch === "}") depth--;
|
|
3509
|
+
else if (depth !== 0) continue;
|
|
3510
|
+
else if (ch === "?") {
|
|
3511
|
+
if (scan[i + 1] === "." || scan[i + 1] === "?" || scan[i - 1] === "?") continue;
|
|
3512
|
+
if (question === -1) question = i;
|
|
3513
|
+
else nested++;
|
|
3514
|
+
} else if (ch === ":" && question !== -1) {
|
|
3515
|
+
if (nested > 0) nested--;
|
|
3516
|
+
else return [question, i];
|
|
3517
|
+
}
|
|
3518
|
+
}
|
|
3519
|
+
return null;
|
|
3520
|
+
}
|
|
3521
|
+
function splitTopLevel(scan, separator) {
|
|
3522
|
+
const out = [];
|
|
3523
|
+
let depth = 0;
|
|
3524
|
+
let start = 0;
|
|
3525
|
+
for (let i = 0; i < scan.length; i++) {
|
|
3526
|
+
const ch = scan[i];
|
|
3527
|
+
if (ch === "(" || ch === "[" || ch === "{") depth++;
|
|
3528
|
+
else if (ch === ")" || ch === "]" || ch === "}") depth--;
|
|
3529
|
+
else if (depth === 0) {
|
|
3530
|
+
separator.lastIndex = i;
|
|
3531
|
+
const m = separator.exec(scan);
|
|
3532
|
+
if (m !== null && m.index === i) {
|
|
3533
|
+
out.push([start, i]);
|
|
3534
|
+
start = i + m[0].length;
|
|
3535
|
+
i = start - 1;
|
|
3536
|
+
}
|
|
3537
|
+
}
|
|
3538
|
+
}
|
|
3539
|
+
out.push([start, scan.length]);
|
|
3540
|
+
return out;
|
|
3541
|
+
}
|
|
3256
3542
|
function hasPath(paths, path) {
|
|
3257
3543
|
return paths.some((p) => p.path === path);
|
|
3258
3544
|
}
|
|
@@ -11290,7 +11576,7 @@ function validateFilterUsage(filter, bindingStart, msgs) {
|
|
|
11290
11576
|
start: bindingStart + filter.offset,
|
|
11291
11577
|
end: bindingStart + filter.offset + filter.name.length,
|
|
11292
11578
|
message: removedFilterMessage(filter.name, filter.args, msgs) ?? msgs.filterUnknown(filter.name),
|
|
11293
|
-
severity: "
|
|
11579
|
+
severity: "error"
|
|
11294
11580
|
});
|
|
11295
11581
|
return diagnostics;
|
|
11296
11582
|
}
|
|
@@ -11978,7 +12264,7 @@ function validateTemplateSyntax(html, stateTagName, bindAttrName = "data-wcs", l
|
|
|
11978
12264
|
start: item.exprStart + filterOffset,
|
|
11979
12265
|
end: item.exprStart + filterOffset + filterName.length,
|
|
11980
12266
|
message: removedFilterMessage(filterName, filterArgsOf(segment), msgs) ?? msgs.filterUnknown(filterName),
|
|
11981
|
-
severity: "
|
|
12267
|
+
severity: "error"
|
|
11982
12268
|
});
|
|
11983
12269
|
}
|
|
11984
12270
|
}
|
package/dist/wcs-schema.mjs
CHANGED
package/dist/wcs-tsc.mjs
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wcstack/typescript",
|
|
3
|
-
"version": "4.0.
|
|
3
|
+
"version": "4.0.1",
|
|
4
4
|
"description": "TypeScript tooling for wcstack apps: wcs-schema generates the sidecar stateSchema from a typed state file, so wcs-validate checks data-wcs paths against real types in CI and every editor.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.esm.js",
|