@wcstack/typescript 4.0.1 → 4.0.3
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 +483 -39
- 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
|
@@ -199,6 +199,13 @@ var WcsDiagnosticCode = {
|
|
|
199
199
|
TriggerSeededTruthy: "wcs/trigger-seeded-truthy",
|
|
200
200
|
// 非 manual <wcs-storage> value バインド先の空値シード(初期書き戻しが保存値を上書き)。
|
|
201
201
|
StorageSeedClobber: "wcs/storage-seed-clobber",
|
|
202
|
+
// `value#init=element` の <wcs-storage> バインド先の null 以外のリテラルシード
|
|
203
|
+
// (空のキーは null を読むので、初回の引き込みでシードが null に置き換わる)。
|
|
204
|
+
StorageInitSeed: "wcs/storage-init-seed",
|
|
205
|
+
// 組み込み I/O ノードの入力を束ねたパスの下へ、ページが書き込む(`value: cart` と
|
|
206
|
+
// `this["cart.0.quantity"] = …`)。束縛はそのパス自身への代入でしか当て直されないので、
|
|
207
|
+
// 下への書き込みは要素に届かない(storage なら保存されない)。
|
|
208
|
+
InputNestedWrite: "wcs/input-nested-write",
|
|
202
209
|
// --- accessibility (docs/a11y-design.md §8 / D9) ---
|
|
203
210
|
// `attr.aria-*` バインドの属性名が WAI-ARIA に存在しない(タイポ)。
|
|
204
211
|
// setAttribute はそのまま書き、支援技術は黙って無視する。severity は warning
|
|
@@ -2822,6 +2829,292 @@ function coversSuffix(spec, familySuffix, suffix) {
|
|
|
2822
2829
|
return false;
|
|
2823
2830
|
}
|
|
2824
2831
|
|
|
2832
|
+
// src/service/jsdocType.ts
|
|
2833
|
+
var UNKNOWN = { kind: "unknown" };
|
|
2834
|
+
var NULL_TYPE = { kind: "name", name: "null" };
|
|
2835
|
+
var ARRAY_GENERICS = /* @__PURE__ */ new Set(["Array", "ReadonlyArray"]);
|
|
2836
|
+
var MAX_RESOLVE_DEPTH = 8;
|
|
2837
|
+
function tokenize(text3) {
|
|
2838
|
+
const tokens = [];
|
|
2839
|
+
let i = 0;
|
|
2840
|
+
while (i < text3.length) {
|
|
2841
|
+
const ch = text3[i];
|
|
2842
|
+
if (/\s/.test(ch)) {
|
|
2843
|
+
i++;
|
|
2844
|
+
continue;
|
|
2845
|
+
}
|
|
2846
|
+
if (ch === "=" && text3[i + 1] === ">") {
|
|
2847
|
+
tokens.push({ t: "punct", v: "=>" });
|
|
2848
|
+
i += 2;
|
|
2849
|
+
continue;
|
|
2850
|
+
}
|
|
2851
|
+
if ("{}[]<>()|,;:?!*.=".includes(ch)) {
|
|
2852
|
+
tokens.push({ t: "punct", v: ch });
|
|
2853
|
+
i++;
|
|
2854
|
+
continue;
|
|
2855
|
+
}
|
|
2856
|
+
if (ch === '"' || ch === "'") {
|
|
2857
|
+
const end = text3.indexOf(ch, i + 1);
|
|
2858
|
+
if (end === -1) return null;
|
|
2859
|
+
tokens.push({ t: "string", v: text3.slice(i + 1, end) });
|
|
2860
|
+
i = end + 1;
|
|
2861
|
+
continue;
|
|
2862
|
+
}
|
|
2863
|
+
const num = /^-?\d+(?:\.\d+)?/.exec(text3.slice(i));
|
|
2864
|
+
if (num) {
|
|
2865
|
+
tokens.push({ t: "number" });
|
|
2866
|
+
i += num[0].length;
|
|
2867
|
+
continue;
|
|
2868
|
+
}
|
|
2869
|
+
const name = /^[$A-Za-z_][$\w]*/.exec(text3.slice(i));
|
|
2870
|
+
if (name) {
|
|
2871
|
+
tokens.push({ t: "name", v: name[0] });
|
|
2872
|
+
i += name[0].length;
|
|
2873
|
+
continue;
|
|
2874
|
+
}
|
|
2875
|
+
return null;
|
|
2876
|
+
}
|
|
2877
|
+
return tokens;
|
|
2878
|
+
}
|
|
2879
|
+
function parseJsDocType(text3) {
|
|
2880
|
+
const tokens = tokenize(text3);
|
|
2881
|
+
if (tokens === null || tokens.length === 0) return null;
|
|
2882
|
+
let pos = 0;
|
|
2883
|
+
const peek = () => tokens[pos];
|
|
2884
|
+
const isPunct = (v) => {
|
|
2885
|
+
const token = tokens[pos];
|
|
2886
|
+
return token !== void 0 && token.t === "punct" && token.v === v;
|
|
2887
|
+
};
|
|
2888
|
+
const expect = (v) => {
|
|
2889
|
+
if (!isPunct(v)) return false;
|
|
2890
|
+
pos++;
|
|
2891
|
+
return true;
|
|
2892
|
+
};
|
|
2893
|
+
const parseUnion = () => {
|
|
2894
|
+
expect("|");
|
|
2895
|
+
const members = [];
|
|
2896
|
+
for (; ; ) {
|
|
2897
|
+
const member = parsePostfix();
|
|
2898
|
+
if (member === null) return null;
|
|
2899
|
+
members.push(member);
|
|
2900
|
+
if (!expect("|")) break;
|
|
2901
|
+
}
|
|
2902
|
+
if (isPunct("=>")) {
|
|
2903
|
+
pos++;
|
|
2904
|
+
if (parseUnion() === null) return null;
|
|
2905
|
+
return UNKNOWN;
|
|
2906
|
+
}
|
|
2907
|
+
return members.length === 1 ? members[0] : { kind: "union", members };
|
|
2908
|
+
};
|
|
2909
|
+
const parsePostfix = () => {
|
|
2910
|
+
let type2 = parsePrimary();
|
|
2911
|
+
if (type2 === null) return null;
|
|
2912
|
+
while (isPunct("[") && tokens[pos + 1]?.t === "punct" && tokens[pos + 1].v === "]") {
|
|
2913
|
+
pos += 2;
|
|
2914
|
+
type2 = { kind: "array", element: type2 };
|
|
2915
|
+
}
|
|
2916
|
+
return type2;
|
|
2917
|
+
};
|
|
2918
|
+
const parsePrimary = () => {
|
|
2919
|
+
const token = peek();
|
|
2920
|
+
if (token === void 0) return null;
|
|
2921
|
+
if (token.t === "string" || token.t === "number") {
|
|
2922
|
+
pos++;
|
|
2923
|
+
return UNKNOWN;
|
|
2924
|
+
}
|
|
2925
|
+
if (token.t === "punct") {
|
|
2926
|
+
if (token.v === "{") return parseObject();
|
|
2927
|
+
if (token.v === "(") {
|
|
2928
|
+
pos++;
|
|
2929
|
+
if (expect(")")) return isPunct("=>") ? UNKNOWN : null;
|
|
2930
|
+
const inner = parseUnion();
|
|
2931
|
+
if (inner === null) return null;
|
|
2932
|
+
if (isPunct(":") || isPunct(",")) {
|
|
2933
|
+
if (!skipUntilClose("(", ")")) return null;
|
|
2934
|
+
return UNKNOWN;
|
|
2935
|
+
}
|
|
2936
|
+
return expect(")") ? inner : null;
|
|
2937
|
+
}
|
|
2938
|
+
if (token.v === "?") {
|
|
2939
|
+
pos++;
|
|
2940
|
+
const next = peek();
|
|
2941
|
+
if (next === void 0 || next.t === "punct" && ["|", ",", ";", ")", ">", "]", "}", "="].includes(next.v)) return UNKNOWN;
|
|
2942
|
+
const inner = parsePostfix();
|
|
2943
|
+
return inner === null ? null : { kind: "union", members: [inner, NULL_TYPE] };
|
|
2944
|
+
}
|
|
2945
|
+
if (token.v === "!") {
|
|
2946
|
+
pos++;
|
|
2947
|
+
return parsePostfix();
|
|
2948
|
+
}
|
|
2949
|
+
if (token.v === "*") {
|
|
2950
|
+
pos++;
|
|
2951
|
+
return UNKNOWN;
|
|
2952
|
+
}
|
|
2953
|
+
return null;
|
|
2954
|
+
}
|
|
2955
|
+
pos++;
|
|
2956
|
+
let name = token.v;
|
|
2957
|
+
while (isPunct(".") && tokens[pos + 1]?.t === "name") {
|
|
2958
|
+
name += "." + tokens[pos + 1].v;
|
|
2959
|
+
pos += 2;
|
|
2960
|
+
}
|
|
2961
|
+
if ((name === "typeof" || name === "keyof") && peek()?.t === "name") {
|
|
2962
|
+
pos++;
|
|
2963
|
+
return UNKNOWN;
|
|
2964
|
+
}
|
|
2965
|
+
if (name === "function" && isPunct("(")) {
|
|
2966
|
+
pos++;
|
|
2967
|
+
if (!skipUntilClose("(", ")")) return null;
|
|
2968
|
+
if (expect(":")) {
|
|
2969
|
+
if (parsePostfix() === null) return null;
|
|
2970
|
+
}
|
|
2971
|
+
return UNKNOWN;
|
|
2972
|
+
}
|
|
2973
|
+
if (isPunct(".") && tokens[pos + 1]?.t === "punct" && tokens[pos + 1].v === "<") pos++;
|
|
2974
|
+
if (isPunct("<")) {
|
|
2975
|
+
pos++;
|
|
2976
|
+
const args = [];
|
|
2977
|
+
for (; ; ) {
|
|
2978
|
+
const arg = parseUnion();
|
|
2979
|
+
if (arg === null) return null;
|
|
2980
|
+
args.push(arg);
|
|
2981
|
+
if (!expect(",")) break;
|
|
2982
|
+
}
|
|
2983
|
+
if (!expect(">")) return null;
|
|
2984
|
+
return ARRAY_GENERICS.has(name) && args.length === 1 ? { kind: "array", element: args[0] } : UNKNOWN;
|
|
2985
|
+
}
|
|
2986
|
+
return { kind: "name", name };
|
|
2987
|
+
};
|
|
2988
|
+
const parseObject = () => {
|
|
2989
|
+
pos++;
|
|
2990
|
+
const fields = [];
|
|
2991
|
+
while (!isPunct("}")) {
|
|
2992
|
+
const key = peek();
|
|
2993
|
+
if (key === void 0) return null;
|
|
2994
|
+
if (key.t === "punct" && key.v === "[") {
|
|
2995
|
+
pos++;
|
|
2996
|
+
if (!skipUntilClose("[", "]")) return null;
|
|
2997
|
+
if (!expect(":") || parseUnion() === null) return null;
|
|
2998
|
+
} else {
|
|
2999
|
+
if (key.t !== "name" && key.t !== "string") return null;
|
|
3000
|
+
pos++;
|
|
3001
|
+
expect("?");
|
|
3002
|
+
if (!expect(":")) return null;
|
|
3003
|
+
const type2 = parseUnion();
|
|
3004
|
+
if (type2 === null) return null;
|
|
3005
|
+
fields.push({ name: key.v, type: type2 });
|
|
3006
|
+
}
|
|
3007
|
+
if (!expect(",") && !expect(";")) break;
|
|
3008
|
+
}
|
|
3009
|
+
return expect("}") ? { kind: "object", fields } : null;
|
|
3010
|
+
};
|
|
3011
|
+
const skipUntilClose = (open, close) => {
|
|
3012
|
+
let depth = 0;
|
|
3013
|
+
while (pos < tokens.length) {
|
|
3014
|
+
const token = tokens[pos++];
|
|
3015
|
+
if (token.t !== "punct") continue;
|
|
3016
|
+
if (token.v === open) depth++;
|
|
3017
|
+
else if (token.v === close) {
|
|
3018
|
+
if (depth === 0) return true;
|
|
3019
|
+
depth--;
|
|
3020
|
+
}
|
|
3021
|
+
}
|
|
3022
|
+
return false;
|
|
3023
|
+
};
|
|
3024
|
+
const type = parseUnion();
|
|
3025
|
+
if (type === null || pos !== tokens.length) return null;
|
|
3026
|
+
return type;
|
|
3027
|
+
}
|
|
3028
|
+
function findJsDocComments(script, scan) {
|
|
3029
|
+
const out = [];
|
|
3030
|
+
let from = 0;
|
|
3031
|
+
for (; ; ) {
|
|
3032
|
+
const start = scan.indexOf("/*", from);
|
|
3033
|
+
if (start === -1) break;
|
|
3034
|
+
const close = scan.indexOf("*/", start + 2);
|
|
3035
|
+
if (close === -1) break;
|
|
3036
|
+
from = close + 2;
|
|
3037
|
+
if (script[start + 2] === "*") out.push({ start, end: close + 2, text: script.slice(start, close + 2) });
|
|
3038
|
+
}
|
|
3039
|
+
return out;
|
|
3040
|
+
}
|
|
3041
|
+
function readJsDocTagType(comment, tag) {
|
|
3042
|
+
return readBracedType(comment, tag)?.type.trim() ?? null;
|
|
3043
|
+
}
|
|
3044
|
+
function readBracedType(comment, tag) {
|
|
3045
|
+
const tagMatch = new RegExp(`@${tag}\\s*\\{`).exec(comment);
|
|
3046
|
+
if (!tagMatch) return null;
|
|
3047
|
+
const open = tagMatch.index + tagMatch[0].length - 1;
|
|
3048
|
+
let depth = 0;
|
|
3049
|
+
for (let i = open; i < comment.length; i++) {
|
|
3050
|
+
const ch = comment[i];
|
|
3051
|
+
if (ch === "{") depth++;
|
|
3052
|
+
else if (ch === "}") {
|
|
3053
|
+
depth--;
|
|
3054
|
+
if (depth === 0) {
|
|
3055
|
+
const type = comment.slice(open + 1, i).replace(/\n\s*\*(?!\/)/g, "\n ");
|
|
3056
|
+
return { type, end: i + 1 };
|
|
3057
|
+
}
|
|
3058
|
+
}
|
|
3059
|
+
}
|
|
3060
|
+
return null;
|
|
3061
|
+
}
|
|
3062
|
+
function readJsDocTypeBefore(content, propIndex) {
|
|
3063
|
+
let end = propIndex;
|
|
3064
|
+
while (end > 0 && /\s/.test(content[end - 1])) end--;
|
|
3065
|
+
if (!content.startsWith("*/", end - 2)) return void 0;
|
|
3066
|
+
const start = content.lastIndexOf("/*", end - 2);
|
|
3067
|
+
if (start === -1 || content[start + 2] !== "*") return void 0;
|
|
3068
|
+
return readBracedType(content.slice(start, end), "type")?.type.trim();
|
|
3069
|
+
}
|
|
3070
|
+
function collectJsDocTypedefs(script, scan) {
|
|
3071
|
+
const typedefs = /* @__PURE__ */ new Map();
|
|
3072
|
+
for (const { text: comment } of findJsDocComments(script, scan)) {
|
|
3073
|
+
const braced = readBracedType(comment, "typedef");
|
|
3074
|
+
if (braced === null) continue;
|
|
3075
|
+
const name = /^\s*([$A-Za-z_][$\w]*)/.exec(comment.slice(braced.end));
|
|
3076
|
+
const type = parseJsDocType(braced.type);
|
|
3077
|
+
if (name && type !== null) typedefs.set(name[1], type);
|
|
3078
|
+
}
|
|
3079
|
+
return typedefs;
|
|
3080
|
+
}
|
|
3081
|
+
function flattenJsDocType(type, typedefs, depth = 0) {
|
|
3082
|
+
if (type.kind === "union") return type.members.flatMap((member) => flattenJsDocType(member, typedefs, depth));
|
|
3083
|
+
if (type.kind === "name" && depth < MAX_RESOLVE_DEPTH) {
|
|
3084
|
+
const resolved = typedefs.get(type.name);
|
|
3085
|
+
if (resolved !== void 0) return flattenJsDocType(resolved, typedefs, depth + 1);
|
|
3086
|
+
}
|
|
3087
|
+
return [type];
|
|
3088
|
+
}
|
|
3089
|
+
function jsDocTypeHint(type, typedefs = /* @__PURE__ */ new Map()) {
|
|
3090
|
+
const hints = /* @__PURE__ */ new Set();
|
|
3091
|
+
for (const member of flattenJsDocType(type, typedefs)) {
|
|
3092
|
+
if (member.kind === "array") hints.add("array");
|
|
3093
|
+
else if (member.kind === "object") hints.add("object");
|
|
3094
|
+
else if (member.kind === "name") {
|
|
3095
|
+
const lower2 = member.name.toLowerCase();
|
|
3096
|
+
if (lower2 === "string" || lower2 === "number" || lower2 === "boolean" || lower2 === "object") hints.add(lower2);
|
|
3097
|
+
else if (lower2 === "null" || lower2 === "undefined") hints.add("null");
|
|
3098
|
+
else if (lower2 === "array" || lower2 === "readonlyarray") hints.add("array");
|
|
3099
|
+
}
|
|
3100
|
+
}
|
|
3101
|
+
return hints.size === 0 ? void 0 : [...hints].sort().join("|");
|
|
3102
|
+
}
|
|
3103
|
+
function jsDocShape(type, typedefs) {
|
|
3104
|
+
const elements = [];
|
|
3105
|
+
const fields = [];
|
|
3106
|
+
for (const member of flattenJsDocType(type, typedefs)) {
|
|
3107
|
+
if (member.kind === "array") elements.push(member.element);
|
|
3108
|
+
else if (member.kind === "object") {
|
|
3109
|
+
for (const field of member.fields) {
|
|
3110
|
+
if (!fields.some((f) => f.name === field.name)) fields.push(field);
|
|
3111
|
+
}
|
|
3112
|
+
}
|
|
3113
|
+
}
|
|
3114
|
+
const element2 = elements.length === 0 ? null : elements.length === 1 ? elements[0] : { kind: "union", members: elements };
|
|
3115
|
+
return { element: element2, fields };
|
|
3116
|
+
}
|
|
3117
|
+
|
|
2825
3118
|
// src/service/stateAnalyzer.ts
|
|
2826
3119
|
var RESERVED_STREAMS_KEYS = /* @__PURE__ */ new Set(["$stream", "$streams"]);
|
|
2827
3120
|
var RESERVED_COMMAND_TOKENS_KEY = "$commandTokens";
|
|
@@ -2839,6 +3132,7 @@ function analyzeStatePaths(scriptContent) {
|
|
|
2839
3132
|
const pendingListKeys = [];
|
|
2840
3133
|
const recursionSpec = specFromRecursionValue(topLevelProps.find((p) => p.name === RESERVED_RECURSION_KEY));
|
|
2841
3134
|
const effectiveDescriptors = effectiveTopLevelDescriptors(topLevelProps);
|
|
3135
|
+
const typedefs = scriptContent.includes("@typedef") ? collectJsDocTypedefs(scriptContent, maskCommentsAndStrings(scriptContent)) : /* @__PURE__ */ new Map();
|
|
2842
3136
|
for (const prop of topLevelProps) {
|
|
2843
3137
|
if (prop.name.startsWith("$")) {
|
|
2844
3138
|
collectReservedKeyPaths(prop, paths, pendingStreamValues, pendingListKeys);
|
|
@@ -2854,15 +3148,17 @@ function analyzeStatePaths(scriptContent) {
|
|
|
2854
3148
|
}
|
|
2855
3149
|
if (prop.kind === "getter") {
|
|
2856
3150
|
if (!paths.some((p) => p.path === prop.name)) {
|
|
2857
|
-
|
|
3151
|
+
const recursive = hasRecursionWildcard(prop.name);
|
|
3152
|
+
const settable = !recursive && effective !== "data" && effective.set;
|
|
3153
|
+
paths.push({ path: prop.name, kind: recursive ? "recursive" : "computed", ...settable ? { settable } : {} });
|
|
2858
3154
|
}
|
|
2859
3155
|
continue;
|
|
2860
3156
|
}
|
|
2861
|
-
pushDataPropertyPaths(prop, paths);
|
|
3157
|
+
pushDataPropertyPaths(prop, paths, typedefs);
|
|
2862
3158
|
}
|
|
2863
3159
|
for (const streamValue of pendingStreamValues) {
|
|
2864
3160
|
if (paths.some((p) => p.path === streamValue.name && p.kind !== "eventToken")) continue;
|
|
2865
|
-
pushDataPropertyPaths(streamValue, paths);
|
|
3161
|
+
pushDataPropertyPaths(streamValue, paths, typedefs);
|
|
2866
3162
|
}
|
|
2867
3163
|
for (const listKeyEntry of pendingListKeys) {
|
|
2868
3164
|
pushListKeyPaths(listKeyEntry, paths);
|
|
@@ -3592,17 +3888,37 @@ function effectiveTopLevelDescriptors(props) {
|
|
|
3592
3888
|
}
|
|
3593
3889
|
return effective;
|
|
3594
3890
|
}
|
|
3595
|
-
function pushDataPropertyPaths(prop, paths) {
|
|
3596
|
-
pushDataPropertyPathsAt(prop.name, prop, paths, 0);
|
|
3891
|
+
function pushDataPropertyPaths(prop, paths, typedefs) {
|
|
3892
|
+
pushDataPropertyPathsAt(prop.name, prop, paths, 0, typedefs);
|
|
3597
3893
|
}
|
|
3598
|
-
function pushDataPropertyPathsAt(path, prop, paths, depth) {
|
|
3894
|
+
function pushDataPropertyPathsAt(path, prop, paths, depth, typedefs) {
|
|
3599
3895
|
paths.push({ path, kind: "data", typeHint: prop.typeHint, rawInitial: prop.value?.trim() });
|
|
3896
|
+
pushLiteralStructurePaths(path, prop, paths, depth, typedefs);
|
|
3897
|
+
if (prop.declaredType !== void 0) pushDeclaredShapePaths(path, prop.declaredType, paths, depth, typedefs);
|
|
3898
|
+
}
|
|
3899
|
+
function pushDeclaredShapePaths(path, type, paths, depth, typedefs) {
|
|
3900
|
+
const shape = jsDocShape(type, typedefs);
|
|
3901
|
+
if (shape.element !== null) {
|
|
3902
|
+
if (!hasPath(paths, `${path}.*`)) paths.push({ path: `${path}.*`, kind: "list" });
|
|
3903
|
+
if (!hasPath(paths, `${path}.length`)) paths.push({ path: `${path}.length`, kind: "data", typeHint: "number" });
|
|
3904
|
+
if (depth < MAX_OBJECT_NEST_DEPTH) pushDeclaredShapePaths(`${path}.*`, shape.element, paths, depth + 1, typedefs);
|
|
3905
|
+
}
|
|
3906
|
+
if (depth >= MAX_OBJECT_NEST_DEPTH) return;
|
|
3907
|
+
for (const field of shape.fields) {
|
|
3908
|
+
const fieldPath = `${path}.${field.name}`;
|
|
3909
|
+
if (!hasPath(paths, fieldPath)) {
|
|
3910
|
+
paths.push(withHint({ path: fieldPath, kind: "data" }, jsDocTypeHint(field.type, typedefs)));
|
|
3911
|
+
}
|
|
3912
|
+
pushDeclaredShapePaths(fieldPath, field.type, paths, depth + 1, typedefs);
|
|
3913
|
+
}
|
|
3914
|
+
}
|
|
3915
|
+
function pushLiteralStructurePaths(path, prop, paths, depth, typedefs) {
|
|
3600
3916
|
if (prop.value && isArrayLiteral(prop.value)) {
|
|
3601
3917
|
paths.push({ path: `${path}.*`, kind: "list" });
|
|
3602
3918
|
paths.push({ path: `${path}.length`, kind: "data", typeHint: "number" });
|
|
3603
3919
|
if (depth >= MAX_OBJECT_NEST_DEPTH) return;
|
|
3604
3920
|
for (const childProp of extractArrayElementDataProperties(prop.value)) {
|
|
3605
|
-
pushDataPropertyPathsAt(`${path}.*.${childProp.name}`, childProp, paths, depth + 1);
|
|
3921
|
+
pushDataPropertyPathsAt(`${path}.*.${childProp.name}`, childProp, paths, depth + 1, typedefs);
|
|
3606
3922
|
}
|
|
3607
3923
|
return;
|
|
3608
3924
|
}
|
|
@@ -3611,7 +3927,7 @@ function pushDataPropertyPathsAt(path, prop, paths, depth) {
|
|
|
3611
3927
|
const childProps = parseTopLevelProperties(extractObjectContent(prop.value));
|
|
3612
3928
|
for (const childProp of childProps) {
|
|
3613
3929
|
if (childProp.kind !== "data") continue;
|
|
3614
|
-
pushDataPropertyPathsAt(`${path}.${childProp.name}`, childProp, paths, depth + 1);
|
|
3930
|
+
pushDataPropertyPathsAt(`${path}.${childProp.name}`, childProp, paths, depth + 1, typedefs);
|
|
3615
3931
|
}
|
|
3616
3932
|
}
|
|
3617
3933
|
}
|
|
@@ -3722,13 +4038,16 @@ function parseTopLevelProperties(objectContent, allowEmptyDataKeys = false) {
|
|
|
3722
4038
|
if (propName !== void 0) {
|
|
3723
4039
|
const valueStartIndex = match.index + match[0].length;
|
|
3724
4040
|
const value = extractFullValue(objectContent, scan, valueStartIndex);
|
|
3725
|
-
const
|
|
4041
|
+
const jsdocText = readJsDocTypeBefore(objectContent, match.index);
|
|
4042
|
+
const declaredType = jsdocText === void 0 ? null : parseJsDocType(jsdocText);
|
|
4043
|
+
const jsdocType = declaredType !== null ? jsDocTypeHint(declaredType) : jsdocText === void 0 ? void 0 : normalizeJsDocType(jsdocText);
|
|
3726
4044
|
const typeHint = jsdocType ?? inferTypeHint(value);
|
|
3727
4045
|
props.push({
|
|
3728
4046
|
name: propName,
|
|
3729
4047
|
kind: "data",
|
|
3730
4048
|
value,
|
|
3731
4049
|
typeHint,
|
|
4050
|
+
...declaredType !== null ? { declaredType } : {},
|
|
3732
4051
|
nameStart: nameSpan[0],
|
|
3733
4052
|
nameEnd: nameSpan[1],
|
|
3734
4053
|
valueStart: valueStartIndex
|
|
@@ -3849,13 +4168,6 @@ function extractArrayElementDataProperties(value) {
|
|
|
3849
4168
|
const objectContent = extractBracedContent(trimmed, scan, first);
|
|
3850
4169
|
return parseTopLevelProperties(objectContent).filter((prop) => prop.kind === "data");
|
|
3851
4170
|
}
|
|
3852
|
-
function extractJsDocType(content, propIndex) {
|
|
3853
|
-
const before = content.slice(Math.max(0, propIndex - 200), propIndex);
|
|
3854
|
-
const jsdocMatch = before.match(/\/\*\*\s*@type\s*\{([^}]+)\}\s*\*\/\s*$/);
|
|
3855
|
-
if (!jsdocMatch) return void 0;
|
|
3856
|
-
const typeExpr = jsdocMatch[1].trim();
|
|
3857
|
-
return normalizeJsDocType(typeExpr);
|
|
3858
|
-
}
|
|
3859
4171
|
function normalizeJsDocType(typeExpr) {
|
|
3860
4172
|
const parts = typeExpr.split("|").map((p) => p.trim());
|
|
3861
4173
|
const normalized = parts.map((p) => {
|
|
@@ -10596,6 +10908,10 @@ function resolveLocale(locale2) {
|
|
|
10596
10908
|
if (locale2 === void 0 || locale2 === "" || /^ja\b|^ja[-_]/i.test(locale2) || locale2.toLowerCase() === "ja") return "ja";
|
|
10597
10909
|
return "en";
|
|
10598
10910
|
}
|
|
10911
|
+
function shortSeed(raw) {
|
|
10912
|
+
const flat = raw.replace(/\s+/g, " ");
|
|
10913
|
+
return flat.length <= 32 ? flat : `${flat.slice(0, 29)}\u2026`;
|
|
10914
|
+
}
|
|
10599
10915
|
var JA_EXPECTED_LABEL = {
|
|
10600
10916
|
array: "\u914D\u5217\u578B\u306E\u30D1\u30B9",
|
|
10601
10917
|
boolean: "\u30D6\u30FC\u30EA\u30A2\u30F3\u578B",
|
|
@@ -10675,7 +10991,11 @@ var ja = {
|
|
|
10675
10991
|
didYouMean: (c) => `\u3002\u3082\u3057\u304B\u3057\u3066: "${c}"`,
|
|
10676
10992
|
none: () => `\u306A\u3057`,
|
|
10677
10993
|
triggerSeededTruthy: (path) => `trigger \u30D0\u30A4\u30F3\u30C9\u5148 "${path}" \u304C true \u3067\u30B7\u30FC\u30C9\u3055\u308C\u3066\u3044\u307E\u3059\u3002trigger \u306F\u30A8\u30C3\u30B8\u691C\u51FA\u306A\u3057\uFF08truthy \u66F8\u304D\u8FBC\u307F\u3067\u5373\u767A\u706B\u30FBmanual \u3082\u30D0\u30A4\u30D1\u30B9\uFF09\u306E\u305F\u3081\u3001\u30D0\u30A4\u30F3\u30C9\u6642\u306B\u5373\u767A\u706B\u3057\u307E\u3059\u3002false \u3067\u30B7\u30FC\u30C9\u3057\u3066\u304F\u3060\u3055\u3044`,
|
|
10678
|
-
storageSeedClobber: (path, raw) => `<wcs-storage> \u306E value \u30D0\u30A4\u30F3\u30C9\u5148 "${path}" \u304C ${raw} \u3067\u30B7\u30FC\u30C9\u3055\u308C\u3066\u3044\u307E\u3059\u3002\u521D\u671F\u66F8\u304D\u623B\u3057\u304C\u4FDD\u5B58\u5024\u3092\u4E0A\u66F8\u304D\u3057\u307E\u3059 \u2014
|
|
10994
|
+
storageSeedClobber: (path, raw) => `<wcs-storage> \u306E value \u30D0\u30A4\u30F3\u30C9\u5148 "${path}" \u304C ${raw} \u3067\u30B7\u30FC\u30C9\u3055\u308C\u3066\u3044\u307E\u3059\u3002\u8AAD\u307F\u8FBC\u307F\u306E\u305F\u3073\u306B\u521D\u671F\u66F8\u304D\u623B\u3057\u304C\u4FDD\u5B58\u5024\u3092\u4E0A\u66F8\u304D\u3057\u307E\u3059 \u2014 \u30D0\u30A4\u30F3\u30C9\u306B #init=element \u3092\u4ED8\u3051\u3066\uFF08\`value#init=element: ${path}\`\uFF09\u4FDD\u5B58\u5024\u3092\u52DD\u305F\u305B\u3001\u30B9\u30ED\u30C3\u30C8\u306F null\uFF08\u7A7A\u306E\u30AD\u30FC\u304C\u8AAD\u3080\u5024\uFF09\u3067\u30B7\u30FC\u30C9\u3057\u3066\u304F\u3060\u3055\u3044`,
|
|
10995
|
+
storageInitSeed: (path, raw) => `<wcs-storage> \u306E value \u30D0\u30A4\u30F3\u30C9\u5148 "${path}" \u304C ${shortSeed(raw)} \u3067\u30B7\u30FC\u30C9\u3055\u308C\u3066\u3044\u307E\u3059\u304C\u3001#init=element \u306F\u8981\u7D20\u306E\u5024\u3092\u3053\u306E\u30B9\u30ED\u30C3\u30C8\u3078\u5F15\u304D\u8FBC\u307F\u307E\u3059\u3002\u521D\u56DE\uFF08\u7A7A\u306E\u30AD\u30FC\uFF09\u306F null \u306A\u306E\u3067\u3001\u30B7\u30FC\u30C9\u306F null \u306B\u7F6E\u304D\u63DB\u308F\u308A\u307E\u3059 \u2014 null \u3067\u30B7\u30FC\u30C9\u3057\u3066\u8AAD\u3080\u5074\u3067\u65E2\u5B9A\u5024\u3092\u88DC\u3046\uFF08\`this.${path} ?? \u2026\`\uFF09\u304B\u3001null \u3092\u7121\u8996\u3059\u308B setter \u3092\u6301\u3064\u30A2\u30AF\u30BB\u30B5\u30DA\u30A2\u3092\u675F\u306D\u3066\u304F\u3060\u3055\u3044`,
|
|
10996
|
+
storagePairClobber: (path) => `<wcs-storage> \u306E value \u3092 #init=element \u306A\u3057\u3067\u30A2\u30AF\u30BB\u30B5\u30DA\u30A2 "${path}" \u306B\u675F\u306D\u3066\u3044\u307E\u3059\u3002state \u306E\u5074\u304C\u5148\u306B\u66F8\u304F\u306E\u3067\u3001\u8AAD\u307F\u8FBC\u307F\u306E\u305F\u3073\u306B getter \u306E\u6700\u521D\u306E\u5024\u304C\u4FDD\u5B58\u5024\u306E\u4E0A\u306B\u4FDD\u5B58\u3055\u308C\u3001setter \u306F\u4FDD\u5B58\u5024\u3092\u53D7\u3051\u53D6\u308C\u307E\u305B\u3093 \u2014 #init=element \u3092\u4ED8\u3051\u3066\u304F\u3060\u3055\u3044\uFF08\`value#init=element: ${path}\`\uFF09`,
|
|
10997
|
+
inputNestedWrite: (tag, input, path, example) => `<${tag}> \u306E\u5165\u529B "${input}" \u306B "${path}" \u3092\u675F\u306D\u3066\u3044\u307E\u3059\u304C\u3001\u30DA\u30FC\u30B8\u306F\u305D\u306E\u4E0B\uFF08"${example}"\uFF09\u3078\u66F8\u304D\u8FBC\u3093\u3067\u3044\u307E\u3059\u3002\u675F\u7E1B\u306F "${path}" \u81EA\u8EAB\u3078\u306E\u4EE3\u5165\u3067\u3057\u304B\u5F53\u3066\u76F4\u3055\u308C\u306A\u3044\u306E\u3067\u3001\u4E0B\u3078\u306E\u66F8\u304D\u8FBC\u307F\u306F <${tag}> \u306B\u5C4A\u304D\u307E\u305B\u3093 \u2014 \u30D5\u30A3\u30FC\u30EB\u30C9\u3092\u30D1\u30B9\u3067\u8AAD\u3093\u3067\u65B0\u3057\u3044\u5024\u3092\u8FD4\u3059 getter\uFF08\`this["${example}"]\`\u30FB\`$getAll\`\uFF09\u3092\u675F\u306D\u308B\u304B\u3001\u4E0B\u3078\u66F8\u304B\u305A\u306B "${path}" \u3092\u5DEE\u3057\u66FF\u3048\u3066\u304F\u3060\u3055\u3044`,
|
|
10998
|
+
inputNestedWriteOneShot: (tag, input, path, example) => `<${tag}> \u306E\u5165\u529B "${input}" \u306B "${path}" \u3092\u675F\u306D\u3066\u3044\u307E\u3059\u304C\u3001\u30DA\u30FC\u30B8\u306F\u305D\u306E\u4E0B\uFF08"${example}"\uFF09\u3078\u66F8\u304D\u8FBC\u3093\u3067\u3044\u307E\u3059\u3002\u675F\u7E1B\u306F "${path}" \u81EA\u8EAB\u3078\u306E\u4EE3\u5165\u3067\u3057\u304B\u5F53\u3066\u76F4\u3055\u308C\u305A\u3001<${tag}> \u306F\u8981\u6C42\u306E\u305F\u3073\u306B "${input}" \u3092 null \u306B\u623B\u3059\u306E\u3067\u3001\u6B21\u306E\u8981\u6C42\u306F "${input}" \u306A\u3057\u3067\u9001\u3089\u308C\u307E\u3059 \u2014 \u8981\u6C42\u3092\u59CB\u3081\u308B\u30CF\u30F3\u30C9\u30E9\u3067\u65B0\u3057\u3044\u30AA\u30D6\u30B8\u30A7\u30AF\u30C8\u3092\u4F5C\u3063\u3066\u4EE3\u5165\u3057\uFF08\`this.submitBody = { \u2026 }\`\uFF09\u3001\u305D\u308C\u3092\u675F\u306D\u3066\u304F\u3060\u3055\u3044`,
|
|
10679
10999
|
devtoolsAfterState: () => `@wcstack/devtools/auto \u306F @wcstack/state/auto \u3088\u308A\u5148\u306B\u8AAD\u307F\u8FBC\u3093\u3067\u304F\u3060\u3055\u3044\uFF08\u5F8C\u3060\u3068\u914D\u7DDA\u53F0\u5E33\u304C\u30E9\u30A4\u30D6\u3067 captured \u3055\u308C\u307E\u305B\u3093\uFF09`,
|
|
10680
11000
|
baseHrefMissing: () => `@wcstack/router \u3092\u4F7F\u3046 SPA \u306B\u306F <head> \u5185\u306E <base href="/"> \u304C\u5FC5\u8981\u3067\u3059\uFF08\u7121\u3044\u3068\u30C7\u30A3\u30FC\u30D7\u30EA\u30F3\u30AF\u3067 basename \u304C\u8AA4\u5C0E\u51FA\u3055\u308C\u307E\u3059\uFF09`,
|
|
10681
11001
|
signalsDualEntry: () => `@wcstack/signals \u3068 @wcstack/signals/dom \u304C\u540C\u4E00\u30DA\u30FC\u30B8\u304B\u3089 import \u3055\u308C\u3066\u3044\u307E\u3059\u3002CDN \u3067\u306F\u5404\u30A8\u30F3\u30C8\u30EA\u304C\u81EA\u5DF1\u5B8C\u7D50\u30D0\u30F3\u30C9\u30EB\u306E\u305F\u3081\u30EA\u30A2\u30AF\u30C6\u30A3\u30D6\u30B3\u30A2\u304C\u4E8C\u91CD\u5316\u3057\u3001\u5883\u754C\u3067\u53CD\u5FDC\u304C\u58CA\u308C\u307E\u3059 \u2014 \u3059\u3079\u3066 /dom \u30A8\u30F3\u30C8\u30EA\u304B\u3089 import \u3057\u3066\u304F\u3060\u3055\u3044`,
|
|
@@ -10861,7 +11181,11 @@ var en = {
|
|
|
10861
11181
|
didYouMean: (c) => `. Did you mean "${c}"?`,
|
|
10862
11182
|
none: () => `none`,
|
|
10863
11183
|
triggerSeededTruthy: (path) => `The trigger-bound slot "${path}" is seeded with true. trigger has no edge detection (any truthy write fires, and it bypasses manual), so it fires immediately at bind. Seed it with false`,
|
|
10864
|
-
storageSeedClobber: (path, raw) => `The <wcs-storage> value-bound slot "${path}" is seeded with ${raw}. The initial write-back overwrites the persisted value \u2014
|
|
11184
|
+
storageSeedClobber: (path, raw) => `The <wcs-storage> value-bound slot "${path}" is seeded with ${raw}. The initial write-back overwrites the persisted value on every load \u2014 add #init=element to the binding (\`value#init=element: ${path}\`) so the stored value wins, and seed the slot with null (what an empty key loads as)`,
|
|
11185
|
+
storageInitSeed: (path, raw) => `The <wcs-storage> value-bound slot "${path}" is seeded with ${shortSeed(raw)}, but #init=element pulls the element's value into it. On a first visit (an empty key) that is null, so the seed is replaced by null \u2014 seed it with null and supply the default where you read it (\`this.${path} ?? \u2026\`), or bind an accessor pair whose setter ignores null`,
|
|
11186
|
+
storagePairClobber: (path) => `The <wcs-storage> value is bound to the accessor pair "${path}" without #init=element. The state's side goes first, so on every load the getter's starting value is saved over the stored one, and the setter never receives it \u2014 add #init=element (\`value#init=element: ${path}\`)`,
|
|
11187
|
+
inputNestedWrite: (tag, input, path, example) => `"${path}" is bound to the input "${input}" of <${tag}>, but the page writes below it ("${example}"). A binding is re-applied only when "${path}" itself is assigned, so those writes never reach <${tag}> \u2014 bind a getter that reads the fields by path (\`this["${example}"]\`, \`$getAll\`) and returns a new value, or replace "${path}" instead of writing below it`,
|
|
11188
|
+
inputNestedWriteOneShot: (tag, input, path, example) => `"${path}" is bound to the input "${input}" of <${tag}>, but the page writes below it ("${example}"). A binding is re-applied only when "${path}" itself is assigned, and <${tag}> resets "${input}" to null after every request, so the next request goes without it \u2014 build a new object in the handler that starts the request (\`this.submitBody = { \u2026 }\`) and bind that`,
|
|
10865
11189
|
devtoolsAfterState: () => `Load @wcstack/devtools/auto BEFORE @wcstack/state/auto (otherwise the wiring ledger is not captured live)`,
|
|
10866
11190
|
baseHrefMissing: () => `An SPA using @wcstack/router needs <base href="/"> in <head> (without it, deep links misderive the basename)`,
|
|
10867
11191
|
signalsDualEntry: () => `Both @wcstack/signals and @wcstack/signals/dom are imported on this page. On a CDN each entry is a self-contained bundle, so the reactive core is duplicated and reactivity breaks at the seam \u2014 import everything from the single /dom entry`,
|
|
@@ -11806,18 +12130,24 @@ function validateStateTypes(html, stateTagName = "wcs-state", locale2) {
|
|
|
11806
12130
|
}
|
|
11807
12131
|
return diagnostics;
|
|
11808
12132
|
}
|
|
12133
|
+
var PROPERTY_AFTER_COMMENT = /^\s*(?:"([^"]+)"|'([^']+)'|(\w+))\s*:\s*/;
|
|
11809
12134
|
function findJsDocTypedProperties(script) {
|
|
11810
12135
|
const results = [];
|
|
11811
|
-
const
|
|
11812
|
-
|
|
11813
|
-
|
|
11814
|
-
const
|
|
11815
|
-
|
|
11816
|
-
const
|
|
12136
|
+
const scan = maskCommentsAndStrings(script);
|
|
12137
|
+
const typedefs = script.includes("@typedef") ? collectJsDocTypedefs(script, scan) : /* @__PURE__ */ new Map();
|
|
12138
|
+
for (const comment of findJsDocComments(script, scan)) {
|
|
12139
|
+
const typeText = readJsDocTagType(comment.text, "type");
|
|
12140
|
+
if (typeText === null) continue;
|
|
12141
|
+
const match = PROPERTY_AFTER_COMMENT.exec(script.slice(comment.end));
|
|
12142
|
+
if (!match) continue;
|
|
12143
|
+
const rawType = typeText.replace(/\s+/g, " ");
|
|
12144
|
+
const name = match[1] ?? match[2] ?? match[3];
|
|
12145
|
+
const valueStart = comment.end + match[0].length;
|
|
11817
12146
|
const valueText = extractValue(script, valueStart);
|
|
11818
12147
|
const valueType = inferValueType(valueText);
|
|
11819
12148
|
if (valueType) {
|
|
11820
|
-
const declaredTypes = rawType
|
|
12149
|
+
const declaredTypes = declaredTypesOf(rawType, typedefs);
|
|
12150
|
+
if (declaredTypes === null) continue;
|
|
11821
12151
|
results.push({
|
|
11822
12152
|
name,
|
|
11823
12153
|
rawType,
|
|
@@ -11830,6 +12160,20 @@ function findJsDocTypedProperties(script) {
|
|
|
11830
12160
|
}
|
|
11831
12161
|
return results;
|
|
11832
12162
|
}
|
|
12163
|
+
var ANY_TYPES = /* @__PURE__ */ new Set(["any", "unknown", "*"]);
|
|
12164
|
+
function declaredTypesOf(rawType, typedefs) {
|
|
12165
|
+
if (rawType.split("|").some((t) => ANY_TYPES.has(t.trim().toLowerCase()))) return null;
|
|
12166
|
+
const parsed = parseJsDocType(rawType);
|
|
12167
|
+
if (parsed === null) return rawType.split("|").map((t) => normalizeType(t.trim()));
|
|
12168
|
+
const out = [];
|
|
12169
|
+
for (const member of flattenJsDocType(parsed, typedefs)) {
|
|
12170
|
+
if (member.kind === "unknown") return null;
|
|
12171
|
+
if (member.kind === "array") out.push("array");
|
|
12172
|
+
else if (member.kind === "object") out.push("object");
|
|
12173
|
+
else if (member.kind === "name") out.push(normalizeType(member.name));
|
|
12174
|
+
}
|
|
12175
|
+
return out;
|
|
12176
|
+
}
|
|
11833
12177
|
function extractValue(script, start) {
|
|
11834
12178
|
let depth = 0;
|
|
11835
12179
|
let inString = null;
|
|
@@ -13850,7 +14194,8 @@ function validateBindingAgainstContract(tagName, contract, parsed, property, sta
|
|
|
13850
14194
|
}
|
|
13851
14195
|
}
|
|
13852
14196
|
if (tagName === "wcs-storage" && property === "value" && !hasManual && parsed.path && !/(?:^|,)init=(?:element|auto)\b/.test(modifiers)) {
|
|
13853
|
-
const
|
|
14197
|
+
const paths = getPaths();
|
|
14198
|
+
const cand = findDataSlot(paths, parsed.path);
|
|
13854
14199
|
if (cand?.rawInitial !== void 0 && EMPTYISH_SEEDS.has(normalizeSeed(cand.rawInitial))) {
|
|
13855
14200
|
diagnostics.push({
|
|
13856
14201
|
code: WcsDiagnosticCode.StorageSeedClobber,
|
|
@@ -13861,9 +14206,37 @@ function validateBindingAgainstContract(tagName, contract, parsed, property, sta
|
|
|
13861
14206
|
statePath: parsed.path,
|
|
13862
14207
|
message: msgs.storageSeedClobber(parsed.path, cand.rawInitial)
|
|
13863
14208
|
});
|
|
14209
|
+
} else if (cand === void 0 && paths.some((c) => c.kind === "computed" && c.settable && c.path === parsed.path)) {
|
|
14210
|
+
diagnostics.push({
|
|
14211
|
+
code: WcsDiagnosticCode.StorageSeedClobber,
|
|
14212
|
+
start,
|
|
14213
|
+
end,
|
|
14214
|
+
severity: "warning",
|
|
14215
|
+
tag: tagName,
|
|
14216
|
+
statePath: parsed.path,
|
|
14217
|
+
message: msgs.storagePairClobber(parsed.path)
|
|
14218
|
+
});
|
|
14219
|
+
}
|
|
14220
|
+
}
|
|
14221
|
+
if (tagName === "wcs-storage" && property === "value" && parsed.path && /(?:^|,)init=element\b/.test(modifiers)) {
|
|
14222
|
+
const cand = findDataSlot(getPaths(), parsed.path);
|
|
14223
|
+
if (cand?.rawInitial !== void 0 && isNonNullLiteralSeed(cand.rawInitial)) {
|
|
14224
|
+
diagnostics.push({
|
|
14225
|
+
code: WcsDiagnosticCode.StorageInitSeed,
|
|
14226
|
+
start,
|
|
14227
|
+
end,
|
|
14228
|
+
severity: "warning",
|
|
14229
|
+
tag: tagName,
|
|
14230
|
+
statePath: parsed.path,
|
|
14231
|
+
message: msgs.storageInitSeed(parsed.path, cand.rawInitial)
|
|
14232
|
+
});
|
|
13864
14233
|
}
|
|
13865
14234
|
}
|
|
13866
14235
|
}
|
|
14236
|
+
function isNonNullLiteralSeed(raw) {
|
|
14237
|
+
const v = raw.trim();
|
|
14238
|
+
return /^(?:["'`[{]|-?\d)/.test(v) || v === "true" || v === "false";
|
|
14239
|
+
}
|
|
13867
14240
|
function findDataSlot(paths, path) {
|
|
13868
14241
|
return paths.find((c) => c.kind === "data" && c.path === path);
|
|
13869
14242
|
}
|
|
@@ -14000,10 +14373,10 @@ function validateDocumentEnv(html, locale2) {
|
|
|
14000
14373
|
const msgs = getMessages(locale2);
|
|
14001
14374
|
const scanText = blankHtmlComments(html);
|
|
14002
14375
|
const autos = findWcstackAutoScripts(scanText);
|
|
14003
|
-
const stateIndex = autos.findIndex((a) => a.
|
|
14376
|
+
const stateIndex = autos.findIndex((a) => a.pkgs.includes("state"));
|
|
14004
14377
|
if (stateIndex !== -1) {
|
|
14005
14378
|
for (const later of autos.slice(stateIndex + 1)) {
|
|
14006
|
-
if (later.
|
|
14379
|
+
if (!later.pkgs.includes("devtools")) continue;
|
|
14007
14380
|
diagnostics.push({
|
|
14008
14381
|
code: WcsDiagnosticCode.ScriptOrder,
|
|
14009
14382
|
start: later.start,
|
|
@@ -14013,7 +14386,7 @@ function validateDocumentEnv(html, locale2) {
|
|
|
14013
14386
|
});
|
|
14014
14387
|
}
|
|
14015
14388
|
}
|
|
14016
|
-
const router = autos.find((a) => a.
|
|
14389
|
+
const router = autos.find((a) => a.pkgs.includes("router") && (!a.bundle || /<wcs-router\b/i.test(scanText)));
|
|
14017
14390
|
if (router && !/<base\b[^>]*\bhref\s*=/i.test(scanText)) {
|
|
14018
14391
|
diagnostics.push({
|
|
14019
14392
|
code: WcsDiagnosticCode.BaseHrefMissing,
|
|
@@ -14038,6 +14411,9 @@ function validateDocumentEnv(html, locale2) {
|
|
|
14038
14411
|
}
|
|
14039
14412
|
return diagnostics;
|
|
14040
14413
|
}
|
|
14414
|
+
var SCOPED_AUTO = /@wcstack\/([a-z0-9-]+)(?:@[^/"'\s]+)?\/(?:auto\b|dist\/(?:split\/)?auto(?:\.min)?\.js\b)/;
|
|
14415
|
+
var BUNDLE_AUTO = /(?:^|\/)wcstack(?:@[^/"'\s]+)?\/(?:auto\b|dist\/auto\.min\.js\b)/;
|
|
14416
|
+
var BUNDLE_PACKAGES = ["state", "router", "fetch", "storage", "autoloader"];
|
|
14041
14417
|
function findWcstackAutoScripts(html) {
|
|
14042
14418
|
const out = [];
|
|
14043
14419
|
const scriptRegex = /<script\b(?:"[^"]*"|'[^']*'|[^>"'])*>/gi;
|
|
@@ -14045,10 +14421,12 @@ function findWcstackAutoScripts(html) {
|
|
|
14045
14421
|
while ((match = scriptRegex.exec(html)) !== null) {
|
|
14046
14422
|
const src = extractSrc(match[0]);
|
|
14047
14423
|
if (!src) continue;
|
|
14048
|
-
const pkgMatch =
|
|
14049
|
-
|
|
14424
|
+
const pkgMatch = SCOPED_AUTO.exec(src.value);
|
|
14425
|
+
const bundle = pkgMatch === null && BUNDLE_AUTO.test(src.value);
|
|
14426
|
+
if (pkgMatch === null && !bundle) continue;
|
|
14050
14427
|
out.push({
|
|
14051
|
-
|
|
14428
|
+
pkgs: bundle ? BUNDLE_PACKAGES : [pkgMatch[1]],
|
|
14429
|
+
bundle,
|
|
14052
14430
|
start: match.index + src.offsetInTag,
|
|
14053
14431
|
end: match.index + src.offsetInTag + src.value.length
|
|
14054
14432
|
});
|
|
@@ -15506,15 +15884,65 @@ function validateGetterUntrackedReads(script, scriptStart, nestedWriteRoots, loc
|
|
|
15506
15884
|
}
|
|
15507
15885
|
return out;
|
|
15508
15886
|
}
|
|
15887
|
+
var NESTED_WRITE_INPUTS = {
|
|
15888
|
+
"wcs-storage": { inputs: ["value"], exemptWhenManual: true },
|
|
15889
|
+
"wcs-fetch": { inputs: ["body"], exemptWhenManual: false, oneShot: true }
|
|
15890
|
+
};
|
|
15891
|
+
var MANUAL_ATTRIBUTE = /(?:^|\s)manual(?:\s|=|$)/i;
|
|
15892
|
+
function validateInputNestedWrites(html, stateTagName, bindAttrName, nestedWriteRoots, locale2) {
|
|
15893
|
+
const tags = findBuiltinTagOccurrences(html).filter((occ) => {
|
|
15894
|
+
const target = NESTED_WRITE_INPUTS[occ.tagName];
|
|
15895
|
+
return target !== void 0 && !(target.exemptWhenManual && MANUAL_ATTRIBUTE.test(occ.attrsText));
|
|
15896
|
+
}).map((occ) => ({ tagName: occ.tagName, start: occ.tagStart, end: occ.attrsStart + occ.attrsText.length }));
|
|
15897
|
+
if (tags.length === 0) return [];
|
|
15898
|
+
const msgs = getMessages(locale2);
|
|
15899
|
+
const out = [];
|
|
15900
|
+
const index = buildReferenceIndex(html, { bindAttribute: bindAttrName, stateTagName });
|
|
15901
|
+
for (const occ of index.occurrences) {
|
|
15902
|
+
if (occ.kind !== "path" || occ.source !== "attribute" || occ.bindingType !== "prop" || occ.propName === null) continue;
|
|
15903
|
+
if (occ.path.startsWith(".")) continue;
|
|
15904
|
+
const at2 = occ.exprRange.start;
|
|
15905
|
+
const tag = tags.find((t) => t.start <= at2 && at2 <= t.end);
|
|
15906
|
+
if (tag === void 0) continue;
|
|
15907
|
+
const input = occ.propName.startsWith(".") ? occ.propName.slice(1) : occ.propName;
|
|
15908
|
+
if (!NESTED_WRITE_INPUTS[tag.tagName].inputs.includes(input)) continue;
|
|
15909
|
+
const example = nestedWriteRoots().get(occ.path);
|
|
15910
|
+
if (example === void 0) continue;
|
|
15911
|
+
const message = NESTED_WRITE_INPUTS[tag.tagName].oneShot ? msgs.inputNestedWriteOneShot(tag.tagName, input, occ.path, example) : msgs.inputNestedWrite(tag.tagName, input, occ.path, example);
|
|
15912
|
+
out.push({
|
|
15913
|
+
code: WcsDiagnosticCode.InputNestedWrite,
|
|
15914
|
+
start: occ.pathRange.start,
|
|
15915
|
+
end: occ.pathRange.end,
|
|
15916
|
+
severity: "warning",
|
|
15917
|
+
tag: tag.tagName,
|
|
15918
|
+
statePath: occ.path,
|
|
15919
|
+
message
|
|
15920
|
+
});
|
|
15921
|
+
}
|
|
15922
|
+
return out;
|
|
15923
|
+
}
|
|
15509
15924
|
var TWO_WAY_PROPS = /* @__PURE__ */ new Set(["value", "checked"]);
|
|
15510
15925
|
var BRACKET_WRITE = new RegExp(`${ROOT_BRACKET}${ASSIGN_TAIL}`, "g");
|
|
15511
15926
|
var PRE_BRACKET_INCDEC2 = new RegExp(`${PRE_INCDEC}${ROOT_BRACKET}`, "g");
|
|
15927
|
+
var TEMPLATE_BRACKET = String.raw`\bthis\s*(?:\?\.)?\s*\[\s*` + "`([^`]*)`" + String.raw`\s*\]`;
|
|
15928
|
+
var TEMPLATE_BRACKET_WRITE = new RegExp(`${TEMPLATE_BRACKET}${ASSIGN_TAIL}`, "g");
|
|
15929
|
+
var PRE_TEMPLATE_BRACKET_INCDEC = new RegExp(`${PRE_INCDEC}${TEMPLATE_BRACKET}`, "g");
|
|
15930
|
+
var TEMPLATE_KEY_PATH = /^[A-Za-z_][\w]*(?:\.(?:\*|[\w]+))*$/;
|
|
15931
|
+
function templateKeyPath(raw) {
|
|
15932
|
+
if (raw.includes("`")) return null;
|
|
15933
|
+
const path = raw.replace(/\$\{[^{}]*\}/g, "*");
|
|
15934
|
+
return TEMPLATE_KEY_PATH.test(path) ? path : null;
|
|
15935
|
+
}
|
|
15512
15936
|
function collectNestedWriteRoots(html, stateTagName, bindAttrName, blocks) {
|
|
15513
|
-
const roots = /* @__PURE__ */ new
|
|
15937
|
+
const roots = /* @__PURE__ */ new Map();
|
|
15514
15938
|
const addPrefixes = (path, inclusive) => {
|
|
15515
15939
|
const segments = path.split(".");
|
|
15516
15940
|
const last = inclusive ? segments.length : segments.length - 1;
|
|
15517
|
-
|
|
15941
|
+
const example = inclusive ? `${path}.*` : path;
|
|
15942
|
+
for (let i = 1; i <= last; i++) {
|
|
15943
|
+
const prefix = segments.slice(0, i).join(".");
|
|
15944
|
+
if (!roots.has(prefix)) roots.set(prefix, example);
|
|
15945
|
+
}
|
|
15518
15946
|
};
|
|
15519
15947
|
const tags = findBuiltinTagOccurrences(html).map((occ) => ({
|
|
15520
15948
|
contract: BUILTIN_TAGS[occ.tagName],
|
|
@@ -15523,23 +15951,29 @@ function collectNestedWriteRoots(html, stateTagName, bindAttrName, blocks) {
|
|
|
15523
15951
|
}));
|
|
15524
15952
|
const index = buildReferenceIndex(html, { bindAttribute: bindAttrName, stateTagName });
|
|
15525
15953
|
for (const occ of index.occurrences) {
|
|
15526
|
-
if (occ.kind !== "path" || occ.
|
|
15954
|
+
if (occ.kind !== "path" || occ.source !== "attribute") continue;
|
|
15955
|
+
let path = occ.path;
|
|
15956
|
+
if (path.startsWith(".")) {
|
|
15957
|
+
const list = getRowShorthandForPath(html, occ.pathRange.start, bindAttrName) === null ? null : getResolvedForListPath(html, occ.pathRange.start, bindAttrName);
|
|
15958
|
+
if (list === null) continue;
|
|
15959
|
+
path = path === "." ? `${list}.*` : `${list}.*.${path.slice(1)}`;
|
|
15960
|
+
}
|
|
15527
15961
|
if (occ.bindingType === "spread") {
|
|
15528
|
-
addPrefixes(
|
|
15962
|
+
addPrefixes(path, true);
|
|
15529
15963
|
continue;
|
|
15530
15964
|
}
|
|
15531
15965
|
if (occ.bindingType === "radio" || occ.bindingType === "checkbox") {
|
|
15532
|
-
addPrefixes(
|
|
15966
|
+
addPrefixes(path, false);
|
|
15533
15967
|
continue;
|
|
15534
15968
|
}
|
|
15535
15969
|
if (occ.bindingType !== "prop" || occ.propName === null) continue;
|
|
15536
15970
|
if (TWO_WAY_PROPS.has(occ.propName)) {
|
|
15537
|
-
addPrefixes(
|
|
15971
|
+
addPrefixes(path, false);
|
|
15538
15972
|
continue;
|
|
15539
15973
|
}
|
|
15540
15974
|
const at2 = occ.exprRange.start;
|
|
15541
15975
|
const tag = tags.find((t) => t.start <= at2 && at2 <= t.end);
|
|
15542
|
-
if (tag?.contract?.hasWcBindable && tag.contract.properties.includes(occ.propName)) addPrefixes(
|
|
15976
|
+
if (tag?.contract?.hasWcBindable && tag.contract.properties.includes(occ.propName)) addPrefixes(path, false);
|
|
15543
15977
|
}
|
|
15544
15978
|
for (const block of blocks) {
|
|
15545
15979
|
if (block.mountPath !== null) addPrefixes(block.mountPath, true);
|
|
@@ -15552,6 +15986,15 @@ function collectNestedWriteRoots(html, stateTagName, bindAttrName, blocks) {
|
|
|
15552
15986
|
addPrefixes(block.content.slice(keyStart, keyStart + match[1].length), false);
|
|
15553
15987
|
}
|
|
15554
15988
|
}
|
|
15989
|
+
for (const regex of [TEMPLATE_BRACKET_WRITE, PRE_TEMPLATE_BRACKET_INCDEC]) {
|
|
15990
|
+
regex.lastIndex = 0;
|
|
15991
|
+
let match;
|
|
15992
|
+
while ((match = regex.exec(scan)) !== null) {
|
|
15993
|
+
const keyStart = match.index + match[0].indexOf("`") + 1;
|
|
15994
|
+
const path = templateKeyPath(block.content.slice(keyStart, keyStart + match[1].length));
|
|
15995
|
+
if (path !== null) addPrefixes(path, false);
|
|
15996
|
+
}
|
|
15997
|
+
}
|
|
15555
15998
|
API_CALL.lastIndex = 0;
|
|
15556
15999
|
let call;
|
|
15557
16000
|
while ((call = API_CALL.exec(scan)) !== null) {
|
|
@@ -15667,6 +16110,7 @@ function validateSemantics(html, stateTagName = "wcs-state", locale2, bindAttrNa
|
|
|
15667
16110
|
out.push(...validateGetterCycles(block.content, block.contentStart, locale2));
|
|
15668
16111
|
out.push(...validateGetterUntrackedReads(block.content, block.contentStart, getNestedWriteRoots, locale2));
|
|
15669
16112
|
}
|
|
16113
|
+
out.push(...validateInputNestedWrites(html, stateTagName, bindAttrName, getNestedWriteRoots, locale2));
|
|
15670
16114
|
out.push(...validateUpdatedCallbackDemand(html, stateTagName, bindAttrName, locale2));
|
|
15671
16115
|
return out;
|
|
15672
16116
|
}
|
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.3",
|
|
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",
|