@wcstack/lint 2.6.1 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/cli.cjs +282 -865
  2. package/package.json +1 -1
package/dist/cli.cjs CHANGED
@@ -16,7 +16,7 @@ var __copyProps = (to, from, except, desc) => {
16
16
  }
17
17
  return to;
18
18
  };
19
- var __toCommonJS = (mod3) => __copyProps(__defProp({}, "__esModule", { value: true }), mod3);
19
+ var __toCommonJS = (mod2) => __copyProps(__defProp({}, "__esModule", { value: true }), mod2);
20
20
 
21
21
  // src/cli.ts
22
22
  var cli_exports = {};
@@ -187,9 +187,10 @@ var WcsDiagnosticCode = {
187
187
  RecursionDeclarationInvalid: "wcs/recursion-declaration-invalid",
188
188
  TypeAnnotation: "wcs/type-annotation",
189
189
  TemplateSyntax: "wcs/template-syntax",
190
- // @wcstack/state 3.0 が拒否する(または読み方を変える)書き方の予告(info)。判定はランタイムの
191
- // [wcs/v3-migration] と同じ正本(@wcstack/state/parser findV3MigrationIssues)。2.x 系だけの code。
192
- V3Migration: "wcs/v3-migration",
190
+ // ランタイムの正本パーサが [wcs/binding-syntax] で拒否する書き方(@wcstack/state 3.0 の文法の
191
+ // 厳格化: 閉じていない引用符・2 つ目の '#'・else: の値・構造ディレクティブの修飾子・空のフィルタ)。
192
+ // 判定は正本パーサに委ねる(service/bindingSyntaxValidator.ts)。
193
+ BindingSyntax: "wcs/binding-syntax",
193
194
  // --- <wcs-state> script: array reactivity hazards ---
194
195
  // 配列破壊的メソッド呼び出し(push 等 9 種)。Proxy を素通りしリアクティブ更新されない。
195
196
  // 同一参照の自己再代入でも要素の追加・削除は反映されない(docs/array-mutation-diagnostic-design.md §3)。
@@ -200,6 +201,9 @@ var WcsDiagnosticCode = {
200
201
  // --- built-in wcs-* tag contract (generated/builtinTags.generated.ts が正本) ---
201
202
  // 未知メンバーへのバインド(プロパティ / command. / eventToken. キー)。黙って無視される。
202
203
  TagMemberUnknown: "wcs/tag-member-unknown",
204
+ // タグのメンバー名が "on" で始まる(`once` 等)のに先頭ドット無しで束縛した: ランタイムはイベント束縛にして
205
+ // "ce" イベントを待ち、値は届かない。明示のプロパティ形 `.once:` を提案する(@wcstack/state 3.1・要件 B5 / 3.x 計画 D36)
206
+ OnPrefixedMember: "wcs/on-prefixed-member",
203
207
  // wcBindable 無宣言タグ(wcs-fetch-header 等のヘルパー)への spread。
204
208
  // ランタイム(expandSpread)は raiseError で落とす。
205
209
  SpreadNoBindable: "wcs/spread-no-bindable",
@@ -263,22 +267,15 @@ function valueMustBeDate(fnName) {
263
267
  function valueMustBeArray(fnName) {
264
268
  raiseError(`filter ${fnName} requires an array value`);
265
269
  }
266
- var warned = /* @__PURE__ */ new Set();
267
- function warnV3Migration(message) {
268
- if (warned.has(message)) {
269
- return;
270
- }
271
- warned.add(message);
272
- console.warn(`[@wcstack/state] [wcs/v3-migration] ${message} See "Preparing for 3.0" in the @wcstack/state README.`);
273
- }
274
270
  function validateNumberString(value) {
275
271
  if (!value || isNaN(Number(value))) {
276
272
  return false;
277
273
  }
278
274
  return true;
279
275
  }
280
- var eq = (options) => {
276
+ var eq = (options, literals) => {
281
277
  const opt = options?.[0] ?? optionsRequired("eq");
278
+ const literal2 = literals !== void 0 && literals.length > 0 ? literals[0] : opt;
282
279
  return (value) => {
283
280
  if (typeof value === "number") {
284
281
  if (!validateNumberString(opt)) {
@@ -289,11 +286,12 @@ var eq = (options) => {
289
286
  if (typeof value === "string") {
290
287
  return value === opt;
291
288
  }
292
- return value === opt;
289
+ return value === literal2;
293
290
  };
294
291
  };
295
- var ne = (options) => {
292
+ var ne = (options, literals) => {
296
293
  const opt = options?.[0] ?? optionsRequired("ne");
294
+ const literal2 = literals !== void 0 && literals.length > 0 ? literals[0] : opt;
297
295
  return (value) => {
298
296
  if (typeof value === "number") {
299
297
  if (!validateNumberString(opt)) {
@@ -304,7 +302,7 @@ var ne = (options) => {
304
302
  if (typeof value === "string") {
305
303
  return value !== opt;
306
304
  }
307
- return value !== opt;
305
+ return value !== literal2;
308
306
  };
309
307
  };
310
308
  var not = (_options) => {
@@ -697,29 +695,18 @@ var hms = (options) => {
697
695
  return `${hours}${opt}${minutes}${opt}${seconds}`;
698
696
  };
699
697
  };
700
- function warnBigIntZero(fnName, value) {
701
- if (value === 0n) {
702
- warnV3Migration(`"${fnName}" got 0n: 3.0 treats it as falsy.`);
703
- }
704
- }
705
698
  var falsy = (_options) => {
706
- return (value) => {
707
- warnBigIntZero("falsy", value);
708
- return value === false || value === null || value === void 0 || value === 0 || value === "" || Number.isNaN(value);
709
- };
699
+ return (value) => !value;
710
700
  };
711
701
  var truthy = (_options) => {
712
- return (value) => {
713
- warnBigIntZero("truthy", value);
714
- return value !== false && value !== null && value !== void 0 && value !== 0 && value !== "" && !Number.isNaN(value);
715
- };
702
+ return (value) => !!value;
716
703
  };
717
- var defaults = (options) => {
704
+ var defaults = (options, literals) => {
718
705
  const opt = options?.[0] ?? optionsRequired("defaults");
706
+ const fallback = literals !== void 0 && literals.length > 0 ? literals[0] : opt;
719
707
  return (value) => {
720
- warnBigIntZero("defaults", value);
721
- if (value === false || value === null || value === void 0 || value === 0 || value === "" || Number.isNaN(value)) {
722
- return opt;
708
+ if (!value) {
709
+ return fallback;
723
710
  }
724
711
  return value;
725
712
  };
@@ -939,6 +926,7 @@ function getWcsManifest() {
939
926
  elseKeyword: ELSE_KEYWORD,
940
927
  spread: SPREAD_PROP,
941
928
  eventPropertyPrefix: EVENT_PROP_PREFIX,
929
+ explicitPropertyPrefix: DELIMITER,
942
930
  propNamespaces: {
943
931
  eventToken: EVENT_TOKEN_NAMESPACE,
944
932
  command: COMMAND_NAMESPACE,
@@ -1034,11 +1022,11 @@ function foldSuffixIndexes(suffix) {
1034
1022
  }
1035
1023
  function foldRecursion(spec, path) {
1036
1024
  if (!path.startsWith(spec.anchor)) return null;
1037
- const unit3 = "." + spec.repeat;
1025
+ const unit2 = "." + spec.repeat;
1038
1026
  let cursor = spec.anchor.length;
1039
1027
  let depth = 0;
1040
- while (path.startsWith(unit3, cursor)) {
1041
- cursor += unit3.length;
1028
+ while (path.startsWith(unit2, cursor)) {
1029
+ cursor += unit2.length;
1042
1030
  depth++;
1043
1031
  }
1044
1032
  if (cursor !== path.length && path.charCodeAt(cursor) !== 46) return null;
@@ -1048,9 +1036,9 @@ function matchesRecursion(specs, path, has) {
1048
1036
  for (const spec of specs) {
1049
1037
  const folded = foldRecursion(spec, path);
1050
1038
  if (folded === null) continue;
1051
- const unit3 = "." + spec.repeat;
1039
+ const unit2 = "." + spec.repeat;
1052
1040
  for (let depth = folded.depth; depth >= 0; depth--) {
1053
- const rest = unit3.repeat(folded.depth - depth) + folded.rest;
1041
+ const rest = unit2.repeat(folded.depth - depth) + folded.rest;
1054
1042
  if (has(spec.anchor + rest)) return true;
1055
1043
  if (has(spec.recursiveAnchor + rest)) return true;
1056
1044
  for (let dot = rest.lastIndexOf("."); dot > 0; dot = rest.lastIndexOf(".", dot - 1)) {
@@ -1064,10 +1052,10 @@ function owningGetterSuffix(spec, getterSuffixes, path) {
1064
1052
  const pattern = indexSegmentsToWildcard(path);
1065
1053
  const folded = foldRecursion(spec, pattern);
1066
1054
  if (folded === null) return null;
1067
- const unit3 = "." + spec.repeat;
1055
+ const unit2 = "." + spec.repeat;
1068
1056
  for (const suffix of getterSuffixes) {
1069
1057
  for (let depth = folded.depth; depth >= 0; depth--) {
1070
- const expansion = spec.anchor + unit3.repeat(depth) + suffix;
1058
+ const expansion = spec.anchor + unit2.repeat(depth) + suffix;
1071
1059
  if (pattern === expansion || pattern.startsWith(expansion + ".")) return suffix;
1072
1060
  }
1073
1061
  }
@@ -1079,10 +1067,10 @@ function indexSegmentsToWildcard(path) {
1079
1067
  function concreteExpansionSuffix(spec, getterSuffixes, key) {
1080
1068
  const folded = foldRecursion(spec, key);
1081
1069
  if (folded === null) return null;
1082
- const unit3 = "." + spec.repeat;
1070
+ const unit2 = "." + spec.repeat;
1083
1071
  for (const suffix of getterSuffixes) {
1084
1072
  for (let depth = folded.depth; depth >= 0; depth--) {
1085
- if (key === spec.anchor + unit3.repeat(depth) + suffix) return suffix;
1073
+ if (key === spec.anchor + unit2.repeat(depth) + suffix) return suffix;
1086
1074
  }
1087
1075
  }
1088
1076
  return null;
@@ -1114,9 +1102,9 @@ function impliedStructurePaths(spec) {
1114
1102
  return out;
1115
1103
  }
1116
1104
  function structuralWriteTarget(spec, suffix) {
1117
- const unit3 = "." + spec.repeat;
1105
+ const unit2 = "." + spec.repeat;
1118
1106
  let rest = suffix;
1119
- while (rest.startsWith(unit3)) rest = rest.slice(unit3.length);
1107
+ while (rest.startsWith(unit2)) rest = rest.slice(unit2.length);
1120
1108
  if (rest.length === 0) return "node";
1121
1109
  if (rest === "." + spec.repeatList + ".length") return "length";
1122
1110
  const segments = spec.repeatList.split(".");
@@ -1126,15 +1114,15 @@ function structuralWriteTarget(spec, suffix) {
1126
1114
  return null;
1127
1115
  }
1128
1116
  function sameFamily(spec, a, b) {
1129
- const unit3 = "." + spec.repeat;
1117
+ const unit2 = "." + spec.repeat;
1130
1118
  const shorter = a.length <= b.length ? a : b;
1131
1119
  const longer = a.length <= b.length ? b : a;
1132
1120
  if (!longer.endsWith(shorter)) return false;
1133
1121
  const gap = longer.slice(0, longer.length - shorter.length);
1134
1122
  if (gap.length === 0) return true;
1135
- if (gap.length % unit3.length !== 0) return false;
1136
- for (let cursor = 0; cursor < gap.length; cursor += unit3.length) {
1137
- if (!gap.startsWith(unit3, cursor)) return false;
1123
+ if (gap.length % unit2.length !== 0) return false;
1124
+ for (let cursor = 0; cursor < gap.length; cursor += unit2.length) {
1125
+ if (!gap.startsWith(unit2, cursor)) return false;
1138
1126
  }
1139
1127
  return true;
1140
1128
  }
@@ -2324,8 +2312,8 @@ function matchOpenTag(html, pos, tagName) {
2324
2312
  const nameStart = pos + 1;
2325
2313
  const nameEnd = nameStart + tagName.length;
2326
2314
  if (nameEnd > html.length) return null;
2327
- const slice3 = html.slice(nameStart, nameEnd);
2328
- if (slice3.toLowerCase() !== tagName.toLowerCase()) return null;
2315
+ const slice2 = html.slice(nameStart, nameEnd);
2316
+ if (slice2.toLowerCase() !== tagName.toLowerCase()) return null;
2329
2317
  const charAfter = html[nameEnd];
2330
2318
  if (charAfter !== ">" && charAfter !== " " && charAfter !== " " && charAfter !== "\n" && charAfter !== "\r" && charAfter !== "/") {
2331
2319
  return null;
@@ -2575,8 +2563,8 @@ function getForTemplateDepthAt(html, openPos, offset2, bindAttrName) {
2575
2563
  }
2576
2564
 
2577
2565
  // src/core/messages.ts
2578
- function resolveLocale(locale3) {
2579
- if (locale3 === void 0 || locale3 === "" || /^ja\b|^ja[-_]/i.test(locale3) || locale3.toLowerCase() === "ja") return "ja";
2566
+ function resolveLocale(locale2) {
2567
+ if (locale2 === void 0 || locale2 === "" || /^ja\b|^ja[-_]/i.test(locale2) || locale2.toLowerCase() === "ja") return "ja";
2580
2568
  return "en";
2581
2569
  }
2582
2570
  var JA_EXPECTED_LABEL = {
@@ -2587,8 +2575,8 @@ var JA_EXPECTED_LABEL = {
2587
2575
  var ja = {
2588
2576
  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`,
2589
2577
  spreadTargetRequired: () => `\u30B9\u30D7\u30EC\u30C3\u30C9\u306B\u306F\u30BF\u30FC\u30B2\u30C3\u30C8\u30D1\u30B9\u304C\u5FC5\u8981\u3067\u3059`,
2590
- v3Migration: (detail) => `3.0 \u3078\u306E\u6E96\u5099\uFF082.x \u3067\u306F\u3053\u306E\u307E\u307E\u52D5\u304D\u307E\u3059\uFF09: ${detail}`,
2591
2578
  structuralMustBeSingle: (d) => `'${d}' \u30D0\u30A4\u30F3\u30C7\u30A3\u30F3\u30B0\u306F\u5358\u72EC\u3067\u6307\u5B9A\u3059\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059\uFF08';' \u3067\u4ED6\u306E\u30D0\u30A4\u30F3\u30C7\u30A3\u30F3\u30B0\u3068\u4F75\u8A18\u3067\u304D\u307E\u305B\u3093\u3002\u30E9\u30F3\u30BF\u30A4\u30E0\u306F\u8AAD\u307F\u8FBC\u307F\u6642\u306B throw \u3057\u307E\u3059\uFF09`,
2579
+ bindingSyntax: (detail) => `\u30D0\u30A4\u30F3\u30C7\u30A3\u30F3\u30B0\u306E\u69CB\u6587\u30A8\u30E9\u30FC\uFF08\u30E9\u30F3\u30BF\u30A4\u30E0\u306F\u8AAD\u307F\u8FBC\u307F\u6642\u306B throw \u3057\u307E\u3059\uFF09: ${detail}`,
2592
2580
  eventTokenUndeclared: (t) => `\u30A4\u30D9\u30F3\u30C8\u30C8\u30FC\u30AF\u30F3 "${t}" \u306F $eventTokens \u306B\u5BA3\u8A00\u3055\u308C\u3066\u3044\u307E\u305B\u3093`,
2593
2581
  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`,
2594
2582
  commandTokenUndeclared: (t) => `\u30B3\u30DE\u30F3\u30C9\u30C8\u30FC\u30AF\u30F3 "${t}" \u306F $commandTokens \u306B\u5BA3\u8A00\u3055\u308C\u3066\u3044\u307E\u305B\u3093`,
@@ -2653,6 +2641,7 @@ var ja = {
2653
2641
  arrayMutation: (m, alt) => `\u914D\u5217\u306E\u7834\u58CA\u7684\u30E1\u30BD\u30C3\u30C9 "${m}" \u306F\u30EA\u30A2\u30AF\u30C6\u30A3\u30D6\u66F4\u65B0\u3092\u30C8\u30EA\u30AC\u30FC\u3057\u307E\u305B\u3093\uFF08\u540C\u4E00\u53C2\u7167\u306E\u81EA\u5DF1\u518D\u4EE3\u5165\u3067\u3082\u8981\u7D20\u306E\u8FFD\u52A0\u30FB\u524A\u9664\u306F\u53CD\u6620\u3055\u308C\u307E\u305B\u3093\uFF09\u3002\u975E\u7834\u58CA\u30E1\u30BD\u30C3\u30C9\u3068\u518D\u4EE3\u5165\u3092\u4F7F\u7528\u3057\u3066\u304F\u3060\u3055\u3044\uFF08\u4F8B: ${alt}\uFF09\u3002`,
2654
2642
  arrayIndexAssign: (sp) => `\u914D\u5217\u30A4\u30F3\u30C7\u30C3\u30AF\u30B9\u3078\u306E\u76F4\u63A5\u4EE3\u5165\u306F\u30EA\u30A2\u30AF\u30C6\u30A3\u30D6\u66F4\u65B0\u3092\u30C8\u30EA\u30AC\u30FC\u3057\u307E\u305B\u3093\u3002this["${sp}"] \u306E\u3088\u3046\u306A\u30C9\u30C3\u30C8\u30D1\u30B9\u4EE3\u5165\u3001\u307E\u305F\u306F with() \u3068\u518D\u4EE3\u5165\u3092\u4F7F\u7528\u3057\u3066\u304F\u3060\u3055\u3044\u3002`,
2655
2643
  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`,
2644
+ onPrefixedMember: (member, tag) => `"${member}" \u306F <${tag}> \u306E\u30E1\u30F3\u30D0\u30FC\u3067\u3059\u304C\u3001"on" \u3067\u59CB\u307E\u308B\u540D\u524D\u306F\u30A4\u30D9\u30F3\u30C8\u675F\u7E1B\u306B\u306A\u308A\uFF08"${member.slice(2)}" \u30A4\u30D9\u30F3\u30C8\u3092\u5F85\u3064\uFF09\u3001\u5024\u306F\u5C4A\u304D\u307E\u305B\u3093\u3002\u30D7\u30ED\u30D1\u30C6\u30A3\u3068\u3057\u3066\u675F\u7E1B\u3059\u308B\u306B\u306F ".${member}:" \u3068\u66F8\u3044\u3066\u304F\u3060\u3055\u3044\uFF08@wcstack/state 3.1\uFF09`,
2656
2645
  tagCommandUnknown: (name, tag, declared) => `"${name}" \u306F <${tag}> \u306E command \u3067\u306F\u3042\u308A\u307E\u305B\u3093\uFF08\u5BA3\u8A00\u6E08\u307F: ${declared}\uFF09`,
2657
2646
  spreadNoBindable: (tag) => `'...'\uFF08spread\uFF09\u306F <${tag}> \u306B\u6709\u52B9\u306A wcBindable \u5BA3\u8A00\u304C\u5FC5\u8981\u3067\u3059 \u2014 \u3053\u306E\u30BF\u30B0\u306F\u5BA3\u8A00\u3092\u6301\u305F\u306A\u3044\u305F\u3081\u3001\u30E9\u30F3\u30BF\u30A4\u30E0\u306F\u30A8\u30E9\u30FC\u3092\u9001\u51FA\u3057\u307E\u3059`,
2658
2647
  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`,
@@ -2776,8 +2765,8 @@ var EN_EXPECTED_LABEL = {
2776
2765
  var en = {
2777
2766
  spreadFilterNotAllowed: () => `Filters cannot be applied to a spread target`,
2778
2767
  spreadTargetRequired: () => `Spread requires a target path`,
2779
- v3Migration: (detail) => `Preparing for 3.0 (this still runs on 2.x): ${detail}`,
2780
2768
  structuralMustBeSingle: (d) => `'${d}' must be the only binding in this attribute (it cannot be combined with ';'; the runtime throws at load time)`,
2769
+ bindingSyntax: (detail) => `Binding syntax error (the runtime throws at load time): ${detail}`,
2781
2770
  eventTokenUndeclared: (t) => `Event token "${t}" is not declared in $eventTokens`,
2782
2771
  commandRhsFormat: () => `The right side of a command binding must be $command.<name> (declared in $commandTokens)`,
2783
2772
  commandTokenUndeclared: (t) => `Command token "${t}" is not declared in $commandTokens`,
@@ -2842,6 +2831,7 @@ var en = {
2842
2831
  arrayMutation: (m, alt) => `Destructive array method "${m}" does not trigger a reactive update (re-assigning the same reference does not reflect added/removed elements either). Use a non-destructive method with reassignment (e.g. ${alt}).`,
2843
2832
  arrayIndexAssign: (sp) => `Assigning directly to an array index does not trigger a reactive update. Use a dot-path assignment like this["${sp}"], or with() plus reassignment.`,
2844
2833
  tagMemberUnknown: (prop, tag) => `"${prop}" is not a wcBindable member of <${tag}> (bindings to unknown members are silently ignored)`,
2834
+ onPrefixedMember: (member, tag) => `"${member}" is a member of <${tag}>, but a name starting with "on" makes an event binding (it listens for a "${member.slice(2)}" event) and the value never arrives. Write ".${member}:" to bind the property (@wcstack/state 3.1)`,
2845
2835
  tagCommandUnknown: (name, tag, declared) => `"${name}" is not a command of <${tag}> (declared: ${declared})`,
2846
2836
  spreadNoBindable: (tag) => `'...' (spread) requires <${tag}> to expose a valid wcBindable declaration \u2014 this tag declares none, so the runtime raises an error`,
2847
2837
  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})`,
@@ -2958,8 +2948,8 @@ var en = {
2958
2948
  recursionInMountedComponent: (subject) => `${subject} is not run by a mounted component (bind-component); the runtime warns with wcs/mount-dollar-declaration and drops it. Declare $recursion and "**" getters on the root state \u2014 the anchor path is resolved against the root tree`
2959
2949
  };
2960
2950
  var CATALOGS = { ja, en };
2961
- function getMessages(locale3) {
2962
- return CATALOGS[resolveLocale(locale3)];
2951
+ function getMessages(locale2) {
2952
+ return CATALOGS[resolveLocale(locale2)];
2963
2953
  }
2964
2954
 
2965
2955
  // src/core/sidecar/schemaSubset.ts
@@ -3156,9 +3146,9 @@ function escape(key) {
3156
3146
 
3157
3147
  // src/service/bindingValidator.ts
3158
3148
  var filterMap = new Map(BUILTIN_FILTERS.map((f) => [f.name, f]));
3159
- function validateBindings(html, attrName, stateTagName = "wcs-state", locale3, fileReader, applicationSchema) {
3149
+ function validateBindings(html, attrName, stateTagName = "wcs-state", locale2, fileReader, applicationSchema) {
3160
3150
  const diagnostics = [];
3161
- const msgs = getMessages(locale3);
3151
+ const msgs = getMessages(locale2);
3162
3152
  const statePaths = mergeSchemaCandidates(getStatePathsFromHtml(html, stateTagName, fileReader), applicationSchema);
3163
3153
  const attrs = findAllBindAttributes(html, attrName);
3164
3154
  let structuralTemplates = null;
@@ -3715,8 +3705,8 @@ function isLiteral(value) {
3715
3705
  }
3716
3706
 
3717
3707
  // src/service/stateTypeValidator.ts
3718
- function validateStateTypes(html, stateTagName = "wcs-state", locale3) {
3719
- const msgs = getMessages(locale3);
3708
+ function validateStateTypes(html, stateTagName = "wcs-state", locale2) {
3709
+ const msgs = getMessages(locale2);
3720
3710
  const blocks = parseWcsScriptBlocks(html, stateTagName);
3721
3711
  const diagnostics = [];
3722
3712
  for (const block of blocks) {
@@ -3843,8 +3833,8 @@ function isApiRoot(root) {
3843
3833
  // src/service/nestedAssignValidator.ts
3844
3834
  var NESTED_ASSIGN = new RegExp(`${ROOT_DOT}(${CHAIN_ONE_PLUS})${ASSIGN_TAIL}`, "g");
3845
3835
  var PRE_NESTED_INCDEC = new RegExp(`${PRE_INCDEC}${ROOT_DOT}(${CHAIN_ONE_PLUS})`, "g");
3846
- function validateNestedAssigns(html, stateTagName = "wcs-state", locale3) {
3847
- const msgs = getMessages(locale3);
3836
+ function validateNestedAssigns(html, stateTagName = "wcs-state", locale2) {
3837
+ const msgs = getMessages(locale2);
3848
3838
  const blocks = parseWcsScriptBlocks(html, stateTagName);
3849
3839
  const diagnostics = [];
3850
3840
  for (const block of blocks) {
@@ -3895,8 +3885,8 @@ var PRE_BRACKET_INDEX = new RegExp(`${PRE_INCDEC}${ROOT_BRACKET}(${BRACKETS_ONLY
3895
3885
  function toAccessor(path) {
3896
3886
  return /^[A-Za-z_]\w*$/.test(path) ? `this.${path}` : `this["${path}"]`;
3897
3887
  }
3898
- function validateArrayMutations(html, stateTagName = "wcs-state", locale3) {
3899
- const msgs = getMessages(locale3);
3888
+ function validateArrayMutations(html, stateTagName = "wcs-state", locale2) {
3889
+ const msgs = getMessages(locale2);
3900
3890
  const blocks = parseWcsScriptBlocks(html, stateTagName);
3901
3891
  const diagnostics = [];
3902
3892
  for (const block of blocks) {
@@ -4006,9 +3996,9 @@ function isInsideTag(html, offset2, tagName) {
4006
3996
  }
4007
3997
 
4008
3998
  // src/service/templateSyntaxValidator.ts
4009
- function validateTemplateSyntax(html, stateTagName, bindAttrName = "data-wcs", locale3, fileReader, applicationSchema) {
3999
+ function validateTemplateSyntax(html, stateTagName, bindAttrName = "data-wcs", locale2, fileReader, applicationSchema) {
4010
4000
  const diagnostics = [];
4011
- const msgs = getMessages(locale3);
4001
+ const msgs = getMessages(locale2);
4012
4002
  const allPaths = mergeSchemaCandidates(getStatePathsFromHtml(html, stateTagName, fileReader), applicationSchema);
4013
4003
  const defaultSchema = applicationSchema;
4014
4004
  const missingVerdict = (path, displayPath, pathSet2, scoped) => {
@@ -4166,6 +4156,10 @@ var ELSE_KEYWORD2 = "else";
4166
4156
  var SPREAD_PROP2 = "...";
4167
4157
  var EVENT_PROP_PREFIX2 = "on";
4168
4158
  var EVENT_TOKEN_NAMESPACE2 = "eventToken";
4159
+ var COMMAND_NAMESPACE2 = "command";
4160
+ var CLASS_NAMESPACE2 = "class";
4161
+ var ATTR_NAMESPACE2 = "attr";
4162
+ var STYLE_NAMESPACE2 = "style";
4169
4163
  var INDEX_PARAM_PREFIX2 = "$";
4170
4164
  var tmpIndexByIndexName2 = {};
4171
4165
  for (let i = 0; i < MAX_WILDCARD_DEPTH2; i++) {
@@ -4274,43 +4268,6 @@ var PathInfo = class {
4274
4268
  this.wildcardCount = wildcardCount;
4275
4269
  }
4276
4270
  };
4277
- function editDistance(a, b, max) {
4278
- if (Math.abs(a.length - b.length) > max) {
4279
- return max + 1;
4280
- }
4281
- const prev = new Array(b.length + 1);
4282
- const curr = new Array(b.length + 1);
4283
- for (let j = 0; j <= b.length; j++) {
4284
- prev[j] = j;
4285
- }
4286
- for (let i = 1; i <= a.length; i++) {
4287
- curr[0] = i;
4288
- for (let j = 1; j <= b.length; j++) {
4289
- const cost = a[i - 1] === b[j - 1] ? 0 : 1;
4290
- curr[j] = Math.min(prev[j] + 1, curr[j - 1] + 1, prev[j - 1] + cost);
4291
- }
4292
- for (let j = 0; j <= b.length; j++) {
4293
- prev[j] = curr[j];
4294
- }
4295
- }
4296
- return prev[b.length];
4297
- }
4298
- function didYouMean(input, candidates) {
4299
- if (input.length === 0) {
4300
- return "";
4301
- }
4302
- const folded = input.toLowerCase();
4303
- let best = null;
4304
- let bestDistance = 3;
4305
- for (const candidate of candidates) {
4306
- const distance = editDistance(folded, candidate.toLowerCase(), 2);
4307
- if (distance < bestDistance) {
4308
- best = candidate;
4309
- bestDistance = distance;
4310
- }
4311
- }
4312
- return best !== null ? ` Did you mean "${best}"?` : "";
4313
- }
4314
4271
  var LINT_HINT = " Validate statically: npx @wcstack/lint <file>.";
4315
4272
  var STRUCTURAL_BINDING_TYPE_SET2 = /* @__PURE__ */ new Set([
4316
4273
  "if",
@@ -4318,660 +4275,10 @@ var STRUCTURAL_BINDING_TYPE_SET2 = /* @__PURE__ */ new Set([
4318
4275
  "else",
4319
4276
  "for"
4320
4277
  ]);
4321
- var _config2 = {
4322
- locale: "en"
4323
- };
4324
- var config2 = _config2;
4325
- function optionsRequired2(fnName) {
4326
- raiseError2(`filter ${fnName} requires at least one option`);
4327
- }
4328
- function optionMustBeNumber2(fnName) {
4329
- raiseError2(`filter ${fnName} requires a number as option`);
4330
- }
4331
- function valueMustBeNumber2(fnName) {
4332
- raiseError2(`filter ${fnName} requires a number value`);
4333
- }
4334
- function valueMustBeBoolean2(fnName) {
4335
- raiseError2(`filter ${fnName} requires a boolean value`);
4336
- }
4337
- function valueMustBeDate2(fnName) {
4338
- raiseError2(`filter ${fnName} requires a date value`);
4339
- }
4340
- function valueMustBeArray2(fnName) {
4341
- raiseError2(`filter ${fnName} requires an array value`);
4342
- }
4343
- var NO_ARGS = /* @__PURE__ */ new Set([
4344
- "not",
4345
- "abs",
4346
- "uc",
4347
- "lc",
4348
- "cap",
4349
- "trim",
4350
- "rev",
4351
- "int",
4352
- "float",
4353
- "date",
4354
- "time",
4355
- "datetime",
4356
- "falsy",
4357
- "truthy",
4358
- "boolean",
4359
- "number",
4360
- "string",
4361
- "null"
4362
- ]);
4363
- var TWO_ARGS = /* @__PURE__ */ new Set(["clamp", "slice", "substr", "pad", "truncate"]);
4364
- function maxFilterArgs(name) {
4365
- return NO_ARGS.has(name) ? 0 : TWO_ARGS.has(name) ? 2 : 1;
4366
- }
4367
- var STRUCTURAL_KEYWORDS = /* @__PURE__ */ new Set(["for", "if", "elseif", "else", "..."]);
4368
- var TYPED_LITERAL = /(?:^|\|)\s*(eq|ne|defaults)\s*\(\s*(true|false|null)\s*\)/g;
4369
- function hasUnterminatedQuote(text) {
4370
- let quote = null;
4371
- for (let i = 0; i < text.length; i++) {
4372
- const c = text[i];
4373
- if (quote !== null) {
4374
- if (c === quote)
4375
- quote = null;
4376
- } else if (c === "'" || c === '"') {
4377
- quote = c;
4378
- }
4379
- }
4380
- return quote !== null;
4381
- }
4382
- function checkStatePart(text, parsed, issues) {
4383
- if (hasUnterminatedQuote(text)) {
4384
- issues.push(`"${text}": 3.0 rejects the unterminated quote.`);
4385
- }
4386
- for (const [, fnName, literal2] of text.matchAll(TYPED_LITERAL)) {
4387
- issues.push(`"${fnName}(${literal2})": 3.0 reads an unquoted ${literal2} as a ${literal2 === "null" ? "null" : "boolean"}, not the text. Write ${fnName}('${literal2}') to keep the text.`);
4388
- }
4389
- if (parsed !== null) {
4390
- issues.push(...findFilterArityIssues([parsed]));
4391
- }
4392
- }
4393
- function findFilterArityIssues(results) {
4394
- const issues = [];
4395
- for (const result of results) {
4396
- for (const filter of [...result.inFilters, ...result.outFilters]) {
4397
- const max = maxFilterArgs(filter.filterName);
4398
- if (filter.args.length > max) {
4399
- issues.push(`"${filter.filterName}" takes at most ${max} argument(s); 3.0 rejects more.`);
4400
- }
4401
- }
4402
- }
4403
- return issues;
4404
- }
4405
- function findV3MigrationIssues(expr, parsed = null) {
4406
- const issues = [];
4407
- const colon = expr.indexOf(":");
4408
- if (colon === -1)
4409
- return issues;
4410
- const propPart = expr.slice(0, colon).trim();
4411
- const modifierParts = propPart.split("#");
4412
- const keyword = modifierParts[0].split("|")[0].trim();
4413
- if (modifierParts.length > 2) {
4414
- issues.push(`"${propPart}": 3.0 rejects a second "#". Write "${modifierParts[0].trim()}#${modifierParts.slice(1).map((m) => m.trim()).join(",")}".`);
4415
- }
4416
- if (keyword === "else" && expr.slice(colon + 1).trim().length > 0) {
4417
- issues.push(`"${expr}": 3.0 rejects a value after "else:".`);
4418
- }
4419
- if (STRUCTURAL_KEYWORDS.has(keyword) && keyword !== propPart) {
4420
- issues.push(`"${propPart}": 3.0 rejects modifiers and filters on "${keyword}". Write "${keyword}:".`);
4421
- }
4422
- if ((keyword === "radio" || keyword === "checkbox") && keyword !== propPart) {
4423
- issues.push(`"${propPart}": 3.0 keeps this a ${keyword} binding that honours the modifiers (2.x binds a property named "${keyword}").`);
4424
- }
4425
- checkStatePart(expr.slice(colon + 1).trim(), parsed, issues);
4426
- return issues;
4427
- }
4428
- function findEmbeddedV3MigrationIssues(expression, parsed = null) {
4429
- const issues = [];
4430
- checkStatePart(expression, parsed, issues);
4431
- return issues;
4432
- }
4433
- var warned2 = /* @__PURE__ */ new Set();
4434
- function warnV3Migration2(message) {
4435
- if (warned2.has(message)) {
4436
- return;
4437
- }
4438
- warned2.add(message);
4439
- console.warn(`[@wcstack/state] [wcs/v3-migration] ${message} See "Preparing for 3.0" in the @wcstack/state README.`);
4440
- }
4441
- function validateNumberString2(value) {
4442
- if (!value || isNaN(Number(value))) {
4443
- return false;
4444
- }
4445
- return true;
4446
- }
4447
- var eq2 = (options) => {
4448
- const opt = options?.[0] ?? optionsRequired2("eq");
4449
- return (value) => {
4450
- if (typeof value === "number") {
4451
- if (!validateNumberString2(opt)) {
4452
- optionMustBeNumber2("eq");
4453
- }
4454
- return value === Number(opt);
4455
- }
4456
- if (typeof value === "string") {
4457
- return value === opt;
4458
- }
4459
- return value === opt;
4460
- };
4461
- };
4462
- var ne2 = (options) => {
4463
- const opt = options?.[0] ?? optionsRequired2("ne");
4464
- return (value) => {
4465
- if (typeof value === "number") {
4466
- if (!validateNumberString2(opt)) {
4467
- optionMustBeNumber2("ne");
4468
- }
4469
- return value !== Number(opt);
4470
- }
4471
- if (typeof value === "string") {
4472
- return value !== opt;
4473
- }
4474
- return value !== opt;
4475
- };
4476
- };
4477
- var not2 = (_options) => {
4478
- return (value) => {
4479
- if (typeof value !== "boolean") {
4480
- valueMustBeBoolean2("not");
4481
- }
4482
- return !value;
4483
- };
4484
- };
4485
- var lt2 = (options) => {
4486
- const opt = options?.[0] ?? optionsRequired2("lt");
4487
- if (!validateNumberString2(opt)) {
4488
- optionMustBeNumber2("lt");
4489
- }
4490
- return (value) => {
4491
- if (typeof value !== "number") {
4492
- valueMustBeNumber2("lt");
4493
- }
4494
- return value < Number(opt);
4495
- };
4496
- };
4497
- var le2 = (options) => {
4498
- const opt = options?.[0] ?? optionsRequired2("le");
4499
- if (!validateNumberString2(opt)) {
4500
- optionMustBeNumber2("le");
4501
- }
4502
- return (value) => {
4503
- if (typeof value !== "number") {
4504
- valueMustBeNumber2("le");
4505
- }
4506
- return value <= Number(opt);
4507
- };
4508
- };
4509
- var gt2 = (options) => {
4510
- const opt = options?.[0] ?? optionsRequired2("gt");
4511
- if (!validateNumberString2(opt)) {
4512
- optionMustBeNumber2("gt");
4513
- }
4514
- return (value) => {
4515
- if (typeof value !== "number") {
4516
- valueMustBeNumber2("gt");
4517
- }
4518
- return value > Number(opt);
4519
- };
4520
- };
4521
- var ge2 = (options) => {
4522
- const opt = options?.[0] ?? optionsRequired2("ge");
4523
- if (!validateNumberString2(opt)) {
4524
- optionMustBeNumber2("ge");
4525
- }
4526
- return (value) => {
4527
- if (typeof value !== "number") {
4528
- valueMustBeNumber2("ge");
4529
- }
4530
- return value >= Number(opt);
4531
- };
4532
- };
4533
- var inc2 = (options) => {
4534
- const opt = options?.[0] ?? optionsRequired2("inc");
4535
- if (!validateNumberString2(opt)) {
4536
- optionMustBeNumber2("inc");
4537
- }
4538
- return (value) => {
4539
- if (typeof value !== "number") {
4540
- valueMustBeNumber2("inc");
4541
- }
4542
- return value + Number(opt);
4543
- };
4544
- };
4545
- var dec2 = (options) => {
4546
- const opt = options?.[0] ?? optionsRequired2("dec");
4547
- if (!validateNumberString2(opt)) {
4548
- optionMustBeNumber2("dec");
4549
- }
4550
- return (value) => {
4551
- if (typeof value !== "number") {
4552
- valueMustBeNumber2("dec");
4553
- }
4554
- return value - Number(opt);
4555
- };
4556
- };
4557
- var mul2 = (options) => {
4558
- const opt = options?.[0] ?? optionsRequired2("mul");
4559
- if (!validateNumberString2(opt)) {
4560
- optionMustBeNumber2("mul");
4561
- }
4562
- return (value) => {
4563
- if (typeof value !== "number") {
4564
- valueMustBeNumber2("mul");
4565
- }
4566
- return value * Number(opt);
4567
- };
4568
- };
4569
- var div2 = (options) => {
4570
- const opt = options?.[0] ?? optionsRequired2("div");
4571
- if (!validateNumberString2(opt)) {
4572
- optionMustBeNumber2("div");
4573
- }
4574
- return (value) => {
4575
- if (typeof value !== "number") {
4576
- valueMustBeNumber2("div");
4577
- }
4578
- return value / Number(opt);
4579
- };
4580
- };
4581
- var mod2 = (options) => {
4582
- const opt = options?.[0] ?? optionsRequired2("mod");
4583
- if (!validateNumberString2(opt)) {
4584
- optionMustBeNumber2("mod");
4585
- }
4586
- return (value) => {
4587
- if (typeof value !== "number") {
4588
- valueMustBeNumber2("mod");
4589
- }
4590
- return value % Number(opt);
4591
- };
4592
- };
4593
- var abs2 = (_options) => {
4594
- return (value) => {
4595
- if (typeof value !== "number") {
4596
- valueMustBeNumber2("abs");
4597
- }
4598
- return Math.abs(value);
4599
- };
4600
- };
4601
- var clamp2 = (options) => {
4602
- const opt1 = options?.[0] ?? optionsRequired2("clamp");
4603
- if (!validateNumberString2(opt1)) {
4604
- optionMustBeNumber2("clamp");
4605
- }
4606
- const opt2 = options?.[1] ?? optionsRequired2("clamp");
4607
- if (!validateNumberString2(opt2)) {
4608
- optionMustBeNumber2("clamp");
4609
- }
4610
- const min = Number(opt1);
4611
- const max = Number(opt2);
4612
- return (value) => {
4613
- if (typeof value !== "number") {
4614
- valueMustBeNumber2("clamp");
4615
- }
4616
- return Math.min(Math.max(value, min), max);
4617
- };
4618
- };
4619
- var fix2 = (options) => {
4620
- const opt = options?.[0] ?? "0";
4621
- if (!validateNumberString2(opt)) {
4622
- optionMustBeNumber2("fix");
4623
- }
4624
- return (value) => {
4625
- if (typeof value !== "number") {
4626
- valueMustBeNumber2("fix");
4627
- }
4628
- return value.toFixed(Number(opt));
4629
- };
4630
- };
4631
- var locale2 = (options) => {
4632
- const explicit = options?.[0];
4633
- return (value) => {
4634
- if (typeof value !== "number") {
4635
- valueMustBeNumber2("locale");
4636
- }
4637
- return value.toLocaleString(explicit ?? config2.locale);
4638
- };
4639
- };
4640
- var uc2 = (_options) => {
4641
- return (value) => {
4642
- return String(value).toUpperCase();
4643
- };
4644
- };
4645
- var lc2 = (_options) => {
4646
- return (value) => {
4647
- return String(value).toLowerCase();
4648
- };
4649
- };
4650
- var cap2 = (_options) => {
4651
- return (value) => {
4652
- const v = String(value);
4653
- if (v.length === 0) {
4654
- return v;
4655
- }
4656
- if (v.length === 1) {
4657
- return v.toUpperCase();
4658
- }
4659
- return v.charAt(0).toUpperCase() + v.slice(1);
4660
- };
4661
- };
4662
- var trim2 = (_options) => {
4663
- return (value) => {
4664
- return String(value).trim();
4665
- };
4666
- };
4667
- var slice2 = (options) => {
4668
- const numberedOpts = [];
4669
- const opt1 = options?.[0] ?? optionsRequired2("slice");
4670
- if (!validateNumberString2(opt1)) {
4671
- optionMustBeNumber2("slice");
4672
- }
4673
- numberedOpts.push(Number(opt1));
4674
- const opt2 = options?.[1];
4675
- if (typeof opt2 !== "undefined") {
4676
- if (!validateNumberString2(opt2)) {
4677
- optionMustBeNumber2("slice");
4678
- }
4679
- numberedOpts.push(Number(opt2));
4680
- }
4681
- return (value) => {
4682
- return String(value).slice(...numberedOpts);
4683
- };
4684
- };
4685
- var substr2 = (options) => {
4686
- const opt1 = options?.[0] ?? optionsRequired2("substr");
4687
- if (!validateNumberString2(opt1)) {
4688
- optionMustBeNumber2("substr");
4689
- }
4690
- const opt2 = options?.[1] ?? optionsRequired2("substr");
4691
- if (!validateNumberString2(opt2)) {
4692
- optionMustBeNumber2("substr");
4693
- }
4694
- return (value) => {
4695
- return String(value).substr(Number(opt1), Number(opt2));
4696
- };
4697
- };
4698
- var pad2 = (options) => {
4699
- const opt1 = options?.[0] ?? optionsRequired2("pad");
4700
- if (!validateNumberString2(opt1)) {
4701
- optionMustBeNumber2("pad");
4702
- }
4703
- const opt2 = options?.[1] ?? "0";
4704
- return (value) => {
4705
- return String(value).padStart(Number(opt1), opt2);
4706
- };
4707
- };
4708
- var rep2 = (options) => {
4709
- const opt = options?.[0] ?? optionsRequired2("rep");
4710
- if (!validateNumberString2(opt)) {
4711
- optionMustBeNumber2("rep");
4712
- }
4713
- return (value) => {
4714
- return String(value).repeat(Number(opt));
4715
- };
4716
- };
4717
- var rev2 = (_options) => {
4718
- return (value) => {
4719
- return String(value).split("").reverse().join("");
4720
- };
4721
- };
4722
- var int2 = (_options) => {
4723
- return (value) => {
4724
- return parseInt(String(value), 10);
4725
- };
4726
- };
4727
- var float2 = (_options) => {
4728
- return (value) => {
4729
- return parseFloat(String(value));
4730
- };
4731
- };
4732
- var round2 = (options) => {
4733
- const opt = options?.[0] ?? "0";
4734
- if (!validateNumberString2(opt)) {
4735
- optionMustBeNumber2("round");
4736
- }
4737
- return (value) => {
4738
- if (typeof value !== "number") {
4739
- valueMustBeNumber2("round");
4740
- }
4741
- const optValue = Math.pow(10, Number(opt));
4742
- return Math.round(value * optValue) / optValue;
4743
- };
4744
- };
4745
- var floor2 = (options) => {
4746
- const opt = options?.[0] ?? "0";
4747
- if (!validateNumberString2(opt)) {
4748
- optionMustBeNumber2("floor");
4749
- }
4750
- return (value) => {
4751
- if (typeof value !== "number") {
4752
- valueMustBeNumber2("floor");
4753
- }
4754
- const optValue = Math.pow(10, Number(opt));
4755
- return Math.floor(value * optValue) / optValue;
4756
- };
4757
- };
4758
- var ceil2 = (options) => {
4759
- const opt = options?.[0] ?? "0";
4760
- if (!validateNumberString2(opt)) {
4761
- optionMustBeNumber2("ceil");
4762
- }
4763
- return (value) => {
4764
- if (typeof value !== "number") {
4765
- valueMustBeNumber2("ceil");
4766
- }
4767
- const optValue = Math.pow(10, Number(opt));
4768
- return Math.ceil(value * optValue) / optValue;
4769
- };
4770
- };
4771
- var percent2 = (options) => {
4772
- const opt = options?.[0] ?? "0";
4773
- if (!validateNumberString2(opt)) {
4774
- optionMustBeNumber2("percent");
4775
- }
4776
- return (value) => {
4777
- if (typeof value !== "number") {
4778
- valueMustBeNumber2("percent");
4779
- }
4780
- return `${(value * 100).toFixed(Number(opt))}%`;
4781
- };
4782
- };
4783
- var unit2 = (options) => {
4784
- const opt = options?.[0] ?? optionsRequired2("unit");
4785
- return (value) => {
4786
- if (value === null || typeof value === "undefined") {
4787
- return value;
4788
- }
4789
- return String(value) + opt;
4790
- };
4791
- };
4792
- var join2 = (options) => {
4793
- const opt = options?.[0] ?? ", ";
4794
- return (value) => {
4795
- if (!Array.isArray(value)) {
4796
- valueMustBeArray2("join");
4797
- }
4798
- return value.join(opt);
4799
- };
4800
- };
4801
- var truncate2 = (options) => {
4802
- const opt1 = options?.[0] ?? optionsRequired2("truncate");
4803
- if (!validateNumberString2(opt1)) {
4804
- optionMustBeNumber2("truncate");
4805
- }
4806
- const maxLength = Number(opt1);
4807
- const suffix = options?.[1] ?? "\u2026";
4808
- return (value) => {
4809
- const v = String(value);
4810
- if (v.length <= maxLength) {
4811
- return v;
4812
- }
4813
- return v.slice(0, maxLength) + suffix;
4814
- };
4815
- };
4816
- var date2 = (options) => {
4817
- const explicit = options?.[0];
4818
- return (value) => {
4819
- if (!(value instanceof Date)) {
4820
- valueMustBeDate2("date");
4821
- }
4822
- return value.toLocaleDateString(explicit ?? config2.locale);
4823
- };
4824
- };
4825
- var time2 = (options) => {
4826
- const explicit = options?.[0];
4827
- return (value) => {
4828
- if (!(value instanceof Date)) {
4829
- valueMustBeDate2("time");
4830
- }
4831
- return value.toLocaleTimeString(explicit ?? config2.locale);
4832
- };
4833
- };
4834
- var datetime2 = (options) => {
4835
- const explicit = options?.[0];
4836
- return (value) => {
4837
- if (!(value instanceof Date)) {
4838
- valueMustBeDate2("datetime");
4839
- }
4840
- return value.toLocaleString(explicit ?? config2.locale);
4841
- };
4842
- };
4843
- var ymd2 = (options) => {
4844
- const opt = options?.[0] ?? "-";
4845
- return (value) => {
4846
- if (!(value instanceof Date)) {
4847
- valueMustBeDate2("ymd");
4848
- }
4849
- const year = value.getFullYear().toString();
4850
- const month = (value.getMonth() + 1).toString().padStart(2, "0");
4851
- const day = value.getDate().toString().padStart(2, "0");
4852
- return `${year}${opt}${month}${opt}${day}`;
4853
- };
4854
- };
4855
- var hms2 = (options) => {
4856
- const opt = options?.[0] ?? ":";
4857
- return (value) => {
4858
- if (!(value instanceof Date)) {
4859
- valueMustBeDate2("hms");
4860
- }
4861
- const hours = value.getHours().toString().padStart(2, "0");
4862
- const minutes = value.getMinutes().toString().padStart(2, "0");
4863
- const seconds = value.getSeconds().toString().padStart(2, "0");
4864
- return `${hours}${opt}${minutes}${opt}${seconds}`;
4865
- };
4866
- };
4867
- function warnBigIntZero2(fnName, value) {
4868
- if (value === 0n) {
4869
- warnV3Migration2(`"${fnName}" got 0n: 3.0 treats it as falsy.`);
4870
- }
4278
+ var resolvedByKey = /* @__PURE__ */ new Map();
4279
+ function clearFilterResolutionCache() {
4280
+ resolvedByKey.clear();
4871
4281
  }
4872
- var falsy2 = (_options) => {
4873
- return (value) => {
4874
- warnBigIntZero2("falsy", value);
4875
- return value === false || value === null || value === void 0 || value === 0 || value === "" || Number.isNaN(value);
4876
- };
4877
- };
4878
- var truthy2 = (_options) => {
4879
- return (value) => {
4880
- warnBigIntZero2("truthy", value);
4881
- return value !== false && value !== null && value !== void 0 && value !== 0 && value !== "" && !Number.isNaN(value);
4882
- };
4883
- };
4884
- var defaults2 = (options) => {
4885
- const opt = options?.[0] ?? optionsRequired2("defaults");
4886
- return (value) => {
4887
- warnBigIntZero2("defaults", value);
4888
- if (value === false || value === null || value === void 0 || value === 0 || value === "" || Number.isNaN(value)) {
4889
- return opt;
4890
- }
4891
- return value;
4892
- };
4893
- };
4894
- var boolean2 = (_options) => {
4895
- return (value) => {
4896
- return Boolean(value);
4897
- };
4898
- };
4899
- var number2 = (_options) => {
4900
- return (value) => {
4901
- return Number(value);
4902
- };
4903
- };
4904
- var string2 = (_options) => {
4905
- return (value) => {
4906
- return String(value);
4907
- };
4908
- };
4909
- var _null2 = (_options) => {
4910
- return (value) => {
4911
- return value === "" ? null : value;
4912
- };
4913
- };
4914
- var builtinFilters2 = {
4915
- "eq": eq2,
4916
- "ne": ne2,
4917
- "not": not2,
4918
- "lt": lt2,
4919
- "le": le2,
4920
- "gt": gt2,
4921
- "ge": ge2,
4922
- "inc": inc2,
4923
- "dec": dec2,
4924
- "mul": mul2,
4925
- "div": div2,
4926
- "mod": mod2,
4927
- "abs": abs2,
4928
- "clamp": clamp2,
4929
- "fix": fix2,
4930
- "locale": locale2,
4931
- "uc": uc2,
4932
- "lc": lc2,
4933
- "cap": cap2,
4934
- "trim": trim2,
4935
- "slice": slice2,
4936
- "substr": substr2,
4937
- "pad": pad2,
4938
- "rep": rep2,
4939
- "rev": rev2,
4940
- "truncate": truncate2,
4941
- "join": join2,
4942
- "int": int2,
4943
- "float": float2,
4944
- "round": round2,
4945
- "floor": floor2,
4946
- "ceil": ceil2,
4947
- "percent": percent2,
4948
- "unit": unit2,
4949
- "date": date2,
4950
- "time": time2,
4951
- "datetime": datetime2,
4952
- "ymd": ymd2,
4953
- "hms": hms2,
4954
- "falsy": falsy2,
4955
- "truthy": truthy2,
4956
- "defaults": defaults2,
4957
- "boolean": boolean2,
4958
- "number": number2,
4959
- "string": string2,
4960
- "null": _null2
4961
- };
4962
- var outputBuiltinFilters2 = builtinFilters2;
4963
- var inputBuiltinFilters = builtinFilters2;
4964
- var builtinFiltersByFilterIOType = {
4965
- "input": inputBuiltinFilters,
4966
- "output": outputBuiltinFilters2
4967
- };
4968
- var builtinFilterFn = (name, options) => (filters) => {
4969
- const filter = filters[name];
4970
- if (!filter) {
4971
- raiseError2(`[wcs/filter-unknown] filter not found: ${name}.${didYouMean(name, Object.keys(filters))}${LINT_HINT}`);
4972
- }
4973
- return filter(options);
4974
- };
4975
4282
  function finalizeArg(text, firstQuoteStart, lastQuoteEnd) {
4976
4283
  const startLimit = firstQuoteStart === -1 ? text.length : firstQuoteStart;
4977
4284
  let start = 0;
@@ -4985,15 +4292,30 @@ function finalizeArg(text, firstQuoteStart, lastQuoteEnd) {
4985
4292
  }
4986
4293
  return text.slice(start, end);
4987
4294
  }
4988
- function parseFilterArgs(argsText) {
4295
+ var NUMBER_LITERAL = /^[+-]?(\d+\.?\d*|\.\d+)([eE][+-]?\d+)?$/;
4296
+ function toLiteral(text, quoted) {
4297
+ if (quoted)
4298
+ return text;
4299
+ if (text === "true")
4300
+ return true;
4301
+ if (text === "false")
4302
+ return false;
4303
+ if (text === "null")
4304
+ return null;
4305
+ return NUMBER_LITERAL.test(text) ? Number(text) : text;
4306
+ }
4307
+ function parseFilterArgsWithLiterals(argsText) {
4989
4308
  const args = [];
4309
+ const literals = [];
4990
4310
  let current2 = "";
4991
4311
  let inQuote = null;
4992
4312
  let hasQuote = false;
4993
4313
  let firstQuoteStart = -1;
4994
4314
  let lastQuoteEnd = -1;
4995
4315
  const flush = () => {
4996
- args.push(finalizeArg(current2, firstQuoteStart, lastQuoteEnd));
4316
+ const arg = finalizeArg(current2, firstQuoteStart, lastQuoteEnd);
4317
+ args.push(arg);
4318
+ literals.push(toLiteral(arg, hasQuote));
4997
4319
  current2 = "";
4998
4320
  hasQuote = false;
4999
4321
  firstQuoteStart = -1;
@@ -5020,19 +4342,21 @@ function parseFilterArgs(argsText) {
5020
4342
  current2 += char;
5021
4343
  }
5022
4344
  }
4345
+ if (inQuote !== null) {
4346
+ raiseError2(`[wcs/binding-syntax] unterminated ${inQuote} quote in the filter arguments "(${argsText})". Close the quote.${LINT_HINT}`);
4347
+ }
5023
4348
  const last = finalizeArg(current2, firstQuoteStart, lastQuoteEnd);
5024
4349
  if (last || hasQuote) {
5025
4350
  args.push(last);
4351
+ literals.push(toLiteral(last, hasQuote));
5026
4352
  }
5027
- return args;
4353
+ return { args, literals };
5028
4354
  }
5029
- var filterFnByKey = /* @__PURE__ */ new Map();
5030
4355
  function clearFilterFnCacheForTooling() {
5031
- filterFnByKey.clear();
4356
+ clearFilterResolutionCache();
5032
4357
  }
5033
- function parseFilters(filterTextList, filterIOType) {
5034
- const builtinFilters3 = builtinFiltersByFilterIOType[filterIOType];
5035
- const filters = filterTextList.map((filterText) => {
4358
+ function parseFilters(filterTextList, _filterIOType) {
4359
+ return filterTextList.map((filterText) => {
5036
4360
  const openParenIndex = filterText.indexOf("(");
5037
4361
  const closeParenIndex = filterText.lastIndexOf(")");
5038
4362
  if (openParenIndex !== -1 && closeParenIndex === -1) {
@@ -5041,45 +4365,59 @@ function parseFilters(filterTextList, filterIOType) {
5041
4365
  if (closeParenIndex !== -1 && openParenIndex === -1) {
5042
4366
  raiseError2(`Invalid filter format: missing opening parenthesis in "${filterText}"`);
5043
4367
  }
4368
+ const filterName = (openParenIndex === -1 ? filterText : filterText.substring(0, openParenIndex)).trim();
4369
+ if (filterName.length === 0) {
4370
+ raiseError2(`[wcs/binding-syntax] an empty filter in "${filterTextList.join("|")}" \u2014 remove the extra "|" or name the filter.${LINT_HINT}`);
4371
+ }
5044
4372
  if (openParenIndex === -1) {
5045
- const filterName = filterText.trim();
5046
- const filterKey = `${filterName}():${filterIOType}`;
5047
- let filterFn = filterFnByKey.get(filterKey);
5048
- if (typeof filterFn === "undefined") {
5049
- filterFn = builtinFilterFn(filterName, [])(builtinFilters3);
5050
- filterFnByKey.set(filterKey, filterFn);
5051
- }
5052
- return {
5053
- filterName,
5054
- args: [],
5055
- filterFn
5056
- };
5057
- } else {
5058
- const argsText = filterText.substring(openParenIndex + 1, closeParenIndex);
5059
- const filterName = filterText.substring(0, openParenIndex).trim();
5060
- const args = parseFilterArgs(argsText);
5061
- const filterKey = `${filterName}(${args.join(",")}):${filterIOType}`;
5062
- let filterFn = filterFnByKey.get(filterKey);
5063
- if (typeof filterFn === "undefined") {
5064
- filterFn = builtinFilterFn(filterName, args)(builtinFilters3);
5065
- filterFnByKey.set(filterKey, filterFn);
5066
- }
5067
- return {
5068
- filterName,
5069
- args,
5070
- filterFn
5071
- };
4373
+ return { filterName, args: [], literals: [] };
5072
4374
  }
4375
+ const argsText = filterText.substring(openParenIndex + 1, closeParenIndex);
4376
+ return { filterName, ...parseFilterArgsWithLiterals(argsText) };
5073
4377
  });
5074
- return filters;
5075
4378
  }
5076
4379
  var trimFn = (s) => s.trim();
4380
+ var isQuote = (c) => c === "'" || c === '"';
4381
+ function indexOfOutsideQuotes(text, char) {
4382
+ let quote = null;
4383
+ for (let i = 0; i < text.length; i++) {
4384
+ const c = text[i];
4385
+ if (quote !== null) {
4386
+ if (c === quote)
4387
+ quote = null;
4388
+ } else if (isQuote(c)) {
4389
+ quote = c;
4390
+ } else if (c === char) {
4391
+ return i;
4392
+ }
4393
+ }
4394
+ return -1;
4395
+ }
4396
+ function splitOutsideQuotes(text, separator) {
4397
+ const parts = [];
4398
+ let quote = null;
4399
+ let start = 0;
4400
+ for (let i = 0; i < text.length; i++) {
4401
+ const c = text[i];
4402
+ if (quote !== null) {
4403
+ if (c === quote)
4404
+ quote = null;
4405
+ } else if (isQuote(c)) {
4406
+ quote = c;
4407
+ } else if (c === separator) {
4408
+ parts.push(text.slice(start, i));
4409
+ start = i + 1;
4410
+ }
4411
+ }
4412
+ parts.push(text.slice(start));
4413
+ return parts;
4414
+ }
5077
4415
  var cacheFilterInfos$1 = /* @__PURE__ */ new Map();
5078
4416
  function clearPropPartCacheForTooling() {
5079
4417
  cacheFilterInfos$1.clear();
5080
4418
  }
5081
4419
  function parsePropPart(propPart) {
5082
- const pos = propPart.indexOf(FILTER_SEPARATOR2);
4420
+ const pos = indexOfOutsideQuotes(propPart, FILTER_SEPARATOR2);
5083
4421
  let propText = "";
5084
4422
  let filterTexts = [];
5085
4423
  let filtersText = "";
@@ -5090,14 +4428,18 @@ function parsePropPart(propPart) {
5090
4428
  if (cacheFilterInfos$1.has(filtersText)) {
5091
4429
  filters = cacheFilterInfos$1.get(filtersText);
5092
4430
  } else {
5093
- filterTexts = filtersText.split(FILTER_SEPARATOR2).map(trimFn);
5094
- filters = parseFilters(filterTexts, "input");
4431
+ filterTexts = splitOutsideQuotes(filtersText, FILTER_SEPARATOR2).map(trimFn);
4432
+ filters = parseFilters(filterTexts);
5095
4433
  cacheFilterInfos$1.set(filtersText, filters);
5096
4434
  }
5097
4435
  } else {
5098
4436
  propText = propPart.trim();
5099
4437
  }
5100
- const [propName, propModifiersText] = propText.split(MODIFIER_SEPARATOR2).map(trimFn);
4438
+ const modifierParts = propText.split(MODIFIER_SEPARATOR2).map(trimFn);
4439
+ if (modifierParts.length > 2) {
4440
+ raiseError2(`[wcs/binding-syntax] "${propText}": a binding takes one modifier list after a single "${MODIFIER_SEPARATOR2}" \u2014 write "${modifierParts[0]}${MODIFIER_SEPARATOR2}${modifierParts.slice(1).join(",")}".${LINT_HINT}`);
4441
+ }
4442
+ const [propName, propModifiersText] = modifierParts;
5101
4443
  const propSegments = propName.split(DELIMITER2).map(trimFn);
5102
4444
  const propModifiers = propModifiersText ? propModifiersText.split(",").map(trimFn) : [];
5103
4445
  return {
@@ -5112,7 +4454,7 @@ function clearStatePartCacheForTooling() {
5112
4454
  cacheFilterInfos.clear();
5113
4455
  }
5114
4456
  function parseStatePart(statePart) {
5115
- const pos = statePart.indexOf(FILTER_SEPARATOR2);
4457
+ const pos = indexOfOutsideQuotes(statePart, FILTER_SEPARATOR2);
5116
4458
  let stateAndPath = "";
5117
4459
  let filterTexts = [];
5118
4460
  let filtersText = "";
@@ -5123,8 +4465,8 @@ function parseStatePart(statePart) {
5123
4465
  if (cacheFilterInfos.has(filtersText)) {
5124
4466
  filters = cacheFilterInfos.get(filtersText);
5125
4467
  } else {
5126
- filterTexts = filtersText.split(FILTER_SEPARATOR2).map(trimFn);
5127
- filters = parseFilters(filterTexts, "output");
4468
+ filterTexts = splitOutsideQuotes(filtersText, FILTER_SEPARATOR2).map(trimFn);
4469
+ filters = parseFilters(filterTexts);
5128
4470
  cacheFilterInfos.set(filtersText, filters);
5129
4471
  }
5130
4472
  } else {
@@ -5141,8 +4483,19 @@ function parseStatePart(statePart) {
5141
4483
  outFilters: filters
5142
4484
  };
5143
4485
  }
4486
+ var KEYWORDS_WITHOUT_MODIFIERS = /* @__PURE__ */ new Set([ELSE_KEYWORD2, "if", "elseif", "for", SPREAD_PROP2]);
4487
+ var EXPLICIT_PROPERTY_REJECTED_HEADS = /* @__PURE__ */ new Set([
4488
+ CLASS_NAMESPACE2,
4489
+ ATTR_NAMESPACE2,
4490
+ STYLE_NAMESPACE2,
4491
+ COMMAND_NAMESPACE2,
4492
+ EVENT_TOKEN_NAMESPACE2
4493
+ ]);
4494
+ function splitBindTexts(bindText) {
4495
+ return splitOutsideQuotes(bindText, BINDING_SEPARATOR2);
4496
+ }
5144
4497
  function parseBindTextsForElement(bindText) {
5145
- const [...bindTexts] = bindText.split(BINDING_SEPARATOR2).map(trimFn).filter((s) => s.length > 0);
4498
+ const [...bindTexts] = splitBindTexts(bindText).map(trimFn).filter((s) => s.length > 0);
5146
4499
  const results = bindTexts.map((bindText2) => {
5147
4500
  const separatorIndex = bindText2.indexOf(PROP_VALUE_SEPARATOR2);
5148
4501
  if (separatorIndex === -1) {
@@ -5150,7 +4503,14 @@ function parseBindTextsForElement(bindText) {
5150
4503
  }
5151
4504
  const propPart = bindText2.slice(0, separatorIndex).trim();
5152
4505
  const statePart = bindText2.slice(separatorIndex + 1).trim();
4506
+ const keyword = propPart.split(MODIFIER_SEPARATOR2)[0].split(FILTER_SEPARATOR2)[0].trim();
4507
+ if (keyword !== propPart && KEYWORDS_WITHOUT_MODIFIERS.has(keyword)) {
4508
+ raiseError2(`[wcs/binding-syntax] "${bindText2}": "${keyword}" takes no modifiers or filters on its left side \u2014 write "${keyword}:".${LINT_HINT}`);
4509
+ }
5153
4510
  if (propPart === ELSE_KEYWORD2) {
4511
+ if (statePart.length > 0) {
4512
+ raiseError2(`[wcs/binding-syntax] "${bindText2}": "else" takes no value \u2014 write "else:".${LINT_HINT}`);
4513
+ }
5154
4514
  const pathInfo = getPathInfo("#else");
5155
4515
  return {
5156
4516
  propName: ELSE_KEYWORD2,
@@ -5178,7 +4538,7 @@ function parseBindTextsForElement(bindText) {
5178
4538
  ...stateResult,
5179
4539
  bindingType: "spread"
5180
4540
  };
5181
- } else if (propPart === "if" || propPart === "elseif" || propPart === "for" || propPart === "radio" || propPart === "checkbox") {
4541
+ } else if (propPart === "if" || propPart === "elseif" || propPart === "for") {
5182
4542
  const stateResult = parseStatePart(statePart);
5183
4543
  return {
5184
4544
  propName: propPart,
@@ -5188,9 +4548,30 @@ function parseBindTextsForElement(bindText) {
5188
4548
  ...stateResult,
5189
4549
  bindingType: propPart
5190
4550
  };
4551
+ } else if (keyword === "radio" || keyword === "checkbox") {
4552
+ const stateResult = parseStatePart(statePart);
4553
+ const propResult = parsePropPart(propPart);
4554
+ return {
4555
+ ...propResult,
4556
+ ...stateResult,
4557
+ bindingType: keyword
4558
+ };
5191
4559
  } else {
5192
4560
  const stateResult = parseStatePart(statePart);
5193
4561
  const propResult = parsePropPart(propPart);
4562
+ if (propResult.propSegments[0] === "" && propResult.propSegments.length > 1) {
4563
+ const propSegments = propResult.propSegments.slice(1);
4564
+ if (propSegments.includes("") || EXPLICIT_PROPERTY_REJECTED_HEADS.has(propSegments[0])) {
4565
+ raiseError2(`[wcs/binding-syntax] "${propPart}": a leading "." binds an element property by name \u2014 write a non-empty property that is not a namespace (${[...EXPLICIT_PROPERTY_REJECTED_HEADS].join(", ")}).${LINT_HINT}`);
4566
+ }
4567
+ return {
4568
+ ...propResult,
4569
+ propName: propSegments.join(DELIMITER2),
4570
+ propSegments,
4571
+ ...stateResult,
4572
+ bindingType: "prop"
4573
+ };
4574
+ }
5194
4575
  if (propResult.propSegments[0] === EVENT_TOKEN_NAMESPACE2) {
5195
4576
  return {
5196
4577
  ...propResult,
@@ -5273,7 +4654,7 @@ function parseEmbeddedTextWithPositions(expression) {
5273
4654
  }
5274
4655
  function parseBindTextWithPositions(bindText) {
5275
4656
  const results = [];
5276
- const segments = bindText.split(delimiters.binding);
4657
+ const segments = splitBindTexts(bindText);
5277
4658
  let segmentStart = 0;
5278
4659
  for (const segment of segments) {
5279
4660
  const leading = segment.length - segment.trimStart().length;
@@ -5316,24 +4697,42 @@ function parseBindTextWithPositions(bindText) {
5316
4697
  return results;
5317
4698
  }
5318
4699
 
5319
- // src/service/v3MigrationValidator.ts
5320
- function validateV3Migration(html, bindAttrName = "data-wcs", locale3) {
4700
+ // src/service/bindingSyntaxValidator.ts
4701
+ var CODE_MARKER = "[wcs/binding-syntax]";
4702
+ function bindingSyntaxDetail(error) {
4703
+ if (error === null) return null;
4704
+ const at2 = error.indexOf(CODE_MARKER);
4705
+ if (at2 === -1) return null;
4706
+ const detail = error.slice(at2 + CODE_MARKER.length).trim();
4707
+ const hint = detail.indexOf(" Validate statically:");
4708
+ return hint === -1 ? detail : detail.slice(0, hint);
4709
+ }
4710
+ function validateBindingSyntax(html, bindAttrName = "data-wcs", locale2) {
5321
4711
  const diagnostics = [];
5322
- const msgs = getMessages(locale3);
5323
- const push2 = (detail, start, end) => {
5324
- diagnostics.push({ code: WcsDiagnosticCode.V3Migration, start, end, message: msgs.v3Migration(detail), severity: "info" });
5325
- };
4712
+ const msgs = getMessages(locale2);
5326
4713
  for (const attr of findAllBindAttributes(html, bindAttrName)) {
5327
4714
  for (const binding of parseBindTextWithPositions(attr.value)) {
5328
- for (const detail of findV3MigrationIssues(binding.exprText)) {
5329
- push2(detail, attr.valueStart + binding.exprRange.start, attr.valueStart + binding.exprRange.end);
5330
- }
4715
+ const detail = bindingSyntaxDetail(binding.error);
4716
+ if (detail === null) continue;
4717
+ diagnostics.push({
4718
+ code: WcsDiagnosticCode.BindingSyntax,
4719
+ start: attr.valueStart + binding.exprRange.start,
4720
+ end: attr.valueStart + binding.exprRange.end,
4721
+ message: msgs.bindingSyntax(detail),
4722
+ severity: "error"
4723
+ });
5331
4724
  }
5332
4725
  }
5333
- for (const item of [...findAllMustacheSyntax(html), ...findAllCommentBindings(html)]) {
5334
- for (const detail of findEmbeddedV3MigrationIssues(item.expression)) {
5335
- push2(detail, item.exprStart, item.exprEnd);
5336
- }
4726
+ for (const mustache of findAllMustacheSyntax(html)) {
4727
+ const detail = bindingSyntaxDetail(parseEmbeddedTextWithPositions(mustache.expression).error);
4728
+ if (detail === null) continue;
4729
+ diagnostics.push({
4730
+ code: WcsDiagnosticCode.BindingSyntax,
4731
+ start: mustache.exprStart,
4732
+ end: mustache.exprStart + mustache.expression.length,
4733
+ message: msgs.bindingSyntax(detail),
4734
+ severity: "error"
4735
+ });
5337
4736
  }
5338
4737
  return diagnostics;
5339
4738
  }
@@ -6626,9 +6025,9 @@ var DOM_COMMON_PROPERTIES = /* @__PURE__ */ new Set([
6626
6025
  ]);
6627
6026
  var STRUCTURAL_DIRECTIVES2 = /* @__PURE__ */ new Set(["for", "if", "elseif", "else"]);
6628
6027
  var EMPTYISH_SEEDS = /* @__PURE__ */ new Set(["''", '""', "``", "null", "[]", "{}"]);
6629
- function validateIoNodes(html, bindAttribute = "data-wcs", stateTagName = "wcs-state", locale3, fileReader) {
6028
+ function validateIoNodes(html, bindAttribute = "data-wcs", stateTagName = "wcs-state", locale2, fileReader) {
6630
6029
  const diagnostics = [];
6631
- const msgs = getMessages(locale3);
6030
+ const msgs = getMessages(locale2);
6632
6031
  const occurrences = findBuiltinTagOccurrences(html);
6633
6032
  if (occurrences.length === 0) return diagnostics;
6634
6033
  let statePaths = null;
@@ -6688,10 +6087,28 @@ function validateBindingAgainstContract(tagName, contract, parsed, property, sta
6688
6087
  const hashIndex = property.indexOf("#");
6689
6088
  const modifiers = hashIndex === -1 ? "" : property.slice(hashIndex + 1);
6690
6089
  property = hashIndex === -1 ? property : property.slice(0, hashIndex);
6090
+ const explicit = property.startsWith(".") && property !== "...";
6091
+ if (explicit) {
6092
+ property = property.slice(1);
6093
+ if (/^(class|style|attr|command|eventToken)(\.|$)/.test(property)) return;
6094
+ }
6691
6095
  if (property === "...") return;
6692
6096
  if (STRUCTURAL_DIRECTIVES2.has(property)) return;
6693
6097
  if (/^(class|style|attr)\./.test(property)) return;
6694
- if (/^on\w/.test(property)) return;
6098
+ if (!explicit && /^on\w/.test(property)) {
6099
+ if (contract.properties.includes(property) || property in contract.inputs) {
6100
+ diagnostics.push({
6101
+ code: WcsDiagnosticCode.OnPrefixedMember,
6102
+ start,
6103
+ end,
6104
+ severity: "warning",
6105
+ tag: tagName,
6106
+ member: property,
6107
+ message: msgs.onPrefixedMember(property, tagName)
6108
+ });
6109
+ }
6110
+ return;
6111
+ }
6695
6112
  const inputNames = Object.keys(contract.inputs);
6696
6113
  if (property.startsWith("command.")) {
6697
6114
  const name = property.slice("command.".length);
@@ -6776,7 +6193,7 @@ function suggestion(input, candidates, msgs) {
6776
6193
  let best = null;
6777
6194
  let bestDistance = 3;
6778
6195
  for (const c of candidates) {
6779
- const d = editDistance2(input.toLowerCase(), c.toLowerCase(), bestDistance);
6196
+ const d = editDistance(input.toLowerCase(), c.toLowerCase(), bestDistance);
6780
6197
  if (d < bestDistance) {
6781
6198
  best = c;
6782
6199
  bestDistance = d;
@@ -6784,7 +6201,7 @@ function suggestion(input, candidates, msgs) {
6784
6201
  }
6785
6202
  return best !== null ? msgs.didYouMean(best) : "";
6786
6203
  }
6787
- function editDistance2(a, b, bound) {
6204
+ function editDistance(a, b, bound) {
6788
6205
  if (Math.abs(a.length - b.length) >= bound) return bound;
6789
6206
  const prev = new Array(b.length + 1);
6790
6207
  const curr = new Array(b.length + 1);
@@ -6898,9 +6315,9 @@ var ARIA_ATTRIBUTES = /* @__PURE__ */ new Set([
6898
6315
  "aria-brailleroledescription",
6899
6316
  "aria-description"
6900
6317
  ]);
6901
- function validateAriaAttributes(html, bindAttribute = "data-wcs", locale3) {
6318
+ function validateAriaAttributes(html, bindAttribute = "data-wcs", locale2) {
6902
6319
  const diagnostics = [];
6903
- const msgs = getMessages(locale3);
6320
+ const msgs = getMessages(locale2);
6904
6321
  for (const attr of findAllBindAttributes(html, bindAttribute)) {
6905
6322
  let exprOffset = 0;
6906
6323
  for (const expr of splitBindingExpressions(attr.value)) {
@@ -6929,9 +6346,9 @@ function validateAriaAttributes(html, bindAttribute = "data-wcs", locale3) {
6929
6346
  }
6930
6347
 
6931
6348
  // src/service/documentEnvValidator.ts
6932
- function validateDocumentEnv(html, locale3) {
6349
+ function validateDocumentEnv(html, locale2) {
6933
6350
  const diagnostics = [];
6934
- const msgs = getMessages(locale3);
6351
+ const msgs = getMessages(locale2);
6935
6352
  const scanText = blankHtmlComments(html);
6936
6353
  const autos = findWcstackAutoScripts(scanText);
6937
6354
  const stateIndex = autos.findIndex((a) => a.pkg === "state");
@@ -7042,8 +6459,8 @@ function blankJsComments(code) {
7042
6459
 
7043
6460
  // src/service/watchDeclarationValidator.ts
7044
6461
  var STATE_NAME_SEPARATOR = "@";
7045
- function validateWatchDeclarations(html, stateTagName = "wcs-state", locale3) {
7046
- const msgs = getMessages(locale3);
6462
+ function validateWatchDeclarations(html, stateTagName = "wcs-state", locale2) {
6463
+ const msgs = getMessages(locale2);
7047
6464
  const out = [];
7048
6465
  for (const block of parseWcsScriptBlocks(html, stateTagName)) {
7049
6466
  const nonObject = findNonObjectWatch(block.content);
@@ -7107,8 +6524,8 @@ function validateEntry(entry, pathSet, paths, msgs) {
7107
6524
  }
7108
6525
 
7109
6526
  // src/service/scanDeclarationValidator.ts
7110
- function validateScanDeclarations(html, stateTagName = "wcs-state", locale3) {
7111
- const msgs = getMessages(locale3);
6527
+ function validateScanDeclarations(html, stateTagName = "wcs-state", locale2) {
6528
+ const msgs = getMessages(locale2);
7112
6529
  const out = [];
7113
6530
  for (const element of parseWcsStateElements(html, stateTagName)) {
7114
6531
  const mounted = element.bindComponent;
@@ -7466,8 +6883,8 @@ var UNSUPPORTED_API_SITE = {
7466
6883
  };
7467
6884
  var BRACKET_ASSIGNMENT = new RegExp(`${ROOT_BRACKET}${ASSIGN_TAIL}`, "g");
7468
6885
  var PRE_BRACKET_INCDEC = new RegExp(`${PRE_INCDEC}${ROOT_BRACKET}`, "g");
7469
- function validateRecursion(html, stateTagName = "wcs-state", locale3) {
7470
- const msgs = getMessages(locale3);
6886
+ function validateRecursion(html, stateTagName = "wcs-state", locale2) {
6887
+ const msgs = getMessages(locale2);
7471
6888
  const out = [];
7472
6889
  for (const element of parseWcsStateElements(html, stateTagName)) {
7473
6890
  const mounted = element.bindComponent;
@@ -7898,8 +7315,8 @@ function findStateSelector(expr, embedded = false) {
7898
7315
  const nameStart = at2 + 1 + (raw.length - raw.trimStart().length);
7899
7316
  return { start: at2, end: name.length === 0 ? at2 + 1 : nameStart + name.length, name: name.length === 0 ? "default" : name };
7900
7317
  }
7901
- function validateNamedState(html, attrName, stateTagName = "wcs-state", locale3) {
7902
- const msgs = getMessages(locale3);
7318
+ function validateNamedState(html, attrName, stateTagName = "wcs-state", locale2) {
7319
+ const msgs = getMessages(locale2);
7903
7320
  const diagnostics = [];
7904
7321
  for (const element of parseWcsStateElements(html, stateTagName)) {
7905
7322
  const tagText = html.slice(element.tagStart, element.tagEnd);
@@ -7957,8 +7374,8 @@ function findMountPathProblem(mountPath) {
7957
7374
  }
7958
7375
  return null;
7959
7376
  }
7960
- function validateMountAttributes(html, stateTagName = "wcs-state", locale3) {
7961
- const msgs = getMessages(locale3);
7377
+ function validateMountAttributes(html, stateTagName = "wcs-state", locale2) {
7378
+ const msgs = getMessages(locale2);
7962
7379
  const diagnostics = [];
7963
7380
  for (const element of parseWcsStateElements(html, stateTagName)) {
7964
7381
  if (element.mountPath === null) continue;
@@ -14101,8 +13518,8 @@ function buildReferenceIndex(html, options = {}) {
14101
13518
  // src/service/semanticValidator.ts
14102
13519
  var STATE_UPDATED_CALLBACK = "$updatedCallback";
14103
13520
  var API_CALL = /\.\s*\$(getAll|setAll|resolve)\s*\(/g;
14104
- function validateIndexArity(script, scriptStart, locale3) {
14105
- const msgs = getMessages(locale3);
13521
+ function validateIndexArity(script, scriptStart, locale2) {
13522
+ const msgs = getMessages(locale2);
14106
13523
  const out = [];
14107
13524
  API_CALL.lastIndex = 0;
14108
13525
  let match;
@@ -14133,8 +13550,8 @@ function validateIndexArity(script, scriptStart, locale3) {
14133
13550
  }
14134
13551
  return out;
14135
13552
  }
14136
- function validateGetterCycles(script, scriptStart, locale3) {
14137
- const msgs = getMessages(locale3);
13553
+ function validateGetterCycles(script, scriptStart, locale2) {
13554
+ const msgs = getMessages(locale2);
14138
13555
  const getters = analyzeCallableBodies(script).filter((entry) => entry.kind === "getter");
14139
13556
  if (getters.length === 0) return [];
14140
13557
  const declared = new Set(getters.map((getter) => getter.name));
@@ -14189,7 +13606,7 @@ function validateGetterCycles(script, scriptStart, locale3) {
14189
13606
  }
14190
13607
  return out;
14191
13608
  }
14192
- function validateGetterUntrackedReads(script, scriptStart, nestedWriteRoots, locale3) {
13609
+ function validateGetterUntrackedReads(script, scriptStart, nestedWriteRoots, locale2) {
14193
13610
  const getters = analyzeCallableBodies(script).filter((entry) => entry.kind === "getter" && entry.accessor === "get");
14194
13611
  if (getters.length === 0) return [];
14195
13612
  const objectRoots = /* @__PURE__ */ new Set();
@@ -14199,7 +13616,7 @@ function validateGetterUntrackedReads(script, scriptStart, nestedWriteRoots, loc
14199
13616
  }
14200
13617
  }
14201
13618
  if (objectRoots.size === 0) return [];
14202
- const msgs = getMessages(locale3);
13619
+ const msgs = getMessages(locale2);
14203
13620
  const out = [];
14204
13621
  for (const getter of getters) {
14205
13622
  for (const read of collectGetterReads(getter.body) ?? []) {
@@ -14278,12 +13695,12 @@ function collectNestedWriteRoots(html, stateTagName, bindAttrName, blocks) {
14278
13695
  return roots;
14279
13696
  }
14280
13697
  var PATH_TEST_LITERAL = /(?:\.\s*(?:includes|indexOf)\s*\(\s*|[!=]==\s*)(["'])((?:\\.|(?!\1)[^\\])*)\1/g;
14281
- function validateUpdatedCallbackDemand(html, stateTagName, bindAttrName, locale3) {
13698
+ function validateUpdatedCallbackDemand(html, stateTagName, bindAttrName, locale2) {
14282
13699
  const blocks = parseWcsScriptBlocks(html, stateTagName);
14283
13700
  if (blocks.length === 0) return [];
14284
13701
  const hasCallback = blocks.some((block) => block.content.includes(STATE_UPDATED_CALLBACK));
14285
13702
  if (!hasCallback) return [];
14286
- const msgs = getMessages(locale3);
13703
+ const msgs = getMessages(locale2);
14287
13704
  const boundPaths = collectBoundPaths(html, stateTagName, bindAttrName);
14288
13705
  const out = [];
14289
13706
  for (const block of blocks) {
@@ -14327,17 +13744,17 @@ function collectBoundPaths(html, stateTagName, bindAttrName) {
14327
13744
  }
14328
13745
  return bound;
14329
13746
  }
14330
- function validateSemantics(html, stateTagName = "wcs-state", locale3, bindAttrName = "data-wcs") {
13747
+ function validateSemantics(html, stateTagName = "wcs-state", locale2, bindAttrName = "data-wcs") {
14331
13748
  const out = [];
14332
13749
  const blocks = parseWcsScriptBlocks(html, stateTagName);
14333
13750
  let nestedWriteRoots = null;
14334
13751
  const getNestedWriteRoots = () => nestedWriteRoots ??= collectNestedWriteRoots(html, stateTagName, bindAttrName, blocks);
14335
13752
  for (const block of blocks) {
14336
- out.push(...validateIndexArity(block.content, block.contentStart, locale3));
14337
- out.push(...validateGetterCycles(block.content, block.contentStart, locale3));
14338
- out.push(...validateGetterUntrackedReads(block.content, block.contentStart, getNestedWriteRoots, locale3));
13753
+ out.push(...validateIndexArity(block.content, block.contentStart, locale2));
13754
+ out.push(...validateGetterCycles(block.content, block.contentStart, locale2));
13755
+ out.push(...validateGetterUntrackedReads(block.content, block.contentStart, getNestedWriteRoots, locale2));
14339
13756
  }
14340
- out.push(...validateUpdatedCallbackDemand(html, stateTagName, bindAttrName, locale3));
13757
+ out.push(...validateUpdatedCallbackDemand(html, stateTagName, bindAttrName, locale2));
14341
13758
  return out;
14342
13759
  }
14343
13760
 
@@ -14732,27 +14149,27 @@ function joinRelativeSource(htmlSource, relativePath) {
14732
14149
  function validateDocument(text, options = {}) {
14733
14150
  const bindAttribute = options.bindAttribute ?? "data-wcs";
14734
14151
  const stateTagName = options.stateTagName ?? "wcs-state";
14735
- const locale3 = options.locale;
14152
+ const locale2 = options.locale;
14736
14153
  const fileReader = options.fileReader;
14737
14154
  const applicationSchema = options.applicationSchema ?? (fileReader !== void 0 ? discoverApplicationManifest(fileReader)?.schema : void 0);
14738
14155
  const out = [];
14739
- out.push(...validateBindings(text, bindAttribute, stateTagName, locale3, fileReader, applicationSchema));
14740
- out.push(...validateTemplateSyntax(text, stateTagName, bindAttribute, locale3, fileReader, applicationSchema));
14741
- out.push(...validateV3Migration(text, bindAttribute, locale3));
14742
- out.push(...validateIoNodes(text, bindAttribute, stateTagName, locale3, fileReader));
14743
- out.push(...validateAriaAttributes(text, bindAttribute, locale3));
14744
- out.push(...validateDocumentEnv(text, locale3));
14745
- out.push(...validateSemantics(text, stateTagName, locale3, bindAttribute));
14746
- out.push(...validateArrayMutations(text, stateTagName, locale3));
14747
- out.push(...validateWatchDeclarations(text, stateTagName, locale3));
14748
- out.push(...validateScanDeclarations(text, stateTagName, locale3));
14749
- out.push(...validateRecursion(text, stateTagName, locale3));
14750
- out.push(...validateNamedState(text, bindAttribute, stateTagName, locale3));
14751
- out.push(...validateMountAttributes(text, stateTagName, locale3));
14752
- for (const d of validateStateTypes(text, stateTagName, locale3)) {
14156
+ out.push(...validateBindings(text, bindAttribute, stateTagName, locale2, fileReader, applicationSchema));
14157
+ out.push(...validateTemplateSyntax(text, stateTagName, bindAttribute, locale2, fileReader, applicationSchema));
14158
+ out.push(...validateBindingSyntax(text, bindAttribute, locale2));
14159
+ out.push(...validateIoNodes(text, bindAttribute, stateTagName, locale2, fileReader));
14160
+ out.push(...validateAriaAttributes(text, bindAttribute, locale2));
14161
+ out.push(...validateDocumentEnv(text, locale2));
14162
+ out.push(...validateSemantics(text, stateTagName, locale2, bindAttribute));
14163
+ out.push(...validateArrayMutations(text, stateTagName, locale2));
14164
+ out.push(...validateWatchDeclarations(text, stateTagName, locale2));
14165
+ out.push(...validateScanDeclarations(text, stateTagName, locale2));
14166
+ out.push(...validateRecursion(text, stateTagName, locale2));
14167
+ out.push(...validateNamedState(text, bindAttribute, stateTagName, locale2));
14168
+ out.push(...validateMountAttributes(text, stateTagName, locale2));
14169
+ for (const d of validateStateTypes(text, stateTagName, locale2)) {
14753
14170
  out.push({ code: WcsDiagnosticCode.TypeAnnotation, start: d.start, end: d.end, message: d.message, severity: d.severity });
14754
14171
  }
14755
- for (const d of validateNestedAssigns(text, stateTagName, locale3)) {
14172
+ for (const d of validateNestedAssigns(text, stateTagName, locale2)) {
14756
14173
  out.push({ code: WcsDiagnosticCode.NestedAssign, start: d.start, end: d.end, message: d.message, severity: d.severity });
14757
14174
  }
14758
14175
  return sortDiagnostics(out);
@@ -14986,7 +14403,7 @@ function resolveCliLocale(explicit, env = process.env) {
14986
14403
  }
14987
14404
  function main(argv) {
14988
14405
  const { options, files } = parseArgs(argv);
14989
- const locale3 = resolveCliLocale(options.locale);
14406
+ const locale2 = resolveCliLocale(options.locale);
14990
14407
  if (files.length === 0) {
14991
14408
  process.stderr.write("usage: wcs-validate [--attr=data-wcs] [--state-tag=wcs-state] [--lang=ja|en] [--errors-only] [--strict] <file> [<file> ...]\n");
14992
14409
  return 2;
@@ -15004,7 +14421,7 @@ function main(argv) {
15004
14421
  const kind = classify(path);
15005
14422
  inputs.push({ source: path, text, kind, fileReader: kind === "html" ? createFileReader(path) : void 0 });
15006
14423
  }
15007
- const result = runValidation(inputs, { ...options, locale: locale3 });
14424
+ const result = runValidation(inputs, { ...options, locale: locale2 });
15008
14425
  for (const line of result.lines) {
15009
14426
  process.stdout.write(line + "\n");
15010
14427
  }