@wcstack/typescript 4.0.3 → 4.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.
package/dist/index.esm.js CHANGED
@@ -572,7 +572,7 @@ function loadSchemaCore() {
572
572
  return cached;
573
573
  }
574
574
 
575
- var version = "4.0.3";
575
+ var version = "4.1.0";
576
576
  var pkg = {
577
577
  version: version};
578
578
 
@@ -94,6 +94,7 @@ var WcsDiagnosticCode = {
94
94
  // 静的にしか検出できない。対象はオブジェクトリテラル初期値のルートだけ(配列は `items`
95
95
  // の依存で足りる — 行の置換は `items` 自体を書き換え、in-place 変異は array-mutation が止める)。
96
96
  GetterUntrackedRead: "wcs/getter-untracked-read",
97
+ OnHandlerThis: "wcs/on-handler-this",
97
98
  // --- <wcs-state> script: $watch declaration ---
98
99
  // ランタイム(watch/processWatchDeclaration.ts)が raiseError で落とす宣言。
99
100
  // 越境 `@` / `$` 始まり / 空キー・空セグメント / 明らかな非関数ハンドラ。
@@ -216,6 +217,10 @@ var WcsDiagnosticCode = {
216
217
  ScriptOrder: "wcs/script-order",
217
218
  // router/auto があるのに <base href> がない(SPA の basename 誤導出)。
218
219
  BaseHrefMissing: "wcs/base-href-missing",
220
+ // <wcs-router> の直後に無い <wcs-outlet>(どの router にも使われない)と、直後に outlet を持たない
221
+ // router。router は自前の outlet を直後に作り、ページはそこに描かれ、置いた outlet は空のまま。
222
+ // ランタイムも同じ code で警告する(docs/router-outlet-placement.md)。
223
+ RouterOutletMisplaced: "wcs/router-outlet-misplaced",
219
224
  // @wcstack/signals と /dom エントリの同一ページ混在(リアクティブコア二重化)。
220
225
  SignalsDualEntry: "wcs/signals-dual-entry",
221
226
  // --- deprecations ---
@@ -3235,9 +3240,15 @@ function analyzeWatchEntries(scriptContent) {
3235
3240
  }));
3236
3241
  }
