@wcstack/lint 1.29.0 → 1.30.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 +106 -36
  2. package/package.json +1 -1
package/dist/cli.cjs CHANGED
@@ -110,6 +110,9 @@ var WcsDiagnosticCode = {
110
110
  // --- built-in wcs-* tag contract (generated/builtinTags.generated.ts が正本) ---
111
111
  // 未知メンバーへのバインド(プロパティ / command. / eventToken. キー)。黙って無視される。
112
112
  TagMemberUnknown: "wcs/tag-member-unknown",
113
+ // wcBindable 無宣言タグ(wcs-fetch-header 等のヘルパー)への spread。
114
+ // ランタイム(expandSpread)は raiseError で落とす。
115
+ SpreadNoBindable: "wcs/spread-no-bindable",
113
116
  // trigger バインド先スロットの true シード(エッジ検出なし・manual バイパスで即発火)。
114
117
  TriggerSeededTruthy: "wcs/trigger-seeded-truthy",
115
118
  // 非 manual <wcs-storage> value バインド先の空値シード(初期書き戻しが保存値を上書き)。
@@ -620,14 +623,9 @@ function pushDataPropertyPathsAt(path, prop, paths, stateName, depth) {
620
623
  if (prop.value && isArrayLiteral(prop.value)) {
621
624
  paths.push({ path: `${path}.*`, kind: "list", stateName });
622
625
  paths.push({ path: `${path}.length`, kind: "data", typeHint: "number", stateName });
623
- const elementProps = extractArrayElementProperties(prop.value);
624
- for (const childProp of elementProps) {
625
- paths.push({
626
- path: `${path}.*.${childProp.name}`,
627
- kind: "data",
628
- typeHint: childProp.typeHint,
629
- stateName
630
- });
626
+ if (depth >= MAX_OBJECT_NEST_DEPTH) return;
627
+ for (const childProp of extractArrayElementDataProperties(prop.value)) {
628
+ pushDataPropertyPathsAt(`${path}.*.${childProp.name}`, childProp, paths, stateName, depth + 1);
631
629
  }
632
630
  return;
633
631
  }
@@ -653,25 +651,27 @@ function analyzeJsonPaths(jsonString, stateName = "default") {
653
651
  return paths;
654
652
  }
655
653
  function collectJsonPaths(obj, prefix, paths, stateName, depth) {
656
- if (depth > 5) return;
654
+ if (depth >= MAX_OBJECT_NEST_DEPTH) return;
657
655
  for (const [key, value] of Object.entries(obj)) {
658
656
  if (prefix === "" && key.startsWith("$")) continue;
659
657
  const path = prefix ? `${prefix}.${key}` : key;
660
- const typeHint = inferJsonTypeHint(value);
661
- paths.push({ path, kind: "data", typeHint, stateName });
662
- if (Array.isArray(value)) {
663
- paths.push({ path: `${path}.*`, kind: "list", stateName });
664
- paths.push({ path: `${path}.length`, kind: "data", typeHint: "number", stateName });
665
- if (value.length > 0 && typeof value[0] === "object" && value[0] !== null && !Array.isArray(value[0])) {
666
- const firstElement = value[0];
667
- for (const [childKey, childValue] of Object.entries(firstElement)) {
668
- const childPath = `${path}.*.${childKey}`;
669
- paths.push({ path: childPath, kind: "data", typeHint: inferJsonTypeHint(childValue), stateName });
670
- }
658
+ pushJsonValuePaths(path, value, paths, stateName, depth);
659
+ }
660
+ }
661
+ function pushJsonValuePaths(path, value, paths, stateName, depth) {
662
+ paths.push({ path, kind: "data", typeHint: inferJsonTypeHint(value), stateName });
663
+ if (Array.isArray(value)) {
664
+ paths.push({ path: `${path}.*`, kind: "list", stateName });
665
+ paths.push({ path: `${path}.length`, kind: "data", typeHint: "number", stateName });
666
+ if (depth >= MAX_OBJECT_NEST_DEPTH) return;
667
+ if (value.length > 0 && typeof value[0] === "object" && value[0] !== null && !Array.isArray(value[0])) {
668
+ const firstElement = value[0];
669
+ for (const [childKey, childValue] of Object.entries(firstElement)) {
670
+ pushJsonValuePaths(`${path}.*.${childKey}`, childValue, paths, stateName, depth + 1);
671
671
  }
672
- } else if (typeof value === "object" && value !== null) {
673
- collectJsonPaths(value, path, paths, stateName, depth + 1);
674
672
  }
673
+ } else if (typeof value === "object" && value !== null) {
674
+ collectJsonPaths(value, path, paths, stateName, depth + 1);
675
675
  }
676
676
  }
677
677
  function inferJsonTypeHint(value) {
@@ -842,21 +842,15 @@ function extractObjectContent(value) {
842
842
  if (start === -1) return "";
843
843
  return extractBracedContent(trimmed, scan, start);
844
844
  }
845
- function extractArrayElementProperties(value) {
845
+ function extractArrayElementDataProperties(value) {
846
846
  const trimmed = value.trim();
847
847
  if (!trimmed.startsWith("[")) return [];
848
848
  const scan = maskCommentsAndStrings(trimmed);
849
- const objectStart = scan.indexOf("{");
850
- if (objectStart === -1) return [];
851
- const objectContent = extractBracedContent(trimmed, scan, objectStart);
852
- const props = [];
853
- const allProps = parseTopLevelProperties(objectContent);
854
- for (const prop of allProps) {
855
- if (prop.kind === "data") {
856
- props.push({ name: prop.name, typeHint: prop.typeHint });
857
- }
858
- }
859
- return props;
849
+ let first = 1;
850
+ while (first < scan.length && /\s/.test(scan[first])) first++;
851
+ if (scan[first] !== "{") return [];
852
+ const objectContent = extractBracedContent(trimmed, scan, first);
853
+ return parseTopLevelProperties(objectContent).filter((prop) => prop.kind === "data");
860
854
  }
861
855
  function extractJsDocType(content, propIndex) {
862
856
  const before = content.slice(Math.max(0, propIndex - 200), propIndex);
@@ -992,6 +986,9 @@ function getInnermostForPath(html, offset, bindAttrName = "data-wcs") {
992
986
  return chain.length === 0 ? null : chain[chain.length - 1];
993
987
  }
994
988
  function getEnclosingForPaths(html, offset, bindAttrName = "data-wcs") {
989
+ return getEnclosingFors(html, offset, bindAttrName).map((entry) => entry.path);
990
+ }
991
+ function getEnclosingFors(html, offset, bindAttrName = "data-wcs") {
995
992
  const escaped = bindAttrName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
996
993
  const openRegex = new RegExp(
997
994
  `<template[^>]*${escaped}\\s*=\\s*["']\\s*for\\s*:\\s*([^"']+?)\\s*["']`,
@@ -1005,7 +1002,7 @@ function getEnclosingForPaths(html, offset, bindAttrName = "data-wcs") {
1005
1002
  if (tagEnd === -1 || tagEnd >= offset) continue;
1006
1003
  const depth = getForTemplateDepthAt(html, match.index, offset, bindAttrName);
1007
1004
  if (depth > 0) {
1008
- enclosing.push(match[1].trim());
1005
+ enclosing.push({ path: match[1].trim(), anchor: match.index });
1009
1006
  }
1010
1007
  }
1011
1008
  return enclosing;
@@ -1084,6 +1081,7 @@ var ja = {
1084
1081
  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`,
1085
1082
  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`,
1086
1083
  tagCommandUnknown: (name, tag, declared) => `"${name}" \u306F <${tag}> \u306E command \u3067\u306F\u3042\u308A\u307E\u305B\u3093\uFF08\u5BA3\u8A00\u6E08\u307F: ${declared}\uFF09`,
1084
+ 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`,
1087
1085
  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`,
1088
1086
  didYouMean: (c) => `\u3002\u3082\u3057\u304B\u3057\u3066: "${c}"`,
1089
1087
  none: () => `\u306A\u3057`,
@@ -1133,6 +1131,7 @@ var en = {
1133
1131
  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.`,
1134
1132
  tagMemberUnknown: (prop, tag) => `"${prop}" is not a wcBindable member of <${tag}> (bindings to unknown members are silently ignored)`,
1135
1133
  tagCommandUnknown: (name, tag, declared) => `"${name}" is not a command of <${tag}> (declared: ${declared})`,
1134
+ spreadNoBindable: (tag) => `'...' (spread) requires <${tag}> to expose a valid wcBindable declaration \u2014 this tag declares none, so the runtime raises an error`,
1136
1135
  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})`,
1137
1136
  didYouMean: (c) => `. Did you mean "${c}"?`,
1138
1137
  none: () => `none`,
@@ -2079,6 +2078,7 @@ function isValidTemplatePath(path, pathSet, scopedPaths) {
2079
2078
  var BUILTIN_TAGS = {
2080
2079
  "wcs-accelerometer": {
2081
2080
  "package": "accelerometer",
2081
+ "hasWcBindable": true,
2082
2082
  "observedAttributes": [],
2083
2083
  "inputs": {
2084
2084
  "frequency": null
@@ -2097,6 +2097,7 @@ var BUILTIN_TAGS = {
2097
2097
  },
2098
2098
  "wcs-ambient-light-sensor": {
2099
2099
  "package": "ambient-light-sensor",
2100
+ "hasWcBindable": true,
2100
2101
  "observedAttributes": [],
2101
2102
  "inputs": {
2102
2103
  "frequency": null
@@ -2113,6 +2114,7 @@ var BUILTIN_TAGS = {
2113
2114
  },
2114
2115
  "wcs-audio": {
2115
2116
  "package": "audio",
2117
+ "hasWcBindable": true,
2116
2118
  "observedAttributes": [
2117
2119
  "volume",
2118
2120
  "limiter",
@@ -2145,6 +2147,7 @@ var BUILTIN_TAGS = {
2145
2147
  },
2146
2148
  "wcs-voice": {
2147
2149
  "package": "audio",
2150
+ "hasWcBindable": false,
2148
2151
  "observedAttributes": [
2149
2152
  "poly"
2150
2153
  ],
@@ -2154,6 +2157,7 @@ var BUILTIN_TAGS = {
2154
2157
  },
2155
2158
  "wcs-osc": {
2156
2159
  "package": "audio",
2160
+ "hasWcBindable": true,
2157
2161
  "observedAttributes": [
2158
2162
  "frequency",
2159
2163
  "detune",
@@ -2179,6 +2183,7 @@ var BUILTIN_TAGS = {
2179
2183
  },
2180
2184
  "wcs-noise": {
2181
2185
  "package": "audio",
2186
+ "hasWcBindable": true,
2182
2187
  "observedAttributes": [
2183
2188
  "id",
2184
2189
  "out",
@@ -2193,6 +2198,7 @@ var BUILTIN_TAGS = {
2193
2198
  },
2194
2199
  "wcs-biquad": {
2195
2200
  "package": "audio",
2201
+ "hasWcBindable": true,
2196
2202
  "observedAttributes": [
2197
2203
  "frequency",
2198
2204
  "q",
@@ -2218,6 +2224,7 @@ var BUILTIN_TAGS = {
2218
2224
  },
2219
2225
  "wcs-gain": {
2220
2226
  "package": "audio",
2227
+ "hasWcBindable": true,
2221
2228
  "observedAttributes": [
2222
2229
  "gain",
2223
2230
  "id",
@@ -2235,6 +2242,7 @@ var BUILTIN_TAGS = {
2235
2242
  },
2236
2243
  "wcs-delay": {
2237
2244
  "package": "audio",
2245
+ "hasWcBindable": true,
2238
2246
  "observedAttributes": [
2239
2247
  "time",
2240
2248
  "feedback",
@@ -2256,6 +2264,7 @@ var BUILTIN_TAGS = {
2256
2264
  },
2257
2265
  "wcs-shaper": {
2258
2266
  "package": "audio",
2267
+ "hasWcBindable": true,
2259
2268
  "observedAttributes": [
2260
2269
  "amount",
2261
2270
  "id",
@@ -2273,6 +2282,7 @@ var BUILTIN_TAGS = {
2273
2282
  },
2274
2283
  "wcs-env": {
2275
2284
  "package": "audio",
2285
+ "hasWcBindable": true,
2276
2286
  "observedAttributes": [
2277
2287
  "attack",
2278
2288
  "decay",
@@ -2298,6 +2308,7 @@ var BUILTIN_TAGS = {
2298
2308
  },
2299
2309
  "wcs-lfo": {
2300
2310
  "package": "audio",
2311
+ "hasWcBindable": true,
2301
2312
  "observedAttributes": [
2302
2313
  "rate",
2303
2314
  "depth",
@@ -2319,6 +2330,7 @@ var BUILTIN_TAGS = {
2319
2330
  },
2320
2331
  "wcs-analyser": {
2321
2332
  "package": "audio",
2333
+ "hasWcBindable": true,
2322
2334
  "observedAttributes": [
2323
2335
  "fft",
2324
2336
  "smoothing",
@@ -2342,6 +2354,7 @@ var BUILTIN_TAGS = {
2342
2354
  },
2343
2355
  "wcs-broadcast": {
2344
2356
  "package": "broadcast",
2357
+ "hasWcBindable": true,
2345
2358
  "observedAttributes": [
2346
2359
  "name"
2347
2360
  ],
@@ -2362,6 +2375,7 @@ var BUILTIN_TAGS = {
2362
2375
  },
2363
2376
  "wcs-camera": {
2364
2377
  "package": "camera",
2378
+ "hasWcBindable": true,
2365
2379
  "observedAttributes": [
2366
2380
  "facing-mode",
2367
2381
  "device-id",
@@ -2397,6 +2411,7 @@ var BUILTIN_TAGS = {
2397
2411
  },
2398
2412
  "wcs-recorder": {
2399
2413
  "package": "camera",
2414
+ "hasWcBindable": true,
2400
2415
  "observedAttributes": [],
2401
2416
  "inputs": {
2402
2417
  "mimeType": "mime-type",
@@ -2426,6 +2441,7 @@ var BUILTIN_TAGS = {
2426
2441
  },
2427
2442
  "wcs-clipboard": {
2428
2443
  "package": "clipboard",
2444
+ "hasWcBindable": true,
2429
2445
  "observedAttributes": [],
2430
2446
  "inputs": {
2431
2447
  "monitor": "monitor"
@@ -2454,6 +2470,7 @@ var BUILTIN_TAGS = {
2454
2470
  },
2455
2471
  "wcs-contacts": {
2456
2472
  "package": "contacts",
2473
+ "hasWcBindable": true,
2457
2474
  "observedAttributes": [],
2458
2475
  "inputs": {},
2459
2476
  "properties": [
@@ -2469,6 +2486,7 @@ var BUILTIN_TAGS = {
2469
2486
  },
2470
2487
  "wcs-credential": {
2471
2488
  "package": "credential",
2489
+ "hasWcBindable": true,
2472
2490
  "observedAttributes": [],
2473
2491
  "inputs": {},
2474
2492
  "properties": [
@@ -2485,6 +2503,7 @@ var BUILTIN_TAGS = {
2485
2503
  },
2486
2504
  "wcs-debounce": {
2487
2505
  "package": "debounce",
2506
+ "hasWcBindable": true,
2488
2507
  "observedAttributes": [],
2489
2508
  "inputs": {
2490
2509
  "source": null,
@@ -2506,6 +2525,7 @@ var BUILTIN_TAGS = {
2506
2525
  },
2507
2526
  "wcs-throttle": {
2508
2527
  "package": "debounce",
2528
+ "hasWcBindable": true,
2509
2529
  "observedAttributes": [],
2510
2530
  "inputs": {
2511
2531
  "source": null,
@@ -2527,6 +2547,7 @@ var BUILTIN_TAGS = {
2527
2547
  },
2528
2548
  "wcs-defined": {
2529
2549
  "package": "defined",
2550
+ "hasWcBindable": true,
2530
2551
  "observedAttributes": [],
2531
2552
  "inputs": {
2532
2553
  "tags": "tags",
@@ -2545,6 +2566,7 @@ var BUILTIN_TAGS = {
2545
2566
  },
2546
2567
  "wcs-eyedropper": {
2547
2568
  "package": "eyedropper",
2569
+ "hasWcBindable": true,
2548
2570
  "observedAttributes": [],
2549
2571
  "inputs": {},
2550
2572
  "properties": [
@@ -2561,6 +2583,7 @@ var BUILTIN_TAGS = {
2561
2583
  },
2562
2584
  "wcs-fetch": {
2563
2585
  "package": "fetch",
2586
+ "hasWcBindable": true,
2564
2587
  "observedAttributes": [
2565
2588
  "url"
2566
2589
  ],
@@ -2589,6 +2612,7 @@ var BUILTIN_TAGS = {
2589
2612
  },
2590
2613
  "wcs-fetch-header": {
2591
2614
  "package": "fetch",
2615
+ "hasWcBindable": false,
2592
2616
  "observedAttributes": [],
2593
2617
  "inputs": {},
2594
2618
  "properties": [],
@@ -2596,6 +2620,7 @@ var BUILTIN_TAGS = {
2596
2620
  },
2597
2621
  "wcs-fetch-body": {
2598
2622
  "package": "fetch",
2623
+ "hasWcBindable": false,
2599
2624
  "observedAttributes": [],
2600
2625
  "inputs": {},
2601
2626
  "properties": [],
@@ -2603,6 +2628,7 @@ var BUILTIN_TAGS = {
2603
2628
  },
2604
2629
  "wcs-infinite-scroll": {
2605
2630
  "package": "fetch",
2631
+ "hasWcBindable": false,
2606
2632
  "observedAttributes": [
2607
2633
  "target",
2608
2634
  "root",
@@ -2616,6 +2642,7 @@ var BUILTIN_TAGS = {
2616
2642
  },
2617
2643
  "wcs-fullscreen": {
2618
2644
  "package": "fullscreen",
2645
+ "hasWcBindable": true,
2619
2646
  "observedAttributes": [
2620
2647
  "target"
2621
2648
  ],
@@ -2634,6 +2661,7 @@ var BUILTIN_TAGS = {
2634
2661
  },
2635
2662
  "wcs-geo": {
2636
2663
  "package": "geolocation",
2664
+ "hasWcBindable": true,
2637
2665
  "observedAttributes": [],
2638
2666
  "inputs": {
2639
2667
  "highAccuracy": "high-accuracy",
@@ -2665,6 +2693,7 @@ var BUILTIN_TAGS = {
2665
2693
  },
2666
2694
  "wcs-gyroscope": {
2667
2695
  "package": "gyroscope",
2696
+ "hasWcBindable": true,
2668
2697
  "observedAttributes": [],
2669
2698
  "inputs": {
2670
2699
  "frequency": null
@@ -2683,6 +2712,7 @@ var BUILTIN_TAGS = {
2683
2712
  },
2684
2713
  "wcs-idle": {
2685
2714
  "package": "idle",
2715
+ "hasWcBindable": true,
2686
2716
  "observedAttributes": [],
2687
2717
  "inputs": {
2688
2718
  "threshold": "threshold"
@@ -2702,6 +2732,7 @@ var BUILTIN_TAGS = {
2702
2732
  },
2703
2733
  "wcs-intersect": {
2704
2734
  "package": "intersection",
2735
+ "hasWcBindable": true,
2705
2736
  "observedAttributes": [
2706
2737
  "target",
2707
2738
  "root",
@@ -2735,6 +2766,7 @@ var BUILTIN_TAGS = {
2735
2766
  },
2736
2767
  "wcs-magnetometer": {
2737
2768
  "package": "magnetometer",
2769
+ "hasWcBindable": true,
2738
2770
  "observedAttributes": [],
2739
2771
  "inputs": {
2740
2772
  "frequency": null
@@ -2753,6 +2785,7 @@ var BUILTIN_TAGS = {
2753
2785
  },
2754
2786
  "wcs-midi": {
2755
2787
  "package": "midi",
2788
+ "hasWcBindable": true,
2756
2789
  "observedAttributes": [
2757
2790
  "input",
2758
2791
  "output",
@@ -2790,6 +2823,7 @@ var BUILTIN_TAGS = {
2790
2823
  },
2791
2824
  "wcs-network": {
2792
2825
  "package": "network",
2826
+ "hasWcBindable": true,
2793
2827
  "observedAttributes": [],
2794
2828
  "inputs": {},
2795
2829
  "properties": [
@@ -2803,6 +2837,7 @@ var BUILTIN_TAGS = {
2803
2837
  },
2804
2838
  "wcs-notify": {
2805
2839
  "package": "notification",
2840
+ "hasWcBindable": true,
2806
2841
  "observedAttributes": [],
2807
2842
  "inputs": {
2808
2843
  "notice": null,
@@ -2839,6 +2874,7 @@ var BUILTIN_TAGS = {
2839
2874
  },
2840
2875
  "wcs-permission": {
2841
2876
  "package": "permission",
2877
+ "hasWcBindable": true,
2842
2878
  "observedAttributes": [],
2843
2879
  "inputs": {
2844
2880
  "name": "name",
@@ -2856,6 +2892,7 @@ var BUILTIN_TAGS = {
2856
2892
  },
2857
2893
  "wcs-pip": {
2858
2894
  "package": "picture-in-picture",
2895
+ "hasWcBindable": true,
2859
2896
  "observedAttributes": [
2860
2897
  "target"
2861
2898
  ],
@@ -2874,6 +2911,7 @@ var BUILTIN_TAGS = {
2874
2911
  },
2875
2912
  "wcs-pointer-lock": {
2876
2913
  "package": "pointer-lock",
2914
+ "hasWcBindable": true,
2877
2915
  "observedAttributes": [
2878
2916
  "target"
2879
2917
  ],
@@ -2892,6 +2930,7 @@ var BUILTIN_TAGS = {
2892
2930
  },
2893
2931
  "wcs-raf": {
2894
2932
  "package": "raf",
2933
+ "hasWcBindable": true,
2895
2934
  "observedAttributes": [],
2896
2935
  "inputs": {
2897
2936
  "once": "once",
@@ -2917,6 +2956,7 @@ var BUILTIN_TAGS = {
2917
2956
  },
2918
2957
  "wcs-resize": {
2919
2958
  "package": "resize",
2959
+ "hasWcBindable": true,
2920
2960
  "observedAttributes": [
2921
2961
  "target",
2922
2962
  "box",
@@ -2945,6 +2985,7 @@ var BUILTIN_TAGS = {
2945
2985
  },
2946
2986
  "wcs-screen-orientation": {
2947
2987
  "package": "screen-orientation",
2988
+ "hasWcBindable": true,
2948
2989
  "observedAttributes": [],
2949
2990
  "inputs": {},
2950
2991
  "properties": [
@@ -2962,6 +3003,7 @@ var BUILTIN_TAGS = {
2962
3003
  },
2963
3004
  "wcs-share": {
2964
3005
  "package": "share",
3006
+ "hasWcBindable": true,
2965
3007
  "observedAttributes": [],
2966
3008
  "inputs": {},
2967
3009
  "properties": [
@@ -2977,6 +3019,7 @@ var BUILTIN_TAGS = {
2977
3019
  },
2978
3020
  "wcs-speak": {
2979
3021
  "package": "speech",
3022
+ "hasWcBindable": true,
2980
3023
  "observedAttributes": [],
2981
3024
  "inputs": {
2982
3025
  "say": null,
@@ -3007,6 +3050,7 @@ var BUILTIN_TAGS = {
3007
3050
  },
3008
3051
  "wcs-listen": {
3009
3052
  "package": "speech",
3053
+ "hasWcBindable": true,
3010
3054
  "observedAttributes": [],
3011
3055
  "inputs": {
3012
3056
  "lang": "lang",
@@ -3035,6 +3079,7 @@ var BUILTIN_TAGS = {
3035
3079
  },
3036
3080
  "wcs-sse": {
3037
3081
  "package": "sse",
3082
+ "hasWcBindable": true,
3038
3083
  "observedAttributes": [
3039
3084
  "url"
3040
3085
  ],
@@ -3062,6 +3107,7 @@ var BUILTIN_TAGS = {
3062
3107
  },
3063
3108
  "wcs-storage": {
3064
3109
  "package": "storage",
3110
+ "hasWcBindable": true,
3065
3111
  "observedAttributes": [
3066
3112
  "key",
3067
3113
  "type"
@@ -3088,6 +3134,7 @@ var BUILTIN_TAGS = {
3088
3134
  },
3089
3135
  "wcs-tilt": {
3090
3136
  "package": "tilt",
3137
+ "hasWcBindable": true,
3091
3138
  "observedAttributes": [],
3092
3139
  "inputs": {},
3093
3140
  "properties": [
@@ -3107,6 +3154,7 @@ var BUILTIN_TAGS = {
3107
3154
  },
3108
3155
  "wcs-timer": {
3109
3156
  "package": "timer",
3157
+ "hasWcBindable": true,
3110
3158
  "observedAttributes": [
3111
3159
  "interval"
3112
3160
  ],
@@ -3134,6 +3182,7 @@ var BUILTIN_TAGS = {
3134
3182
  },
3135
3183
  "wcs-upload": {
3136
3184
  "package": "upload",
3185
+ "hasWcBindable": true,
3137
3186
  "observedAttributes": [
3138
3187
  "url"
3139
3188
  ],
@@ -3165,6 +3214,7 @@ var BUILTIN_TAGS = {
3165
3214
  },
3166
3215
  "wcs-wakelock": {
3167
3216
  "package": "wakelock",
3217
+ "hasWcBindable": true,
3168
3218
  "observedAttributes": [
3169
3219
  "active",
3170
3220
  "type"
@@ -3186,6 +3236,7 @@ var BUILTIN_TAGS = {
3186
3236
  },
3187
3237
  "wcs-ws": {
3188
3238
  "package": "websocket",
3239
+ "hasWcBindable": true,
3189
3240
  "observedAttributes": [
3190
3241
  "url"
3191
3242
  ],
@@ -3218,6 +3269,7 @@ var BUILTIN_TAGS = {
3218
3269
  },
3219
3270
  "wcs-worker": {
3220
3271
  "package": "worker",
3272
+ "hasWcBindable": true,
3221
3273
  "observedAttributes": [
3222
3274
  "src"
3223
3275
  ],
@@ -3271,10 +3323,28 @@ function validateIoNodes(html, bindAttribute = "data-wcs", stateTagName = "wcs-s
3271
3323
  const getPaths = () => statePaths ??= getStatePathsFromHtml(html, stateTagName, fileReader);
3272
3324
  for (const occ of occurrences) {
3273
3325
  const contract = BUILTIN_TAGS[occ.tagName];
3274
- if (contract.properties.length === 0 && contract.commands.length === 0 && Object.keys(contract.inputs).length === 0) continue;
3275
3326
  const bindAttr = extractAttributeValue(occ.attrsText, bindAttribute);
3276
3327
  if (!bindAttr) continue;
3277
3328
  const valueStart = occ.attrsStart + bindAttr.valueOffsetInAttrs;
3329
+ if (!contract.hasWcBindable) {
3330
+ let spreadExprOffset = 0;
3331
+ for (const expr of splitBindingExpressions(bindAttr.value)) {
3332
+ const exprStart = valueStart + spreadExprOffset;
3333
+ spreadExprOffset += expr.length + 1;
3334
+ if (parseBindingExpression(expr).property !== "...") continue;
3335
+ const start = exprStart + expr.indexOf("...");
3336
+ diagnostics.push({
3337
+ code: WcsDiagnosticCode.SpreadNoBindable,
3338
+ start,
3339
+ end: start + 3,
3340
+ severity: "error",
3341
+ tag: occ.tagName,
3342
+ message: msgs.spreadNoBindable(occ.tagName)
3343
+ });
3344
+ }
3345
+ continue;
3346
+ }
3347
+ if (contract.properties.length === 0 && contract.commands.length === 0 && Object.keys(contract.inputs).length === 0) continue;
3278
3348
  const hasManual = hasBooleanAttribute(occ.attrsText, "manual");
3279
3349
  let exprOffset = 0;
3280
3350
  for (const expr of splitBindingExpressions(bindAttr.value)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wcstack/lint",
3
- "version": "1.29.0",
3
+ "version": "1.30.0",
4
4
  "description": "Static-contract validator CLI (wcs-validate) for wcstack data-wcs bindings and wcstack.manifest.json sidecars. Thin npm wrapper around the wcstack-intellisense validator core.",
5
5
  "type": "module",
6
6
  "bin": {