@wcstack/typescript 4.0.4 → 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.4";
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
  // 越境 `@` / `$` 始まり / 空キー・空セグメント / 明らかな非関数ハンドラ。
@@ -3239,9 +3240,15 @@ function analyzeWatchEntries(scriptContent) {
3239
3240
  }));
3240
3241
  }
3241
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) {
3242
3249
  const root = locateDefaultExportObject(scriptContent);
3243
3250
  if (!root) return [];
3244
- const prop = parseTopLevelProperties(root.content).find((p) => p.name === RESERVED_WATCH_KEY);
3251
+ const prop = parseTopLevelProperties(root.content).find((p) => p.name === key);
3245
3252
  if (!prop || prop.kind !== "data" || !prop.value || !isObjectLiteral(prop.value) || prop.valueStart === void 0) {
3246
3253
  return [];
3247
3254
  }
@@ -3251,6 +3258,7 @@ function analyzeWatchHandlerSources(scriptContent) {
3251
3258
  for (const entry of parseTopLevelProperties(extractObjectContent(prop.value))) {
3252
3259
  if (entry.value === void 0 || entry.valueStart === void 0) continue;
3253
3260
  out.push({
3261
+ name: entry.name,
3254
3262
  kind: entry.kind === "data" ? "value" : "body",
3255
3263
  text: entry.value,
3256
3264
  start: innerStart + entry.valueStart
@@ -10945,6 +10953,7 @@ var ja = {
10945
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`,
10946
10954
  getterCycle: (cycle) => `\u30D1\u30B9 getter \u304C\u5FAA\u74B0\u53C2\u7167\u3057\u3066\u3044\u307E\u3059: ${cycle}`,
10947
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`,
10948
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`,
10949
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`,
10950
10959
  typeExpectation: (label, expected, resultType) => `"${label}" \u306B\u306F${JA_EXPECTED_LABEL[expected]}\u304C\u5FC5\u8981\u3067\u3059\uFF08\u73FE\u5728\u306E\u578B: ${resultType}\uFF09`,
@@ -10995,7 +11004,7 @@ var ja = {
10995
11004
  didYouMean: (c) => `\u3002\u3082\u3057\u304B\u3057\u3066: "${c}"`,
10996
11005
  none: () => `\u306A\u3057`,
10997
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`,
10998
- 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`,
10999
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`,
11000
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`,
11001
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`,
@@ -11136,6 +11145,7 @@ var en = {
11136
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(...)`,
11137
11146
  getterCycle: (cycle) => `Path getters form a dependency cycle: ${cycle}`,
11138
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`,
11139
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`,
11140
11150
  handlerFilterNotAllowed: (prop) => `Filters cannot be applied to event handler "${prop}"`,
11141
11151
  typeExpectation: (label, expected, resultType) => `"${label}" requires ${EN_EXPECTED_LABEL[expected]} (current type: ${resultType})`,
@@ -11186,7 +11196,7 @@ var en = {
11186
11196
  didYouMean: (c) => `. Did you mean "${c}"?`,
11187
11197
  none: () => `none`,
11188
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`,
11189
- 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)`,
11190
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`,
11191
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}\`)`,
11192
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`,
@@ -14055,7 +14065,6 @@ var EXPLICIT_PROPERTY_REJECTED_HEADS2 = /* @__PURE__ */ new Set([
14055
14065
  var EXPLICIT_PROPERTY_REJECTED_HEADS_RE = new RegExp(
14056
14066
  `^(?:${[...EXPLICIT_PROPERTY_REJECTED_HEADS2].map((w) => w.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|")})(\\.|$)`
14057
14067
  );
14058
- var EMPTYISH_SEEDS = /* @__PURE__ */ new Set(["''", '""', "``", "null", "[]", "{}"]);
14059
14068
  function validateIoNodes(html, bindAttribute = "data-wcs", stateTagName = "wcs-state", locale2, fileReader) {
14060
14069
  const diagnostics = [];
14061
14070
  const msgs = getMessages(locale2);
@@ -14202,12 +14211,12 @@ function validateBindingAgainstContract(tagName, contract, parsed, property, sta
14202
14211
  if (tagName === "wcs-storage" && property === "value" && !hasManual && parsed.path && !/(?:^|,)init=(?:element|auto)\b/.test(modifiers)) {
14203
14212
  const paths = getPaths();
14204
14213
  const cand = findDataSlot(paths, parsed.path);
14205
- if (cand?.rawInitial !== void 0 && EMPTYISH_SEEDS.has(normalizeSeed(cand.rawInitial))) {
14214
+ if (cand?.rawInitial !== void 0 && (cand.rawInitial.trim() === "null" || isNonNullLiteralSeed(cand.rawInitial))) {
14206
14215
  diagnostics.push({
14207
14216
  code: WcsDiagnosticCode.StorageSeedClobber,
14208
14217
  start,
14209
14218
  end,
14210
- severity: "warning",
14219
+ severity: "error",
14211
14220
  tag: tagName,
14212
14221
  statePath: parsed.path,
14213
14222
  message: msgs.storageSeedClobber(parsed.path, cand.rawInitial)
@@ -14217,7 +14226,7 @@ function validateBindingAgainstContract(tagName, contract, parsed, property, sta
14217
14226
  code: WcsDiagnosticCode.StorageSeedClobber,
14218
14227
  start,
14219
14228
  end,
14220
- severity: "warning",
14229
+ severity: "error",
14221
14230
  tag: tagName,
14222
14231
  statePath: parsed.path,
14223
14232
  message: msgs.storagePairClobber(parsed.path)
@@ -14246,10 +14255,6 @@ function isNonNullLiteralSeed(raw) {
14246
14255
  function findDataSlot(paths, path) {
14247
14256
  return paths.find((c) => c.kind === "data" && c.path === path);
14248
14257
  }
14249
- function normalizeSeed(raw) {
14250
- const compact = raw.replace(/\s+/g, "");
14251
- return compact === "" ? raw : compact;
14252
- }
14253
14258
  function findBuiltinTagOccurrences(html) {
14254
14259
  const out = [];
14255
14260
  const regex = /<(wcs-[a-z0-9-]+)((?:"[^"]*"|'[^']*'|[^>"'])*)>/gi;
@@ -14392,7 +14397,8 @@ function validateDocumentEnv(html, locale2) {
14392
14397
  });
14393
14398
  }
14394
14399
  }
14395
- 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);
14396
14402
  if (router && !/<base\b[^>]*\bhref\s*=/i.test(scanText)) {
14397
14403
  diagnostics.push({
14398
14404
  code: WcsDiagnosticCode.BaseHrefMissing,
@@ -14450,35 +14456,41 @@ function findWcstackAutoScripts(html) {
14450
14456
  }
14451
14457
  function collectSignalsRefs(html) {
14452
14458
  const refs = [];
14453
- const scriptRegex = /<script\b((?:"[^"]*"|'[^']*'|[^>"'])*)>([\s\S]*?)<\/script\s*>/gi;
14459
+ const scriptRegex = /<script\b((?:"[^"]*"|'[^']*'|[^>"'])*)>/gi;
14454
14460
  let match;
14455
14461
  while ((match = scriptRegex.exec(html)) !== null) {
14456
- const openTag = html.slice(match.index, match.index + match[0].indexOf(">") + 1);
14457
- const src = extractSrc(openTag);
14458
- if (src) {
14459
- const kind = classifySignalsSpecifier(src.value);
14460
- if (kind) {
14461
- refs.push({
14462
- kind,
14463
- start: match.index + src.offsetInTag,
14464
- end: match.index + src.offsetInTag + src.value.length
14465
- });
14466
- }
14467
- 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
+ });
14468
14470
  }
14469
- 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;
14470
14484
  const bodyStart = match.index + match[0].indexOf(">") + 1;
14471
14485
  const body = blankJsComments(match[2]);
14472
- const importRegex = /(?:\bfrom\s*|\bimport\s*\(?\s*)(["'])([^"']*@wcstack\/signals[^"']*)\1/g;
14486
+ const importRegex = /(?:\bfrom\s*|\bimport\s*\(?\s*)(["'])([^"']*)\1/g;
14473
14487
  let im;
14474
14488
  while ((im = importRegex.exec(body)) !== null) {
14475
- const kind = classifySignalsSpecifier(im[2]);
14476
- if (!kind) continue;
14477
- const specStart = bodyStart + im.index + im[0].indexOf(im[1]) + 1;
14478
- 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 });
14479
14491
  }
14480
14492
  }
14481
- return refs;
14493
+ return out;
14482
14494
  }
14483
14495
  function classifySignalsSpecifier(spec) {
14484
14496
  if (!spec.includes("@wcstack/signals")) return null;
@@ -16141,6 +16153,24 @@ function hasOutOfRangeIndexName(script) {
16141
16153
  }
16142
16154
  return false;
16143
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
+ }
16144
16174
  function validateSemantics(html, stateTagName = "wcs-state", locale2, bindAttrName = "data-wcs") {
16145
16175
  const out = [];
16146
16176
  const blocks = parseLoadedScriptBlocks(html, stateTagName);
@@ -16152,6 +16182,7 @@ function validateSemantics(html, stateTagName = "wcs-state", locale2, bindAttrNa
16152
16182
  out.push(...validateIndexParamReads(block.content, block.contentStart, locale2));
16153
16183
  out.push(...validateGetterCycles(block.content, block.contentStart, locale2));
16154
16184
  out.push(...validateGetterUntrackedReads(block.content, block.contentStart, getNestedWriteRoots, locale2));
16185
+ out.push(...validateOnHandlerThis(block.content, block.contentStart, locale2));
16155
16186
  }
16156
16187
  out.push(...validateInputNestedWrites(html, stateTagName, bindAttrName, getNestedWriteRoots, locale2));
16157
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.4";
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.4";
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.4",
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",