3237
3242
  function analyzeWatchHandlerSources(scriptContent) {
3243
+ return analyzeHandlerSources(scriptContent, RESERVED_WATCH_KEY);
3244
+ }
3245
+ function analyzeOnHandlerSources(scriptContent) {
3246
+ return analyzeHandlerSources(scriptContent, "$on");
3247
+ }
3248
+ function analyzeHandlerSources(scriptContent, key) {
3238
3249
  const root = locateDefaultExportObject(scriptContent);
3239
3250
  if (!root) return [];
3240
- const prop = parseTopLevelProperties(root.content).find((p) => p.name === RESERVED_WATCH_KEY);
3251
+ const prop = parseTopLevelProperties(root.content).find((p) => p.name === key);
3241
3252
  if (!prop || prop.kind !== "data" || !prop.value || !isObjectLiteral(prop.value) || prop.valueStart === void 0) {
3242
3253
  return [];
3243
3254
  }
@@ -3247,6 +3258,7 @@ function analyzeWatchHandlerSources(scriptContent) {
3247
3258
  for (const entry of parseTopLevelProperties(extractObjectContent(prop.value))) {
3248
3259
  if (entry.value === void 0 || entry.valueStart === void 0) continue;
3249
3260
  out.push({
3261
+ name: entry.name,
3250
3262
  kind: entry.kind === "data" ? "value" : "body",
3251
3263
  text: entry.value,
3252
3264
  start: innerStart + entry.valueStart
@@ -10941,6 +10953,7 @@ var ja = {
10941
10953
  delegatedCurrentTarget: (suggested, eventName, handler) => `"${eventName}" \u30A4\u30D9\u30F3\u30C8\u306E\u675F\u7E1B\u306F root \u3078\u59D4\u8B72\u3055\u308C\u308B\u306E\u3067\u3001"${handler}" \u306E\u4E2D\u306E event.currentTarget \u306F\u3053\u306E\u8981\u7D20\u3067\u306F\u306A\u304F root \u306B\u306A\u308A\u307E\u3059\u3002\u8981\u7D20\u306B\u30EA\u30B9\u30CA\u30FC\u3092\u4ED8\u3051\u308B\u306B\u306F "${suggested}:" \u3068\u66F8\u304F\u304B\u3001event.target.closest(...) \u3067\u8981\u7D20\u3092\u63A2\u3057\u3066\u304F\u3060\u3055\u3044`,
10942
10954
  getterCycle: (cycle) => `\u30D1\u30B9 getter \u304C\u5FAA\u74B0\u53C2\u7167\u3057\u3066\u3044\u307E\u3059: ${cycle}`,
10943
10955
  updatedCallbackUnbound: (p) => `$renderedCallback \u306F binding \u99C6\u52D5\u3067\u3059\u3002"${p}" \u306F\u3053\u306E\u30C9\u30AD\u30E5\u30E1\u30F3\u30C8\u306E\u3069\u306E\u30D0\u30A4\u30F3\u30C7\u30A3\u30F3\u30B0\u306B\u3082\u73FE\u308C\u306A\u3044\u305F\u3081\u3001\u3053\u306E\u5206\u5C90\u306F\u4E00\u5EA6\u3082\u5B9F\u884C\u3055\u308C\u307E\u305B\u3093\u3002\u63CF\u753B\u306B\u4F9D\u5B58\u305B\u305A\u53CD\u5FDC\u3059\u308B\u306A\u3089 $watch \u3092\u4F7F\u3063\u3066\u304F\u3060\u3055\u3044`,
10956
+ onHandlerThis: (name) => `$on \u306E\u30CF\u30F3\u30C9\u30E9 "${name}" \u304C this \u3092\u4F7F\u3063\u3066\u3044\u307E\u3059\u3002$on \u306E\u30CF\u30F3\u30C9\u30E9\u306F state \u3092\u7B2C 1 \u5F15\u6570\u3067\u53D7\u3051\u53D6\u308A\uFF08\`${name}(state, event) { state.x = \u2026 }\`\uFF09\u3001this \u306F undefined \u306A\u306E\u3067\u3001this.\u2026 \u306F\u547C\u3070\u308C\u305F\u3068\u304D\u306B\u4F8B\u5916\u306B\u306A\u308A\u307E\u3059\u3002$watch \u3084\u30E1\u30BD\u30C3\u30C9\u3068\u9055\u3046\u70B9\u306B\u6CE8\u610F`,
10944
10957
  getterUntrackedRead: (root, sp) => `\u3053\u3053\u3067\u8FFD\u8DE1\u3055\u308C\u308B\u306E\u306F "${root}" \u3060\u3051\u3067\u3059\u3002"${sp}" \u304C\u5909\u308F\u3063\u3066\u3082\u3053\u306E getter \u306F\u518D\u8A55\u4FA1\u3055\u308C\u307E\u305B\u3093\uFF08\u30D1\u30B9\u8AAD\u307F\u53D6\u308A\u306E\u5148\u306E\u7D20\u306E\u30D7\u30ED\u30D1\u30C6\u30A3\u30A2\u30AF\u30BB\u30B9\u306F\u8FFD\u8DE1\u3055\u308C\u306A\u3044\uFF09\u3002this["${sp}"] \u3067\u8AAD\u3093\u3067\u304F\u3060\u3055\u3044`,
10945
10958
  handlerFilterNotAllowed: (prop) => `\u30A4\u30D9\u30F3\u30C8\u30CF\u30F3\u30C9\u30E9 "${prop}" \u306B\u30D5\u30A3\u30EB\u30BF\u306F\u4F7F\u7528\u3067\u304D\u307E\u305B\u3093`,
10946
10959
  typeExpectation: (label, expected, resultType) => `"${label}" \u306B\u306F${JA_EXPECTED_LABEL[expected]}\u304C\u5FC5\u8981\u3067\u3059\uFF08\u73FE\u5728\u306E\u578B: ${resultType}\uFF09`,
@@ -10991,13 +11004,14 @@ var ja = {
10991
11004
  didYouMean: (c) => `\u3002\u3082\u3057\u304B\u3057\u3066: "${c}"`,
10992
11005
  none: () => `\u306A\u3057`,
10993
11006
  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`,
10994
- storageSeedClobber: (path, raw) => `<wcs-storage> \u306E value \u30D0\u30A4\u30F3\u30C9\u5148 "${path}" \u304C ${raw} \u3067\u30B7\u30FC\u30C9\u3055\u308C\u3066\u3044\u307E\u3059\u3002\u8AAD\u307F\u8FBC\u307F\u306E\u305F\u3073\u306B\u521D\u671F\u66F8\u304D\u623B\u3057\u304C\u4FDD\u5B58\u5024\u3092\u4E0A\u66F8\u304D\u3057\u307E\u3059 \u2014 \u30D0\u30A4\u30F3\u30C9\u306B #init=element \u3092\u4ED8\u3051\u3066\uFF08\`value#init=element: ${path}\`\uFF09\u4FDD\u5B58\u5024\u3092\u52DD\u305F\u305B\u3001\u30B9\u30ED\u30C3\u30C8\u306F null\uFF08\u7A7A\u306E\u30AD\u30FC\u304C\u8AAD\u3080\u5024\uFF09\u3067\u30B7\u30FC\u30C9\u3057\u3066\u304F\u3060\u3055\u3044`,
11007
+ storageSeedClobber: (path, raw) => `<wcs-storage> \u306E value \u30D0\u30A4\u30F3\u30C9\u5148 "${path}" \u304C ${shortSeed(raw)} \u3067\u30B7\u30FC\u30C9\u3055\u308C\u3066\u3044\u307E\u3059\u3002\u8AAD\u307F\u8FBC\u307F\u306E\u305F\u3073\u306B\u521D\u671F\u66F8\u304D\u623B\u3057\u304C\u4FDD\u5B58\u5024\u3092\u4E0A\u66F8\u304D\u3057\u307E\u3059 \u2014 \u30D0\u30A4\u30F3\u30C9\u306B #init=element \u3092\u4ED8\u3051\u3066\uFF08\`value#init=element: ${path}\`\uFF09\u4FDD\u5B58\u5024\u3092\u52DD\u305F\u305B\u3001\u30B9\u30ED\u30C3\u30C8\u306F null\uFF08\u7A7A\u306E\u30AD\u30FC\u304C\u8AAD\u3080\u5024\uFF09\u3067\u30B7\u30FC\u30C9\u3057\u3066\u304F\u3060\u3055\u3044`,
10995
11008
  storageInitSeed: (path, raw) => `<wcs-storage> \u306E value \u30D0\u30A4\u30F3\u30C9\u5148 "${path}" \u304C ${shortSeed(raw)} \u3067\u30B7\u30FC\u30C9\u3055\u308C\u3066\u3044\u307E\u3059\u304C\u3001#init=element \u306F\u8981\u7D20\u306E\u5024\u3092\u3053\u306E\u30B9\u30ED\u30C3\u30C8\u3078\u5F15\u304D\u8FBC\u307F\u307E\u3059\u3002\u521D\u56DE\uFF08\u7A7A\u306E\u30AD\u30FC\uFF09\u306F null \u306A\u306E\u3067\u3001\u30B7\u30FC\u30C9\u306F null \u306B\u7F6E\u304D\u63DB\u308F\u308A\u307E\u3059 \u2014 null \u3067\u30B7\u30FC\u30C9\u3057\u3066\u8AAD\u3080\u5074\u3067\u65E2\u5B9A\u5024\u3092\u88DC\u3046\uFF08\`this.${path} ?? \u2026\`\uFF09\u304B\u3001null \u3092\u7121\u8996\u3059\u308B setter \u3092\u6301\u3064\u30A2\u30AF\u30BB\u30B5\u30DA\u30A2\u3092\u675F\u306D\u3066\u304F\u3060\u3055\u3044`,
10996
11009
  storagePairClobber: (path) => `<wcs-storage> \u306E value \u3092 #init=element \u306A\u3057\u3067\u30A2\u30AF\u30BB\u30B5\u30DA\u30A2 "${path}" \u306B\u675F\u306D\u3066\u3044\u307E\u3059\u3002state \u306E\u5074\u304C\u5148\u306B\u66F8\u304F\u306E\u3067\u3001\u8AAD\u307F\u8FBC\u307F\u306E\u305F\u3073\u306B getter \u306E\u6700\u521D\u306E\u5024\u304C\u4FDD\u5B58\u5024\u306E\u4E0A\u306B\u4FDD\u5B58\u3055\u308C\u3001setter \u306F\u4FDD\u5B58\u5024\u3092\u53D7\u3051\u53D6\u308C\u307E\u305B\u3093 \u2014 #init=element \u3092\u4ED8\u3051\u3066\u304F\u3060\u3055\u3044\uFF08\`value#init=element: ${path}\`\uFF09`,
10997
11010
  inputNestedWrite: (tag, input, path, example) => `<${tag}> \u306E\u5165\u529B "${input}" \u306B "${path}" \u3092\u675F\u306D\u3066\u3044\u307E\u3059\u304C\u3001\u30DA\u30FC\u30B8\u306F\u305D\u306E\u4E0B\uFF08"${example}"\uFF09\u3078\u66F8\u304D\u8FBC\u3093\u3067\u3044\u307E\u3059\u3002\u675F\u7E1B\u306F "${path}" \u81EA\u8EAB\u3078\u306E\u4EE3\u5165\u3067\u3057\u304B\u5F53\u3066\u76F4\u3055\u308C\u306A\u3044\u306E\u3067\u3001\u4E0B\u3078\u306E\u66F8\u304D\u8FBC\u307F\u306F <${tag}> \u306B\u5C4A\u304D\u307E\u305B\u3093 \u2014 \u30D5\u30A3\u30FC\u30EB\u30C9\u3092\u30D1\u30B9\u3067\u8AAD\u3093\u3067\u65B0\u3057\u3044\u5024\u3092\u8FD4\u3059 getter\uFF08\`this["${example}"]\`\u30FB\`$getAll\`\uFF09\u3092\u675F\u306D\u308B\u304B\u3001\u4E0B\u3078\u66F8\u304B\u305A\u306B "${path}" \u3092\u5DEE\u3057\u66FF\u3048\u3066\u304F\u3060\u3055\u3044`,
10998
11011
  inputNestedWriteOneShot: (tag, input, path, example) => `<${tag}> \u306E\u5165\u529B "${input}" \u306B "${path}" \u3092\u675F\u306D\u3066\u3044\u307E\u3059\u304C\u3001\u30DA\u30FC\u30B8\u306F\u305D\u306E\u4E0B\uFF08"${example}"\uFF09\u3078\u66F8\u304D\u8FBC\u3093\u3067\u3044\u307E\u3059\u3002\u675F\u7E1B\u306F "${path}" \u81EA\u8EAB\u3078\u306E\u4EE3\u5165\u3067\u3057\u304B\u5F53\u3066\u76F4\u3055\u308C\u305A\u3001<${tag}> \u306F\u8981\u6C42\u306E\u305F\u3073\u306B "${input}" \u3092 null \u306B\u623B\u3059\u306E\u3067\u3001\u6B21\u306E\u8981\u6C42\u306F "${input}" \u306A\u3057\u3067\u9001\u3089\u308C\u307E\u3059 \u2014 \u8981\u6C42\u3092\u59CB\u3081\u308B\u30CF\u30F3\u30C9\u30E9\u3067\u65B0\u3057\u3044\u30AA\u30D6\u30B8\u30A7\u30AF\u30C8\u3092\u4F5C\u3063\u3066\u4EE3\u5165\u3057\uFF08\`this.submitBody = { \u2026 }\`\uFF09\u3001\u305D\u308C\u3092\u675F\u306D\u3066\u304F\u3060\u3055\u3044`,
10999
11012
  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`,
11000
11013
  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`,
11014
+ routerOutletMisplaced: () => `\u3053\u306E <wcs-outlet> \u306F <wcs-router> \u306E\u76F4\u5F8C\u306B\u7121\u3044\u306E\u3067\u3001\u3069\u306E router \u3082\u4F7F\u3044\u307E\u305B\u3093\u3002router \u306F\u81EA\u5206\u306E\u76F4\u5F8C\u306E\u8981\u7D20\u3060\u3051\u3092 outlet \u3068\u3057\u3066\u4F7F\u3044\u3001\u305D\u3053\u306B <wcs-outlet> \u304C\u7121\u3051\u308C\u3070\u81EA\u524D\u306E outlet \u3092\u4F5C\u308A\u307E\u3059\u3002\u30EB\u30FC\u30C8\u306F router \u306E\u76F4\u5F8C\u306B\u63CF\u304B\u308C\u3001\u3053\u306E outlet \u306F\u7A7A\u306E\u307E\u307E\u3067\u3059 \u2014 <wcs-router> \u306E\u76F4\u5F8C\u3078\u79FB\u3057\u3066\u304F\u3060\u3055\u3044`,
11001
11015
  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`,
11002
11016
  namedStateAttrDeprecated: (name) => `name \u5C5E\u6027\u306F v2 \u3067\u64A4\u53BB\u3055\u308C\u307E\u3057\u305F\uFF081 root 1 \u30C4\u30EA\u30FC\uFF09\u3002\u30EB\u30FC\u30C8\u30C4\u30EA\u30FC\u3078\u306E\u30DE\u30A6\u30F3\u30C8 <wcs-state mount="${name}"> \u306B\u7F6E\u304D\u63DB\u3048\u3001\u30D1\u30B9\u306F "${name}.<path>" \u3067\u53C2\u7167\u3057\u3066\u304F\u3060\u3055\u3044\uFF08docs/state-mount-design.md \xA79\uFF09`,
11003
11017
  namedStatePathDeprecated: (name) => name === "default" ? `"@default" \u30BB\u30EC\u30AF\u30BF\u306F v2 \u3067\u64A4\u53BB\u3055\u308C\u307E\u3057\u305F\u3002"@default" \u3092\u5916\u3057\u3066\u304F\u3060\u3055\u3044\uFF08docs/state-mount-design.md \xA79\uFF09` : `"@name" \u30BB\u30EC\u30AF\u30BF\u306F v2 \u3067\u64A4\u53BB\u3055\u308C\u307E\u3057\u305F\uFF081 root 1 \u30C4\u30EA\u30FC\uFF09\u3002\u30DE\u30A6\u30F3\u30C8\u3057\u305F\u30C4\u30EA\u30FC\u3092 "${name}.<path>" \u3067\u53C2\u7167\u3057\u3066\u304F\u3060\u3055\u3044\uFF08docs/state-mount-design.md \xA79\uFF09`,
@@ -11131,6 +11145,7 @@ var en = {
11131
11145
  delegatedCurrentTarget: (suggested, eventName, handler) => `"${eventName}" bindings are delegated to the root, so event.currentTarget in "${handler}" is the root, not this element. Write "${suggested}:" to keep the listener on the element, or find the element with event.target.closest(...)`,
11132
11146
  getterCycle: (cycle) => `Path getters form a dependency cycle: ${cycle}`,
11133
11147
  updatedCallbackUnbound: (p) => `$renderedCallback is binding-driven. "${p}" is not bound anywhere in this document, so this branch never runs. Use $watch to react without depending on what is rendered`,
11148
+ onHandlerThis: (name) => `The $on handler "${name}" uses this. A $on handler receives the state as its first argument (\`${name}(state, event) { state.x = \u2026 }\`) and this is undefined, so this.\u2026 throws when it is called \u2014 unlike $watch handlers and methods`,
11134
11149
  getterUntrackedRead: (root, sp) => `Only "${root}" is tracked here: the getter is not re-evaluated when "${sp}" changes (plain property access after a path read is not tracked). Read this["${sp}"] instead`,
11135
11150
  handlerFilterNotAllowed: (prop) => `Filters cannot be applied to event handler "${prop}"`,
11136
11151
  typeExpectation: (label, expected, resultType) => `"${label}" requires ${EN_EXPECTED_LABEL[expected]} (current type: ${resultType})`,
@@ -11181,13 +11196,14 @@ var en = {
11181
11196
  didYouMean: (c) => `. Did you mean "${c}"?`,
11182
11197
  none: () => `none`,
11183
11198
  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`,
11184
- storageSeedClobber: (path, raw) => `The <wcs-storage> value-bound slot "${path}" is seeded with ${raw}. The initial write-back overwrites the persisted value on every load \u2014 add #init=element to the binding (\`value#init=element: ${path}\`) so the stored value wins, and seed the slot with null (what an empty key loads as)`,
11199
+ storageSeedClobber: (path, raw) => `The <wcs-storage> value-bound slot "${path}" is seeded with ${shortSeed(raw)}. The initial write-back overwrites the persisted value on every load \u2014 add #init=element to the binding (\`value#init=element: ${path}\`) so the stored value wins, and seed the slot with null (what an empty key loads as)`,
11185
11200
  storageInitSeed: (path, raw) => `The <wcs-storage> value-bound slot "${path}" is seeded with ${shortSeed(raw)}, but #init=element pulls the element's value into it. On a first visit (an empty key) that is null, so the seed is replaced by null \u2014 seed it with null and supply the default where you read it (\`this.${path} ?? \u2026\`), or bind an accessor pair whose setter ignores null`,
11186
11201
  storagePairClobber: (path) => `The <wcs-storage> value is bound to the accessor pair "${path}" without #init=element. The state's side goes first, so on every load the getter's starting value is saved over the stored one, and the setter never receives it \u2014 add #init=element (\`value#init=element: ${path}\`)`,
11187
11202
  inputNestedWrite: (tag, input, path, example) => `"${path}" is bound to the input "${input}" of <${tag}>, but the page writes below it ("${example}"). A binding is re-applied only when "${path}" itself is assigned, so those writes never reach <${tag}> \u2014 bind a getter that reads the fields by path (\`this["${example}"]\`, \`$getAll\`) and returns a new value, or replace "${path}" instead of writing below it`,
11188
11203
  inputNestedWriteOneShot: (tag, input, path, example) => `"${path}" is bound to the input "${input}" of <${tag}>, but the page writes below it ("${example}"). A binding is re-applied only when "${path}" itself is assigned, and <${tag}> resets "${input}" to null after every request, so the next request goes without it \u2014 build a new object in the handler that starts the request (\`this.submitBody = { \u2026 }\`) and bind that`,
11189
11204
  devtoolsAfterState: () => `Load @wcstack/devtools/auto BEFORE @wcstack/state/auto (otherwise the wiring ledger is not captured live)`,
11190
11205
  baseHrefMissing: () => `An SPA using @wcstack/router needs <base href="/"> in <head> (without it, deep links misderive the basename)`,
11206
+ routerOutletMisplaced: () => `This <wcs-outlet> is not right after a <wcs-router>, so no router uses it: a router uses only the element right after it as its outlet, and makes an outlet of its own there when that is not a <wcs-outlet>. The routes render right after the router and this outlet stays empty \u2014 move it right after the <wcs-router>`,
11191
11207
  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`,
11192
11208
  namedStateAttrDeprecated: (name) => `The "name" attribute was removed in v2 \u2014 there is a single state tree per root. Mount this state onto the tree instead: <wcs-state mount="${name}"> and read it as "${name}.<path>" (docs/state-mount-design.md \xA79)`,
11193
11209
  namedStatePathDeprecated: (name) => name === "default" ? `The "@default" selector was removed in v2 \u2014 drop it (docs/state-mount-design.md \xA79)` : `The "@name" selector was removed in v2 \u2014 there is a single state tree. Mount the named state onto the tree (<wcs-state mount="...">) and read it as "${name}.<path>" (docs/state-mount-design.md \xA79)`,
@@ -14049,7 +14065,6 @@ var EXPLICIT_PROPERTY_REJECTED_HEADS2 = /* @__PURE__ */ new Set([
14049
14065
  var EXPLICIT_PROPERTY_REJECTED_HEADS_RE = new RegExp(
14050
14066
  `^(?:${[...EXPLICIT_PROPERTY_REJECTED_HEADS2].map((w) => w.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|")})(\\.|$)`
14051
14067
  );
14052
- var EMPTYISH_SEEDS = /* @__PURE__ */ new Set(["''", '""', "``", "null", "[]", "{}"]);
14053
14068
  function validateIoNodes(html, bindAttribute = "data-wcs", stateTagName = "wcs-state", locale2, fileReader) {
14054
14069
  const diagnostics = [];
14055
14070
  const msgs = getMessages(locale2);
@@ -14196,12 +14211,12 @@ function validateBindingAgainstContract(tagName, contract, parsed, property, sta
14196
14211
  if (tagName === "wcs-storage" && property === "value" && !hasManual && parsed.path && !/(?:^|,)init=(?:element|auto)\b/.test(modifiers)) {
14197
14212
  const paths = getPaths();
14198
14213
  const cand = findDataSlot(paths, parsed.path);
14199
- if (cand?.rawInitial !== void 0 && EMPTYISH_SEEDS.has(normalizeSeed(cand.rawInitial))) {
14214
+ if (cand?.rawInitial !== void 0 && (cand.rawInitial.trim() === "null" || isNonNullLiteralSeed(cand.rawInitial))) {
14200
14215
  diagnostics.push({
14201
14216
  code: WcsDiagnosticCode.StorageSeedClobber,
14202
14217
  start,
14203
14218
  end,
14204
- severity: "warning",
14219
+ severity: "error",
14205
14220
  tag: tagName,
14206
14221
  statePath: parsed.path,
14207
14222
  message: msgs.storageSeedClobber(parsed.path, cand.rawInitial)
@@ -14211,7 +14226,7 @@ function validateBindingAgainstContract(tagName, contract, parsed, property, sta
14211
14226
  code: WcsDiagnosticCode.StorageSeedClobber,
14212
14227
  start,
14213
14228
  end,
14214
- severity: "warning",
14229
+ severity: "error",
14215
14230
  tag: tagName,
14216
14231
  statePath: parsed.path,
14217
14232
  message: msgs.storagePairClobber(parsed.path)
@@ -14240,10 +14255,6 @@ function isNonNullLiteralSeed(raw) {
14240
14255
  function findDataSlot(paths, path) {
14241
14256
  return paths.find((c) => c.kind === "data" && c.path === path);
14242
14257
  }
14243
- function normalizeSeed(raw) {
14244
- const compact = raw.replace(/\s+/g, "");
14245
- return compact === "" ? raw : compact;
14246
- }
14247
14258
  function findBuiltinTagOccurrences(html) {
14248
14259
  const out = [];
14249
14260
  const regex = /<(wcs-[a-z0-9-]+)((?:"[^"]*"|'[^']*'|[^>"'])*)>/gi;
@@ -14386,7 +14397,8 @@ function validateDocumentEnv(html, locale2) {
14386
14397
  });
14387
14398
  }
14388
14399
  }
14389
- const router = autos.find((a) => a.pkgs.includes("router") && (!a.bundle || /<wcs-router\b/i.test(scanText)));
14400
+ const hasRouterTag = /<wcs-router\b/i.test(scanText);
14401
+ const router = autos.find((a) => a.pkgs.includes("router") && (!a.bundle || hasRouterTag)) ?? (hasRouterTag ? collectInlineImports(scanText).find((i) => /@wcstack\/router\b/.test(i.spec) || BUNDLE_AUTO.test(i.spec)) : void 0);
14390
14402
  if (router && !/<base\b[^>]*\bhref\s*=/i.test(scanText)) {
14391
14403
  diagnostics.push({
14392
14404
  code: WcsDiagnosticCode.BaseHrefMissing,
@@ -14396,6 +14408,15 @@ function validateDocumentEnv(html, locale2) {
14396
14408
  message: msgs.baseHrefMissing()
14397
14409
  });
14398
14410
  }
14411
+ for (const outlet of findMisplacedOutlets(scanText)) {
14412
+ diagnostics.push({
14413
+ code: WcsDiagnosticCode.RouterOutletMisplaced,
14414
+ start: outlet.start,
14415
+ end: outlet.end,
14416
+ severity: "warning",
14417
+ message: msgs.routerOutletMisplaced()
14418
+ });
14419
+ }
14399
14420
  const refs = collectSignalsRefs(scanText);
14400
14421
  const dom = refs.find((r) => r.kind === "dom");
14401
14422
  const bare = refs.find((r) => r.kind === "bare");
@@ -14435,35 +14456,41 @@ function findWcstackAutoScripts(html) {
14435
14456
  }
14436
14457
  function collectSignalsRefs(html) {
14437
14458
  const refs = [];
14438
- const scriptRegex = /<script\b((?:"[^"]*"|'[^']*'|[^>"'])*)>([\s\S]*?)<\/script\s*>/gi;
14459
+ const scriptRegex = /<script\b((?:"[^"]*"|'[^']*'|[^>"'])*)>/gi;
14439
14460
  let match;
14440
14461
  while ((match = scriptRegex.exec(html)) !== null) {
14441
- const openTag = html.slice(match.index, match.index + match[0].indexOf(">") + 1);
14442
- const src = extractSrc(openTag);
14443
- if (src) {
14444
- const kind = classifySignalsSpecifier(src.value);
14445
- if (kind) {
14446
- refs.push({
14447
- kind,
14448
- start: match.index + src.offsetInTag,
14449
- end: match.index + src.offsetInTag + src.value.length
14450
- });
14451
- }
14452
- continue;
14462
+ const src = extractSrc(match[0]);
14463
+ const kind = src ? classifySignalsSpecifier(src.value) : null;
14464
+ if (src && kind) {
14465
+ refs.push({
14466
+ kind,
14467
+ start: match.index + src.offsetInTag,
14468
+ end: match.index + src.offsetInTag + src.value.length
14469
+ });
14453
14470
  }
14454
- if (!/\btype\s*=\s*(["'])module\1/i.test(match[1])) continue;
14471
+ }
14472
+ for (const im of collectInlineImports(html)) {
14473
+ const kind = classifySignalsSpecifier(im.spec);
14474
+ if (kind) refs.push({ kind, start: im.start, end: im.end });
14475
+ }
14476
+ return refs.sort((a, b) => a.start - b.start);
14477
+ }
14478
+ function collectInlineImports(html) {
14479
+ const out = [];
14480
+ const scriptRegex = /<script\b((?:"[^"]*"|'[^']*'|[^>"'])*)>([\s\S]*?)<\/script\s*>/gi;
14481
+ let match;
14482
+ while ((match = scriptRegex.exec(html)) !== null) {
14483
+ if (extractSrc(`<script${match[1]}>`) || !/\btype\s*=\s*(["'])module\1/i.test(match[1])) continue;
14455
14484
  const bodyStart = match.index + match[0].indexOf(">") + 1;
14456
14485
  const body = blankJsComments(match[2]);
14457
- const importRegex = /(?:\bfrom\s*|\bimport\s*\(?\s*)(["'])([^"']*@wcstack\/signals[^"']*)\1/g;
14486
+ const importRegex = /(?:\bfrom\s*|\bimport\s*\(?\s*)(["'])([^"']*)\1/g;
14458
14487
  let im;
14459
14488
  while ((im = importRegex.exec(body)) !== null) {
14460
- const kind = classifySignalsSpecifier(im[2]);
14461
- if (!kind) continue;
14462
- const specStart = bodyStart + im.index + im[0].indexOf(im[1]) + 1;
14463
- refs.push({ kind, start: specStart, end: specStart + im[2].length });
14489
+ const start = bodyStart + im.index + im[0].indexOf(im[1]) + 1;
14490
+ out.push({ spec: im[2], start, end: start + im[2].length });
14464
14491
  }
14465
14492
  }
14466
- return refs;
14493
+ return out;
14467
14494
  }
14468
14495
  function classifySignalsSpecifier(spec) {
14469
14496
  if (!spec.includes("@wcstack/signals")) return null;
@@ -14483,6 +14510,34 @@ function blankHtmlComments(html) {
14483
14510
  function blankJsComments(code) {
14484
14511
  return code.replace(/\/\*[\s\S]*?\*\//g, (m) => " ".repeat(m.length)).replace(/(^|[^:])\/\/[^\n]*/g, (m, pre) => pre + " ".repeat(m.length - pre.length));
14485
14512
  }
14513
+ var ROUTER_TAG2 = "wcs-router";
14514
+ var OUTLET_TAG = "wcs-outlet";
14515
+ function findMisplacedOutlets(html) {
14516
+ const scan = html.replace(
14517
+ /(<(script|style)\b[^>]*>)([\s\S]*?)(<\/\2\s*>)/gi,
14518
+ (_m, open, _name, body, close) => open + " ".repeat(body.length) + close
14519
+ );
14520
+ const tokens = [];
14521
+ const tagRegex = /<(\/?)([a-zA-Z][\w-]*)(?:"[^"]*"|'[^']*'|[^>"'])*>/g;
14522
+ let match;
14523
+ while ((match = tagRegex.exec(scan)) !== null) {
14524
+ const nameStart = match.index + 1 + match[1].length;
14525
+ tokens.push({ name: match[2].toLowerCase(), close: match[1] === "/", start: nameStart, end: nameStart + match[2].length });
14526
+ }
14527
+ let routerWithoutOutlet = false;
14528
+ const misplaced = [];
14529
+ tokens.forEach((token, i) => {
14530
+ if (token.name === ROUTER_TAG2 && token.close) {
14531
+ const next = tokens[i + 1];
14532
+ if (!next || next.close || next.name !== OUTLET_TAG) routerWithoutOutlet = true;
14533
+ }
14534
+ if (token.name === OUTLET_TAG && !token.close) {
14535
+ const prev = tokens[i - 1];
14536
+ if (!(prev && prev.close && prev.name === ROUTER_TAG2)) misplaced.push({ start: token.start, end: token.end });
14537
+ }
14538
+ });
14539
+ return routerWithoutOutlet ? misplaced : [];
14540
+ }
14486
14541
 
14487
14542
  // src/service/watchDeclarationValidator.ts
14488
14543
  var STATE_NAME_SEPARATOR = "@";
@@ -16098,6 +16153,24 @@ function hasOutOfRangeIndexName(script) {
16098
16153
  }
16099
16154
  return false;
16100
16155
  }
16156
+ var ANY_MEMBER = { has: () => true };
16157
+ function validateOnHandlerThis(script, scriptStart, locale2) {
16158
+ const msgs = getMessages(locale2);
16159
+ const out = [];
16160
+ for (const handler of analyzeOnHandlerSources(script)) {
16161
+ const refs = handler.kind === "body" ? collectThisMemberRefs(handler.text, ANY_MEMBER) : collectThisMemberRefsInValue(handler.text, ANY_MEMBER);
16162
+ const first = refs?.[0];
16163
+ if (first === void 0) continue;
16164
+ out.push({
16165
+ code: WcsDiagnosticCode.OnHandlerThis,
16166
+ start: scriptStart + handler.start + first.start,
16167
+ end: scriptStart + handler.start + first.end,
16168
+ severity: "error",
16169
+ message: msgs.onHandlerThis(handler.name)
16170
+ });
16171
+ }
16172
+ return out;
16173
+ }
16101
16174
  function validateSemantics(html, stateTagName = "wcs-state", locale2, bindAttrName = "data-wcs") {
16102
16175
  const out = [];
16103
16176
  const blocks = parseLoadedScriptBlocks(html, stateTagName);
@@ -16109,6 +16182,7 @@ function validateSemantics(html, stateTagName = "wcs-state", locale2, bindAttrNa
16109
16182
  out.push(...validateIndexParamReads(block.content, block.contentStart, locale2));
16110
16183
  out.push(...validateGetterCycles(block.content, block.contentStart, locale2));
16111
16184
  out.push(...validateGetterUntrackedReads(block.content, block.contentStart, getNestedWriteRoots, locale2));
16185
+ out.push(...validateOnHandlerThis(block.content, block.contentStart, locale2));
16112
16186
  }
16113
16187
  out.push(...validateInputNestedWrites(html, stateTagName, bindAttrName, getNestedWriteRoots, locale2));
16114
16188
  out.push(...validateUpdatedCallbackDemand(html, stateTagName, bindAttrName, locale2));
@@ -573,7 +573,7 @@ function loadSchemaCore() {
573
573
  return cached;
574
574
  }
575
575
 
576
- var version = "4.0.3";
576
+ var version = "4.1.0";
577
577
  var pkg = {
578
578
  version: version};
579
579
 
package/dist/wcs-tsc.mjs CHANGED
@@ -5,7 +5,7 @@ import { tmpdir } from 'node:os';
5
5
  import { dirname, join, isAbsolute, resolve, basename } from 'node:path';
6
6
  import { fileURLToPath } from 'node:url';
7
7
 
8
- var version = "4.0.3";
8
+ var version = "4.1.0";
9
9
  var pkg = {
10
10
  version: version};
11
11
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wcstack/typescript",
3
- "version": "4.0.3",
3
+ "version": "4.1.0",
4
4
  "description": "TypeScript tooling for wcstack apps: wcs-schema generates the sidecar stateSchema from a typed state file, so wcs-validate checks data-wcs paths against real types in CI and every editor.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.esm.js",