@wcstack/lint 1.22.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/cli.cjs ADDED
@@ -0,0 +1,3550 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+ var __copyProps = (to, from, except, desc) => {
12
+ if (from && typeof from === "object" || typeof from === "function") {
13
+ for (let key of __getOwnPropNames(from))
14
+ if (!__hasOwnProp.call(to, key) && key !== except)
15
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
16
+ }
17
+ return to;
18
+ };
19
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
20
+
21
+ // src/cli.ts
22
+ var cli_exports = {};
23
+ __export(cli_exports, {
24
+ main: () => main,
25
+ parseArgs: () => parseArgs,
26
+ resolveCliLocale: () => resolveCliLocale
27
+ });
28
+ module.exports = __toCommonJS(cli_exports);
29
+ var import_node_fs = require("node:fs");
30
+
31
+ // src/core/offsetToPosition.ts
32
+ function createPositionMapper(text) {
33
+ const lineStarts = [0];
34
+ for (let i = 0; i < text.length; i++) {
35
+ const c = text.charCodeAt(i);
36
+ if (c === 10) {
37
+ lineStarts.push(i + 1);
38
+ } else if (c === 13) {
39
+ if (text.charCodeAt(i + 1) === 10) i++;
40
+ lineStarts.push(i + 1);
41
+ }
42
+ }
43
+ return (offset) => {
44
+ const clamped = Math.max(0, Math.min(offset, text.length));
45
+ let lo = 0;
46
+ let hi = lineStarts.length - 1;
47
+ while (lo < hi) {
48
+ const mid = lo + hi + 1 >> 1;
49
+ if (lineStarts[mid] <= clamped) lo = mid;
50
+ else hi = mid - 1;
51
+ }
52
+ return { line: lo + 1, column: clamped - lineStarts[lo] + 1 };
53
+ };
54
+ }
55
+
56
+ // src/core/diagnostics.ts
57
+ var WcsDiagnosticCode = {
58
+ // --- sidecar manifest envelope / schema subset ---
59
+ ManifestBroken: "wcs/manifest-broken",
60
+ ManifestSchemaVersion: "wcs/manifest-schema-version",
61
+ ManifestKindInvalid: "wcs/manifest-kind-invalid",
62
+ ManifestUnknownKeyword: "wcs/manifest-unknown-keyword",
63
+ ManifestExternalRef: "wcs/manifest-external-ref",
64
+ ManifestRefCycle: "wcs/manifest-ref-cycle",
65
+ ManifestRefUnresolved: "wcs/manifest-ref-unresolved",
66
+ ManifestNamespaceVersion: "wcs/manifest-namespace-version",
67
+ // --- sidecar resolution: collision / override ---
68
+ // 同名 tag / filter の後勝ち禁止(§5-3)。override:true が無い再定義もこの collision で表す。
69
+ ManifestTagCollision: "wcs/manifest-tag-collision",
70
+ ManifestFilterCollision: "wcs/manifest-filter-collision",
71
+ // 明示 override:true(§5-4)。衝突ではなく意図的な shadow の告知(info)。
72
+ ManifestOverride: "wcs/manifest-override",
73
+ // --- sidecar vs live declaration drift ---
74
+ DriftMissingMember: "wcs/drift-missing-member",
75
+ DriftEventMismatch: "wcs/drift-event-mismatch",
76
+ // --- path / type resolution against a stateSchema ---
77
+ PathNonexistent: "wcs/path-nonexistent",
78
+ PathTypeMismatch: "wcs/path-type-mismatch",
79
+ PathReadonly: "wcs/path-readonly",
80
+ PathReservedName: "wcs/path-reserved-name",
81
+ PathDynamicUnknown: "wcs/path-dynamic-unknown",
82
+ // --- existing binding-expression validators (retrofitted) ---
83
+ FilterUnknown: "wcs/filter-unknown",
84
+ FilterArity: "wcs/filter-arity",
85
+ FilterArgType: "wcs/filter-arg-type",
86
+ FilterInputType: "wcs/filter-input-type",
87
+ BindingPathMissing: "wcs/binding-path-missing",
88
+ BindingTypeExpectation: "wcs/binding-type-expectation",
89
+ TokenUndeclared: "wcs/token-undeclared",
90
+ TokenMisconfigured: "wcs/token-misconfigured",
91
+ NestedAssign: "wcs/nested-assign",
92
+ TypeAnnotation: "wcs/type-annotation",
93
+ TemplateSyntax: "wcs/template-syntax",
94
+ // --- built-in wcs-* tag contract (generated/builtinTags.generated.ts が正本) ---
95
+ // 未知メンバーへのバインド(プロパティ / command. / eventToken. キー)。黙って無視される。
96
+ TagMemberUnknown: "wcs/tag-member-unknown",
97
+ // trigger バインド先スロットの true シード(エッジ検出なし・manual バイパスで即発火)。
98
+ TriggerSeededTruthy: "wcs/trigger-seeded-truthy",
99
+ // 非 manual <wcs-storage> value バインド先の空値シード(初期書き戻しが保存値を上書き)。
100
+ StorageSeedClobber: "wcs/storage-seed-clobber",
101
+ // --- document-level load configuration ---
102
+ // @wcstack/state/auto より後に他 wcstack /auto が読まれている。
103
+ ScriptOrder: "wcs/script-order",
104
+ // router/auto があるのに <base href> がない(SPA の basename 誤導出)。
105
+ BaseHrefMissing: "wcs/base-href-missing",
106
+ // @wcstack/signals と /dom エントリの同一ページ混在(リアクティブコア二重化)。
107
+ SignalsDualEntry: "wcs/signals-dual-entry"
108
+ };
109
+ function sortDiagnostics(diagnostics) {
110
+ const severityRank = { error: 0, warning: 1, info: 2 };
111
+ return [...diagnostics].sort((a, b) => a.start - b.start || severityRank[a.severity] - severityRank[b.severity] || (a.code < b.code ? -1 : a.code > b.code ? 1 : 0));
112
+ }
113
+
114
+ // ../state/dist/manifest.esm.js
115
+ var builtinFilterMeta = {
116
+ // 比較・論理
117
+ eq: { description: "\u7B49\u3057\u3044\u304B\u6BD4\u8F03", hasArgs: true, resultType: "boolean", acceptTypes: "any", minArgs: 1, maxArgs: 1, argTypes: ["any"] },
118
+ ne: { description: "\u7570\u306A\u308B\u304B\u6BD4\u8F03", hasArgs: true, resultType: "boolean", acceptTypes: "any", minArgs: 1, maxArgs: 1, argTypes: ["any"] },
119
+ not: { description: "\u30D6\u30FC\u30EB\u5024\u3092\u53CD\u8EE2", hasArgs: false, resultType: "boolean", acceptTypes: ["boolean"], minArgs: 0, maxArgs: 0 },
120
+ lt: { description: "\u3088\u308A\u5C0F\u3055\u3044\u304B", hasArgs: true, resultType: "boolean", acceptTypes: ["number", "string"], minArgs: 1, maxArgs: 1, argTypes: ["number"] },
121
+ le: { description: "\u4EE5\u4E0B\u304B", hasArgs: true, resultType: "boolean", acceptTypes: ["number", "string"], minArgs: 1, maxArgs: 1, argTypes: ["number"] },
122
+ gt: { description: "\u3088\u308A\u5927\u304D\u3044\u304B", hasArgs: true, resultType: "boolean", acceptTypes: ["number", "string"], minArgs: 1, maxArgs: 1, argTypes: ["number"] },
123
+ ge: { description: "\u4EE5\u4E0A\u304B", hasArgs: true, resultType: "boolean", acceptTypes: ["number", "string"], minArgs: 1, maxArgs: 1, argTypes: ["number"] },
124
+ // 算術
125
+ inc: { description: "\u52A0\u7B97", hasArgs: true, resultType: "number", acceptTypes: ["number"], minArgs: 0, maxArgs: 1, argTypes: ["number"] },
126
+ dec: { description: "\u6E1B\u7B97", hasArgs: true, resultType: "number", acceptTypes: ["number"], minArgs: 0, maxArgs: 1, argTypes: ["number"] },
127
+ mul: { description: "\u4E57\u7B97", hasArgs: true, resultType: "number", acceptTypes: ["number"], minArgs: 1, maxArgs: 1, argTypes: ["number"] },
128
+ div: { description: "\u9664\u7B97", hasArgs: true, resultType: "number", acceptTypes: ["number"], minArgs: 1, maxArgs: 1, argTypes: ["number"] },
129
+ mod: { description: "\u5270\u4F59", hasArgs: true, resultType: "number", acceptTypes: ["number"], minArgs: 1, maxArgs: 1, argTypes: ["number"] },
130
+ // 数値フォーマット
131
+ fix: { description: "\u56FA\u5B9A\u5C0F\u6570\u70B9\u8868\u8A18", hasArgs: true, resultType: "string", acceptTypes: ["number"], minArgs: 0, maxArgs: 1, argTypes: ["number"] },
132
+ locale: { description: "\u30ED\u30B1\u30FC\u30EB\u5F62\u5F0F\u3067\u6570\u5024\u30D5\u30A9\u30FC\u30DE\u30C3\u30C8", hasArgs: true, resultType: "string", acceptTypes: ["number"], minArgs: 0, maxArgs: 1, argTypes: ["string"] },
133
+ // 文字列
134
+ uc: { description: "\u5927\u6587\u5B57\u306B\u5909\u63DB", hasArgs: false, resultType: "string", acceptTypes: ["string"], minArgs: 0, maxArgs: 0 },
135
+ lc: { description: "\u5C0F\u6587\u5B57\u306B\u5909\u63DB", hasArgs: false, resultType: "string", acceptTypes: ["string"], minArgs: 0, maxArgs: 0 },
136
+ cap: { description: "\u5148\u982D\u6587\u5B57\u3092\u5927\u6587\u5B57\u306B", hasArgs: false, resultType: "string", acceptTypes: ["string"], minArgs: 0, maxArgs: 0 },
137
+ trim: { description: "\u524D\u5F8C\u306E\u7A7A\u767D\u3092\u524A\u9664", hasArgs: false, resultType: "string", acceptTypes: ["string"], minArgs: 0, maxArgs: 0 },
138
+ slice: { description: "\u90E8\u5206\u6587\u5B57\u5217 (start[,end])", hasArgs: true, resultType: "string", acceptTypes: ["string"], minArgs: 1, maxArgs: 2, argTypes: ["number", "number"] },
139
+ substr: { description: "\u90E8\u5206\u6587\u5B57\u5217 (pos,len)", hasArgs: true, resultType: "string", acceptTypes: ["string"], minArgs: 1, maxArgs: 2, argTypes: ["number", "number"] },
140
+ pad: { description: "\u30D1\u30C7\u30A3\u30F3\u30B0 (length[,char])", hasArgs: true, resultType: "string", acceptTypes: ["string"], minArgs: 1, maxArgs: 2, argTypes: ["number", "string"] },
141
+ rep: { description: "\u7E70\u308A\u8FD4\u3057 (count)", hasArgs: true, resultType: "string", acceptTypes: ["string"], minArgs: 1, maxArgs: 1, argTypes: ["number"] },
142
+ rev: { description: "\u6587\u5B57\u9806\u3092\u53CD\u8EE2", hasArgs: false, resultType: "string", acceptTypes: ["string"], minArgs: 0, maxArgs: 0 },
143
+ // 数値パース・丸め
144
+ int: { description: "\u6574\u6570\u306B\u30D1\u30FC\u30B9", hasArgs: false, resultType: "number", acceptTypes: ["string", "number"], minArgs: 0, maxArgs: 0 },
145
+ float: { description: "\u6D6E\u52D5\u5C0F\u6570\u70B9\u6570\u306B\u30D1\u30FC\u30B9", hasArgs: false, resultType: "number", acceptTypes: ["string", "number"], minArgs: 0, maxArgs: 0 },
146
+ round: { description: "\u56DB\u6368\u4E94\u5165", hasArgs: true, resultType: "number", acceptTypes: ["number"], minArgs: 0, maxArgs: 1, argTypes: ["number"] },
147
+ floor: { description: "\u5207\u308A\u4E0B\u3052", hasArgs: true, resultType: "number", acceptTypes: ["number"], minArgs: 0, maxArgs: 1, argTypes: ["number"] },
148
+ ceil: { description: "\u5207\u308A\u4E0A\u3052", hasArgs: true, resultType: "number", acceptTypes: ["number"], minArgs: 0, maxArgs: 1, argTypes: ["number"] },
149
+ percent: { description: "\u30D1\u30FC\u30BB\u30F3\u30C6\u30FC\u30B8\u5F62\u5F0F", hasArgs: true, resultType: "string", acceptTypes: ["number"], minArgs: 0, maxArgs: 1, argTypes: ["number"] },
150
+ // 日付・時刻
151
+ date: { description: "\u30ED\u30B1\u30FC\u30EB\u5F62\u5F0F\u306E\u65E5\u4ED8", hasArgs: false, resultType: "string", acceptTypes: "any", minArgs: 0, maxArgs: 0 },
152
+ time: { description: "\u30ED\u30B1\u30FC\u30EB\u5F62\u5F0F\u306E\u6642\u523B", hasArgs: false, resultType: "string", acceptTypes: "any", minArgs: 0, maxArgs: 0 },
153
+ datetime: { description: "\u30ED\u30B1\u30FC\u30EB\u5F62\u5F0F\u306E\u65E5\u6642", hasArgs: false, resultType: "string", acceptTypes: "any", minArgs: 0, maxArgs: 0 },
154
+ ymd: { description: "YYYY-MM-DD \u5F62\u5F0F", hasArgs: true, resultType: "string", acceptTypes: "any", minArgs: 0, maxArgs: 1, argTypes: ["string"] },
155
+ // 真偽値・変換
156
+ falsy: { description: "\u507D\u5024\u304B\u5224\u5B9A", hasArgs: false, resultType: "boolean", acceptTypes: "any", minArgs: 0, maxArgs: 0 },
157
+ truthy: { description: "\u771F\u5024\u304B\u5224\u5B9A", hasArgs: false, resultType: "boolean", acceptTypes: "any", minArgs: 0, maxArgs: 0 },
158
+ defaults: { description: "\u507D\u5024\u306E\u5834\u5408\u30C7\u30D5\u30A9\u30EB\u30C8\u5024", hasArgs: true, resultType: "passthrough", acceptTypes: "any", minArgs: 1, maxArgs: 1, argTypes: ["any"] },
159
+ boolean: { description: "\u30D6\u30FC\u30EB\u5024\u306B\u5909\u63DB", hasArgs: false, resultType: "boolean", acceptTypes: "any", minArgs: 0, maxArgs: 0 },
160
+ number: { description: "\u6570\u5024\u306B\u5909\u63DB", hasArgs: false, resultType: "number", acceptTypes: "any", minArgs: 0, maxArgs: 0 },
161
+ string: { description: "\u6587\u5B57\u5217\u306B\u5909\u63DB", hasArgs: false, resultType: "string", acceptTypes: "any", minArgs: 0, maxArgs: 0 },
162
+ null: { description: "\u7A7A\u6587\u5B57\u5217\u3092null\u306B\u5909\u63DB", hasArgs: false, resultType: "passthrough", acceptTypes: ["string"], minArgs: 0, maxArgs: 0 }
163
+ };
164
+ var STRUCTURAL_BINDING_TYPE_SET = /* @__PURE__ */ new Set([
165
+ "if",
166
+ "elseif",
167
+ "else",
168
+ "for"
169
+ ]);
170
+ var MAX_WILDCARD_DEPTH = 128;
171
+ var tmpIndexByIndexName = {};
172
+ for (let i = 0; i < MAX_WILDCARD_DEPTH; i++) {
173
+ tmpIndexByIndexName[`$${i + 1}`] = i;
174
+ }
175
+ Object.freeze(tmpIndexByIndexName);
176
+
177
+ // src/service/completionData.ts
178
+ var BUILTIN_FILTERS = Object.entries(builtinFilterMeta).map(
179
+ ([name, meta]) => ({ name, ...meta })
180
+ );
181
+ var STRUCTURAL_DIRECTIVE_INFO = {
182
+ for: { description: "\u30EA\u30B9\u30C8\u30EC\u30F3\u30C0\u30EA\u30F3\u30B0 (<template>)", insertColon: true },
183
+ if: { description: "\u6761\u4EF6\u4ED8\u304D\u30EC\u30F3\u30C0\u30EA\u30F3\u30B0 (<template>)", insertColon: true },
184
+ elseif: { description: "else-if \u6761\u4EF6 (<template>)", insertColon: true },
185
+ else: { description: "else \u30D6\u30ED\u30C3\u30AF (<template>)", insertColon: false }
186
+ };
187
+ var STRUCTURAL_DIRECTIVES = [...STRUCTURAL_BINDING_TYPE_SET].map((name) => ({
188
+ name,
189
+ ...STRUCTURAL_DIRECTIVE_INFO[name]
190
+ }));
191
+
192
+ // src/language/htmlParse.ts
193
+ function parseWcsScriptBlocks(html, stateTagName = "wcs-state") {
194
+ const blocks = [];
195
+ let pos = 0;
196
+ const len = html.length;
197
+ while (pos < len) {
198
+ if (html.startsWith("<!--", pos)) {
199
+ const commentEnd = html.indexOf("-->", pos + 4);
200
+ if (commentEnd === -1) break;
201
+ pos = commentEnd + 3;
202
+ continue;
203
+ }
204
+ const wcsMatch = matchOpenTag(html, pos, stateTagName);
205
+ if (wcsMatch === null) {
206
+ pos++;
207
+ continue;
208
+ }
209
+ const stateName = extractAttribute(wcsMatch.tagContent, "name") ?? "default";
210
+ pos = wcsMatch.end;
211
+ const wcsCloseIdx = findCloseTag(html, pos, stateTagName);
212
+ const wcsEnd = wcsCloseIdx === -1 ? len : wcsCloseIdx;
213
+ while (pos < wcsEnd) {
214
+ if (html.startsWith("<!--", pos)) {
215
+ const commentEnd = html.indexOf("-->", pos + 4);
216
+ if (commentEnd === -1) break;
217
+ pos = commentEnd + 3;
218
+ continue;
219
+ }
220
+ const scriptMatch = matchOpenTag(html, pos, "script");
221
+ if (scriptMatch === null) {
222
+ pos++;
223
+ continue;
224
+ }
225
+ const typeAttr = extractAttribute(scriptMatch.tagContent, "type");
226
+ if (typeAttr !== "module") {
227
+ pos = scriptMatch.end;
228
+ continue;
229
+ }
230
+ const contentStart = scriptMatch.end;
231
+ const scriptCloseIdx = findCloseTag(html, contentStart, "script");
232
+ if (scriptCloseIdx === -1) {
233
+ pos = contentStart;
234
+ break;
235
+ }
236
+ const contentEnd = scriptCloseIdx;
237
+ blocks.push({
238
+ contentStart,
239
+ contentEnd,
240
+ content: html.slice(contentStart, contentEnd),
241
+ stateName
242
+ });
243
+ pos = html.indexOf(">", scriptCloseIdx) + 1;
244
+ if (pos === 0) break;
245
+ }
246
+ pos = wcsEnd;
247
+ if (wcsCloseIdx !== -1) {
248
+ const closeEnd = html.indexOf(">", wcsCloseIdx);
249
+ if (closeEnd !== -1) pos = closeEnd + 1;
250
+ }
251
+ }
252
+ return blocks;
253
+ }
254
+ function parseWcsStateElements(html, stateTagName = "wcs-state") {
255
+ const elements = [];
256
+ let pos = 0;
257
+ const len = html.length;
258
+ while (pos < len) {
259
+ if (html.startsWith("<!--", pos)) {
260
+ const commentEnd = html.indexOf("-->", pos + 4);
261
+ if (commentEnd === -1) break;
262
+ pos = commentEnd + 3;
263
+ continue;
264
+ }
265
+ const wcsMatch = matchOpenTag(html, pos, stateTagName);
266
+ if (wcsMatch === null) {
267
+ pos++;
268
+ continue;
269
+ }
270
+ const stateName = extractAttribute(wcsMatch.tagContent, "name") ?? "default";
271
+ const jsonAttr = extractAttribute(wcsMatch.tagContent, "json") ?? void 0;
272
+ const stateAttr = extractAttribute(wcsMatch.tagContent, "state") ?? void 0;
273
+ const srcAttr = extractAttribute(wcsMatch.tagContent, "src") ?? void 0;
274
+ pos = wcsMatch.end;
275
+ const scriptBlocks = [];
276
+ const wcsCloseIdx = findCloseTag(html, pos, stateTagName);
277
+ const wcsEnd = wcsCloseIdx === -1 ? len : wcsCloseIdx;
278
+ while (pos < wcsEnd) {
279
+ if (html.startsWith("<!--", pos)) {
280
+ const commentEnd = html.indexOf("-->", pos + 4);
281
+ if (commentEnd === -1) break;
282
+ pos = commentEnd + 3;
283
+ continue;
284
+ }
285
+ const scriptMatch = matchOpenTag(html, pos, "script");
286
+ if (scriptMatch === null) {
287
+ pos++;
288
+ continue;
289
+ }
290
+ const typeAttr = extractAttribute(scriptMatch.tagContent, "type");
291
+ if (typeAttr !== "module") {
292
+ pos = scriptMatch.end;
293
+ continue;
294
+ }
295
+ const contentStart = scriptMatch.end;
296
+ const scriptCloseIdx = findCloseTag(html, contentStart, "script");
297
+ if (scriptCloseIdx === -1) {
298
+ pos = contentStart;
299
+ break;
300
+ }
301
+ scriptBlocks.push({
302
+ contentStart,
303
+ contentEnd: scriptCloseIdx,
304
+ content: html.slice(contentStart, scriptCloseIdx),
305
+ stateName
306
+ });
307
+ pos = html.indexOf(">", scriptCloseIdx) + 1;
308
+ if (pos === 0) break;
309
+ }
310
+ elements.push({ stateName, jsonAttr, stateAttr, srcAttr, scriptBlocks });
311
+ pos = wcsEnd;
312
+ if (wcsCloseIdx !== -1) {
313
+ const closeEnd = html.indexOf(">", wcsCloseIdx);
314
+ if (closeEnd !== -1) pos = closeEnd + 1;
315
+ }
316
+ }
317
+ return elements;
318
+ }
319
+ function findScriptJsonById(html, id) {
320
+ let pos = 0;
321
+ const len = html.length;
322
+ while (pos < len) {
323
+ if (html.startsWith("<!--", pos)) {
324
+ const commentEnd = html.indexOf("-->", pos + 4);
325
+ if (commentEnd === -1) break;
326
+ pos = commentEnd + 3;
327
+ continue;
328
+ }
329
+ const scriptMatch = matchOpenTag(html, pos, "script");
330
+ if (scriptMatch === null) {
331
+ pos++;
332
+ continue;
333
+ }
334
+ const typeAttr = extractAttribute(scriptMatch.tagContent, "type");
335
+ const idAttr = extractAttribute(scriptMatch.tagContent, "id");
336
+ if (typeAttr === "application/json" && idAttr === id) {
337
+ const contentStart = scriptMatch.end;
338
+ const scriptCloseIdx = findCloseTag(html, contentStart, "script");
339
+ if (scriptCloseIdx === -1) return null;
340
+ return html.slice(contentStart, scriptCloseIdx);
341
+ }
342
+ pos = scriptMatch.end;
343
+ }
344
+ return null;
345
+ }
346
+ function matchOpenTag(html, pos, tagName) {
347
+ if (html[pos] !== "<") return null;
348
+ const nameStart = pos + 1;
349
+ const nameEnd = nameStart + tagName.length;
350
+ if (nameEnd > html.length) return null;
351
+ const slice = html.slice(nameStart, nameEnd);
352
+ if (slice.toLowerCase() !== tagName.toLowerCase()) return null;
353
+ const charAfter = html[nameEnd];
354
+ if (charAfter !== ">" && charAfter !== " " && charAfter !== " " && charAfter !== "\n" && charAfter !== "\r" && charAfter !== "/") {
355
+ return null;
356
+ }
357
+ let i = nameEnd;
358
+ let inSingleQuote = false;
359
+ let inDoubleQuote = false;
360
+ while (i < html.length) {
361
+ const ch = html[i];
362
+ if (inSingleQuote) {
363
+ if (ch === "'") inSingleQuote = false;
364
+ } else if (inDoubleQuote) {
365
+ if (ch === '"') inDoubleQuote = false;
366
+ } else if (ch === "'") {
367
+ inSingleQuote = true;
368
+ } else if (ch === '"') {
369
+ inDoubleQuote = true;
370
+ } else if (ch === ">") {
371
+ return {
372
+ start: pos,
373
+ end: i + 1,
374
+ tagContent: html.slice(nameEnd, i)
375
+ };
376
+ }
377
+ i++;
378
+ }
379
+ return null;
380
+ }
381
+ function findCloseTag(html, startPos, tagName) {
382
+ const pattern = "</" + tagName;
383
+ const patternLower = pattern.toLowerCase();
384
+ const htmlLower = html.toLowerCase();
385
+ let pos = startPos;
386
+ while (pos < html.length) {
387
+ const idx = htmlLower.indexOf(patternLower, pos);
388
+ if (idx === -1) return -1;
389
+ const afterIdx = idx + pattern.length;
390
+ if (afterIdx < html.length) {
391
+ const ch = html[afterIdx];
392
+ if (ch === ">" || ch === " " || ch === " " || ch === "\n" || ch === "\r") {
393
+ return idx;
394
+ }
395
+ }
396
+ pos = idx + 1;
397
+ }
398
+ return -1;
399
+ }
400
+ function extractAttribute(tagContent, attrName) {
401
+ const regex = new RegExp(
402
+ `(?:^|\\s)${attrName}\\s*=\\s*(?:"([^"]*)"|'([^']*)'|(\\S+))`,
403
+ "i"
404
+ );
405
+ const match = tagContent.match(regex);
406
+ if (!match) return null;
407
+ return match[1] ?? match[2] ?? match[3] ?? null;
408
+ }
409
+
410
+ // src/service/stateAnalyzer.ts
411
+ var RESERVED_STREAMS_KEY = "$streams";
412
+ var RESERVED_COMMAND_TOKENS_KEY = "$commandTokens";
413
+ var RESERVED_EVENT_TOKENS_KEY = "$eventTokens";
414
+ function analyzeStatePaths(scriptContent, stateName = "default") {
415
+ const objectContent = extractDefaultExportObject(scriptContent);
416
+ if (!objectContent) return [];
417
+ const paths = [];
418
+ const topLevelProps = parseTopLevelProperties(objectContent);
419
+ const pendingStreamValues = [];
420
+ for (const prop of topLevelProps) {
421
+ if (prop.name.startsWith("$")) {
422
+ collectReservedKeyPaths(prop, paths, pendingStreamValues, stateName);
423
+ continue;
424
+ }
425
+ if (prop.kind === "method") {
426
+ paths.push({ path: prop.name, kind: "method", stateName });
427
+ continue;
428
+ }
429
+ if (prop.kind === "getter") {
430
+ paths.push({ path: prop.name, kind: "computed", stateName });
431
+ continue;
432
+ }
433
+ pushDataPropertyPaths(prop, paths, stateName);
434
+ }
435
+ for (const streamValue of pendingStreamValues) {
436
+ if (paths.some((p) => p.stateName === stateName && p.path === streamValue.name)) continue;
437
+ pushDataPropertyPaths(streamValue, paths, stateName);
438
+ }
439
+ return paths;
440
+ }
441
+ function collectReservedKeyPaths(prop, paths, pendingStreamValues, stateName) {
442
+ if (prop.name === RESERVED_STREAMS_KEY && prop.kind === "data" && prop.value && isObjectLiteral(prop.value)) {
443
+ const entries = parseTopLevelProperties(extractObjectContent(prop.value));
444
+ for (const entry of entries) {
445
+ if (entry.kind !== "data" || entry.name.startsWith("$")) continue;
446
+ const initial = entry.value && isObjectLiteral(entry.value) ? findStreamInitialProperty(entry.value) : void 0;
447
+ pendingStreamValues.push({
448
+ name: entry.name,
449
+ kind: "data",
450
+ value: initial?.value,
451
+ typeHint: initial?.typeHint
452
+ });
453
+ paths.push({ path: `$streamStatus.${entry.name}`, kind: "data", typeHint: "string", stateName });
454
+ paths.push({ path: `$streamError.${entry.name}`, kind: "data", stateName });
455
+ }
456
+ return;
457
+ }
458
+ if (prop.name === RESERVED_COMMAND_TOKENS_KEY && prop.value) {
459
+ for (const name of extractStringArrayItems(prop.value)) {
460
+ paths.push({ path: `$command.${name}`, kind: "command", stateName });
461
+ }
462
+ return;
463
+ }
464
+ if (prop.name === RESERVED_EVENT_TOKENS_KEY && prop.value) {
465
+ for (const name of extractStringArrayItems(prop.value)) {
466
+ paths.push({ path: name, kind: "eventToken", stateName });
467
+ }
468
+ return;
469
+ }
470
+ }
471
+ function findStreamInitialProperty(entryValue) {
472
+ const defProps = parseTopLevelProperties(extractObjectContent(entryValue));
473
+ return defProps.find((p) => p.kind === "data" && p.name === "initial");
474
+ }
475
+ function extractStringArrayItems(value) {
476
+ if (!isArrayLiteral(value)) return [];
477
+ const items = [];
478
+ const regex = /["']([^"'\\]+)["']/g;
479
+ let match;
480
+ while ((match = regex.exec(value)) !== null) {
481
+ items.push(match[1]);
482
+ }
483
+ return items;
484
+ }
485
+ function pushDataPropertyPaths(prop, paths, stateName) {
486
+ paths.push({ path: prop.name, kind: "data", typeHint: prop.typeHint, rawInitial: prop.value?.trim(), stateName });
487
+ if (prop.value && isArrayLiteral(prop.value)) {
488
+ paths.push({ path: `${prop.name}.*`, kind: "list", stateName });
489
+ paths.push({ path: `${prop.name}.length`, kind: "data", typeHint: "number", stateName });
490
+ const elementProps = extractArrayElementProperties(prop.value);
491
+ for (const childProp of elementProps) {
492
+ paths.push({
493
+ path: `${prop.name}.*.${childProp.name}`,
494
+ kind: "data",
495
+ typeHint: childProp.typeHint,
496
+ stateName
497
+ });
498
+ }
499
+ }
500
+ if (prop.value && isObjectLiteral(prop.value)) {
501
+ const childProps = parseTopLevelProperties(extractObjectContent(prop.value));
502
+ for (const childProp of childProps) {
503
+ if (childProp.kind === "data") {
504
+ paths.push({
505
+ path: `${prop.name}.${childProp.name}`,
506
+ kind: "data",
507
+ typeHint: childProp.typeHint,
508
+ rawInitial: childProp.value?.trim(),
509
+ stateName
510
+ });
511
+ if (childProp.value && isArrayLiteral(childProp.value)) {
512
+ paths.push({ path: `${prop.name}.${childProp.name}.*`, kind: "list", stateName });
513
+ paths.push({ path: `${prop.name}.${childProp.name}.length`, kind: "data", typeHint: "number", stateName });
514
+ const grandchildProps = extractArrayElementProperties(childProp.value);
515
+ for (const gc of grandchildProps) {
516
+ paths.push({
517
+ path: `${prop.name}.${childProp.name}.*.${gc.name}`,
518
+ kind: "data",
519
+ typeHint: gc.typeHint,
520
+ stateName
521
+ });
522
+ }
523
+ }
524
+ }
525
+ }
526
+ }
527
+ }
528
+ function analyzeJsonPaths(jsonString, stateName = "default") {
529
+ let data;
530
+ try {
531
+ data = JSON.parse(jsonString);
532
+ } catch {
533
+ return [];
534
+ }
535
+ if (typeof data !== "object" || data === null || Array.isArray(data)) return [];
536
+ const paths = [];
537
+ collectJsonPaths(data, "", paths, stateName, 0);
538
+ return paths;
539
+ }
540
+ function collectJsonPaths(obj, prefix, paths, stateName, depth) {
541
+ if (depth > 5) return;
542
+ for (const [key, value] of Object.entries(obj)) {
543
+ if (prefix === "" && key.startsWith("$")) continue;
544
+ const path = prefix ? `${prefix}.${key}` : key;
545
+ const typeHint = inferJsonTypeHint(value);
546
+ paths.push({ path, kind: "data", typeHint, stateName });
547
+ if (Array.isArray(value)) {
548
+ paths.push({ path: `${path}.*`, kind: "list", stateName });
549
+ paths.push({ path: `${path}.length`, kind: "data", typeHint: "number", stateName });
550
+ if (value.length > 0 && typeof value[0] === "object" && value[0] !== null && !Array.isArray(value[0])) {
551
+ const firstElement = value[0];
552
+ for (const [childKey, childValue] of Object.entries(firstElement)) {
553
+ const childPath = `${path}.*.${childKey}`;
554
+ paths.push({ path: childPath, kind: "data", typeHint: inferJsonTypeHint(childValue), stateName });
555
+ }
556
+ }
557
+ } else if (typeof value === "object" && value !== null) {
558
+ collectJsonPaths(value, path, paths, stateName, depth + 1);
559
+ }
560
+ }
561
+ }
562
+ function inferJsonTypeHint(value) {
563
+ if (value === null) return "null";
564
+ if (typeof value === "string") return "string";
565
+ if (typeof value === "number") return "number";
566
+ if (typeof value === "boolean") return "boolean";
567
+ if (Array.isArray(value)) return "array";
568
+ if (typeof value === "object") return "object";
569
+ return void 0;
570
+ }
571
+ function extractDefaultExportObject(script) {
572
+ const match = script.match(/export\s+default\s+(?:defineState\s*\(\s*)?(\{)/);
573
+ if (!match) return null;
574
+ const startIndex = script.indexOf(match[1], match.index);
575
+ return extractBracedContent(script, startIndex);
576
+ }
577
+ function parseTopLevelProperties(objectContent) {
578
+ const props = [];
579
+ const regex = /(?:get\s+(?:"([^"]+)"|'([^']+)'|([$\w]+))\s*\(\s*\))|(?:(?:async\s+)?([$\w]+)\s*\([^)]*\)\s*\{)|(?:(?:"([^"]+)"|'([^']+)'|([$\w]+))\s*:\s*)/g;
580
+ let match;
581
+ while ((match = regex.exec(objectContent)) !== null) {
582
+ const getterName = match[1] ?? match[2] ?? match[3];
583
+ if (getterName) {
584
+ props.push({ name: getterName, kind: "getter" });
585
+ continue;
586
+ }
587
+ const methodName = match[4];
588
+ if (methodName) {
589
+ props.push({ name: methodName, kind: "method" });
590
+ const braceStart = objectContent.indexOf("{", match.index + match[0].length - 1);
591
+ if (braceStart !== -1) {
592
+ const body = extractBracedContent(objectContent, braceStart);
593
+ regex.lastIndex = braceStart + body.length + 2;
594
+ }
595
+ continue;
596
+ }
597
+ const propName = match[5] ?? match[6] ?? match[7];
598
+ if (propName) {
599
+ const valueStartIndex = match.index + match[0].length;
600
+ const value = extractFullValue(objectContent, valueStartIndex);
601
+ const jsdocType = extractJsDocType(objectContent, match.index);
602
+ const typeHint = jsdocType ?? inferTypeHint(value);
603
+ props.push({ name: propName, kind: "data", value, typeHint });
604
+ regex.lastIndex = valueStartIndex + value.length;
605
+ }
606
+ }
607
+ return props;
608
+ }
609
+ function extractFullValue(content, startIndex) {
610
+ let depth = 0;
611
+ let i = startIndex;
612
+ const len = content.length;
613
+ let inString = null;
614
+ while (i < len) {
615
+ const ch = content[i];
616
+ if (inString) {
617
+ if (ch === inString && !isEscaped(content, i)) {
618
+ inString = null;
619
+ }
620
+ i++;
621
+ continue;
622
+ }
623
+ if (ch === '"' || ch === "'" || ch === "`") {
624
+ inString = ch;
625
+ } else if (ch === "{" || ch === "[" || ch === "(") {
626
+ depth++;
627
+ } else if (ch === "}" || ch === "]" || ch === ")") {
628
+ if (depth === 0) break;
629
+ depth--;
630
+ } else if (ch === "," && depth === 0) {
631
+ break;
632
+ }
633
+ i++;
634
+ }
635
+ return content.slice(startIndex, i).trim();
636
+ }
637
+ function extractBracedContent(text, openBraceIndex) {
638
+ let depth = 0;
639
+ let inString = null;
640
+ for (let i = openBraceIndex; i < text.length; i++) {
641
+ const ch = text[i];
642
+ if (inString) {
643
+ if (ch === inString && !isEscaped(text, i)) {
644
+ inString = null;
645
+ }
646
+ continue;
647
+ }
648
+ if (ch === '"' || ch === "'" || ch === "`") {
649
+ inString = ch;
650
+ } else if (ch === "{") {
651
+ depth++;
652
+ } else if (ch === "}") {
653
+ depth--;
654
+ if (depth === 0) {
655
+ return text.slice(openBraceIndex + 1, i);
656
+ }
657
+ }
658
+ }
659
+ return text.slice(openBraceIndex + 1);
660
+ }
661
+ function isArrayLiteral(value) {
662
+ return value.trimStart().startsWith("[");
663
+ }
664
+ function isObjectLiteral(value) {
665
+ return value.trimStart().startsWith("{");
666
+ }
667
+ function extractObjectContent(value) {
668
+ const trimmed = value.trim();
669
+ const start = trimmed.indexOf("{");
670
+ if (start === -1) return "";
671
+ return extractBracedContent(trimmed, start);
672
+ }
673
+ function extractArrayElementProperties(value) {
674
+ const trimmed = value.trim();
675
+ if (!trimmed.startsWith("[")) return [];
676
+ const objectStart = trimmed.indexOf("{");
677
+ if (objectStart === -1) return [];
678
+ const objectContent = extractBracedContent(trimmed, objectStart);
679
+ const props = [];
680
+ const allProps = parseTopLevelProperties(objectContent);
681
+ for (const prop of allProps) {
682
+ if (prop.kind === "data") {
683
+ props.push({ name: prop.name, typeHint: prop.typeHint });
684
+ }
685
+ }
686
+ return props;
687
+ }
688
+ function extractJsDocType(content, propIndex) {
689
+ const before = content.slice(Math.max(0, propIndex - 200), propIndex);
690
+ const jsdocMatch = before.match(/\/\*\*\s*@type\s*\{([^}]+)\}\s*\*\/\s*$/);
691
+ if (!jsdocMatch) return void 0;
692
+ const typeExpr = jsdocMatch[1].trim();
693
+ return normalizeJsDocType(typeExpr);
694
+ }
695
+ function normalizeJsDocType(typeExpr) {
696
+ const parts = typeExpr.split("|").map((p) => p.trim());
697
+ const normalized = parts.map((p) => {
698
+ const lower = p.toLowerCase();
699
+ if (lower === "string") return "string";
700
+ if (lower === "number") return "number";
701
+ if (lower === "boolean") return "boolean";
702
+ if (lower === "null") return "null";
703
+ if (lower === "undefined") return "null";
704
+ if (lower.endsWith("[]") || lower.startsWith("array")) return "array";
705
+ if (lower === "object") return "object";
706
+ return null;
707
+ }).filter((p) => p !== null);
708
+ if (normalized.length === 0) return void 0;
709
+ const unique = [...new Set(normalized)].sort();
710
+ return unique.join("|");
711
+ }
712
+ function isEscaped(text, i) {
713
+ let backslashCount = 0;
714
+ let j = i - 1;
715
+ while (j >= 0 && text[j] === "\\") {
716
+ backslashCount++;
717
+ j--;
718
+ }
719
+ return backslashCount % 2 === 1;
720
+ }
721
+ function inferTypeHint(valueStart) {
722
+ const v = valueStart.trim().replace(/,\s*$/, "");
723
+ if (/^-?\d+\.\d/.test(v)) return "number";
724
+ if (/^-?\d/.test(v)) return "number";
725
+ if (/^["'`]/.test(v)) return "string";
726
+ if (v === "true" || v === "false") return "boolean";
727
+ if (v === "null") return "null";
728
+ if (v.startsWith("[")) return "array";
729
+ if (v.startsWith("{")) return "object";
730
+ return void 0;
731
+ }
732
+
733
+ // src/service/statePathResolver.ts
734
+ function getStatePathsFromHtml(html, stateTagName = "wcs-state", fileReader) {
735
+ const elements = parseWcsStateElements(html, stateTagName);
736
+ const allPaths = [];
737
+ for (const element of elements) {
738
+ const paths = resolveElementPaths(element, html, fileReader);
739
+ allPaths.push(...paths);
740
+ }
741
+ return allPaths;
742
+ }
743
+ function resolveElementPaths(element, html, fileReader) {
744
+ if (element.stateAttr) {
745
+ const jsonContent = findScriptJsonById(html, element.stateAttr);
746
+ if (jsonContent) {
747
+ const paths = analyzeJsonPaths(jsonContent, element.stateName);
748
+ if (paths.length > 0) return paths;
749
+ }
750
+ }
751
+ if (element.srcAttr && fileReader) {
752
+ const paths = resolveSrcAttribute(element.srcAttr, element.stateName, fileReader);
753
+ if (paths.length > 0) return paths;
754
+ }
755
+ if (element.jsonAttr) {
756
+ const paths = analyzeJsonPaths(element.jsonAttr, element.stateName);
757
+ if (paths.length > 0) return paths;
758
+ }
759
+ if (element.scriptBlocks.length > 0) {
760
+ return element.scriptBlocks.flatMap(
761
+ (block) => analyzeStatePaths(block.content, block.stateName)
762
+ );
763
+ }
764
+ return [];
765
+ }
766
+ function resolveSrcAttribute(srcPath, stateName, fileReader) {
767
+ if (srcPath.endsWith(".json")) {
768
+ const content = fileReader(srcPath);
769
+ if (content) {
770
+ return analyzeJsonPaths(content, stateName);
771
+ }
772
+ return [];
773
+ }
774
+ if (srcPath.endsWith(".js")) {
775
+ const tsPath = srcPath.replace(/\.js$/, ".ts");
776
+ const tsContent = fileReader(tsPath);
777
+ if (tsContent) {
778
+ return analyzeStatePaths(tsContent, stateName);
779
+ }
780
+ const jsContent = fileReader(srcPath);
781
+ if (jsContent) {
782
+ return analyzeStatePaths(jsContent, stateName);
783
+ }
784
+ return [];
785
+ }
786
+ if (srcPath.endsWith(".ts")) {
787
+ const content = fileReader(srcPath);
788
+ if (content) {
789
+ return analyzeStatePaths(content, stateName);
790
+ }
791
+ return [];
792
+ }
793
+ return [];
794
+ }
795
+
796
+ // src/service/forContext.ts
797
+ function isInsideForTemplate(html, offset, bindAttrName = "data-wcs") {
798
+ const escaped = bindAttrName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
799
+ const openRegex = new RegExp(
800
+ `<template[^>]*${escaped}\\s*=\\s*["']\\s*for\\s*:`,
801
+ "gi"
802
+ );
803
+ const closeRegex = /<\/template\s*>/gi;
804
+ const opens = [];
805
+ let match;
806
+ while ((match = openRegex.exec(html)) !== null) {
807
+ if (match.index >= offset) break;
808
+ opens.push(match.index);
809
+ }
810
+ if (opens.length === 0) return false;
811
+ for (const openPos of opens) {
812
+ const depth = getForTemplateDepthAt(html, openPos, offset, bindAttrName);
813
+ if (depth > 0) return true;
814
+ }
815
+ return false;
816
+ }
817
+ function getInnermostForPath(html, offset, bindAttrName = "data-wcs") {
818
+ const escaped = bindAttrName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
819
+ const openRegex = new RegExp(
820
+ `<template[^>]*${escaped}\\s*=\\s*["']\\s*for\\s*:\\s*([^"']+?)\\s*["']`,
821
+ "gi"
822
+ );
823
+ let bestMatch = null;
824
+ let bestPos = -1;
825
+ let match;
826
+ while ((match = openRegex.exec(html)) !== null) {
827
+ if (match.index >= offset) break;
828
+ const tagEnd = html.indexOf(">", match.index);
829
+ if (tagEnd === -1 || tagEnd >= offset) continue;
830
+ const depth = getForTemplateDepthAt(html, match.index, offset, bindAttrName);
831
+ if (depth > 0 && match.index > bestPos) {
832
+ bestMatch = match[1].trim();
833
+ bestPos = match.index;
834
+ }
835
+ }
836
+ return bestMatch;
837
+ }
838
+ function getForTemplateDepthAt(html, openPos, offset, bindAttrName) {
839
+ const tagEnd = html.indexOf(">", openPos);
840
+ if (tagEnd === -1 || tagEnd >= offset) return 0;
841
+ let depth = 1;
842
+ let pos = tagEnd + 1;
843
+ const templateOpenRegex = /<template[\s>]/gi;
844
+ const templateCloseRegex = /<\/template\s*>/gi;
845
+ while (pos < offset && depth > 0) {
846
+ templateOpenRegex.lastIndex = pos;
847
+ templateCloseRegex.lastIndex = pos;
848
+ const nextOpen = templateOpenRegex.exec(html);
849
+ const nextClose = templateCloseRegex.exec(html);
850
+ const openIdx = nextOpen && nextOpen.index < offset ? nextOpen.index : Infinity;
851
+ const closeIdx = nextClose && nextClose.index < offset ? nextClose.index : Infinity;
852
+ if (openIdx === Infinity && closeIdx === Infinity) break;
853
+ if (openIdx < closeIdx) {
854
+ depth++;
855
+ pos = openIdx + 1;
856
+ } else {
857
+ depth--;
858
+ if (depth === 0 && closeIdx < offset) {
859
+ return 0;
860
+ }
861
+ pos = closeIdx + (nextClose ? nextClose[0].length : 1);
862
+ }
863
+ }
864
+ return depth;
865
+ }
866
+
867
+ // src/core/messages.ts
868
+ function resolveLocale(locale) {
869
+ if (locale === void 0 || locale === "" || /^ja\b|^ja[-_]/i.test(locale) || locale.toLowerCase() === "ja") return "ja";
870
+ return "en";
871
+ }
872
+ var JA_EXPECTED_LABEL = {
873
+ array: "\u914D\u5217\u578B\u306E\u30D1\u30B9",
874
+ boolean: "\u30D6\u30FC\u30EA\u30A2\u30F3\u578B",
875
+ string: "\u6587\u5B57\u5217\u578B"
876
+ };
877
+ var ja = {
878
+ spreadFilterNotAllowed: () => `\u30B9\u30D7\u30EC\u30C3\u30C9\u306E\u30BF\u30FC\u30B2\u30C3\u30C8\u306B\u30D5\u30A3\u30EB\u30BF\u306F\u4F7F\u7528\u3067\u304D\u307E\u305B\u3093`,
879
+ spreadTargetRequired: () => `\u30B9\u30D7\u30EC\u30C3\u30C9\u306B\u306F\u30BF\u30FC\u30B2\u30C3\u30C8\u30D1\u30B9\u304C\u5FC5\u8981\u3067\u3059`,
880
+ eventTokenUndeclared: (t) => `\u30A4\u30D9\u30F3\u30C8\u30C8\u30FC\u30AF\u30F3 "${t}" \u306F $eventTokens \u306B\u5BA3\u8A00\u3055\u308C\u3066\u3044\u307E\u305B\u3093`,
881
+ commandRhsFormat: () => `command \u30D0\u30A4\u30F3\u30C7\u30A3\u30F3\u30B0\u306E\u53F3\u8FBA\u306B\u306F $command.<name>\uFF08$commandTokens \u3067\u5BA3\u8A00\uFF09\u3092\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044`,
882
+ commandTokenUndeclared: (t) => `\u30B3\u30DE\u30F3\u30C9\u30C8\u30FC\u30AF\u30F3 "${t}" \u306F $commandTokens \u306B\u5BA3\u8A00\u3055\u308C\u3066\u3044\u307E\u305B\u3093`,
883
+ streamPathMissing: (p) => `\u30D1\u30B9 "${p}" \u306F $streams \u5BA3\u8A00\u306B\u5B58\u5728\u3057\u307E\u305B\u3093`,
884
+ pathMissing: (p) => `\u30D1\u30B9 "${p}" \u306F\u72B6\u614B\u5B9A\u7FA9\u306B\u5B58\u5728\u3057\u307E\u305B\u3093`,
885
+ expansionSuffix: (x) => `\uFF08\u5C55\u958B: ${x}\uFF09`,
886
+ patternPathOutsideFor: (p) => `\u30D1\u30BF\u30FC\u30F3\u30D1\u30B9 "${p}" \u306F <template for> \u306E\u5916\u5074\u3067\u306F\u4F7F\u7528\u3067\u304D\u307E\u305B\u3093`,
887
+ omittedPathOutsideFor: (p) => `\u7701\u7565\u30D1\u30B9 "${p}" \u306F <template for> \u306E\u5916\u5074\u3067\u306F\u4F7F\u7528\u3067\u304D\u307E\u305B\u3093`,
888
+ loopIndexOutsideFor: (p) => `\u30EB\u30FC\u30D7\u30A4\u30F3\u30C7\u30C3\u30AF\u30B9 "${p}" \u306F <template for> \u306E\u5916\u5074\u3067\u306F\u4F7F\u7528\u3067\u304D\u307E\u305B\u3093`,
889
+ resolvedPathInUi: (p) => `\u89E3\u6C7A\u6E08\u307F\u30D1\u30B9 "${p}" \u306F UI \u30D0\u30A4\u30F3\u30C7\u30A3\u30F3\u30B0\u3067\u306F\u4F7F\u7528\u3067\u304D\u307E\u305B\u3093\u3002\u30D1\u30BF\u30FC\u30F3\u30D1\u30B9\u3092\u4F7F\u7528\u3057\u3066\u304F\u3060\u3055\u3044`,
890
+ handlerFilterNotAllowed: (prop) => `\u30A4\u30D9\u30F3\u30C8\u30CF\u30F3\u30C9\u30E9 "${prop}" \u306B\u30D5\u30A3\u30EB\u30BF\u306F\u4F7F\u7528\u3067\u304D\u307E\u305B\u3093`,
891
+ typeExpectation: (label, expected, resultType) => `"${label}" \u306B\u306F${JA_EXPECTED_LABEL[expected]}\u304C\u5FC5\u8981\u3067\u3059\uFF08\u73FE\u5728\u306E\u578B: ${resultType}\uFF09`,
892
+ filterUnknown: (n) => `\u30D5\u30A3\u30EB\u30BF "${n}" \u306F\u7D44\u307F\u8FBC\u307F\u30D5\u30A3\u30EB\u30BF\u306B\u5B58\u5728\u3057\u307E\u305B\u3093`,
893
+ filterMinArgs: (n, min, c) => `\u30D5\u30A3\u30EB\u30BF "${n}" \u306B\u306F\u6700\u4F4E ${min} \u500B\u306E\u5F15\u6570\u304C\u5FC5\u8981\u3067\u3059\uFF08${c} \u500B\u6307\u5B9A\uFF09`,
894
+ filterMaxArgs: (n, max, c) => `\u30D5\u30A3\u30EB\u30BF "${n}" \u306E\u5F15\u6570\u306F\u6700\u5927 ${max} \u500B\u3067\u3059\uFF08${c} \u500B\u6307\u5B9A\uFF09`,
895
+ filterArgType: (n, i, exp, arg, act) => `\u30D5\u30A3\u30EB\u30BF "${n}" \u306E\u7B2C${i}\u5F15\u6570\u306F ${exp} \u578B\u304C\u5FC5\u8981\u3067\u3059\uFF08"${arg}" \u306F ${act} \u578B\uFF09`,
896
+ filterInputType: (n, accepts, cur) => `\u30D5\u30A3\u30EB\u30BF "${n}" \u306F ${accepts} \u578B\u306E\u5165\u529B\u304C\u5FC5\u8981\u3067\u3059\uFF08\u73FE\u5728\u306E\u578B: ${cur}\uFF09`,
897
+ wcsTextInfo: (e) => `wcs-text \u30D0\u30A4\u30F3\u30C7\u30A3\u30F3\u30B0: ${e}`,
898
+ moustacheFouc: (e) => `<template> \u5916\u306E {{ }} \u69CB\u6587\u306F FOUC\uFF08\u521D\u671F\u8868\u793A\u6642\u306B\u30C6\u30F3\u30D7\u30EC\u30FC\u30C8\u6587\u5B57\u5217\u304C\u898B\u3048\u308B\uFF09\u306E\u539F\u56E0\u306B\u306A\u308A\u307E\u3059\u3002<!--@@:${e}--> \u307E\u305F\u306F\u30B3\u30E1\u30F3\u30C8\u69CB\u6587\u306E\u4F7F\u7528\u3092\u691C\u8A0E\u3057\u3066\u304F\u3060\u3055\u3044\u3002`,
899
+ nestedAssign: (sp) => `\u30CD\u30B9\u30C8\u3055\u308C\u305F\u30D7\u30ED\u30D1\u30C6\u30A3\u3078\u306E\u4EE3\u5165\u306F\u30EA\u30A2\u30AF\u30C6\u30A3\u30D6\u66F4\u65B0\u3092\u30C8\u30EA\u30AC\u30FC\u3057\u307E\u305B\u3093\u3002this["${sp}"] \u3092\u4F7F\u7528\u3057\u3066\u304F\u3060\u3055\u3044\u3002`,
900
+ typeAnnotationIncompatible: (vt, rt) => `\u578B "${vt}" \u306F @type {${rt}} \u3068\u4E92\u63DB\u6027\u304C\u3042\u308A\u307E\u305B\u3093`,
901
+ tagMemberUnknown: (prop, tag) => `"${prop}" \u306F <${tag}> \u306E wcBindable \u30E1\u30F3\u30D0\u30FC\u3067\u306F\u3042\u308A\u307E\u305B\u3093\uFF08\u672A\u77E5\u30E1\u30F3\u30D0\u30FC\u3078\u306E\u30D0\u30A4\u30F3\u30C9\u306F\u9ED9\u3063\u3066\u7121\u8996\u3055\u308C\u307E\u3059\uFF09`,
902
+ tagCommandUnknown: (name, tag, declared) => `"${name}" \u306F <${tag}> \u306E command \u3067\u306F\u3042\u308A\u307E\u305B\u3093\uFF08\u5BA3\u8A00\u6E08\u307F: ${declared}\uFF09`,
903
+ tagEventTokenKeyUnknown: (name, tag, declared) => `eventToken \u306E\u30AD\u30FC "${name}" \u306F <${tag}> \u306E wcBindable \u30D7\u30ED\u30D1\u30C6\u30A3\u3067\u306F\u3042\u308A\u307E\u305B\u3093\u3002\u751F DOM \u30A4\u30D9\u30F3\u30C8\u540D\u306F\u767A\u706B\u3057\u307E\u305B\u3093 \u2014 \u30D7\u30ED\u30D1\u30C6\u30A3\u540D\u3092\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044\uFF08\u5BA3\u8A00\u6E08\u307F: ${declared}\uFF09`,
904
+ didYouMean: (c) => `\u3002\u3082\u3057\u304B\u3057\u3066: "${c}"`,
905
+ none: () => `\u306A\u3057`,
906
+ 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`,
907
+ 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 undefined \u3067\u30B7\u30FC\u30C9\uFF08\`${path}: undefined\`\uFF09\u3059\u308B\u304B manual \u3092\u4ED8\u3051\u3066\u304F\u3060\u3055\u3044`,
908
+ 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`,
909
+ 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`,
910
+ 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`
911
+ };
912
+ var EN_EXPECTED_LABEL = {
913
+ array: "an array-typed path",
914
+ boolean: "a boolean",
915
+ string: "a string"
916
+ };
917
+ var en = {
918
+ spreadFilterNotAllowed: () => `Filters cannot be applied to a spread target`,
919
+ spreadTargetRequired: () => `Spread requires a target path`,
920
+ eventTokenUndeclared: (t) => `Event token "${t}" is not declared in $eventTokens`,
921
+ commandRhsFormat: () => `The right side of a command binding must be $command.<name> (declared in $commandTokens)`,
922
+ commandTokenUndeclared: (t) => `Command token "${t}" is not declared in $commandTokens`,
923
+ streamPathMissing: (p) => `Path "${p}" does not exist in the $streams declaration`,
924
+ pathMissing: (p) => `Path "${p}" does not exist in the state definition`,
925
+ expansionSuffix: (x) => ` (expanded: ${x})`,
926
+ patternPathOutsideFor: (p) => `Pattern path "${p}" cannot be used outside a <template for>`,
927
+ omittedPathOutsideFor: (p) => `Shorthand path "${p}" cannot be used outside a <template for>`,
928
+ loopIndexOutsideFor: (p) => `Loop index "${p}" cannot be used outside a <template for>`,
929
+ resolvedPathInUi: (p) => `Resolved path "${p}" cannot be used in a UI binding. Use a pattern path instead`,
930
+ handlerFilterNotAllowed: (prop) => `Filters cannot be applied to event handler "${prop}"`,
931
+ typeExpectation: (label, expected, resultType) => `"${label}" requires ${EN_EXPECTED_LABEL[expected]} (current type: ${resultType})`,
932
+ filterUnknown: (n) => `Filter "${n}" is not a built-in filter`,
933
+ filterMinArgs: (n, min, c) => `Filter "${n}" requires at least ${min} argument(s) (${c} given)`,
934
+ filterMaxArgs: (n, max, c) => `Filter "${n}" accepts at most ${max} argument(s) (${c} given)`,
935
+ filterArgType: (n, i, exp, arg, act) => `Argument ${i} of filter "${n}" must be of type ${exp} ("${arg}" is ${act})`,
936
+ filterInputType: (n, accepts, cur) => `Filter "${n}" requires input of type ${accepts} (current type: ${cur})`,
937
+ wcsTextInfo: (e) => `wcs-text binding: ${e}`,
938
+ moustacheFouc: (e) => `{{ }} outside a <template> causes FOUC (the raw template string is visible before binding). Consider the comment syntax <!--@@:${e}--> instead.`,
939
+ nestedAssign: (sp) => `Assigning to a nested property does not trigger a reactive update. Use this["${sp}"] instead.`,
940
+ typeAnnotationIncompatible: (vt, rt) => `Type "${vt}" is not compatible with @type {${rt}}`,
941
+ tagMemberUnknown: (prop, tag) => `"${prop}" is not a wcBindable member of <${tag}> (bindings to unknown members are silently ignored)`,
942
+ tagCommandUnknown: (name, tag, declared) => `"${name}" is not a command of <${tag}> (declared: ${declared})`,
943
+ tagEventTokenKeyUnknown: (name, tag, declared) => `eventToken key "${name}" is not a wcBindable property of <${tag}>. Raw DOM event names never fire \u2014 use the property name (declared: ${declared})`,
944
+ didYouMean: (c) => `. Did you mean "${c}"?`,
945
+ none: () => `none`,
946
+ 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`,
947
+ storageSeedClobber: (path, raw) => `The <wcs-storage> value-bound slot "${path}" is seeded with ${raw}. The initial write-back overwrites the persisted value \u2014 seed it with undefined (\`${path}: undefined\`) or add manual`,
948
+ devtoolsAfterState: () => `Load @wcstack/devtools/auto BEFORE @wcstack/state/auto (otherwise the wiring ledger is not captured live)`,
949
+ baseHrefMissing: () => `An SPA using @wcstack/router needs <base href="/"> in <head> (without it, deep links misderive the basename)`,
950
+ 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`
951
+ };
952
+ var CATALOGS = { ja, en };
953
+ function getMessages(locale) {
954
+ return CATALOGS[resolveLocale(locale)];
955
+ }
956
+
957
+ // src/service/bindingValidator.ts
958
+ var filterMap = new Map(BUILTIN_FILTERS.map((f) => [f.name, f]));
959
+ function validateBindings(html, attrName, stateTagName = "wcs-state", locale) {
960
+ const diagnostics = [];
961
+ const msgs = getMessages(locale);
962
+ const statePaths = getStatePathsFromHtml(html, stateTagName);
963
+ const pathsByState = /* @__PURE__ */ new Map();
964
+ for (const p of statePaths) {
965
+ const list = pathsByState.get(p.stateName) ?? [];
966
+ list.push(p);
967
+ pathsByState.set(p.stateName, list);
968
+ }
969
+ const attrs = findAllBindAttributes(html, attrName);
970
+ const filterNameSet = new Set(BUILTIN_FILTERS.map((f) => f.name));
971
+ for (const attr of attrs) {
972
+ const bindings = splitBindingExpressions(attr.value);
973
+ let pos = 0;
974
+ for (const binding of bindings) {
975
+ const bindingStart = attr.valueStart + pos;
976
+ const parsed = parseBindingExpression(binding);
977
+ const scopedPaths = pathsByState.get(parsed.targetState) ?? [];
978
+ const scopedPathSet = new Set(scopedPaths.map((p) => p.path));
979
+ const propNoMod = parsed.property.replace(/#.*$/, "").trim();
980
+ if (propNoMod === "...") {
981
+ for (const filter of parsed.filters) {
982
+ diagnostics.push({
983
+ code: WcsDiagnosticCode.TemplateSyntax,
984
+ start: bindingStart + filter.offset,
985
+ end: bindingStart + filter.offset + filter.name.length,
986
+ message: msgs.spreadFilterNotAllowed(),
987
+ severity: "error"
988
+ });
989
+ }
990
+ if (!parsed.path || parsed.path.trim() === "") {
991
+ diagnostics.push({
992
+ code: WcsDiagnosticCode.TemplateSyntax,
993
+ start: bindingStart,
994
+ end: bindingStart + binding.length,
995
+ message: msgs.spreadTargetRequired(),
996
+ severity: "error"
997
+ });
998
+ }
999
+ }
1000
+ if (propNoMod.startsWith("eventToken.")) {
1001
+ const tokenNames = new Set(
1002
+ scopedPaths.filter((p) => p.kind === "eventToken").map((p) => p.path)
1003
+ );
1004
+ const tokenName = parsed.path?.trim() ?? "";
1005
+ if (tokenName && tokenNames.size > 0 && !tokenNames.has(tokenName)) {
1006
+ const pathOffset = binding.indexOf(parsed.path);
1007
+ const pathStart = bindingStart + pathOffset;
1008
+ diagnostics.push({
1009
+ code: WcsDiagnosticCode.TokenUndeclared,
1010
+ start: pathStart,
1011
+ end: pathStart + tokenName.length,
1012
+ message: msgs.eventTokenUndeclared(tokenName),
1013
+ severity: "warning"
1014
+ });
1015
+ }
1016
+ pos += binding.length + 1;
1017
+ continue;
1018
+ }
1019
+ const commandNames = new Set(
1020
+ scopedPaths.filter((p) => p.kind === "command").map((p) => p.path)
1021
+ );
1022
+ if (propNoMod.startsWith("command.")) {
1023
+ const tokenPath = parsed.path?.trim() ?? "";
1024
+ if (tokenPath) {
1025
+ const pathOffset = binding.indexOf(parsed.path);
1026
+ const pathStart = bindingStart + pathOffset;
1027
+ if (!tokenPath.startsWith("$command.")) {
1028
+ diagnostics.push({
1029
+ code: WcsDiagnosticCode.TokenMisconfigured,
1030
+ start: pathStart,
1031
+ end: pathStart + tokenPath.length,
1032
+ message: msgs.commandRhsFormat(),
1033
+ severity: "warning"
1034
+ });
1035
+ } else if (commandNames.size > 0 && !commandNames.has(tokenPath)) {
1036
+ diagnostics.push({
1037
+ code: WcsDiagnosticCode.TokenUndeclared,
1038
+ start: pathStart,
1039
+ end: pathStart + tokenPath.length,
1040
+ message: msgs.commandTokenUndeclared(tokenPath),
1041
+ severity: "warning"
1042
+ });
1043
+ }
1044
+ }
1045
+ pos += binding.length + 1;
1046
+ continue;
1047
+ }
1048
+ if (parsed.path && scopedPaths.length > 0) {
1049
+ const pathTrimmed = parsed.path.trim();
1050
+ if (pathTrimmed && !isLiteral(pathTrimmed)) {
1051
+ let checkPath = pathTrimmed;
1052
+ if (pathTrimmed.startsWith(".")) {
1053
+ const forPath = getInnermostForPath(html, attr.valueStart, attrName);
1054
+ if (forPath && !forPath.startsWith(".")) {
1055
+ checkPath = `${forPath}.*.${pathTrimmed.slice(1)}`;
1056
+ } else {
1057
+ checkPath = "";
1058
+ }
1059
+ }
1060
+ if (checkPath) {
1061
+ const message = validatePathExistence(checkPath, pathTrimmed, scopedPaths, scopedPathSet, commandNames, msgs);
1062
+ if (message) {
1063
+ const pathOffset = binding.indexOf(parsed.path);
1064
+ const pathStart = bindingStart + pathOffset;
1065
+ diagnostics.push({
1066
+ code: WcsDiagnosticCode.BindingPathMissing,
1067
+ start: pathStart,
1068
+ end: pathStart + pathTrimmed.length,
1069
+ message: `${message}${pathTrimmed.startsWith(".") ? msgs.expansionSuffix(checkPath) : ""}`,
1070
+ severity: "warning"
1071
+ });
1072
+ }
1073
+ }
1074
+ }
1075
+ }
1076
+ if (parsed.path) {
1077
+ const pathTrimmed = parsed.path.trim();
1078
+ const prop = parsed.property.replace(/#.*$/, "");
1079
+ const insideFor = isInsideForTemplate(html, attr.valueStart, attrName);
1080
+ if (pathTrimmed && !prop.startsWith("on")) {
1081
+ if (!insideFor && pathTrimmed.includes("*")) {
1082
+ const pathOffset = binding.indexOf(parsed.path);
1083
+ const pathStart = bindingStart + pathOffset;
1084
+ diagnostics.push({
1085
+ code: WcsDiagnosticCode.TemplateSyntax,
1086
+ start: pathStart,
1087
+ end: pathStart + pathTrimmed.length,
1088
+ message: msgs.patternPathOutsideFor(pathTrimmed),
1089
+ severity: "warning"
1090
+ });
1091
+ }
1092
+ if (!insideFor && pathTrimmed.startsWith(".")) {
1093
+ const pathOffset = binding.indexOf(parsed.path);
1094
+ const pathStart = bindingStart + pathOffset;
1095
+ diagnostics.push({
1096
+ code: WcsDiagnosticCode.TemplateSyntax,
1097
+ start: pathStart,
1098
+ end: pathStart + pathTrimmed.length,
1099
+ message: msgs.omittedPathOutsideFor(pathTrimmed),
1100
+ severity: "warning"
1101
+ });
1102
+ }
1103
+ if (!insideFor && /^\$\d+$/.test(pathTrimmed)) {
1104
+ const pathOffset = binding.indexOf(parsed.path);
1105
+ const pathStart = bindingStart + pathOffset;
1106
+ diagnostics.push({
1107
+ code: WcsDiagnosticCode.TemplateSyntax,
1108
+ start: pathStart,
1109
+ end: pathStart + pathTrimmed.length,
1110
+ message: msgs.loopIndexOutsideFor(pathTrimmed),
1111
+ severity: "warning"
1112
+ });
1113
+ }
1114
+ if (/\.\d+\.|\.\d+$/.test(pathTrimmed)) {
1115
+ const pathOffset = binding.indexOf(parsed.path);
1116
+ const pathStart = bindingStart + pathOffset;
1117
+ diagnostics.push({
1118
+ code: WcsDiagnosticCode.TemplateSyntax,
1119
+ start: pathStart,
1120
+ end: pathStart + pathTrimmed.length,
1121
+ message: msgs.resolvedPathInUi(pathTrimmed),
1122
+ severity: "warning"
1123
+ });
1124
+ }
1125
+ }
1126
+ }
1127
+ if (propNoMod === "...") {
1128
+ } else if (parsed.property.startsWith("on") && parsed.filters.length > 0) {
1129
+ for (const filter of parsed.filters) {
1130
+ diagnostics.push({
1131
+ code: WcsDiagnosticCode.TemplateSyntax,
1132
+ start: bindingStart + filter.offset,
1133
+ end: bindingStart + filter.offset + filter.name.length,
1134
+ message: msgs.handlerFilterNotAllowed(parsed.property),
1135
+ severity: "warning"
1136
+ });
1137
+ }
1138
+ } else {
1139
+ for (const filter of parsed.filters) {
1140
+ diagnostics.push(...validateFilterUsage(filter, bindingStart, msgs));
1141
+ }
1142
+ if (parsed.path && statePaths.length > 0) {
1143
+ const pathTrimmed = parsed.path.trim();
1144
+ if (pathTrimmed && !pathTrimmed.startsWith(".") && !isLiteral(pathTrimmed)) {
1145
+ const chainDiags = validateFilterChainTypes(
1146
+ pathTrimmed,
1147
+ parsed.filters,
1148
+ scopedPaths,
1149
+ bindingStart,
1150
+ msgs
1151
+ );
1152
+ diagnostics.push(...chainDiags);
1153
+ }
1154
+ }
1155
+ }
1156
+ for (const filter of parsed.inputFilters) {
1157
+ diagnostics.push(...validateFilterUsage(filter, bindingStart, msgs));
1158
+ }
1159
+ if (parsed.path && scopedPaths.length > 0) {
1160
+ const pathTrimmed = parsed.path.trim();
1161
+ if (pathTrimmed && !pathTrimmed.startsWith(".") && !isLiteral(pathTrimmed)) {
1162
+ const resultType = resolveResultType(pathTrimmed, parsed.filters, scopedPaths);
1163
+ if (resultType !== null) {
1164
+ const typeReq = getExpectedType(parsed.property);
1165
+ if (typeReq && resultType !== typeReq.expected) {
1166
+ const pathOffset = binding.indexOf(parsed.path);
1167
+ const pathStart = bindingStart + pathOffset;
1168
+ diagnostics.push({
1169
+ code: WcsDiagnosticCode.BindingTypeExpectation,
1170
+ start: pathStart,
1171
+ end: pathStart + pathTrimmed.length,
1172
+ message: msgs.typeExpectation(typeReq.label, typeReq.expected, resultType),
1173
+ severity: typeReq.severity
1174
+ });
1175
+ }
1176
+ }
1177
+ }
1178
+ }
1179
+ pos += binding.length + 1;
1180
+ }
1181
+ }
1182
+ return diagnostics;
1183
+ }
1184
+ function findAllBindAttributes(html, attrName) {
1185
+ const attrs = [];
1186
+ const escaped = attrName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1187
+ const regex = new RegExp(`${escaped}\\s*=\\s*(["'])`, "gi");
1188
+ let match;
1189
+ while ((match = regex.exec(html)) !== null) {
1190
+ const quote = match[1];
1191
+ const valueStart = match.index + match[0].length;
1192
+ const valueEnd = html.indexOf(quote, valueStart);
1193
+ if (valueEnd === -1) continue;
1194
+ attrs.push({
1195
+ value: html.slice(valueStart, valueEnd),
1196
+ valueStart
1197
+ });
1198
+ }
1199
+ return attrs;
1200
+ }
1201
+ function splitBindingExpressions(value) {
1202
+ const result = [];
1203
+ let current = "";
1204
+ let parenDepth = 0;
1205
+ for (const ch of value) {
1206
+ if (ch === "(") parenDepth++;
1207
+ else if (ch === ")") parenDepth = Math.max(0, parenDepth - 1);
1208
+ else if (ch === ";" && parenDepth === 0) {
1209
+ result.push(current);
1210
+ current = "";
1211
+ continue;
1212
+ }
1213
+ current += ch;
1214
+ }
1215
+ result.push(current);
1216
+ return result;
1217
+ }
1218
+ function parseBindingExpression(expr) {
1219
+ const colonIndex = expr.indexOf(":");
1220
+ if (colonIndex === -1) {
1221
+ return { property: expr.trim(), path: null, targetState: "default", filters: [], inputFilters: [] };
1222
+ }
1223
+ const rawProp = expr.slice(0, colonIndex);
1224
+ const propSegments = splitByPipe(rawProp);
1225
+ const property = propSegments[0].trim();
1226
+ const inputFilters = parseFilterSegments(expr, propSegments.slice(1), propSegments[0].length + 1);
1227
+ const afterColon = expr.slice(colonIndex + 1);
1228
+ const segments = splitByPipe(afterColon);
1229
+ const pathSegment = segments[0] || "";
1230
+ const filterSegments = segments.slice(1);
1231
+ const atIndex = pathSegment.indexOf("@");
1232
+ const path = atIndex !== -1 ? pathSegment.slice(0, atIndex) : pathSegment;
1233
+ const targetState = atIndex !== -1 ? pathSegment.slice(atIndex + 1).trim() || "default" : "default";
1234
+ const filters = parseFilterSegments(expr, filterSegments, colonIndex + 1 + pathSegment.length + 1);
1235
+ return { property, path: path.trim() || null, targetState, filters, inputFilters };
1236
+ }
1237
+ function parseFilterSegments(expr, segments, searchStart) {
1238
+ const filters = [];
1239
+ let filterSearchStart = searchStart;
1240
+ for (const seg of segments) {
1241
+ const trimmed = seg.trim();
1242
+ const filterMatch = trimmed.match(/^(\w+)(?:\(([^)]*)\))?/);
1243
+ if (filterMatch) {
1244
+ const nameOffset = expr.indexOf(trimmed, filterSearchStart);
1245
+ const args = filterMatch[2] !== void 0 ? filterMatch[2].split(",").map((a) => a.trim()).filter((a) => a !== "") : [];
1246
+ filters.push({
1247
+ name: filterMatch[1],
1248
+ offset: nameOffset >= 0 ? nameOffset : filterSearchStart,
1249
+ args,
1250
+ argsOffset: nameOffset >= 0 ? nameOffset + filterMatch[1].length : filterSearchStart
1251
+ });
1252
+ }
1253
+ filterSearchStart += seg.length + 1;
1254
+ }
1255
+ return filters;
1256
+ }
1257
+ function validateFilterUsage(filter, bindingStart, msgs) {
1258
+ const diagnostics = [];
1259
+ const info = filterMap.get(filter.name);
1260
+ if (!info) {
1261
+ diagnostics.push({
1262
+ code: WcsDiagnosticCode.FilterUnknown,
1263
+ start: bindingStart + filter.offset,
1264
+ end: bindingStart + filter.offset + filter.name.length,
1265
+ message: msgs.filterUnknown(filter.name),
1266
+ severity: "warning"
1267
+ });
1268
+ return diagnostics;
1269
+ }
1270
+ const argCount = filter.args.length;
1271
+ if (argCount < info.minArgs) {
1272
+ diagnostics.push({
1273
+ code: WcsDiagnosticCode.FilterArity,
1274
+ start: bindingStart + filter.offset,
1275
+ end: bindingStart + filter.offset + filter.name.length,
1276
+ message: msgs.filterMinArgs(filter.name, info.minArgs, argCount),
1277
+ severity: "error"
1278
+ });
1279
+ } else if (argCount > info.maxArgs) {
1280
+ diagnostics.push({
1281
+ code: WcsDiagnosticCode.FilterArity,
1282
+ start: bindingStart + filter.offset,
1283
+ end: bindingStart + filter.offset + filter.name.length,
1284
+ message: msgs.filterMaxArgs(filter.name, info.maxArgs, argCount),
1285
+ severity: "error"
1286
+ });
1287
+ }
1288
+ if (info.argTypes && argCount > 0) {
1289
+ for (let i = 0; i < Math.min(argCount, info.argTypes.length); i++) {
1290
+ const expectedArgType = info.argTypes[i];
1291
+ if (expectedArgType === "any") continue;
1292
+ const actualArgType = inferArgType(filter.args[i]);
1293
+ if (actualArgType !== expectedArgType) {
1294
+ diagnostics.push({
1295
+ code: WcsDiagnosticCode.FilterArgType,
1296
+ start: bindingStart + filter.argsOffset,
1297
+ end: bindingStart + filter.argsOffset + filter.name.length,
1298
+ message: msgs.filterArgType(filter.name, i + 1, expectedArgType, filter.args[i], actualArgType),
1299
+ severity: "warning"
1300
+ });
1301
+ }
1302
+ }
1303
+ }
1304
+ return diagnostics;
1305
+ }
1306
+ function splitByPipe(value) {
1307
+ const result = [];
1308
+ let current = "";
1309
+ let parenDepth = 0;
1310
+ for (const ch of value) {
1311
+ if (ch === "(") parenDepth++;
1312
+ else if (ch === ")") parenDepth = Math.max(0, parenDepth - 1);
1313
+ else if (ch === "|" && parenDepth === 0) {
1314
+ result.push(current);
1315
+ current = "";
1316
+ continue;
1317
+ }
1318
+ current += ch;
1319
+ }
1320
+ result.push(current);
1321
+ return result;
1322
+ }
1323
+ function validatePathExistence(checkPath, displayPath, scopedPaths, scopedPathSet, commandNames, msgs) {
1324
+ if (/^\$\d+$/.test(checkPath)) return null;
1325
+ if (checkPath.startsWith("$command.")) {
1326
+ if (commandNames.size > 0 && !commandNames.has(checkPath)) {
1327
+ return msgs.commandTokenUndeclared(displayPath);
1328
+ }
1329
+ return null;
1330
+ }
1331
+ if (checkPath.startsWith("$streamStatus.") || checkPath.startsWith("$streamError.")) {
1332
+ const prefix = checkPath.startsWith("$streamStatus.") ? "$streamStatus." : "$streamError.";
1333
+ const hasNamespace = scopedPaths.some((p) => p.path.startsWith(prefix));
1334
+ if (hasNamespace && !scopedPathSet.has(checkPath)) {
1335
+ return msgs.streamPathMissing(displayPath);
1336
+ }
1337
+ return null;
1338
+ }
1339
+ if (!scopedPathSet.has(checkPath)) {
1340
+ return msgs.pathMissing(displayPath);
1341
+ }
1342
+ return null;
1343
+ }
1344
+ function getExpectedType(property) {
1345
+ const prop = property.replace(/#.*$/, "");
1346
+ if (prop === "for") {
1347
+ return { label: "for", expected: "array", severity: "error" };
1348
+ }
1349
+ if (prop === "if" || prop === "elseif") {
1350
+ return { label: prop, expected: "boolean", severity: "warning" };
1351
+ }
1352
+ if (prop.startsWith("class.")) {
1353
+ return { label: prop, expected: "boolean", severity: "warning" };
1354
+ }
1355
+ if (prop.startsWith("attr.")) {
1356
+ return { label: prop, expected: "string", severity: "warning" };
1357
+ }
1358
+ if (prop.startsWith("style.")) {
1359
+ return { label: prop, expected: "string", severity: "warning" };
1360
+ }
1361
+ return null;
1362
+ }
1363
+ function validateFilterChainTypes(path, filters, statePaths, bindingStart, msgs) {
1364
+ const diagnostics = [];
1365
+ const pathInfo = statePaths.find((p) => p.path === path);
1366
+ if (!pathInfo?.typeHint) return diagnostics;
1367
+ let currentType = pathInfo.typeHint;
1368
+ for (const filter of filters) {
1369
+ const info = filterMap.get(filter.name);
1370
+ if (!info) break;
1371
+ if (info.acceptTypes !== "any") {
1372
+ const currentTypes = currentType.split("|");
1373
+ const hasMatch = currentTypes.some((t) => info.acceptTypes.includes(t));
1374
+ if (!hasMatch) {
1375
+ diagnostics.push({
1376
+ code: WcsDiagnosticCode.FilterInputType,
1377
+ start: bindingStart + filter.offset,
1378
+ end: bindingStart + filter.offset + filter.name.length,
1379
+ message: msgs.filterInputType(filter.name, info.acceptTypes.join("|"), currentType),
1380
+ severity: "warning"
1381
+ });
1382
+ }
1383
+ }
1384
+ if (info.resultType !== "passthrough") {
1385
+ currentType = info.resultType;
1386
+ }
1387
+ }
1388
+ return diagnostics;
1389
+ }
1390
+ function resolveResultType(path, filters, statePaths) {
1391
+ const pathInfo = statePaths.find((p) => p.path === path);
1392
+ if (!pathInfo?.typeHint) return null;
1393
+ let currentType = pathInfo.typeHint;
1394
+ for (const filter of filters) {
1395
+ const info = filterMap.get(filter.name);
1396
+ if (!info) return null;
1397
+ if (info.resultType === "passthrough") continue;
1398
+ currentType = info.resultType;
1399
+ }
1400
+ return currentType;
1401
+ }
1402
+ function inferArgType(arg) {
1403
+ const v = arg.trim();
1404
+ if (/^-?\d+(\.\d+)?$/.test(v)) return "number";
1405
+ return "string";
1406
+ }
1407
+ function isLiteral(value) {
1408
+ return /^-?\d/.test(value) || /^["'`]/.test(value) || value === "true" || value === "false" || value === "null";
1409
+ }
1410
+
1411
+ // src/service/stateTypeValidator.ts
1412
+ function validateStateTypes(html, stateTagName = "wcs-state", locale) {
1413
+ const msgs = getMessages(locale);
1414
+ const blocks = parseWcsScriptBlocks(html, stateTagName);
1415
+ const diagnostics = [];
1416
+ for (const block of blocks) {
1417
+ const props = findJsDocTypedProperties(block.content);
1418
+ for (const prop of props) {
1419
+ if (!isValueCompatible(prop.declaredTypes, prop.valueType)) {
1420
+ const absStart = block.contentStart + prop.valueOffset;
1421
+ const absEnd = absStart + prop.valueLength;
1422
+ diagnostics.push({
1423
+ start: absStart,
1424
+ end: absEnd,
1425
+ message: msgs.typeAnnotationIncompatible(prop.valueType, prop.rawType),
1426
+ severity: "warning"
1427
+ });
1428
+ }
1429
+ }
1430
+ }
1431
+ return diagnostics;
1432
+ }
1433
+ function findJsDocTypedProperties(script) {
1434
+ const results = [];
1435
+ const regex = /\/\*\*\s*@type\s*\{([^}]+)\}\s*\*\/\s*(?:"([^"]+)"|'([^']+)'|(\w+))\s*:\s*/g;
1436
+ let match;
1437
+ while ((match = regex.exec(script)) !== null) {
1438
+ const rawType = match[1].trim();
1439
+ const name = match[2] ?? match[3] ?? match[4];
1440
+ const valueStart = match.index + match[0].length;
1441
+ const valueText = extractValue(script, valueStart);
1442
+ const valueType = inferValueType(valueText);
1443
+ if (valueType) {
1444
+ const declaredTypes = rawType.split("|").map((t) => normalizeType(t.trim()));
1445
+ results.push({
1446
+ name,
1447
+ rawType,
1448
+ declaredTypes,
1449
+ valueType,
1450
+ valueOffset: valueStart,
1451
+ valueLength: valueText.length
1452
+ });
1453
+ }
1454
+ }
1455
+ return results;
1456
+ }
1457
+ function extractValue(script, start) {
1458
+ let depth = 0;
1459
+ let inString = null;
1460
+ let i = start;
1461
+ while (i < script.length) {
1462
+ const ch = script[i];
1463
+ if (inString) {
1464
+ if (ch === inString && script[i - 1] !== "\\") inString = null;
1465
+ } else if (ch === '"' || ch === "'" || ch === "`") {
1466
+ inString = ch;
1467
+ } else if (ch === "{" || ch === "[" || ch === "(") {
1468
+ depth++;
1469
+ } else if (ch === "}" || ch === "]" || ch === ")") {
1470
+ if (depth === 0) break;
1471
+ depth--;
1472
+ } else if ((ch === "," || ch === "\n") && depth === 0) {
1473
+ break;
1474
+ }
1475
+ i++;
1476
+ }
1477
+ return script.slice(start, i).trim();
1478
+ }
1479
+ function inferValueType(value) {
1480
+ const v = value.replace(/,\s*$/, "").trim();
1481
+ if (v === "null") return "null";
1482
+ if (v === "undefined") return "null";
1483
+ if (v === "true" || v === "false") return "boolean";
1484
+ if (/^-?\d+\.\d/.test(v)) return "number";
1485
+ if (/^-?\d/.test(v)) return "number";
1486
+ if (/^["'`]/.test(v)) return "string";
1487
+ if (v.startsWith("[")) return "array";
1488
+ if (v.startsWith("{")) return "object";
1489
+ return null;
1490
+ }
1491
+ function normalizeType(type) {
1492
+ const lower = type.toLowerCase();
1493
+ if (lower === "null" || lower === "undefined") return "null";
1494
+ if (lower === "string") return "string";
1495
+ if (lower === "number") return "number";
1496
+ if (lower === "boolean") return "boolean";
1497
+ if (lower.endsWith("[]") || lower.startsWith("array")) return "array";
1498
+ if (lower === "object") return "object";
1499
+ return type;
1500
+ }
1501
+ function isValueCompatible(declaredTypes, valueType) {
1502
+ return declaredTypes.includes(valueType);
1503
+ }
1504
+
1505
+ // src/service/nestedAssignValidator.ts
1506
+ function validateNestedAssigns(html, stateTagName = "wcs-state", locale) {
1507
+ const msgs = getMessages(locale);
1508
+ const blocks = parseWcsScriptBlocks(html, stateTagName);
1509
+ const diagnostics = [];
1510
+ for (const block of blocks) {
1511
+ const blockDiags = findNestedAssigns(block.content, block.contentStart, msgs);
1512
+ diagnostics.push(...blockDiags);
1513
+ }
1514
+ return diagnostics;
1515
+ }
1516
+ function findNestedAssigns(script, baseOffset, msgs = getMessages()) {
1517
+ const diagnostics = [];
1518
+ const regex = /\bthis\.(\w+)((?:\.\w+|\[\w+\])+)\s*=[^=]/g;
1519
+ let match;
1520
+ while ((match = regex.exec(script)) !== null) {
1521
+ const fullMatch = match[0];
1522
+ const topProp = match[1];
1523
+ const chainPart = match[2];
1524
+ if (topProp.startsWith("$")) continue;
1525
+ if (!/\.\w+/.test(chainPart)) continue;
1526
+ const assignStart = baseOffset + match.index;
1527
+ const assignEnd = assignStart + fullMatch.length - 1;
1528
+ const dotPath = topProp + chainPart.replace(/\[(\w+)\]/g, ".$1").replace(/^\./, "");
1529
+ const suggestedPath = topProp + chainPart.replace(/\[(\w+)\]/g, ".$1");
1530
+ diagnostics.push({
1531
+ start: assignStart,
1532
+ end: assignEnd,
1533
+ message: msgs.nestedAssign(suggestedPath),
1534
+ severity: "warning"
1535
+ });
1536
+ }
1537
+ return diagnostics;
1538
+ }
1539
+
1540
+ // src/service/templateSyntax.ts
1541
+ function findAllMustacheSyntax(html) {
1542
+ const results = [];
1543
+ const regex = /\{\{\s*(.+?)\s*\}\}/g;
1544
+ let match;
1545
+ while ((match = regex.exec(html)) !== null) {
1546
+ if (isInsideTag(html, match.index, "script") || isInsideTag(html, match.index, "style")) {
1547
+ continue;
1548
+ }
1549
+ const expr = match[1];
1550
+ const exprStart = match.index + match[0].indexOf(expr);
1551
+ results.push({
1552
+ kind: "mustache",
1553
+ expression: expr,
1554
+ exprStart,
1555
+ exprEnd: exprStart + expr.length,
1556
+ matchStart: match.index,
1557
+ matchEnd: match.index + match[0].length,
1558
+ insideTemplate: isInsideTag(html, match.index, "template")
1559
+ });
1560
+ }
1561
+ return results;
1562
+ }
1563
+ function findAllCommentBindings(html, commentTextPrefix = "wcs-text") {
1564
+ const results = [];
1565
+ const escaped = commentTextPrefix.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1566
+ const regex = new RegExp(`<!--\\s*@@\\s*(?:${escaped})?\\s*:\\s*(.+?)\\s*-->`, "g");
1567
+ let match;
1568
+ while ((match = regex.exec(html)) !== null) {
1569
+ const expr = match[1];
1570
+ if (!expr) continue;
1571
+ const exprStart = match.index + match[0].indexOf(expr);
1572
+ results.push({
1573
+ kind: "comment",
1574
+ expression: expr,
1575
+ exprStart,
1576
+ exprEnd: exprStart + expr.length,
1577
+ matchStart: match.index,
1578
+ matchEnd: match.index + match[0].length,
1579
+ insideTemplate: isInsideTag(html, match.index, "template")
1580
+ });
1581
+ }
1582
+ return results;
1583
+ }
1584
+ function isInsideTag(html, offset, tagName) {
1585
+ const openRegex = new RegExp(`<${tagName}[\\s>]`, "gi");
1586
+ const closeRegex = new RegExp(`</${tagName}>`, "gi");
1587
+ let lastOpenEnd = -1;
1588
+ let lastCloseEnd = -1;
1589
+ let match;
1590
+ while ((match = openRegex.exec(html)) !== null) {
1591
+ if (match.index > offset) break;
1592
+ lastOpenEnd = match.index;
1593
+ }
1594
+ while ((match = closeRegex.exec(html)) !== null) {
1595
+ if (match.index > offset) break;
1596
+ lastCloseEnd = match.index;
1597
+ }
1598
+ return lastOpenEnd > lastCloseEnd;
1599
+ }
1600
+
1601
+ // src/service/templateSyntaxValidator.ts
1602
+ function validateTemplateSyntax(html, stateTagName, bindAttrName = "data-wcs", locale) {
1603
+ const diagnostics = [];
1604
+ const msgs = getMessages(locale);
1605
+ const allPaths = getStatePathsFromHtml(html, stateTagName);
1606
+ if (allPaths.length === 0) return diagnostics;
1607
+ const defaultPaths = allPaths.filter((p) => p.stateName === "default");
1608
+ const pathSet = new Set(defaultPaths.map((p) => p.path));
1609
+ const filterNameSet = new Set(BUILTIN_FILTERS.map((f) => f.name));
1610
+ const mustaches = findAllMustacheSyntax(html);
1611
+ const comments = findAllCommentBindings(html);
1612
+ for (const item of [...mustaches, ...comments]) {
1613
+ if (item.kind === "comment") {
1614
+ diagnostics.push({
1615
+ code: WcsDiagnosticCode.TemplateSyntax,
1616
+ start: item.matchStart,
1617
+ end: item.matchEnd,
1618
+ message: msgs.wcsTextInfo(item.expression),
1619
+ severity: "info"
1620
+ });
1621
+ }
1622
+ if (item.kind === "mustache" && !item.insideTemplate) {
1623
+ diagnostics.push({
1624
+ code: WcsDiagnosticCode.TemplateSyntax,
1625
+ start: item.matchStart,
1626
+ end: item.matchEnd,
1627
+ message: msgs.moustacheFouc(item.expression),
1628
+ severity: "info"
1629
+ });
1630
+ }
1631
+ if (!item.expression) continue;
1632
+ const parts = item.expression.split("|");
1633
+ let pathPart = (parts[0] || "").trim();
1634
+ const atIdx = pathPart.indexOf("@");
1635
+ if (atIdx !== -1) pathPart = pathPart.slice(0, atIdx).trim();
1636
+ const insideFor = item.insideTemplate && isInsideForTemplate(html, item.matchStart, bindAttrName);
1637
+ if (pathPart && !/^-?\d|^["'`]|^true$|^false$|^null$/.test(pathPart)) {
1638
+ if (!insideFor && pathPart.includes("*")) {
1639
+ diagnostics.push({
1640
+ code: WcsDiagnosticCode.TemplateSyntax,
1641
+ start: item.exprStart,
1642
+ end: item.exprStart + pathPart.length,
1643
+ message: msgs.patternPathOutsideFor(pathPart),
1644
+ severity: "warning"
1645
+ });
1646
+ }
1647
+ if (!insideFor && pathPart.startsWith(".")) {
1648
+ diagnostics.push({
1649
+ code: WcsDiagnosticCode.TemplateSyntax,
1650
+ start: item.exprStart,
1651
+ end: item.exprStart + pathPart.length,
1652
+ message: msgs.omittedPathOutsideFor(pathPart),
1653
+ severity: "warning"
1654
+ });
1655
+ }
1656
+ if (/\.\d+\.|\.\d+$/.test(pathPart)) {
1657
+ diagnostics.push({
1658
+ code: WcsDiagnosticCode.TemplateSyntax,
1659
+ start: item.exprStart,
1660
+ end: item.exprStart + pathPart.length,
1661
+ message: msgs.resolvedPathInUi(pathPart),
1662
+ severity: "warning"
1663
+ });
1664
+ }
1665
+ if (pathPart.startsWith(".")) {
1666
+ const forPath = insideFor ? getInnermostForPath(html, item.matchStart, bindAttrName) : null;
1667
+ if (forPath && !forPath.startsWith(".")) {
1668
+ const expandedPath = `${forPath}.*.${pathPart.slice(1)}`;
1669
+ if (!isValidTemplatePath(expandedPath, pathSet, defaultPaths)) {
1670
+ diagnostics.push({
1671
+ code: WcsDiagnosticCode.BindingPathMissing,
1672
+ start: item.exprStart,
1673
+ end: item.exprStart + pathPart.length,
1674
+ message: msgs.pathMissing(pathPart) + msgs.expansionSuffix(expandedPath),
1675
+ severity: "warning"
1676
+ });
1677
+ }
1678
+ }
1679
+ } else if (!isValidTemplatePath(pathPart, pathSet, defaultPaths)) {
1680
+ diagnostics.push({
1681
+ code: WcsDiagnosticCode.BindingPathMissing,
1682
+ start: item.exprStart,
1683
+ end: item.exprStart + pathPart.length,
1684
+ message: msgs.pathMissing(pathPart),
1685
+ severity: "warning"
1686
+ });
1687
+ }
1688
+ }
1689
+ for (let i = 1; i < parts.length; i++) {
1690
+ const filterName = parts[i].trim().replace(/\(.*$/, "");
1691
+ if (filterName && !filterNameSet.has(filterName)) {
1692
+ const filterOffset = item.expression.indexOf(parts[i]);
1693
+ diagnostics.push({
1694
+ code: WcsDiagnosticCode.FilterUnknown,
1695
+ start: item.exprStart + filterOffset,
1696
+ end: item.exprStart + filterOffset + filterName.length,
1697
+ message: msgs.filterUnknown(filterName),
1698
+ severity: "warning"
1699
+ });
1700
+ }
1701
+ }
1702
+ }
1703
+ return diagnostics;
1704
+ }
1705
+ function isValidTemplatePath(path, pathSet, scopedPaths) {
1706
+ if (/^\$\d+$/.test(path)) return true;
1707
+ if (path.startsWith("$streamStatus.") || path.startsWith("$streamError.")) {
1708
+ const prefix = path.startsWith("$streamStatus.") ? "$streamStatus." : "$streamError.";
1709
+ const hasNamespace = scopedPaths.some((p) => p.path.startsWith(prefix));
1710
+ return !hasNamespace || pathSet.has(path);
1711
+ }
1712
+ return pathSet.has(path);
1713
+ }
1714
+
1715
+ // src/service/generated/builtinTags.generated.ts
1716
+ var BUILTIN_TAGS = {
1717
+ "wcs-accelerometer": {
1718
+ "package": "accelerometer",
1719
+ "inputs": {
1720
+ "frequency": null
1721
+ },
1722
+ "properties": [
1723
+ "x",
1724
+ "y",
1725
+ "z",
1726
+ "error",
1727
+ "errorInfo"
1728
+ ],
1729
+ "commands": [
1730
+ "start",
1731
+ "stop"
1732
+ ]
1733
+ },
1734
+ "wcs-ambient-light-sensor": {
1735
+ "package": "ambient-light-sensor",
1736
+ "inputs": {
1737
+ "frequency": null
1738
+ },
1739
+ "properties": [
1740
+ "illuminance",
1741
+ "error",
1742
+ "errorInfo"
1743
+ ],
1744
+ "commands": [
1745
+ "start",
1746
+ "stop"
1747
+ ]
1748
+ },
1749
+ "wcs-broadcast": {
1750
+ "package": "broadcast",
1751
+ "inputs": {
1752
+ "name": "name",
1753
+ "manual": "manual"
1754
+ },
1755
+ "properties": [
1756
+ "message",
1757
+ "error",
1758
+ "errorInfo"
1759
+ ],
1760
+ "commands": [
1761
+ "open",
1762
+ "post",
1763
+ "close"
1764
+ ]
1765
+ },
1766
+ "wcs-camera": {
1767
+ "package": "camera",
1768
+ "inputs": {
1769
+ "audio": "audio",
1770
+ "facingMode": "facing-mode",
1771
+ "deviceId": "device-id",
1772
+ "width": "width",
1773
+ "height": "height",
1774
+ "autostart": "autostart",
1775
+ "keepAlive": "keep-alive"
1776
+ },
1777
+ "properties": [
1778
+ "active",
1779
+ "permission",
1780
+ "audioPermission",
1781
+ "deviceId",
1782
+ "devices",
1783
+ "error",
1784
+ "errorInfo",
1785
+ "streamReady",
1786
+ "ended"
1787
+ ],
1788
+ "commands": [
1789
+ "start",
1790
+ "stop",
1791
+ "switchCamera"
1792
+ ]
1793
+ },
1794
+ "wcs-recorder": {
1795
+ "package": "camera",
1796
+ "inputs": {
1797
+ "mimeType": "mime-type",
1798
+ "timeslice": "timeslice",
1799
+ "audioBitsPerSecond": "audio-bits",
1800
+ "videoBitsPerSecond": "video-bits"
1801
+ },
1802
+ "properties": [
1803
+ "recording",
1804
+ "paused",
1805
+ "duration",
1806
+ "mimeType",
1807
+ "blob",
1808
+ "objectURL",
1809
+ "error",
1810
+ "errorInfo",
1811
+ "recorded",
1812
+ "dataavailable"
1813
+ ],
1814
+ "commands": [
1815
+ "attachStream",
1816
+ "start",
1817
+ "stop",
1818
+ "pause",
1819
+ "resume"
1820
+ ]
1821
+ },
1822
+ "wcs-clipboard": {
1823
+ "package": "clipboard",
1824
+ "inputs": {
1825
+ "monitor": "monitor"
1826
+ },
1827
+ "properties": [
1828
+ "text",
1829
+ "items",
1830
+ "loading",
1831
+ "error",
1832
+ "readPermission",
1833
+ "writePermission",
1834
+ "monitoring",
1835
+ "errorInfo",
1836
+ "copied",
1837
+ "cut",
1838
+ "pasted"
1839
+ ],
1840
+ "commands": [
1841
+ "writeText",
1842
+ "write",
1843
+ "readText",
1844
+ "read",
1845
+ "startMonitor",
1846
+ "stopMonitor"
1847
+ ]
1848
+ },
1849
+ "wcs-contacts": {
1850
+ "package": "contacts",
1851
+ "inputs": {},
1852
+ "properties": [
1853
+ "value",
1854
+ "loading",
1855
+ "error",
1856
+ "cancelled",
1857
+ "errorInfo"
1858
+ ],
1859
+ "commands": [
1860
+ "select"
1861
+ ]
1862
+ },
1863
+ "wcs-credential": {
1864
+ "package": "credential",
1865
+ "inputs": {},
1866
+ "properties": [
1867
+ "value",
1868
+ "loading",
1869
+ "error",
1870
+ "cancelled",
1871
+ "errorInfo"
1872
+ ],
1873
+ "commands": [
1874
+ "get",
1875
+ "store"
1876
+ ]
1877
+ },
1878
+ "wcs-debounce": {
1879
+ "package": "debounce",
1880
+ "inputs": {
1881
+ "source": null,
1882
+ "wait": "wait",
1883
+ "leading": null,
1884
+ "trailing": null,
1885
+ "maxWait": "max-wait"
1886
+ },
1887
+ "properties": [
1888
+ "value",
1889
+ "fired",
1890
+ "pending"
1891
+ ],
1892
+ "commands": [
1893
+ "trigger",
1894
+ "cancel",
1895
+ "flush"
1896
+ ]
1897
+ },
1898
+ "wcs-throttle": {
1899
+ "package": "debounce",
1900
+ "inputs": {
1901
+ "source": null,
1902
+ "wait": "wait",
1903
+ "leading": null,
1904
+ "trailing": null,
1905
+ "maxWait": "max-wait"
1906
+ },
1907
+ "properties": [
1908
+ "value",
1909
+ "fired",
1910
+ "pending"
1911
+ ],
1912
+ "commands": [
1913
+ "trigger",
1914
+ "cancel",
1915
+ "flush"
1916
+ ]
1917
+ },
1918
+ "wcs-defined": {
1919
+ "package": "defined",
1920
+ "inputs": {
1921
+ "tags": "tags",
1922
+ "mode": "mode",
1923
+ "timeout": "timeout"
1924
+ },
1925
+ "properties": [
1926
+ "defined",
1927
+ "pending",
1928
+ "missing",
1929
+ "count",
1930
+ "total",
1931
+ "error"
1932
+ ],
1933
+ "commands": []
1934
+ },
1935
+ "wcs-eyedropper": {
1936
+ "package": "eyedropper",
1937
+ "inputs": {},
1938
+ "properties": [
1939
+ "value",
1940
+ "loading",
1941
+ "error",
1942
+ "cancelled",
1943
+ "errorInfo"
1944
+ ],
1945
+ "commands": [
1946
+ "open",
1947
+ "abort"
1948
+ ]
1949
+ },
1950
+ "wcs-fetch": {
1951
+ "package": "fetch",
1952
+ "inputs": {
1953
+ "url": null,
1954
+ "method": null,
1955
+ "target": null,
1956
+ "manual": null,
1957
+ "body": null,
1958
+ "responseType": null,
1959
+ "trigger": null
1960
+ },
1961
+ "properties": [
1962
+ "value",
1963
+ "loading",
1964
+ "error",
1965
+ "status",
1966
+ "objectURL",
1967
+ "errorInfo",
1968
+ "trigger"
1969
+ ],
1970
+ "commands": [
1971
+ "fetch",
1972
+ "abort"
1973
+ ]
1974
+ },
1975
+ "wcs-fetch-header": {
1976
+ "package": "fetch",
1977
+ "inputs": {},
1978
+ "properties": [],
1979
+ "commands": []
1980
+ },
1981
+ "wcs-fetch-body": {
1982
+ "package": "fetch",
1983
+ "inputs": {},
1984
+ "properties": [],
1985
+ "commands": []
1986
+ },
1987
+ "wcs-infinite-scroll": {
1988
+ "package": "fetch",
1989
+ "inputs": {},
1990
+ "properties": [],
1991
+ "commands": []
1992
+ },
1993
+ "wcs-fullscreen": {
1994
+ "package": "fullscreen",
1995
+ "inputs": {
1996
+ "target": "target"
1997
+ },
1998
+ "properties": [
1999
+ "active",
2000
+ "error",
2001
+ "errorInfo"
2002
+ ],
2003
+ "commands": [
2004
+ "requestFullscreen",
2005
+ "exitFullscreen"
2006
+ ]
2007
+ },
2008
+ "wcs-geo": {
2009
+ "package": "geolocation",
2010
+ "inputs": {
2011
+ "highAccuracy": "high-accuracy",
2012
+ "timeout": "timeout",
2013
+ "maximumAge": "maximum-age",
2014
+ "watch": "watch",
2015
+ "manual": "manual",
2016
+ "trigger": null
2017
+ },
2018
+ "properties": [
2019
+ "position",
2020
+ "latitude",
2021
+ "longitude",
2022
+ "accuracy",
2023
+ "coords",
2024
+ "timestamp",
2025
+ "watching",
2026
+ "loading",
2027
+ "error",
2028
+ "permission",
2029
+ "errorInfo",
2030
+ "trigger"
2031
+ ],
2032
+ "commands": [
2033
+ "getCurrentPosition",
2034
+ "watchPosition",
2035
+ "clearWatch"
2036
+ ]
2037
+ },
2038
+ "wcs-gyroscope": {
2039
+ "package": "gyroscope",
2040
+ "inputs": {
2041
+ "frequency": null
2042
+ },
2043
+ "properties": [
2044
+ "x",
2045
+ "y",
2046
+ "z",
2047
+ "error",
2048
+ "errorInfo"
2049
+ ],
2050
+ "commands": [
2051
+ "start",
2052
+ "stop"
2053
+ ]
2054
+ },
2055
+ "wcs-idle": {
2056
+ "package": "idle",
2057
+ "inputs": {
2058
+ "threshold": "threshold"
2059
+ },
2060
+ "properties": [
2061
+ "userState",
2062
+ "screenState",
2063
+ "active",
2064
+ "error",
2065
+ "errorInfo"
2066
+ ],
2067
+ "commands": [
2068
+ "requestPermission",
2069
+ "start",
2070
+ "stop"
2071
+ ]
2072
+ },
2073
+ "wcs-intersect": {
2074
+ "package": "intersection",
2075
+ "inputs": {
2076
+ "target": "target",
2077
+ "root": "root",
2078
+ "rootMargin": "root-margin",
2079
+ "threshold": "threshold",
2080
+ "once": "once",
2081
+ "manual": "manual",
2082
+ "trigger": null
2083
+ },
2084
+ "properties": [
2085
+ "entry",
2086
+ "intersecting",
2087
+ "ratio",
2088
+ "visible",
2089
+ "observing",
2090
+ "trigger"
2091
+ ],
2092
+ "commands": [
2093
+ "observe",
2094
+ "reobserve",
2095
+ "unobserve",
2096
+ "disconnect",
2097
+ "reset"
2098
+ ]
2099
+ },
2100
+ "wcs-magnetometer": {
2101
+ "package": "magnetometer",
2102
+ "inputs": {
2103
+ "frequency": null
2104
+ },
2105
+ "properties": [
2106
+ "x",
2107
+ "y",
2108
+ "z",
2109
+ "error",
2110
+ "errorInfo"
2111
+ ],
2112
+ "commands": [
2113
+ "start",
2114
+ "stop"
2115
+ ]
2116
+ },
2117
+ "wcs-network": {
2118
+ "package": "network",
2119
+ "inputs": {},
2120
+ "properties": [
2121
+ "effectiveType",
2122
+ "downlink",
2123
+ "rtt",
2124
+ "saveData",
2125
+ "supported"
2126
+ ],
2127
+ "commands": []
2128
+ },
2129
+ "wcs-notify": {
2130
+ "package": "notification",
2131
+ "inputs": {
2132
+ "notice": null,
2133
+ "mode": "mode",
2134
+ "body": "body",
2135
+ "icon": "icon",
2136
+ "badge": "badge",
2137
+ "tag": "tag",
2138
+ "lang": "lang",
2139
+ "dir": "dir",
2140
+ "requireInteraction": "require-interaction",
2141
+ "silent": "silent",
2142
+ "renotify": "renotify",
2143
+ "manual": "manual"
2144
+ },
2145
+ "properties": [
2146
+ "permission",
2147
+ "granted",
2148
+ "denied",
2149
+ "prompt",
2150
+ "unsupported",
2151
+ "error",
2152
+ "errorInfo",
2153
+ "clicked",
2154
+ "closed",
2155
+ "shown"
2156
+ ],
2157
+ "commands": [
2158
+ "request",
2159
+ "notify",
2160
+ "close",
2161
+ "closeAll"
2162
+ ]
2163
+ },
2164
+ "wcs-permission": {
2165
+ "package": "permission",
2166
+ "inputs": {
2167
+ "name": "name",
2168
+ "userVisibleOnly": "user-visible-only",
2169
+ "sysex": "sysex"
2170
+ },
2171
+ "properties": [
2172
+ "state",
2173
+ "granted",
2174
+ "denied",
2175
+ "prompt",
2176
+ "unsupported"
2177
+ ],
2178
+ "commands": []
2179
+ },
2180
+ "wcs-pip": {
2181
+ "package": "picture-in-picture",
2182
+ "inputs": {
2183
+ "target": "target"
2184
+ },
2185
+ "properties": [
2186
+ "active",
2187
+ "error",
2188
+ "errorInfo"
2189
+ ],
2190
+ "commands": [
2191
+ "requestPictureInPicture",
2192
+ "exitPictureInPicture"
2193
+ ]
2194
+ },
2195
+ "wcs-pointer-lock": {
2196
+ "package": "pointer-lock",
2197
+ "inputs": {
2198
+ "target": "target"
2199
+ },
2200
+ "properties": [
2201
+ "active",
2202
+ "error",
2203
+ "errorInfo"
2204
+ ],
2205
+ "commands": [
2206
+ "requestPointerLock",
2207
+ "exitPointerLock"
2208
+ ]
2209
+ },
2210
+ "wcs-raf": {
2211
+ "package": "raf",
2212
+ "inputs": {
2213
+ "once": "once",
2214
+ "repeat": "repeat",
2215
+ "manual": "manual",
2216
+ "trigger": null
2217
+ },
2218
+ "properties": [
2219
+ "tick",
2220
+ "elapsed",
2221
+ "dt",
2222
+ "running",
2223
+ "suspended",
2224
+ "trigger"
2225
+ ],
2226
+ "commands": [
2227
+ "start",
2228
+ "stop",
2229
+ "reset",
2230
+ "pause",
2231
+ "resume"
2232
+ ]
2233
+ },
2234
+ "wcs-resize": {
2235
+ "package": "resize",
2236
+ "inputs": {
2237
+ "target": "target",
2238
+ "box": "box",
2239
+ "round": "round",
2240
+ "once": "once",
2241
+ "manual": "manual",
2242
+ "trigger": null
2243
+ },
2244
+ "properties": [
2245
+ "entry",
2246
+ "width",
2247
+ "height",
2248
+ "observing",
2249
+ "trigger"
2250
+ ],
2251
+ "commands": [
2252
+ "observe",
2253
+ "unobserve",
2254
+ "disconnect"
2255
+ ]
2256
+ },
2257
+ "wcs-screen-orientation": {
2258
+ "package": "screen-orientation",
2259
+ "inputs": {},
2260
+ "properties": [
2261
+ "type",
2262
+ "angle",
2263
+ "portrait",
2264
+ "landscape",
2265
+ "error",
2266
+ "errorInfo"
2267
+ ],
2268
+ "commands": [
2269
+ "lock",
2270
+ "unlock"
2271
+ ]
2272
+ },
2273
+ "wcs-share": {
2274
+ "package": "share",
2275
+ "inputs": {},
2276
+ "properties": [
2277
+ "value",
2278
+ "loading",
2279
+ "error",
2280
+ "cancelled",
2281
+ "errorInfo"
2282
+ ],
2283
+ "commands": [
2284
+ "share"
2285
+ ]
2286
+ },
2287
+ "wcs-speak": {
2288
+ "package": "speech",
2289
+ "inputs": {
2290
+ "say": null,
2291
+ "rate": "rate",
2292
+ "pitch": "pitch",
2293
+ "volume": "volume",
2294
+ "voice": "voice",
2295
+ "lang": "lang",
2296
+ "manual": "manual"
2297
+ },
2298
+ "properties": [
2299
+ "voices",
2300
+ "speaking",
2301
+ "paused",
2302
+ "pending",
2303
+ "charIndex",
2304
+ "spokenWord",
2305
+ "error",
2306
+ "errorInfo",
2307
+ "unsupported"
2308
+ ],
2309
+ "commands": [
2310
+ "speak",
2311
+ "cancel",
2312
+ "pause",
2313
+ "resume"
2314
+ ]
2315
+ },
2316
+ "wcs-listen": {
2317
+ "package": "speech",
2318
+ "inputs": {
2319
+ "lang": "lang",
2320
+ "continuous": "continuous",
2321
+ "interim": "interim",
2322
+ "maxRestarts": "max-restarts",
2323
+ "manual": "manual",
2324
+ "trigger": null
2325
+ },
2326
+ "properties": [
2327
+ "interimTranscript",
2328
+ "finalTranscript",
2329
+ "result",
2330
+ "listening",
2331
+ "permission",
2332
+ "error",
2333
+ "errorInfo",
2334
+ "unsupported",
2335
+ "trigger"
2336
+ ],
2337
+ "commands": [
2338
+ "start",
2339
+ "stop",
2340
+ "abort"
2341
+ ]
2342
+ },
2343
+ "wcs-sse": {
2344
+ "package": "sse",
2345
+ "inputs": {
2346
+ "url": "url",
2347
+ "withCredentials": "with-credentials",
2348
+ "events": "events",
2349
+ "raw": "raw",
2350
+ "manual": "manual",
2351
+ "trigger": null
2352
+ },
2353
+ "properties": [
2354
+ "message",
2355
+ "connected",
2356
+ "loading",
2357
+ "error",
2358
+ "errorInfo",
2359
+ "readyState",
2360
+ "trigger"
2361
+ ],
2362
+ "commands": [
2363
+ "connect",
2364
+ "close"
2365
+ ]
2366
+ },
2367
+ "wcs-storage": {
2368
+ "package": "storage",
2369
+ "inputs": {
2370
+ "key": null,
2371
+ "type": null,
2372
+ "value": null,
2373
+ "manual": null,
2374
+ "trigger": null
2375
+ },
2376
+ "properties": [
2377
+ "value",
2378
+ "loading",
2379
+ "error",
2380
+ "errorInfo",
2381
+ "trigger"
2382
+ ],
2383
+ "commands": [
2384
+ "load",
2385
+ "save",
2386
+ "remove"
2387
+ ]
2388
+ },
2389
+ "wcs-tilt": {
2390
+ "package": "tilt",
2391
+ "inputs": {},
2392
+ "properties": [
2393
+ "alpha",
2394
+ "beta",
2395
+ "gamma",
2396
+ "absolute",
2397
+ "permissionState",
2398
+ "error",
2399
+ "errorInfo"
2400
+ ],
2401
+ "commands": [
2402
+ "requestPermission",
2403
+ "start",
2404
+ "stop"
2405
+ ]
2406
+ },
2407
+ "wcs-timer": {
2408
+ "package": "timer",
2409
+ "inputs": {
2410
+ "interval": "interval",
2411
+ "once": "once",
2412
+ "repeat": "repeat",
2413
+ "immediate": "immediate",
2414
+ "manual": "manual",
2415
+ "trigger": null
2416
+ },
2417
+ "properties": [
2418
+ "tick",
2419
+ "elapsed",
2420
+ "running",
2421
+ "trigger"
2422
+ ],
2423
+ "commands": [
2424
+ "start",
2425
+ "stop",
2426
+ "reset",
2427
+ "pause",
2428
+ "resume"
2429
+ ]
2430
+ },
2431
+ "wcs-upload": {
2432
+ "package": "upload",
2433
+ "inputs": {
2434
+ "url": null,
2435
+ "method": null,
2436
+ "fieldName": null,
2437
+ "multiple": null,
2438
+ "maxSize": null,
2439
+ "accept": null,
2440
+ "manual": null,
2441
+ "files": null,
2442
+ "trigger": null
2443
+ },
2444
+ "properties": [
2445
+ "value",
2446
+ "loading",
2447
+ "progress",
2448
+ "error",
2449
+ "status",
2450
+ "errorInfo",
2451
+ "trigger",
2452
+ "files"
2453
+ ],
2454
+ "commands": [
2455
+ "upload",
2456
+ "abort"
2457
+ ]
2458
+ },
2459
+ "wcs-wakelock": {
2460
+ "package": "wakelock",
2461
+ "inputs": {
2462
+ "active": "active",
2463
+ "type": "type",
2464
+ "manual": "manual"
2465
+ },
2466
+ "properties": [
2467
+ "held",
2468
+ "error",
2469
+ "errorInfo"
2470
+ ],
2471
+ "commands": [
2472
+ "request",
2473
+ "release"
2474
+ ]
2475
+ },
2476
+ "wcs-ws": {
2477
+ "package": "websocket",
2478
+ "inputs": {
2479
+ "url": "url",
2480
+ "protocols": "protocols",
2481
+ "autoReconnect": "auto-reconnect",
2482
+ "reconnectInterval": "reconnect-interval",
2483
+ "maxReconnects": "max-reconnects",
2484
+ "binaryType": "binary-type",
2485
+ "manual": "manual",
2486
+ "trigger": null,
2487
+ "send": null
2488
+ },
2489
+ "properties": [
2490
+ "message",
2491
+ "connected",
2492
+ "loading",
2493
+ "error",
2494
+ "errorInfo",
2495
+ "readyState",
2496
+ "trigger",
2497
+ "send"
2498
+ ],
2499
+ "commands": [
2500
+ "connect",
2501
+ "sendMessage",
2502
+ "close"
2503
+ ]
2504
+ },
2505
+ "wcs-worker": {
2506
+ "package": "worker",
2507
+ "inputs": {
2508
+ "src": "src",
2509
+ "type": "type",
2510
+ "name": "name",
2511
+ "manual": "manual",
2512
+ "keepAlive": "keep-alive",
2513
+ "restartOnError": "restart-on-error",
2514
+ "maxRestarts": "max-restarts",
2515
+ "restartInterval": "restart-interval"
2516
+ },
2517
+ "properties": [
2518
+ "message",
2519
+ "error",
2520
+ "errorInfo",
2521
+ "running"
2522
+ ],
2523
+ "commands": [
2524
+ "start",
2525
+ "post",
2526
+ "terminate"
2527
+ ]
2528
+ }
2529
+ };
2530
+
2531
+ // src/service/ioNodeValidator.ts
2532
+ var DOM_COMMON_PROPERTIES = /* @__PURE__ */ new Set([
2533
+ "textContent",
2534
+ "innerHTML",
2535
+ "innerText",
2536
+ "hidden",
2537
+ "title",
2538
+ "id",
2539
+ "slot",
2540
+ "dir",
2541
+ "lang",
2542
+ "role",
2543
+ "tabIndex",
2544
+ "className"
2545
+ ]);
2546
+ var STRUCTURAL_DIRECTIVES2 = /* @__PURE__ */ new Set(["for", "if", "elseif", "else"]);
2547
+ var EMPTYISH_SEEDS = /* @__PURE__ */ new Set(["''", '""', "``", "null", "[]", "{}"]);
2548
+ function validateIoNodes(html, bindAttribute = "data-wcs", stateTagName = "wcs-state", locale) {
2549
+ const diagnostics = [];
2550
+ const msgs = getMessages(locale);
2551
+ const occurrences = findBuiltinTagOccurrences(html);
2552
+ if (occurrences.length === 0) return diagnostics;
2553
+ let statePaths = null;
2554
+ const getPaths = () => statePaths ??= getStatePathsFromHtml(html, stateTagName);
2555
+ for (const occ of occurrences) {
2556
+ const contract = BUILTIN_TAGS[occ.tagName];
2557
+ if (contract.properties.length === 0 && contract.commands.length === 0 && Object.keys(contract.inputs).length === 0) continue;
2558
+ const bindAttr = extractAttributeValue(occ.attrsText, bindAttribute);
2559
+ if (!bindAttr) continue;
2560
+ const valueStart = occ.attrsStart + bindAttr.valueOffsetInAttrs;
2561
+ const hasManual = hasBooleanAttribute(occ.attrsText, "manual");
2562
+ let exprOffset = 0;
2563
+ for (const expr of splitBindingExpressions(bindAttr.value)) {
2564
+ const exprStart = valueStart + exprOffset;
2565
+ exprOffset += expr.length + 1;
2566
+ const parsed = parseBindingExpression(expr);
2567
+ const property = parsed.property;
2568
+ if (!property) continue;
2569
+ const propIndex = expr.indexOf(property);
2570
+ const start = propIndex === -1 ? exprStart : exprStart + propIndex;
2571
+ const end = propIndex === -1 ? exprStart + expr.length : start + property.length;
2572
+ validateBindingAgainstContract(
2573
+ occ.tagName,
2574
+ contract,
2575
+ parsed,
2576
+ property,
2577
+ start,
2578
+ end,
2579
+ hasManual,
2580
+ getPaths,
2581
+ diagnostics,
2582
+ msgs
2583
+ );
2584
+ }
2585
+ }
2586
+ return diagnostics;
2587
+ }
2588
+ function validateBindingAgainstContract(tagName, contract, parsed, property, start, end, hasManual, getPaths, diagnostics, msgs) {
2589
+ const hashIndex = property.indexOf("#");
2590
+ const modifiers = hashIndex === -1 ? "" : property.slice(hashIndex + 1);
2591
+ property = hashIndex === -1 ? property : property.slice(0, hashIndex);
2592
+ if (property === "...") return;
2593
+ if (STRUCTURAL_DIRECTIVES2.has(property)) return;
2594
+ if (/^(class|style|attr)\./.test(property)) return;
2595
+ if (/^on\w/.test(property)) return;
2596
+ const inputNames = Object.keys(contract.inputs);
2597
+ if (property.startsWith("command.")) {
2598
+ const name = property.slice("command.".length);
2599
+ if (!contract.commands.includes(name)) {
2600
+ diagnostics.push({
2601
+ code: WcsDiagnosticCode.TagMemberUnknown,
2602
+ start,
2603
+ end,
2604
+ severity: "warning",
2605
+ tag: tagName,
2606
+ member: name,
2607
+ message: msgs.tagCommandUnknown(name, tagName, contract.commands.join(", ") || msgs.none()) + suggestion(name, contract.commands, msgs)
2608
+ });
2609
+ }
2610
+ return;
2611
+ }
2612
+ if (property.startsWith("eventToken.")) {
2613
+ const name = property.slice("eventToken.".length);
2614
+ if (!contract.properties.includes(name)) {
2615
+ diagnostics.push({
2616
+ code: WcsDiagnosticCode.TagMemberUnknown,
2617
+ start,
2618
+ end,
2619
+ severity: "warning",
2620
+ tag: tagName,
2621
+ member: name,
2622
+ message: msgs.tagEventTokenKeyUnknown(name, tagName, contract.properties.join(", ")) + suggestion(name, contract.properties, msgs)
2623
+ });
2624
+ }
2625
+ return;
2626
+ }
2627
+ if (!contract.properties.includes(property) && !(property in contract.inputs) && !DOM_COMMON_PROPERTIES.has(property)) {
2628
+ const members = [...contract.properties, ...inputNames];
2629
+ diagnostics.push({
2630
+ code: WcsDiagnosticCode.TagMemberUnknown,
2631
+ start,
2632
+ end,
2633
+ severity: "warning",
2634
+ tag: tagName,
2635
+ member: property,
2636
+ message: msgs.tagMemberUnknown(property, tagName) + suggestion(property, members, msgs)
2637
+ });
2638
+ return;
2639
+ }
2640
+ if (property === "trigger" && "trigger" in contract.inputs && parsed.path) {
2641
+ const cand = findDataSlot(getPaths(), parsed.path, parsed.targetState);
2642
+ if (cand?.rawInitial === "true") {
2643
+ diagnostics.push({
2644
+ code: WcsDiagnosticCode.TriggerSeededTruthy,
2645
+ start,
2646
+ end,
2647
+ severity: "warning",
2648
+ tag: tagName,
2649
+ statePath: parsed.path,
2650
+ message: msgs.triggerSeededTruthy(parsed.path)
2651
+ });
2652
+ }
2653
+ }
2654
+ if (tagName === "wcs-storage" && property === "value" && !hasManual && parsed.path && !/(?:^|,)init=(?:element|auto)\b/.test(modifiers)) {
2655
+ const cand = findDataSlot(getPaths(), parsed.path, parsed.targetState);
2656
+ if (cand?.rawInitial !== void 0 && EMPTYISH_SEEDS.has(normalizeSeed(cand.rawInitial))) {
2657
+ diagnostics.push({
2658
+ code: WcsDiagnosticCode.StorageSeedClobber,
2659
+ start,
2660
+ end,
2661
+ severity: "warning",
2662
+ tag: tagName,
2663
+ statePath: parsed.path,
2664
+ message: msgs.storageSeedClobber(parsed.path, cand.rawInitial)
2665
+ });
2666
+ }
2667
+ }
2668
+ }
2669
+ function findDataSlot(paths, path, stateName) {
2670
+ return paths.find((c) => c.kind === "data" && c.path === path && c.stateName === stateName);
2671
+ }
2672
+ function normalizeSeed(raw) {
2673
+ const compact = raw.replace(/\s+/g, "");
2674
+ return compact === "" ? raw : compact;
2675
+ }
2676
+ function suggestion(input, candidates, msgs) {
2677
+ let best = null;
2678
+ let bestDistance = 3;
2679
+ for (const c of candidates) {
2680
+ const d = editDistance(input.toLowerCase(), c.toLowerCase(), bestDistance);
2681
+ if (d < bestDistance) {
2682
+ best = c;
2683
+ bestDistance = d;
2684
+ }
2685
+ }
2686
+ return best !== null ? msgs.didYouMean(best) : "";
2687
+ }
2688
+ function editDistance(a, b, bound) {
2689
+ if (Math.abs(a.length - b.length) >= bound) return bound;
2690
+ const prev = new Array(b.length + 1);
2691
+ const curr = new Array(b.length + 1);
2692
+ for (let j = 0; j <= b.length; j++) prev[j] = j;
2693
+ for (let i = 1; i <= a.length; i++) {
2694
+ curr[0] = i;
2695
+ let rowMin = curr[0];
2696
+ for (let j = 1; j <= b.length; j++) {
2697
+ curr[j] = Math.min(
2698
+ prev[j] + 1,
2699
+ curr[j - 1] + 1,
2700
+ prev[j - 1] + (a[i - 1] === b[j - 1] ? 0 : 1)
2701
+ );
2702
+ if (curr[j] < rowMin) rowMin = curr[j];
2703
+ }
2704
+ if (rowMin >= bound) return bound;
2705
+ for (let j = 0; j <= b.length; j++) prev[j] = curr[j];
2706
+ }
2707
+ return Math.min(prev[b.length], bound);
2708
+ }
2709
+ function findBuiltinTagOccurrences(html) {
2710
+ const out = [];
2711
+ const regex = /<(wcs-[a-z0-9-]+)((?:"[^"]*"|'[^']*'|[^>"'])*)>/gi;
2712
+ let match;
2713
+ while ((match = regex.exec(html)) !== null) {
2714
+ const tagName = match[1].toLowerCase();
2715
+ if (!(tagName in BUILTIN_TAGS)) continue;
2716
+ out.push({
2717
+ tagName,
2718
+ tagStart: match.index,
2719
+ attrsText: match[2],
2720
+ attrsStart: match.index + 1 + match[1].length
2721
+ });
2722
+ }
2723
+ return out;
2724
+ }
2725
+ function extractAttributeValue(attrsText, attrName) {
2726
+ const escaped = attrName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2727
+ const regex = new RegExp(`(?:^|\\s)${escaped}\\s*=\\s*(["'])`, "i");
2728
+ const match = regex.exec(attrsText);
2729
+ if (!match) return null;
2730
+ const quote = match[1];
2731
+ const valueStart = match.index + match[0].length;
2732
+ const valueEnd = attrsText.indexOf(quote, valueStart);
2733
+ if (valueEnd === -1) return null;
2734
+ return { value: attrsText.slice(valueStart, valueEnd), valueOffsetInAttrs: valueStart };
2735
+ }
2736
+ function hasBooleanAttribute(attrsText, attrName) {
2737
+ return new RegExp(`(?:^|\\s)${attrName}(?:\\s|=|$)`, "i").test(attrsText);
2738
+ }
2739
+
2740
+ // src/service/documentEnvValidator.ts
2741
+ function validateDocumentEnv(html, locale) {
2742
+ const diagnostics = [];
2743
+ const msgs = getMessages(locale);
2744
+ const scanText = blankHtmlComments(html);
2745
+ const autos = findWcstackAutoScripts(scanText);
2746
+ const stateIndex = autos.findIndex((a) => a.pkg === "state");
2747
+ if (stateIndex !== -1) {
2748
+ for (const later of autos.slice(stateIndex + 1)) {
2749
+ if (later.pkg !== "devtools") continue;
2750
+ diagnostics.push({
2751
+ code: WcsDiagnosticCode.ScriptOrder,
2752
+ start: later.start,
2753
+ end: later.end,
2754
+ severity: "warning",
2755
+ message: msgs.devtoolsAfterState()
2756
+ });
2757
+ }
2758
+ }
2759
+ const router = autos.find((a) => a.pkg === "router");
2760
+ if (router && !/<base\b[^>]*\bhref\s*=/i.test(scanText)) {
2761
+ diagnostics.push({
2762
+ code: WcsDiagnosticCode.BaseHrefMissing,
2763
+ start: router.start,
2764
+ end: router.end,
2765
+ severity: "warning",
2766
+ message: msgs.baseHrefMissing()
2767
+ });
2768
+ }
2769
+ const refs = collectSignalsRefs(scanText);
2770
+ const dom = refs.find((r) => r.kind === "dom");
2771
+ const bare = refs.find((r) => r.kind === "bare");
2772
+ if (dom && bare) {
2773
+ const later = bare.start > dom.start ? bare : dom;
2774
+ diagnostics.push({
2775
+ code: WcsDiagnosticCode.SignalsDualEntry,
2776
+ start: later.start,
2777
+ end: later.end,
2778
+ severity: "error",
2779
+ message: msgs.signalsDualEntry()
2780
+ });
2781
+ }
2782
+ return diagnostics;
2783
+ }
2784
+ function findWcstackAutoScripts(html) {
2785
+ const out = [];
2786
+ const scriptRegex = /<script\b(?:"[^"]*"|'[^']*'|[^>"'])*>/gi;
2787
+ let match;
2788
+ while ((match = scriptRegex.exec(html)) !== null) {
2789
+ const src = extractSrc(match[0]);
2790
+ if (!src) continue;
2791
+ const pkgMatch = /@wcstack\/([a-z0-9-]+)\/auto\b/.exec(src.value);
2792
+ if (!pkgMatch) continue;
2793
+ out.push({
2794
+ pkg: pkgMatch[1],
2795
+ start: match.index + src.offsetInTag,
2796
+ end: match.index + src.offsetInTag + src.value.length
2797
+ });
2798
+ }
2799
+ return out;
2800
+ }
2801
+ function collectSignalsRefs(html) {
2802
+ const refs = [];
2803
+ const scriptRegex = /<script\b((?:"[^"]*"|'[^']*'|[^>"'])*)>([\s\S]*?)<\/script\s*>/gi;
2804
+ let match;
2805
+ while ((match = scriptRegex.exec(html)) !== null) {
2806
+ const openTag = html.slice(match.index, match.index + match[0].indexOf(">") + 1);
2807
+ const src = extractSrc(openTag);
2808
+ if (src) {
2809
+ const kind = classifySignalsSpecifier(src.value);
2810
+ if (kind) {
2811
+ refs.push({
2812
+ kind,
2813
+ start: match.index + src.offsetInTag,
2814
+ end: match.index + src.offsetInTag + src.value.length
2815
+ });
2816
+ }
2817
+ continue;
2818
+ }
2819
+ if (!/\btype\s*=\s*(["'])module\1/i.test(match[1])) continue;
2820
+ const bodyStart = match.index + match[0].indexOf(">") + 1;
2821
+ const body = blankJsComments(match[2]);
2822
+ const importRegex = /(?:\bfrom\s*|\bimport\s*\(?\s*)(["'])([^"']*@wcstack\/signals[^"']*)\1/g;
2823
+ let im;
2824
+ while ((im = importRegex.exec(body)) !== null) {
2825
+ const kind = classifySignalsSpecifier(im[2]);
2826
+ if (!kind) continue;
2827
+ const specStart = bodyStart + im.index + im[0].indexOf(im[1]) + 1;
2828
+ refs.push({ kind, start: specStart, end: specStart + im[2].length });
2829
+ }
2830
+ }
2831
+ return refs;
2832
+ }
2833
+ function classifySignalsSpecifier(spec) {
2834
+ if (!spec.includes("@wcstack/signals")) return null;
2835
+ return /@wcstack\/signals\/dom\b/.test(spec) ? "dom" : "bare";
2836
+ }
2837
+ function extractSrc(openTag) {
2838
+ const srcMatch = /\bsrc\s*=\s*(["'])(.*?)\1/i.exec(openTag);
2839
+ if (!srcMatch) return null;
2840
+ return {
2841
+ value: srcMatch[2],
2842
+ offsetInTag: srcMatch.index + srcMatch[0].indexOf(srcMatch[1]) + 1
2843
+ };
2844
+ }
2845
+ function blankHtmlComments(html) {
2846
+ return html.replace(/<!--[\s\S]*?-->/g, (m) => " ".repeat(m.length));
2847
+ }
2848
+ function blankJsComments(code) {
2849
+ return code.replace(/\/\*[\s\S]*?\*\//g, (m) => " ".repeat(m.length)).replace(/(^|[^:])\/\/[^\n]*/g, (m, pre) => pre + " ".repeat(m.length - pre.length));
2850
+ }
2851
+
2852
+ // src/core/validateDocument.ts
2853
+ function validateDocument(text, options = {}) {
2854
+ const bindAttribute = options.bindAttribute ?? "data-wcs";
2855
+ const stateTagName = options.stateTagName ?? "wcs-state";
2856
+ const locale = options.locale;
2857
+ const out = [];
2858
+ out.push(...validateBindings(text, bindAttribute, stateTagName, locale));
2859
+ out.push(...validateTemplateSyntax(text, stateTagName, bindAttribute, locale));
2860
+ out.push(...validateIoNodes(text, bindAttribute, stateTagName, locale));
2861
+ out.push(...validateDocumentEnv(text, locale));
2862
+ for (const d of validateStateTypes(text, stateTagName, locale)) {
2863
+ out.push({ code: WcsDiagnosticCode.TypeAnnotation, start: d.start, end: d.end, message: d.message, severity: d.severity });
2864
+ }
2865
+ for (const d of validateNestedAssigns(text, stateTagName, locale)) {
2866
+ out.push({ code: WcsDiagnosticCode.NestedAssign, start: d.start, end: d.end, message: d.message, severity: d.severity });
2867
+ }
2868
+ return sortDiagnostics(out);
2869
+ }
2870
+
2871
+ // src/core/sidecar/schemaSubset.ts
2872
+ var ALLOWED_SCHEMA_KEYWORDS = /* @__PURE__ */ new Set([
2873
+ "type",
2874
+ "properties",
2875
+ "required",
2876
+ "items",
2877
+ "enum",
2878
+ "const",
2879
+ "anyOf",
2880
+ "$defs",
2881
+ "$ref"
2882
+ ]);
2883
+ var DiagnosticContext = class {
2884
+ constructor(spans) {
2885
+ this.spans = spans;
2886
+ }
2887
+ diagnostics = [];
2888
+ add(code, pointer2, message, severity, extra = {}, useKeySpan = false) {
2889
+ const span = this.spans.get(pointer2);
2890
+ const start = span === void 0 ? 0 : useKeySpan ? span.keyStart ?? span.start : span.start;
2891
+ const end = span === void 0 ? 0 : useKeySpan ? span.keyEnd ?? span.end : span.end;
2892
+ this.diagnostics.push({ code, start, end, message, severity, ...extra });
2893
+ }
2894
+ };
2895
+ function isSchemaObject(value) {
2896
+ return value !== null && typeof value === "object" && !Array.isArray(value);
2897
+ }
2898
+ function isSchemaMap(value) {
2899
+ return value !== null && typeof value === "object" && !Array.isArray(value);
2900
+ }
2901
+ function validateSchemaSubset(schema, pointerBase, ctx, rootDefs) {
2902
+ walkKeywords(schema, pointerBase, ctx, rootDefs);
2903
+ const safe = /* @__PURE__ */ new Set();
2904
+ detectCycles(schema, pointerBase, ctx, rootDefs, /* @__PURE__ */ new Set(), safe);
2905
+ for (const [name, def] of Object.entries(rootDefs)) {
2906
+ detectCycles(def, `${pointerBase}/$defs/${escape(name)}`, ctx, rootDefs, /* @__PURE__ */ new Set(), safe);
2907
+ }
2908
+ }
2909
+ function walkKeywords(node, ptr, ctx, rootDefs) {
2910
+ if (!isSchemaObject(node)) return;
2911
+ for (const keyword of Object.keys(node)) {
2912
+ if (!ALLOWED_SCHEMA_KEYWORDS.has(keyword)) {
2913
+ ctx.add(
2914
+ WcsDiagnosticCode.ManifestUnknownKeyword,
2915
+ `${ptr}/${escape(keyword)}`,
2916
+ `Unsupported schema keyword "${keyword}". Allowed: ${[...ALLOWED_SCHEMA_KEYWORDS].join(", ")}.`,
2917
+ "warning",
2918
+ {},
2919
+ true
2920
+ );
2921
+ }
2922
+ }
2923
+ if (typeof node.$ref === "string") {
2924
+ if (!node.$ref.startsWith("#/")) {
2925
+ ctx.add(
2926
+ WcsDiagnosticCode.ManifestExternalRef,
2927
+ `${ptr}/$ref`,
2928
+ `External $ref "${node.$ref}" is forbidden; only local "#/$defs/..." references are allowed.`,
2929
+ "error"
2930
+ );
2931
+ } else if (resolveLocalRef(node.$ref, rootDefs) === void 0) {
2932
+ ctx.add(
2933
+ WcsDiagnosticCode.ManifestRefUnresolved,
2934
+ `${ptr}/$ref`,
2935
+ `Unresolved local $ref "${node.$ref}".`,
2936
+ "error"
2937
+ );
2938
+ }
2939
+ }
2940
+ if (isSchemaMap(node.properties)) {
2941
+ for (const [name, child] of Object.entries(node.properties)) {
2942
+ walkKeywords(child, `${ptr}/properties/${escape(name)}`, ctx, rootDefs);
2943
+ }
2944
+ }
2945
+ if (node.items !== void 0 && isSchemaObject(node.items)) {
2946
+ walkKeywords(node.items, `${ptr}/items`, ctx, rootDefs);
2947
+ }
2948
+ if (Array.isArray(node.anyOf)) {
2949
+ node.anyOf.forEach((child, i) => walkKeywords(child, `${ptr}/anyOf/${i}`, ctx, rootDefs));
2950
+ }
2951
+ if (isSchemaMap(node.$defs)) {
2952
+ for (const [name, child] of Object.entries(node.$defs)) {
2953
+ walkKeywords(child, `${ptr}/$defs/${escape(name)}`, ctx, rootDefs);
2954
+ }
2955
+ }
2956
+ }
2957
+ function detectCycles(node, ptr, ctx, rootDefs, refStack, safe) {
2958
+ if (!isSchemaObject(node)) return;
2959
+ if (typeof node.$ref === "string") {
2960
+ const ref = node.$ref;
2961
+ if (!ref.startsWith("#/")) return;
2962
+ if (refStack.has(ref)) {
2963
+ ctx.add(WcsDiagnosticCode.ManifestRefCycle, `${ptr}/$ref`, `Cyclic $ref detected at "${ref}".`, "error");
2964
+ return;
2965
+ }
2966
+ if (safe.has(ref)) return;
2967
+ const target = resolveLocalRef(ref, rootDefs);
2968
+ if (target === void 0) return;
2969
+ refStack.add(ref);
2970
+ detectCycles(target, ptr, ctx, rootDefs, refStack, safe);
2971
+ refStack.delete(ref);
2972
+ safe.add(ref);
2973
+ return;
2974
+ }
2975
+ if (isSchemaMap(node.properties)) {
2976
+ for (const child of Object.values(node.properties)) detectCycles(child, ptr, ctx, rootDefs, refStack, safe);
2977
+ }
2978
+ if (node.items !== void 0 && isSchemaObject(node.items)) {
2979
+ detectCycles(node.items, ptr, ctx, rootDefs, refStack, safe);
2980
+ }
2981
+ if (Array.isArray(node.anyOf)) {
2982
+ for (const child of node.anyOf) detectCycles(child, ptr, ctx, rootDefs, refStack, safe);
2983
+ }
2984
+ }
2985
+ function resolveLocalRef(ref, rootDefs) {
2986
+ const match = /^#\/\$defs\/(.+)$/.exec(ref);
2987
+ if (match === null) return void 0;
2988
+ const name = match[1].replace(/~1/g, "/").replace(/~0/g, "~");
2989
+ return rootDefs[name];
2990
+ }
2991
+ function escape(key) {
2992
+ return key.replace(/~/g, "~0").replace(/\//g, "~1");
2993
+ }
2994
+
2995
+ // src/core/sidecar/jsonSource.ts
2996
+ var JsonReader = class {
2997
+ constructor(text) {
2998
+ this.text = text;
2999
+ }
3000
+ pos = 0;
3001
+ spans = /* @__PURE__ */ new Map();
3002
+ parse() {
3003
+ this.skipWs();
3004
+ const value = this.parseValue("", void 0);
3005
+ this.skipWs();
3006
+ if (this.pos < this.text.length) {
3007
+ throw this.fail(`Unexpected trailing content`);
3008
+ }
3009
+ return { value };
3010
+ }
3011
+ fail(message) {
3012
+ const err = new Error(message);
3013
+ err.offset = Math.min(this.pos, this.text.length);
3014
+ return err;
3015
+ }
3016
+ skipWs() {
3017
+ while (this.pos < this.text.length) {
3018
+ const c = this.text.charCodeAt(this.pos);
3019
+ if (c === 32 || c === 9 || c === 10 || c === 13) this.pos++;
3020
+ else break;
3021
+ }
3022
+ }
3023
+ parseValue(pointer2, keySpan) {
3024
+ this.skipWs();
3025
+ const start = this.pos;
3026
+ const c = this.text[this.pos];
3027
+ let value;
3028
+ if (c === "{") value = this.parseObject(pointer2);
3029
+ else if (c === "[") value = this.parseArray(pointer2);
3030
+ else if (c === '"') value = this.parseString();
3031
+ else if (c === "t" || c === "f") value = this.parseKeyword();
3032
+ else if (c === "n") value = this.parseNull();
3033
+ else if (c === "-" || c >= "0" && c <= "9") value = this.parseNumber();
3034
+ else throw this.fail(`Unexpected character`);
3035
+ const end = this.pos;
3036
+ this.spans.set(pointer2, keySpan === void 0 ? { start, end } : { start, end, ...keySpan });
3037
+ return value;
3038
+ }
3039
+ parseObject(pointer2) {
3040
+ this.pos++;
3041
+ const obj = {};
3042
+ this.skipWs();
3043
+ if (this.text[this.pos] === "}") {
3044
+ this.pos++;
3045
+ return obj;
3046
+ }
3047
+ for (; ; ) {
3048
+ this.skipWs();
3049
+ if (this.text[this.pos] !== '"') throw this.fail(`Expected object key`);
3050
+ const keyStart = this.pos;
3051
+ const key = this.parseString();
3052
+ const keyEnd = this.pos;
3053
+ this.skipWs();
3054
+ if (this.text[this.pos] !== ":") throw this.fail(`Expected ':'`);
3055
+ this.pos++;
3056
+ const childPointer = `${pointer2}/${escapePointer(key)}`;
3057
+ obj[key] = this.parseValue(childPointer, { keyStart, keyEnd });
3058
+ this.skipWs();
3059
+ const sep = this.text[this.pos];
3060
+ if (sep === ",") {
3061
+ this.pos++;
3062
+ continue;
3063
+ }
3064
+ if (sep === "}") {
3065
+ this.pos++;
3066
+ return obj;
3067
+ }
3068
+ throw this.fail(`Expected ',' or '}'`);
3069
+ }
3070
+ }
3071
+ parseArray(pointer2) {
3072
+ this.pos++;
3073
+ const arr = [];
3074
+ this.skipWs();
3075
+ if (this.text[this.pos] === "]") {
3076
+ this.pos++;
3077
+ return arr;
3078
+ }
3079
+ let index = 0;
3080
+ for (; ; ) {
3081
+ const childPointer = `${pointer2}/${index}`;
3082
+ arr.push(this.parseValue(childPointer, void 0));
3083
+ index++;
3084
+ this.skipWs();
3085
+ const sep = this.text[this.pos];
3086
+ if (sep === ",") {
3087
+ this.pos++;
3088
+ continue;
3089
+ }
3090
+ if (sep === "]") {
3091
+ this.pos++;
3092
+ return arr;
3093
+ }
3094
+ throw this.fail(`Expected ',' or ']'`);
3095
+ }
3096
+ }
3097
+ parseString() {
3098
+ this.pos++;
3099
+ let result = "";
3100
+ for (; ; ) {
3101
+ if (this.pos >= this.text.length) throw this.fail(`Unterminated string`);
3102
+ const ch = this.text[this.pos++];
3103
+ if (ch === '"') return result;
3104
+ if (ch === "\\") {
3105
+ const esc = this.text[this.pos++];
3106
+ if (esc === '"') result += '"';
3107
+ else if (esc === "\\") result += "\\";
3108
+ else if (esc === "/") result += "/";
3109
+ else if (esc === "b") result += "\b";
3110
+ else if (esc === "f") result += "\f";
3111
+ else if (esc === "n") result += "\n";
3112
+ else if (esc === "r") result += "\r";
3113
+ else if (esc === "t") result += " ";
3114
+ else if (esc === "u") {
3115
+ const hex = this.text.slice(this.pos, this.pos + 4);
3116
+ if (!/^[0-9a-fA-F]{4}$/.test(hex)) throw this.fail(`Invalid unicode escape`);
3117
+ result += String.fromCharCode(parseInt(hex, 16));
3118
+ this.pos += 4;
3119
+ } else throw this.fail(`Invalid escape`);
3120
+ } else {
3121
+ result += ch;
3122
+ }
3123
+ }
3124
+ }
3125
+ parseKeyword() {
3126
+ if (this.text.startsWith("true", this.pos)) {
3127
+ this.pos += 4;
3128
+ return true;
3129
+ }
3130
+ if (this.text.startsWith("false", this.pos)) {
3131
+ this.pos += 5;
3132
+ return false;
3133
+ }
3134
+ throw this.fail(`Invalid literal`);
3135
+ }
3136
+ parseNull() {
3137
+ if (this.text.startsWith("null", this.pos)) {
3138
+ this.pos += 4;
3139
+ return null;
3140
+ }
3141
+ throw this.fail(`Invalid literal`);
3142
+ }
3143
+ parseNumber() {
3144
+ const match = /^-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?/.exec(this.text.slice(this.pos));
3145
+ if (match === null) throw this.fail(`Invalid number`);
3146
+ this.pos += match[0].length;
3147
+ return Number(match[0]);
3148
+ }
3149
+ };
3150
+ function escapePointer(key) {
3151
+ return key.replace(/~/g, "~0").replace(/\//g, "~1");
3152
+ }
3153
+ function pointer(...segments) {
3154
+ return segments.map((s) => `/${escapePointer(String(s))}`).join("");
3155
+ }
3156
+ function parseJsonWithSpans(text) {
3157
+ const reader = new JsonReader(text);
3158
+ try {
3159
+ const { value } = reader.parse();
3160
+ return { value, spans: reader.spans, error: null };
3161
+ } catch (e) {
3162
+ const offset = e.offset ?? 0;
3163
+ return { value: void 0, spans: reader.spans, error: { offset, message: e.message } };
3164
+ }
3165
+ }
3166
+
3167
+ // src/core/sidecar/types.ts
3168
+ var SUPPORTED_SCHEMA_VERSION = 1;
3169
+ var SUPPORTED_NAMESPACE_VERSION = 1;
3170
+
3171
+ // src/core/sidecar/loader.ts
3172
+ var NAMESPACE_KEYS = ["wcstack.types", "wcstack.async", "wcstack.platformCapabilities", "wcstack.application"];
3173
+ function loadManifest(artifact) {
3174
+ const parsed = parseJsonWithSpans(artifact.text);
3175
+ const ctx = new DiagnosticContext(parsed.spans);
3176
+ if (parsed.error !== null) {
3177
+ ctx.diagnostics.push({
3178
+ code: WcsDiagnosticCode.ManifestBroken,
3179
+ start: parsed.error.offset,
3180
+ end: Math.min(parsed.error.offset + 1, artifact.text.length),
3181
+ message: `Broken manifest JSON: ${parsed.error.message}.`,
3182
+ severity: "error"
3183
+ });
3184
+ return { artifact, manifest: null, ctx, spans: parsed.spans };
3185
+ }
3186
+ const root = parsed.value;
3187
+ if (root === null || typeof root !== "object" || Array.isArray(root)) {
3188
+ ctx.add(WcsDiagnosticCode.ManifestBroken, "", `Manifest root must be a JSON object.`, "error");
3189
+ return { artifact, manifest: null, ctx, spans: parsed.spans };
3190
+ }
3191
+ const obj = root;
3192
+ if (obj.schemaVersion === void 0) {
3193
+ ctx.add(WcsDiagnosticCode.ManifestSchemaVersion, "", `Manifest is missing an integer "schemaVersion".`, "error");
3194
+ return { artifact, manifest: null, ctx, spans: parsed.spans };
3195
+ }
3196
+ if (typeof obj.schemaVersion !== "number" || !Number.isInteger(obj.schemaVersion)) {
3197
+ ctx.add(
3198
+ WcsDiagnosticCode.ManifestSchemaVersion,
3199
+ pointer("schemaVersion"),
3200
+ `Manifest "schemaVersion" must be an integer.`,
3201
+ "error"
3202
+ );
3203
+ return { artifact, manifest: null, ctx, spans: parsed.spans };
3204
+ }
3205
+ if (obj.schemaVersion !== SUPPORTED_SCHEMA_VERSION) {
3206
+ ctx.add(
3207
+ WcsDiagnosticCode.ManifestSchemaVersion,
3208
+ pointer("schemaVersion"),
3209
+ `Unsupported schemaVersion ${obj.schemaVersion}; this reader supports ${SUPPORTED_SCHEMA_VERSION}.`,
3210
+ "error"
3211
+ );
3212
+ return { artifact, manifest: null, ctx, spans: parsed.spans };
3213
+ }
3214
+ if (obj.kind !== "package" && obj.kind !== "application") {
3215
+ ctx.add(
3216
+ WcsDiagnosticCode.ManifestKindInvalid,
3217
+ obj.kind === void 0 ? "" : pointer("kind"),
3218
+ `Manifest "kind" must be "package" or "application".`,
3219
+ "error"
3220
+ );
3221
+ return { artifact, manifest: null, ctx, spans: parsed.spans };
3222
+ }
3223
+ const extensions = obj.manifestExtensions;
3224
+ if (extensions !== null && typeof extensions === "object") {
3225
+ for (const ns of NAMESPACE_KEYS) {
3226
+ const nsObj = extensions[ns];
3227
+ if (nsObj !== null && typeof nsObj === "object") {
3228
+ const version = nsObj.version;
3229
+ if (typeof version === "number" && version !== SUPPORTED_NAMESPACE_VERSION) {
3230
+ ctx.add(
3231
+ WcsDiagnosticCode.ManifestNamespaceVersion,
3232
+ pointer("manifestExtensions", ns, "version"),
3233
+ `Namespace "${ns}" version ${version} is unsupported (expected ${SUPPORTED_NAMESPACE_VERSION}).`,
3234
+ "warning"
3235
+ );
3236
+ }
3237
+ }
3238
+ }
3239
+ }
3240
+ return { artifact, manifest: obj, ctx, spans: parsed.spans };
3241
+ }
3242
+ function resolvePackageContracts(loaded) {
3243
+ const perSource = /* @__PURE__ */ new Map();
3244
+ const ctxBySource = /* @__PURE__ */ new Map();
3245
+ const ctxFor = (lm) => {
3246
+ let ctx = ctxBySource.get(lm.artifact.source);
3247
+ if (ctx === void 0) {
3248
+ ctx = new DiagnosticContext(lm.spans);
3249
+ ctxBySource.set(lm.artifact.source, ctx);
3250
+ perSource.set(lm.artifact.source, ctx.diagnostics);
3251
+ }
3252
+ return ctx;
3253
+ };
3254
+ const winners = /* @__PURE__ */ new Map();
3255
+ const collided = /* @__PURE__ */ new Set();
3256
+ const firstSource = /* @__PURE__ */ new Map();
3257
+ const filterOwner = /* @__PURE__ */ new Map();
3258
+ for (const lm of loaded) {
3259
+ if (lm.manifest === null) continue;
3260
+ const types = lm.manifest.manifestExtensions?.["wcstack.types"];
3261
+ if (lm.manifest.kind === "package" && types !== void 0) {
3262
+ for (const [tag, component] of Object.entries(types.components ?? {})) {
3263
+ const ptr = pointer("manifestExtensions", "wcstack.types", "components", tag);
3264
+ if (!winners.has(tag) && !collided.has(tag)) {
3265
+ winners.set(tag, { tag, component, source: lm.artifact.source });
3266
+ firstSource.set(tag, lm.artifact.source);
3267
+ continue;
3268
+ }
3269
+ if (component.override === true) {
3270
+ ctxFor(lm).add(
3271
+ WcsDiagnosticCode.ManifestOverride,
3272
+ ptr,
3273
+ `Component "${tag}" explicitly overrides a prior package contract.`,
3274
+ "info",
3275
+ { tag },
3276
+ true
3277
+ );
3278
+ continue;
3279
+ }
3280
+ const priorSource = firstSource.get(tag) ?? "an earlier artifact";
3281
+ collided.add(tag);
3282
+ winners.delete(tag);
3283
+ ctxFor(lm).add(
3284
+ WcsDiagnosticCode.ManifestTagCollision,
3285
+ ptr,
3286
+ `Component tag "${tag}" is defined by multiple package artifacts (also in "${priorSource}"). Set "override": true to intentionally shadow.`,
3287
+ "error",
3288
+ { tag },
3289
+ true
3290
+ );
3291
+ }
3292
+ }
3293
+ const application = lm.manifest.manifestExtensions?.["wcstack.application"];
3294
+ if (lm.manifest.kind === "application" && application?.filters !== void 0) {
3295
+ for (const name of Object.keys(application.filters)) {
3296
+ const priorSource = filterOwner.get(name);
3297
+ if (priorSource === void 0) {
3298
+ filterOwner.set(name, lm.artifact.source);
3299
+ continue;
3300
+ }
3301
+ ctxFor(lm).add(
3302
+ WcsDiagnosticCode.ManifestFilterCollision,
3303
+ pointer("manifestExtensions", "wcstack.application", "filters", name),
3304
+ `Filter "${name}" is defined by multiple application artifacts (also in "${priorSource}").`,
3305
+ "error",
3306
+ { member: name },
3307
+ true
3308
+ );
3309
+ }
3310
+ }
3311
+ }
3312
+ const diagnosticsBySource = /* @__PURE__ */ new Map();
3313
+ for (const [source, diags] of perSource) {
3314
+ const kept = diags.filter((d) => !(d.code === WcsDiagnosticCode.ManifestOverride && d.tag !== void 0 && collided.has(d.tag)));
3315
+ if (kept.length > 0) diagnosticsBySource.set(source, kept);
3316
+ }
3317
+ return { tags: winners, diagnosticsBySource };
3318
+ }
3319
+
3320
+ // src/core/sidecar/drift.ts
3321
+ function checkDrift(tag, component, live, ctx) {
3322
+ const liveProps = new Map(live.properties.map((p) => [p.name, p.event]));
3323
+ const liveInputs = new Set((live.inputs ?? []).map((i) => i.name));
3324
+ const liveCommands = new Set((live.commands ?? []).map((c) => c.name));
3325
+ for (const [name, observable] of Object.entries(component.observables ?? {})) {
3326
+ const memberPtr = pointer("manifestExtensions", "wcstack.types", "components", tag, "observables", name);
3327
+ if (!liveProps.has(name)) {
3328
+ ctx.add(
3329
+ WcsDiagnosticCode.DriftMissingMember,
3330
+ memberPtr,
3331
+ `Sidecar declares observable "${name}" on <${tag}>, but the live wcBindable declaration has no such property.`,
3332
+ "error",
3333
+ { tag, member: name },
3334
+ true
3335
+ );
3336
+ continue;
3337
+ }
3338
+ const liveEvent = liveProps.get(name);
3339
+ if (observable.event !== liveEvent) {
3340
+ ctx.add(
3341
+ WcsDiagnosticCode.DriftEventMismatch,
3342
+ pointer("manifestExtensions", "wcstack.types", "components", tag, "observables", name, "event"),
3343
+ `Sidecar observable "${name}" on <${tag}> declares event "${observable.event}", but the live declaration uses "${liveEvent}".`,
3344
+ "error",
3345
+ { tag, member: name }
3346
+ );
3347
+ }
3348
+ }
3349
+ for (const name of Object.keys(component.inputs ?? {})) {
3350
+ if (!liveInputs.has(name)) {
3351
+ ctx.add(
3352
+ WcsDiagnosticCode.DriftMissingMember,
3353
+ pointer("manifestExtensions", "wcstack.types", "components", tag, "inputs", name),
3354
+ `Sidecar declares input "${name}" on <${tag}>, but the live wcBindable declaration has no such input.`,
3355
+ "error",
3356
+ { tag, member: name },
3357
+ true
3358
+ );
3359
+ }
3360
+ }
3361
+ for (const name of Object.keys(component.commands ?? {})) {
3362
+ if (!liveCommands.has(name)) {
3363
+ ctx.add(
3364
+ WcsDiagnosticCode.DriftMissingMember,
3365
+ pointer("manifestExtensions", "wcstack.types", "components", tag, "commands", name),
3366
+ `Sidecar declares command "${name}" on <${tag}>, but the live wcBindable declaration has no such command.`,
3367
+ "error",
3368
+ { tag, member: name },
3369
+ true
3370
+ );
3371
+ }
3372
+ }
3373
+ }
3374
+
3375
+ // src/core/sidecar/validate.ts
3376
+ function validateLoadedSchemas(loaded) {
3377
+ if (loaded.manifest === null) return;
3378
+ const types = loaded.manifest.manifestExtensions?.["wcstack.types"];
3379
+ if (types === void 0) return;
3380
+ for (const [tag, component] of Object.entries(types.components ?? {})) {
3381
+ validateComponentSchemas(tag, component, loaded.ctx);
3382
+ }
3383
+ }
3384
+ function validateComponentSchemas(tag, component, ctx) {
3385
+ const base = pointer("manifestExtensions", "wcstack.types", "components", tag);
3386
+ const walkSchema = (schema, ptr) => {
3387
+ if (schema === void 0) return;
3388
+ validateSchemaSubset(schema, ptr, ctx, schema.$defs ?? {});
3389
+ };
3390
+ for (const [name, observable] of Object.entries(component.observables ?? {})) {
3391
+ walkSchema(observable.schema, `${base}/observables/${escapePtr(name)}/schema`);
3392
+ }
3393
+ for (const [name, input] of Object.entries(component.inputs ?? {})) {
3394
+ walkSchema(input.schema, `${base}/inputs/${escapePtr(name)}/schema`);
3395
+ }
3396
+ for (const [name, command] of Object.entries(component.commands ?? {})) {
3397
+ walkSchema(command.args, `${base}/commands/${escapePtr(name)}/args`);
3398
+ walkSchema(command.result, `${base}/commands/${escapePtr(name)}/result`);
3399
+ }
3400
+ }
3401
+ function validateManifestSet(input) {
3402
+ const loadedList = input.artifacts.map(loadManifest);
3403
+ const byArtifact = /* @__PURE__ */ new Map();
3404
+ for (const loaded of loadedList) {
3405
+ validateLoadedSchemas(loaded);
3406
+ if (input.liveDeclarations !== void 0 && loaded.manifest?.kind === "package") {
3407
+ const types = loaded.manifest.manifestExtensions?.["wcstack.types"];
3408
+ for (const [tag, component] of Object.entries(types?.components ?? {})) {
3409
+ const live = input.liveDeclarations.get(tag);
3410
+ if (live !== void 0) {
3411
+ checkDrift(tag, component, live, loaded.ctx);
3412
+ }
3413
+ }
3414
+ }
3415
+ const existing = byArtifact.get(loaded.artifact.source) ?? [];
3416
+ byArtifact.set(loaded.artifact.source, [...existing, ...loaded.ctx.diagnostics]);
3417
+ }
3418
+ const resolved = resolvePackageContracts(loadedList);
3419
+ for (const [source, diags] of resolved.diagnosticsBySource) {
3420
+ const existing = byArtifact.get(source) ?? [];
3421
+ byArtifact.set(source, [...existing, ...diags]);
3422
+ }
3423
+ const all = [];
3424
+ for (const diags of byArtifact.values()) all.push(...diags);
3425
+ const resolvedTags = /* @__PURE__ */ new Map();
3426
+ for (const [tag, contract] of resolved.tags) resolvedTags.set(tag, contract.source);
3427
+ const sortedByArtifact = /* @__PURE__ */ new Map();
3428
+ for (const [source, diags] of byArtifact) sortedByArtifact.set(source, sortDiagnostics(diags));
3429
+ return {
3430
+ diagnostics: sortDiagnostics(all),
3431
+ byArtifact: sortedByArtifact,
3432
+ resolvedTags
3433
+ };
3434
+ }
3435
+ function escapePtr(key) {
3436
+ return key.replace(/~/g, "~0").replace(/\//g, "~1");
3437
+ }
3438
+
3439
+ // src/core/cli/runValidation.ts
3440
+ var severityLabel = { error: "error", warning: "warning", info: "info" };
3441
+ function runValidation(inputs, options = {}) {
3442
+ const diagnosticsBySource = /* @__PURE__ */ new Map();
3443
+ for (const input of inputs) {
3444
+ if (input.kind === "html") {
3445
+ diagnosticsBySource.set(input.source, validateDocument(input.text, options));
3446
+ }
3447
+ }
3448
+ const manifestInputs = inputs.filter((i) => i.kind === "manifest");
3449
+ if (manifestInputs.length > 0) {
3450
+ const result = validateManifestSet({
3451
+ artifacts: manifestInputs.map((m) => ({ text: m.text, source: m.source })),
3452
+ liveDeclarations: options.liveDeclarations
3453
+ });
3454
+ for (const input of manifestInputs) {
3455
+ diagnosticsBySource.set(input.source, result.byArtifact.get(input.source) ?? []);
3456
+ }
3457
+ }
3458
+ const textBySource = new Map(inputs.map((i) => [i.source, i.text]));
3459
+ const lines = [];
3460
+ let errorCount = 0;
3461
+ let warningCount = 0;
3462
+ let infoCount = 0;
3463
+ for (const source of [...diagnosticsBySource.keys()].sort()) {
3464
+ const diags = diagnosticsBySource.get(source);
3465
+ const mapper = createPositionMapper(textBySource.get(source) ?? "");
3466
+ for (const d of diags) {
3467
+ if (d.severity === "error") errorCount++;
3468
+ else if (d.severity === "warning") warningCount++;
3469
+ else infoCount++;
3470
+ if (options.errorsOnly && d.severity !== "error") continue;
3471
+ const pos = mapper(d.start);
3472
+ lines.push(`${source}:${pos.line}:${pos.column} ${severityLabel[d.severity]} ${d.code} ${d.message}`);
3473
+ }
3474
+ }
3475
+ return {
3476
+ lines,
3477
+ errorCount,
3478
+ warningCount,
3479
+ infoCount,
3480
+ exitCode: errorCount > 0 ? 1 : 0,
3481
+ diagnosticsBySource
3482
+ };
3483
+ }
3484
+
3485
+ // src/cli.ts
3486
+ function classify(path) {
3487
+ return path.endsWith(".manifest.json") ? "manifest" : "html";
3488
+ }
3489
+ function parseArgs(argv) {
3490
+ const options = {};
3491
+ const files = [];
3492
+ for (const arg of argv) {
3493
+ if (arg.startsWith("--attr=")) options.bindAttribute = arg.slice("--attr=".length);
3494
+ else if (arg.startsWith("--state-tag=")) options.stateTagName = arg.slice("--state-tag=".length);
3495
+ else if (arg.startsWith("--lang=")) options.locale = arg.slice("--lang=".length);
3496
+ else if (arg === "--errors-only" || arg === "--quiet") options.errorsOnly = true;
3497
+ else if (!arg.startsWith("-")) files.push(arg);
3498
+ }
3499
+ return { options, files };
3500
+ }
3501
+ function resolveCliLocale(explicit, env = process.env) {
3502
+ if (explicit) return explicit;
3503
+ const fromEnv = env.LC_ALL || env.LC_MESSAGES || env.LANG;
3504
+ if (fromEnv) return fromEnv;
3505
+ try {
3506
+ return new Intl.DateTimeFormat().resolvedOptions().locale || "en";
3507
+ } catch {
3508
+ return "en";
3509
+ }
3510
+ }
3511
+ function main(argv) {
3512
+ const { options, files } = parseArgs(argv);
3513
+ const locale = resolveCliLocale(options.locale);
3514
+ if (files.length === 0) {
3515
+ process.stderr.write("usage: wcs-validate [--attr=data-wcs] [--state-tag=wcs-state] [--lang=ja|en] <file> [<file> ...]\n");
3516
+ return 2;
3517
+ }
3518
+ const inputs = [];
3519
+ for (const path of files) {
3520
+ let text;
3521
+ try {
3522
+ text = (0, import_node_fs.readFileSync)(path, "utf8");
3523
+ } catch (e) {
3524
+ process.stderr.write(`cannot read ${path}: ${e.message}
3525
+ `);
3526
+ return 2;
3527
+ }
3528
+ inputs.push({ source: path, text, kind: classify(path) });
3529
+ }
3530
+ const result = runValidation(inputs, { ...options, locale });
3531
+ for (const line of result.lines) {
3532
+ process.stdout.write(line + "\n");
3533
+ }
3534
+ process.stdout.write(
3535
+ `
3536
+ ${result.errorCount} error(s), ${result.warningCount} warning(s), ${result.infoCount} info
3537
+ `
3538
+ );
3539
+ return result.exitCode;
3540
+ }
3541
+ if (typeof require !== "undefined" && typeof module !== "undefined" && require.main === module) {
3542
+ process.exit(main(process.argv.slice(2)));
3543
+ }
3544
+ // Annotate the CommonJS export names for ESM import in node:
3545
+ 0 && (module.exports = {
3546
+ main,
3547
+ parseArgs,
3548
+ resolveCliLocale
3549
+ });
3550
+ //# sourceMappingURL=cli.cjs.map