@wcstack/typescript 4.7.0 → 4.9.0
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 +164 -29
- 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
|
@@ -115,6 +115,9 @@ var WcsDiagnosticCode = {
|
|
|
115
115
|
// (state README「依存追跡の境界」規則 1)。ランタイムは素のアクセスと区別できないので
|
|
116
116
|
// 静的にしか検出できない。対象はオブジェクトリテラル初期値のルートだけ(配列は `items`
|
|
117
117
|
// の依存で足りる — 行の置換は `items` 自体を書き換え、in-place 変異は array-mutation が止める)。
|
|
118
|
+
// 読んだパスをページの束縛(組み込みタグの spread・書き戻す束縛・要素の出力)が書くなら error(その書き込みは
|
|
119
|
+
// getter に届かないと断定できる — 継ぎ目アプリ family-ledger)、スクリプトの書き込みだけなら warning(ルートも
|
|
120
|
+
// 置き換えるなら壊れない)。時刻・`location` を読む getter も同じ code(warning)。
|
|
118
121
|
GetterUntrackedRead: "wcs/getter-untracked-read",
|
|
119
122
|
OnHandlerThis: "wcs/on-handler-this",
|
|
120
123
|
// --- <wcs-state> script: $watch declaration ---
|
|
@@ -1213,7 +1216,7 @@ var SENTENCES = {
|
|
|
1213
1216
|
[
|
|
1214
1217
|
29
|
|
1215
1218
|
/* FilterValueNotDate */
|
|
1216
|
-
]: (fn, v) => `filter ${fn} requires a date value (a Date), not ${was(v)}${typeof v === "string" || typeof v === "number" ? " \u2014 make the Date where it is read: a getter returning new Date(\u2026)" : ""}`,
|
|
1219
|
+
]: (fn, v) => `filter ${fn} requires a date value (a Date), not ${was(v)}${typeof v === "string" || typeof v === "number" ? " \u2014 make the Date where it is read: a getter returning new Date(\u2026)" : ""}${typeof v === "string" && /^\d{4}-\d\d-\d\d$/.test(v) ? `. A date-only string is a day, not a moment: new Date(${JSON.stringify(v)}) is midnight UTC, the day before west of Greenwich \u2014 new Date(y, m - 1, d) is that day where the page is` : ""}`,
|
|
1217
1220
|
[
|
|
1218
1221
|
52
|
|
1219
1222
|
/* FilterStyleInvalid */
|
|
@@ -3025,7 +3028,7 @@ function tokenize(text3) {
|
|
|
3025
3028
|
i += 2;
|
|
3026
3029
|
continue;
|
|
3027
3030
|
}
|
|
3028
|
-
if ("{}[]<>()
|
|
3031
|
+
if ("{}[]<>()|&,;:?!*.=".includes(ch)) {
|
|
3029
3032
|
tokens.push({ t: "punct", v: ch });
|
|
3030
3033
|
i++;
|
|
3031
3034
|
continue;
|
|
@@ -3074,6 +3077,7 @@ function parseJsDocType(text3) {
|
|
|
3074
3077
|
const member = parsePostfix();
|
|
3075
3078
|
if (member === null) return null;
|
|
3076
3079
|
members.push(member);
|
|
3080
|
+
if (expect("&")) continue;
|
|
3077
3081
|
if (!expect("|")) break;
|
|
3078
3082
|
}
|
|
3079
3083
|
if (isPunct("=>")) {
|
|
@@ -3237,12 +3241,21 @@ function readBracedType(comment, tag) {
|
|
|
3237
3241
|
return null;
|
|
3238
3242
|
}
|
|
3239
3243
|
function readJsDocTypeBefore(content, propIndex) {
|
|
3244
|
+
const comment = jsDocCommentBefore(content, propIndex);
|
|
3245
|
+
return comment === void 0 ? void 0 : readBracedType(comment, "type")?.type.trim();
|
|
3246
|
+
}
|
|
3247
|
+
function readJsDocReturnsBefore(content, propIndex) {
|
|
3248
|
+
const comment = jsDocCommentBefore(content, propIndex);
|
|
3249
|
+
if (comment === void 0) return void 0;
|
|
3250
|
+
return (readBracedType(comment, "returns") ?? readBracedType(comment, "return"))?.type.trim();
|
|
3251
|
+
}
|
|
3252
|
+
function jsDocCommentBefore(content, propIndex) {
|
|
3240
3253
|
let end = propIndex;
|
|
3241
3254
|
while (end > 0 && /\s/.test(content[end - 1])) end--;
|
|
3242
3255
|
if (!content.startsWith("*/", end - 2)) return void 0;
|
|
3243
3256
|
const start = content.lastIndexOf("/*", end - 2);
|
|
3244
3257
|
if (start === -1 || content[start + 2] !== "*") return void 0;
|
|
3245
|
-
return
|
|
3258
|
+
return content.slice(start, end);
|
|
3246
3259
|
}
|
|
3247
3260
|
function collectJsDocTypedefs(script, scan) {
|
|
3248
3261
|
const typedefs = /* @__PURE__ */ new Map();
|
|
@@ -3301,10 +3314,10 @@ var RESERVED_WATCH_KEY = "$watch";
|
|
|
3301
3314
|
var RESERVED_SCAN_KEY = "$scan";
|
|
3302
3315
|
var RESERVED_RECURSION_KEY = RECURSION_KEY;
|
|
3303
3316
|
function analyzeStatePaths(scriptContent) {
|
|
3304
|
-
const
|
|
3305
|
-
if (!
|
|
3317
|
+
const root = locateDefaultExportObject(scriptContent);
|
|
3318
|
+
if (!root || !root.content) return [];
|
|
3306
3319
|
const paths = [];
|
|
3307
|
-
const topLevelProps = parseTopLevelProperties(
|
|
3320
|
+
const topLevelProps = parseTopLevelProperties(root.content);
|
|
3308
3321
|
const pendingStreamValues = [];
|
|
3309
3322
|
const pendingListKeys = [];
|
|
3310
3323
|
const recursionSpec = specFromRecursionValue(topLevelProps.find((p) => p.name === RESERVED_RECURSION_KEY));
|
|
@@ -3329,6 +3342,7 @@ function analyzeStatePaths(scriptContent) {
|
|
|
3329
3342
|
const settable = !recursive && effective !== "data" && effective.set;
|
|
3330
3343
|
paths.push({ path: prop.name, kind: recursive ? "recursive" : "computed", ...settable ? { settable } : {} });
|
|
3331
3344
|
}
|
|
3345
|
+
if (prop.declaredType !== void 0) pushDeclaredShapePaths(prop.name, prop.declaredType, paths, 0, typedefs);
|
|
3332
3346
|
continue;
|
|
3333
3347
|
}
|
|
3334
3348
|
pushDataPropertyPaths(prop, paths, typedefs);
|
|
@@ -3350,7 +3364,7 @@ function analyzeStatePaths(scriptContent) {
|
|
|
3350
3364
|
}
|
|
3351
3365
|
}
|
|
3352
3366
|
collectRowShapesFromAssignments(scriptContent, paths);
|
|
3353
|
-
collectGetterResultShapes(topLevelProps, effectiveDescriptors, paths);
|
|
3367
|
+
collectGetterResultShapes(scriptContent, root.start, topLevelProps, effectiveDescriptors, paths);
|
|
3354
3368
|
return paths;
|
|
3355
3369
|
}
|
|
3356
3370
|
function specFromRecursionValue(prop) {
|
|
@@ -3762,23 +3776,45 @@ var ROW_PRESERVING_METHODS = /* @__PURE__ */ new Set([
|
|
|
3762
3776
|
]);
|
|
3763
3777
|
var ROW_PICKING_METHODS = /* @__PURE__ */ new Set(["find", "findLast", "at"]);
|
|
3764
3778
|
var MAX_GETTER_SHAPE_PASSES = 4;
|
|
3765
|
-
function collectGetterResultShapes(props, effective, paths) {
|
|
3779
|
+
function collectGetterResultShapes(script, rootStart, props, effective, paths) {
|
|
3766
3780
|
const getters = /* @__PURE__ */ new Map();
|
|
3767
3781
|
for (const prop of props) {
|
|
3768
3782
|
if (prop.kind !== "getter" || prop.accessor !== "get" || prop.value === void 0) continue;
|
|
3769
3783
|
if (prop.name.startsWith("$") || hasRecursionWildcard(prop.name) || effective.get(prop.name) === "data") continue;
|
|
3770
3784
|
getters.set(prop.name, prop);
|
|
3771
3785
|
}
|
|
3786
|
+
const assignments = collectAssignments(script, rootStart, props);
|
|
3772
3787
|
for (let pass = 0; pass < MAX_GETTER_SHAPE_PASSES; pass++) {
|
|
3773
3788
|
const before = paths.length;
|
|
3774
3789
|
for (const [name, prop] of getters) {
|
|
3775
3790
|
const shape = getterResultShape(prop.value, paths);
|
|
3776
3791
|
if (shape !== null) applyResultShape(name, shape, paths);
|
|
3777
3792
|
}
|
|
3793
|
+
const aliasesByBody = /* @__PURE__ */ new Map();
|
|
3794
|
+
for (const assignment of assignments) {
|
|
3795
|
+
let aliases = /* @__PURE__ */ new Map();
|
|
3796
|
+
if (assignment.body !== null) {
|
|
3797
|
+
aliases = aliasesByBody.get(assignment.body) ?? collectAliases(assignment.body, paths);
|
|
3798
|
+
aliasesByBody.set(assignment.body, aliases);
|
|
3799
|
+
}
|
|
3800
|
+
const shape = expressionShape(assignment.expr, paths, aliases);
|
|
3801
|
+
if (shape !== null) applyAssignedShape(assignment.target, shape, paths);
|
|
3802
|
+
}
|
|
3778
3803
|
if (paths.length === before) break;
|
|
3779
3804
|
}
|
|
3780
3805
|
}
|
|
3781
3806
|
function getterResultShape(body, paths) {
|
|
3807
|
+
const scan = maskCommentsAndStrings(body);
|
|
3808
|
+
const aliases = collectAliases(body, paths);
|
|
3809
|
+
const shapes = [];
|
|
3810
|
+
for (const m of scan.matchAll(/\breturn\b/g)) {
|
|
3811
|
+
const start = m.index + m[0].length;
|
|
3812
|
+
const shape = expressionShape(readExpression(body, scan, start), paths, aliases);
|
|
3813
|
+
if (shape !== null) shapes.push(shape);
|
|
3814
|
+
}
|
|
3815
|
+
return shapes.length === 0 ? null : mergeShapes(shapes);
|
|
3816
|
+
}
|
|
3817
|
+
function collectAliases(body, paths) {
|
|
3782
3818
|
const scan = maskCommentsAndStrings(body);
|
|
3783
3819
|
const aliases = /* @__PURE__ */ new Map();
|
|
3784
3820
|
for (const m of scan.matchAll(/\b(?:const|let|var)\s+([$\w]+)\s*=(?![=>])/g)) {
|
|
@@ -3788,13 +3824,36 @@ function getterResultShape(body, paths) {
|
|
|
3788
3824
|
const shape = expressionShape(readExpression(body, scan, start), paths, aliases);
|
|
3789
3825
|
if (shape !== null) aliases.set(name, shape);
|
|
3790
3826
|
}
|
|
3791
|
-
|
|
3792
|
-
|
|
3793
|
-
|
|
3794
|
-
|
|
3795
|
-
|
|
3827
|
+
return aliases;
|
|
3828
|
+
}
|
|
3829
|
+
var PLAIN_ASSIGN = new RegExp(String.raw`\bthis\s*(?:\.\s*([$\w]+)|\[\s*["']([^"']+)["']\s*\])\s*=(?![=>])`, "gd");
|
|
3830
|
+
function collectAssignments(script, rootStart, props) {
|
|
3831
|
+
const scan = maskCommentsAndStrings(script);
|
|
3832
|
+
const bodies = props.filter((p) => (p.kind === "getter" || p.kind === "method") && p.value !== void 0 && p.valueStart !== void 0).map((p) => ({ start: rootStart + p.valueStart, body: p.value }));
|
|
3833
|
+
const out = [];
|
|
3834
|
+
PLAIN_ASSIGN.lastIndex = 0;
|
|
3835
|
+
let match;
|
|
3836
|
+
while ((match = PLAIN_ASSIGN.exec(scan)) !== null) {
|
|
3837
|
+
if (/\.\s*$/.test(scan.slice(Math.max(0, match.index - 8), match.index))) continue;
|
|
3838
|
+
const span = match.indices[1] ?? match.indices[2];
|
|
3839
|
+
const target = script.slice(span[0], span[1]);
|
|
3840
|
+
if (target.startsWith("$") || target.includes("*")) continue;
|
|
3841
|
+
const expr = readExpression(script, scan, match.index + match[0].length);
|
|
3842
|
+
const owner = bodies.find((b) => b.start <= match.index && match.index < b.start + b.body.length);
|
|
3843
|
+
out.push({ target, expr, body: owner?.body ?? null });
|
|
3844
|
+
}
|
|
3845
|
+
return out;
|
|
3846
|
+
}
|
|
3847
|
+
function applyAssignedShape(target, shape, paths) {
|
|
3848
|
+
const declared = paths.find((p) => p.path === target);
|
|
3849
|
+
if (declared === void 0 || declared.kind !== "data") return;
|
|
3850
|
+
if (hasPath(paths, `${target}.*`)) {
|
|
3851
|
+
if (shape.isList) applyResultShape(target, shape, paths);
|
|
3852
|
+
return;
|
|
3853
|
+
}
|
|
3854
|
+
if (!shape.isList && declared.rawInitial !== void 0 && isObjectLiteral(declared.rawInitial)) {
|
|
3855
|
+
applyResultShape(target, shape, paths);
|
|
3796
3856
|
}
|
|
3797
|
-
return shapes.length === 0 ? null : mergeShapes(shapes);
|
|
3798
3857
|
}
|
|
3799
3858
|
function readExpression(text3, scan, start) {
|
|
3800
3859
|
let depth = 0;
|
|
@@ -3849,9 +3908,7 @@ function expressionShape(text3, paths, aliases) {
|
|
|
3849
3908
|
}
|
|
3850
3909
|
} else if (scan.startsWith("(")) {
|
|
3851
3910
|
const inner = extractDelimitedContent(expr, scan, 0, "(", ")");
|
|
3852
|
-
|
|
3853
|
-
if (base === null) return null;
|
|
3854
|
-
shape = base;
|
|
3911
|
+
shape = expressionShape(inner, paths, aliases) ?? OPAQUE;
|
|
3855
3912
|
rest = inner.length + 2;
|
|
3856
3913
|
} else {
|
|
3857
3914
|
const base = readBase(expr, scan, aliases, paths);
|
|
@@ -4200,13 +4257,15 @@ function locateDefaultExportObject(script) {
|
|
|
4200
4257
|
const braceIndex = scan.indexOf(match[1], match.index);
|
|
4201
4258
|
return { content: extractBracedContent(script, scan, braceIndex), start: braceIndex + 1 };
|
|
4202
4259
|
}
|
|
4203
|
-
function extractDefaultExportObject(script) {
|
|
4204
|
-
return locateDefaultExportObject(script)?.content ?? null;
|
|
4205
|
-
}
|
|
4206
4260
|
function parseTopLevelProperties(objectContent, allowEmptyDataKeys = false) {
|
|
4207
4261
|
const props = [];
|
|
4208
4262
|
const scan = maskCommentsAndStrings(objectContent);
|
|
4209
|
-
const
|
|
4263
|
+
const params = String.raw`\((?:[^()]|\((?:[^()]|\((?:[^()]|\([^()]*\))*\))*\))*\)`;
|
|
4264
|
+
const dataKey = allowEmptyDataKeys ? String.raw`(?:"([^"]*)"|'([^']*)'|([$\w]+))` : String.raw`(?:"([^"]+)"|'([^']+)'|([$\w]+))`;
|
|
4265
|
+
const regex = new RegExp(
|
|
4266
|
+
String.raw`(?:(?:get|set)\s+(?:"([^"]+)"|'([^']+)'|([$\w]+))\s*${params}\s*\{)|(?:(?:async\s+)?(?:"([^"]+)"|'([^']+)'|([$\w]+))\s*${params}\s*\{)|(?:${dataKey}\s*:\s*)`,
|
|
4267
|
+
"gd"
|
|
4268
|
+
);
|
|
4210
4269
|
let match;
|
|
4211
4270
|
while ((match = regex.exec(scan)) !== null) {
|
|
4212
4271
|
const indices = match.indices;
|
|
@@ -4227,6 +4286,8 @@ function parseTopLevelProperties(objectContent, allowEmptyDataKeys = false) {
|
|
|
4227
4286
|
if (accessorName) {
|
|
4228
4287
|
const { body, bodyStart } = skipBody();
|
|
4229
4288
|
const accessor = match[0].startsWith("set") ? "set" : "get";
|
|
4289
|
+
const returnsText = accessor === "get" ? readJsDocReturnsBefore(objectContent, match.index) : void 0;
|
|
4290
|
+
const declaredType = returnsText === void 0 ? null : parseJsDocType(returnsText);
|
|
4230
4291
|
props.push({
|
|
4231
4292
|
name: accessorName,
|
|
4232
4293
|
kind: "getter",
|
|
@@ -4234,7 +4295,8 @@ function parseTopLevelProperties(objectContent, allowEmptyDataKeys = false) {
|
|
|
4234
4295
|
value: body,
|
|
4235
4296
|
valueStart: bodyStart,
|
|
4236
4297
|
nameStart: nameSpan[0],
|
|
4237
|
-
nameEnd: nameSpan[1]
|
|
4298
|
+
nameEnd: nameSpan[1],
|
|
4299
|
+
...declaredType !== null ? { declaredType } : {}
|
|
4238
4300
|
});
|
|
4239
4301
|
continue;
|
|
4240
4302
|
}
|
|
@@ -11389,6 +11451,7 @@ var ja = {
|
|
|
11389
11451
|
pathReadonlyTwoWay: (p, prop) => `"${p}" \u306F setter \u306E\u7121\u3044 getter \u3067\u3059\u304C\u3001${prop}: \u306F\u8AAD\u8005\u306E\u5165\u529B\u3092\u66F8\u304D\u623B\u3059\u306E\u3067\u3001\u5165\u529B\u306E\u305F\u3073\u306B\u66F8\u304D\u8FBC\u307F\u304C\u5931\u6557\u3057\u307E\u3059\uFF08"${p}" is a getter without a setter\uFF09\u3002setter \u3092\u8DB3\u3059\uFF08set "${p}"(v) { \u2026 }\uFF09\u304B\u3001#ro \u3067\u4E00\u65B9\u5411\u306B\u3057\u3066\u304F\u3060\u3055\u3044\uFF08${prop}#ro: ${p}\uFF09`,
|
|
11390
11452
|
submitWithoutPrevent: (h) => `onsubmit \u306B #prevent \u304C\u7121\u304F\u3001"${h}" \u3082 preventDefault \u3092\u547C\u3073\u307E\u305B\u3093\u3002\u30D6\u30E9\u30A6\u30B6\u306F\u30D5\u30A9\u30FC\u30E0\u3082\u9001\u308B\u306E\u3067\u3001\u30DA\u30FC\u30B8\u304C\u8AAD\u307F\u76F4\u3055\u308C\u308B\u304B\u3001router \u304C\u3042\u308C\u3070 GET \u304C\u6B04\u306E\u5024\uFF08\u30D1\u30B9\u30EF\u30FC\u30C9\u3082\uFF09\u3092 URL \u3068\u5C65\u6B74\u306B\u8F09\u305B\u307E\u3059\u3002onsubmit#prevent: ${h} \u3068\u66F8\u304F\u304B\u3001\u30CF\u30F3\u30C9\u30E9\u3067 event.preventDefault() \u3092\u547C\u3093\u3067\u304F\u3060\u3055\u3044`,
|
|
11391
11453
|
getterUntrackedRead: (root, sp) => `\u3053\u3053\u3067\u8FFD\u8DE1\u3055\u308C\u308B\u306E\u306F "${root}" \u3060\u3051\u3067\u3059\u3002"${sp}" \u304C\u5909\u308F\u3063\u3066\u3082\u3053\u306E getter \u306F\u518D\u8A55\u4FA1\u3055\u308C\u307E\u305B\u3093\uFF08\u30D1\u30B9\u8AAD\u307F\u53D6\u308A\u306E\u5148\u306E\u7D20\u306E\u30D7\u30ED\u30D1\u30C6\u30A3\u30A2\u30AF\u30BB\u30B9\u306F\u8FFD\u8DE1\u3055\u308C\u306A\u3044\uFF09\u3002this["${sp}"] \u3067\u8AAD\u3093\u3067\u304F\u3060\u3055\u3044`,
|
|
11454
|
+
getterUntrackedReadBound: (root, sp, binding, tag, written) => `\u3053\u3053\u3067\u8FFD\u8DE1\u3055\u308C\u308B\u306E\u306F "${root}" \u3060\u3051\u3067\u3059\u304C\u3001<${tag}> \u306E\u675F\u7E1B "${binding}" \u306F "${written}" \u3092\u30D1\u30B9\u3067\u66F8\u304D\u307E\u3059\u3002\u305D\u306E\u66F8\u304D\u8FBC\u307F\u306F\u3053\u306E getter \u306B\u5C4A\u304B\u305A\u3001getter \u306F\u524D\u306B\u8A08\u7B97\u3057\u305F\u5024\u3092\u8FD4\u3057\u7D9A\u3051\u307E\u3059\uFF08\u30D1\u30B9\u8AAD\u307F\u53D6\u308A\u306E\u5148\u306E\u7D20\u306E\u30D7\u30ED\u30D1\u30C6\u30A3\u30A2\u30AF\u30BB\u30B9\u306F\u8FFD\u8DE1\u3055\u308C\u306A\u3044\uFF09\u3002this["${sp}"] \u3067\u8AAD\u3093\u3067\u304F\u3060\u3055\u3044`,
|
|
11392
11455
|
handlerFilterNotAllowed: (prop) => `\u30A4\u30D9\u30F3\u30C8\u30CF\u30F3\u30C9\u30E9 "${prop}" \u306B\u30D5\u30A3\u30EB\u30BF\u306F\u4F7F\u7528\u3067\u304D\u307E\u305B\u3093`,
|
|
11393
11456
|
typeExpectation: (label, expected, resultType) => `"${label}" \u306B\u306F${JA_EXPECTED_LABEL[expected]}\u304C\u5FC5\u8981\u3067\u3059\uFF08\u73FE\u5728\u306E\u578B: ${resultType}\uFF09`,
|
|
11394
11457
|
filterUnknown: (n) => `\u30D5\u30A3\u30EB\u30BF "${n}" \u306F\u7D44\u307F\u8FBC\u307F\u30D5\u30A3\u30EB\u30BF\u306B\u5B58\u5728\u3057\u307E\u305B\u3093`,
|
|
@@ -11599,6 +11662,7 @@ var en = {
|
|
|
11599
11662
|
pathReadonlyTwoWay: (p, prop) => `"${p}" is a getter without a setter, and ${prop}: writes back what the reader enters, so every edit fails ("${p}" is a getter without a setter). Add a setter (set "${p}"(v) { \u2026 }), or bind it one way with #ro (${prop}#ro: ${p})`,
|
|
11600
11663
|
submitWithoutPrevent: (h) => `onsubmit has no #prevent and "${h}" does not call preventDefault, so the browser submits the form too: the page reloads, or with the router a GET puts the fields (a password too) in the URL and the history. Write onsubmit#prevent: ${h}, or call event.preventDefault() in the handler`,
|
|
11601
11664
|
getterUntrackedRead: (root, sp) => `Only "${root}" is tracked here: the getter is not re-evaluated when "${sp}" changes (plain property access after a path read is not tracked). Read this["${sp}"] instead`,
|
|
11665
|
+
getterUntrackedReadBound: (root, sp, binding, tag, written) => `Only "${root}" is tracked here, but the binding "${binding}" on <${tag}> writes "${written}" by path: that write never reaches this getter, which keeps returning the value it last computed (plain property access after a path read is not tracked). Read this["${sp}"] instead`,
|
|
11602
11666
|
handlerFilterNotAllowed: (prop) => `Filters cannot be applied to event handler "${prop}"`,
|
|
11603
11667
|
typeExpectation: (label, expected, resultType) => `"${label}" requires ${EN_EXPECTED_LABEL[expected]} (current type: ${resultType})`,
|
|
11604
11668
|
filterUnknown: (n) => `Filter "${n}" is not a built-in filter`,
|
|
@@ -11916,7 +11980,7 @@ function forEachStateMethod(elements, html, fileReader, spelling, fn) {
|
|
|
11916
11980
|
for (const callable of analyzeCallableBodies(script)) {
|
|
11917
11981
|
if (callable.kind !== "method" || spelling !== null && !callable.body.includes(spelling)) continue;
|
|
11918
11982
|
const head = script.slice(callable.end, callable.bodyStart - 1);
|
|
11919
|
-
const params = /\(([
|
|
11983
|
+
const params = /\(([\s\S]*)\)\s*$/.exec(head);
|
|
11920
11984
|
if (params === null) continue;
|
|
11921
11985
|
fn(volume === null ? callable.name : `${volume}.${callable.name}`, params[1], callable.body);
|
|
11922
11986
|
}
|
|
@@ -12336,7 +12400,8 @@ function openTagBefore(html, valueStart) {
|
|
|
12336
12400
|
return lt2 === -1 ? "" : html.slice(lt2, valueStart);
|
|
12337
12401
|
}
|
|
12338
12402
|
function writesBack(attr, prop, written) {
|
|
12339
|
-
|
|
12403
|
+
const modifiers = written.split("#").slice(1).flatMap((part) => part.split(","));
|
|
12404
|
+
if (modifiers.some((modifier) => modifier.trim() === "ro")) return false;
|
|
12340
12405
|
if (prop === "radio" || prop === "checkbox") return true;
|
|
12341
12406
|
const tag = attr.tagName;
|
|
12342
12407
|
if (tag === void 0 || tag.includes("-")) return false;
|
|
@@ -14279,6 +14344,10 @@ var BUILTIN_TAGS = {
|
|
|
14279
14344
|
"withCredentials": "with-credentials",
|
|
14280
14345
|
"events": "events",
|
|
14281
14346
|
"raw": "raw",
|
|
14347
|
+
"autoReconnect": "auto-reconnect",
|
|
14348
|
+
"reconnectInterval": "reconnect-interval",
|
|
14349
|
+
"maxReconnects": "max-reconnects",
|
|
14350
|
+
"staleAfter": "stale-after",
|
|
14282
14351
|
"manual": "manual",
|
|
14283
14352
|
"trigger": null
|
|
14284
14353
|
},
|
|
@@ -14405,6 +14474,14 @@ var BUILTIN_TAGS = {
|
|
|
14405
14474
|
"abort"
|
|
14406
14475
|
]
|
|
14407
14476
|
},
|
|
14477
|
+
"wcs-upload-header": {
|
|
14478
|
+
"package": "upload",
|
|
14479
|
+
"hasWcBindable": false,
|
|
14480
|
+
"observedAttributes": [],
|
|
14481
|
+
"inputs": {},
|
|
14482
|
+
"properties": [],
|
|
14483
|
+
"commands": []
|
|
14484
|
+
},
|
|
14408
14485
|
"wcs-view-transition": {
|
|
14409
14486
|
"package": "view-transition",
|
|
14410
14487
|
"hasWcBindable": true,
|
|
@@ -14469,6 +14546,7 @@ var BUILTIN_TAGS = {
|
|
|
14469
14546
|
"reconnectInterval": "reconnect-interval",
|
|
14470
14547
|
"maxReconnects": "max-reconnects",
|
|
14471
14548
|
"binaryType": "binary-type",
|
|
14549
|
+
"staleAfter": "stale-after",
|
|
14472
14550
|
"manual": "manual",
|
|
14473
14551
|
"trigger": null,
|
|
14474
14552
|
"send": null
|
|
@@ -16692,7 +16770,7 @@ function validateGetterCycles(script, scriptStart, locale2) {
|
|
|
16692
16770
|
}
|
|
16693
16771
|
return out;
|
|
16694
16772
|
}
|
|
16695
|
-
function validateGetterUntrackedReads(script, scriptStart, nestedWriteRoots, locale2) {
|
|
16773
|
+
function validateGetterUntrackedReads(script, scriptStart, mountPath, nestedWriteRoots, bindingWrites, locale2) {
|
|
16696
16774
|
const getters = analyzeCallableBodies(script).filter((entry) => entry.kind === "getter" && entry.accessor === "get");
|
|
16697
16775
|
if (getters.length === 0) return [];
|
|
16698
16776
|
const objectRoots = /* @__PURE__ */ new Set();
|
|
@@ -16710,18 +16788,66 @@ function validateGetterUntrackedReads(script, scriptStart, nestedWriteRoots, loc
|
|
|
16710
16788
|
if (read.written) continue;
|
|
16711
16789
|
const segments = read.callee ? read.chain.slice(0, -1) : read.chain;
|
|
16712
16790
|
if (segments.length < 2 || !objectRoots.has(segments[0])) continue;
|
|
16713
|
-
|
|
16791
|
+
const prefix = mountPath === null ? [] : mountPath.split(".");
|
|
16792
|
+
const write = boundWriteOf([...prefix, ...segments], prefix.length + read.path.split(".").length, bindingWrites());
|
|
16793
|
+
if (write === void 0 && !nestedWriteRoots().has([...prefix, segments[0]].join("."))) continue;
|
|
16794
|
+
const sp = segments.join(".");
|
|
16714
16795
|
out.push({
|
|
16715
16796
|
code: WcsDiagnosticCode.GetterUntrackedRead,
|
|
16716
16797
|
start: scriptStart + getter.bodyStart + read.start,
|
|
16717
16798
|
end: scriptStart + getter.bodyStart + read.end,
|
|
16718
|
-
message: msgs.getterUntrackedRead(segments[0],
|
|
16719
|
-
severity: "warning"
|
|
16799
|
+
message: write === void 0 ? msgs.getterUntrackedRead(segments[0], sp) : msgs.getterUntrackedReadBound(segments[0], sp, write.binding, write.tag, write.path),
|
|
16800
|
+
severity: write === void 0 ? "warning" : "error"
|
|
16720
16801
|
});
|
|
16721
16802
|
}
|
|
16722
16803
|
}
|
|
16723
16804
|
return out;
|
|
16724
16805
|
}
|
|
16806
|
+
function boundWriteOf(segments, tracked, writes) {
|
|
16807
|
+
return writes.find((write) => {
|
|
16808
|
+
const written = write.path.split(".");
|
|
16809
|
+
if (written.length <= tracked || written.length > segments.length) return false;
|
|
16810
|
+
return written.every((s, i) => s === segments[i] || s === "*" && /^\d+$/.test(segments[i]));
|
|
16811
|
+
});
|
|
16812
|
+
}
|
|
16813
|
+
function collectBindingWrites(html, bindAttrName) {
|
|
16814
|
+
const out = [];
|
|
16815
|
+
const ignored = findIgnoredRanges(html, bindAttrName);
|
|
16816
|
+
for (const attr of findAllBindAttributes(html, bindAttrName)) {
|
|
16817
|
+
if (ignored.some(([start, end]) => attr.valueStart >= start && attr.valueStart < end)) continue;
|
|
16818
|
+
const tag = attr.tagName ?? "";
|
|
16819
|
+
const contract = BUILTIN_TAGS[tag];
|
|
16820
|
+
const outputs = contract?.hasWcBindable === true ? contract.properties : null;
|
|
16821
|
+
const bindings = parseBindTextWithPositions(attr.value);
|
|
16822
|
+
bindings.forEach((binding, index) => {
|
|
16823
|
+
const parsed = binding.parsed;
|
|
16824
|
+
if (parsed === null || binding.pathRange === null) return;
|
|
16825
|
+
let path = parsed.statePathName;
|
|
16826
|
+
if (path.startsWith(".")) {
|
|
16827
|
+
const at2 = attr.valueStart + binding.pathRange.start;
|
|
16828
|
+
const list = getRowShorthandForPath(html, at2, bindAttrName) === null ? null : getResolvedForListPath(html, at2, bindAttrName);
|
|
16829
|
+
if (list === null) return;
|
|
16830
|
+
path = path === "." ? `${list}.*` : `${list}.*.${path.slice(1)}`;
|
|
16831
|
+
}
|
|
16832
|
+
const write = (written) => {
|
|
16833
|
+
out.push({ path: written, binding: binding.exprText, tag });
|
|
16834
|
+
};
|
|
16835
|
+
if (parsed.bindingType === "spread") {
|
|
16836
|
+
if (outputs === null) return;
|
|
16837
|
+
const later = new Set(bindings.slice(index + 1).flatMap((b) => b.parsed?.bindingType === "prop" ? [b.parsed.propName] : []));
|
|
16838
|
+
for (const output of outputs) if (!later.has(output)) write(`${path}.${output}`);
|
|
16839
|
+
return;
|
|
16840
|
+
}
|
|
16841
|
+
if (parsed.propModifiers.includes("ro")) return;
|
|
16842
|
+
if (parsed.bindingType === "radio" || parsed.bindingType === "checkbox") {
|
|
16843
|
+
write(path);
|
|
16844
|
+
} else if (parsed.bindingType === "prop" && (writesBack(attr, parsed.propName, parsed.propName) || outputs?.includes(parsed.propName) === true)) {
|
|
16845
|
+
write(path);
|
|
16846
|
+
}
|
|
16847
|
+
});
|
|
16848
|
+
}
|
|
16849
|
+
return out;
|
|
16850
|
+
}
|
|
16725
16851
|
function validateGetterLocationReads(script, scriptStart, locale2) {
|
|
16726
16852
|
if (!script.includes("location")) return [];
|
|
16727
16853
|
const msgs = getMessages(locale2);
|
|
@@ -17052,12 +17178,21 @@ function validateSemantics(html, stateTagName = "wcs-state", locale2, bindAttrNa
|
|
|
17052
17178
|
const blocks = parseLoadedScriptBlocks(html, stateTagName);
|
|
17053
17179
|
let nestedWriteRoots = null;
|
|
17054
17180
|
const getNestedWriteRoots = () => nestedWriteRoots ??= collectNestedWriteRoots(html, stateTagName, bindAttrName, blocks);
|
|
17181
|
+
let bindingWrites = null;
|
|
17182
|
+
const getBindingWrites = () => bindingWrites ??= collectBindingWrites(html, bindAttrName);
|
|
17055
17183
|
for (const block of blocks) {
|
|
17056
17184
|
out.push(...validateIndexArity(block.content, block.contentStart, locale2));
|
|
17057
17185
|
out.push(...validateNameAliases(block.content, block.contentStart, locale2, block.mountPath));
|
|
17058
17186
|
out.push(...validateIndexParamReads(block.content, block.contentStart, locale2));
|
|
17059
17187
|
out.push(...validateGetterCycles(block.content, block.contentStart, locale2));
|
|
17060
|
-
out.push(...validateGetterUntrackedReads(
|
|
17188
|
+
out.push(...validateGetterUntrackedReads(
|
|
17189
|
+
block.content,
|
|
17190
|
+
block.contentStart,
|
|
17191
|
+
block.mountPath,
|
|
17192
|
+
getNestedWriteRoots,
|
|
17193
|
+
getBindingWrites,
|
|
17194
|
+
locale2
|
|
17195
|
+
));
|
|
17061
17196
|
out.push(...validateGetterLocationReads(block.content, block.contentStart, locale2));
|
|
17062
17197
|
out.push(...validateGetterClockReads(block.content, block.contentStart, locale2));
|
|
17063
17198
|
out.push(...validateOnHandlerThis(block.content, block.contentStart, locale2));
|
package/dist/wcs-schema.mjs
CHANGED
package/dist/wcs-tsc.mjs
CHANGED
|
@@ -25,7 +25,7 @@ function unsupportedTypeScriptMessage(version) {
|
|
|
25
25
|
return `@wcstack/typescript runs on TypeScript 5 or 6, and the project has TypeScript ${version}, which has no JavaScript compiler API: npm i -D typescript@6`;
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
var version = "4.
|
|
28
|
+
var version = "4.9.0";
|
|
29
29
|
var pkg = {
|
|
30
30
|
version: version};
|
|
31
31
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wcstack/typescript",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.9.0",
|
|
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",
|