@wcstack/typescript 3.2.0 → 3.3.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.
@@ -109,7 +109,7 @@ var WcsDiagnosticCode = {
109
109
  // 依存する wcs/recursion-context は runtime 専用(静的側は出さない)。
110
110
  //
111
111
  // `**` を解釈しない場所へ `**` が渡った(data-wcs / mustache / $watch キー / $listKeys
112
- // キー / $resolve / $postUpdate / $trackDependency / 代入)、または `$recursion` 宣言が
112
+ // キー / $resolve / $postUpdate / $dependOn(旧名 $trackDependency) / 代入)、または `$recursion` 宣言が
113
113
  // 無いのに `**` を使った。runtime は PathInfo の不変条件として raiseError するか
114
114
  //(API 経由)、getter を黙って無視する(宣言なしの `**` getter)。
115
115
  RecursionUnsupported: "wcs/recursion-unsupported",
@@ -153,6 +153,19 @@ var WcsDiagnosticCode = {
153
153
  // 3.x の間だけ残る旧名(フィルタ `uc` → `upper`、`$trackDependency` → `$dependOn` …)を書いた(info)。
154
154
  // 動くが 4.0 で外れるので正式名を提案する(@wcstack/state 3.2・要件 B12 / 3.x 計画 D39)
155
155
  NameAlias: "wcs/name-alias",
156
+ // 旧名の宣言キーを `this.` 越しに**読んだ**(`this.$streams`)。宣言と違い旧名のままでは
157
+ // 3.x でも動かない — 正規化(@wcstack/state declarationAliases.ts)が正式名へ写したあと
158
+ // 旧名の自前プロパティを delete するので、読み出しは例外も出さずに undefined になる。
159
+ // `wcs/name-alias`(「動くが 4.0 で外れる」)とは別の事実なので code を分ける: 移行中に
160
+ // name-alias を抑制したチームが、この「今日すでに壊れている」まで一緒に消さないため。
161
+ // severity は検出経路で変わる — AST で断定できたら warning、読めない形(class 構文など)の
162
+ // 正規表現フォールバックは info(誤検出しうる経路を warning にしない)。
163
+ DeclarationAliasRead: "wcs/declaration-alias-read",
164
+ // 旧名と正式名の宣言キーを**両方**書いた(`$streams` と `$stream` 等)。どちらが効くのか
165
+ // 書き手に見えないので、ランタイム(@wcstack/state declarationAliases.ts)は正規化の時点で
166
+ // 名指しで raiseError する = ページ初期化ごと止まるので error。3.2 への移行中
167
+ //(新名を足して旧名を消し忘れる)にちょうど起きる形。
168
+ DeclarationAlias: "wcs/declaration-alias",
156
169
  // wcBindable 無宣言タグ(wcs-fetch-header 等のヘルパー)への spread。
157
170
  // ランタイム(expandSpread)は raiseError で落とす。
158
171
  SpreadNoBindable: "wcs/spread-no-bindable",
@@ -667,488 +680,1319 @@ function escapePtr(key) {
667
680
  return key.replace(/~/g, "~0").replace(/\//g, "~1");
668
681
  }
669
682
 
670
- // ../state/dist/manifest.esm.js
671
- var _config = {
672
- bindAttributeName: "data-wcs",
673
- tagNames: {
674
- state: "wcs-state"
675
- },
676
- locale: "en"
677
- };
678
- var config = _config;
683
+ // ../state/dist/parser.esm.js
684
+ var DELIMITER = ".";
685
+ var WILDCARD = "*";
686
+ var MAX_WILDCARD_DEPTH = 128;
687
+ var MAX_PATH_SEGMENTS = 512;
688
+ var BINDING_SEPARATOR = ";";
689
+ var PROP_VALUE_SEPARATOR = ":";
690
+ var MODIFIER_SEPARATOR = "#";
691
+ var FILTER_SEPARATOR = "|";
692
+ var ELSE_KEYWORD = "else";
693
+ var SPREAD_PROP = "...";
694
+ var EVENT_PROP_PREFIX = "on";
695
+ var EVENT_TOKEN_NAMESPACE = "eventToken";
696
+ var VOLUME_INJECTION_PROP = "state";
697
+ var COMMAND_NAMESPACE = "command";
698
+ var CLASS_NAMESPACE = "class";
699
+ var ATTR_NAMESPACE = "attr";
700
+ var STYLE_NAMESPACE = "style";
701
+ var INDEX_PARAM_PREFIX = "$";
702
+ var tmpIndexByIndexName = {};
703
+ for (let i = 0; i < MAX_WILDCARD_DEPTH; i++) {
704
+ tmpIndexByIndexName[`${INDEX_PARAM_PREFIX}${i + 1}`] = i;
705
+ }
706
+ Object.freeze(tmpIndexByIndexName);
707
+ var RECURSION_WILDCARD = "**";
679
708
  function raiseError(message) {
680
709
  throw new Error(`[@wcstack/state] ${message}`);
681
710
  }
682
- function optionsRequired(fnName) {
683
- raiseError(`filter ${fnName} requires at least one option`);
684
- }
685
- function optionMustBeNumber(fnName) {
686
- raiseError(`filter ${fnName} requires a number as option`);
687
- }
688
- function valueMustBeNumber(fnName) {
689
- raiseError(`filter ${fnName} requires a number value`);
690
- }
691
- function valueMustBeBoolean(fnName) {
692
- raiseError(`filter ${fnName} requires a boolean value`);
693
- }
694
- function valueMustBeDate(fnName) {
695
- raiseError(`filter ${fnName} requires a date value`);
696
- }
697
- function valueMustBeArray(fnName) {
698
- raiseError(`filter ${fnName} requires an array value`);
699
- }
700
- var builtinFilterAliases = {
701
- inc: "add",
702
- dec: "sub",
703
- fix: "toFixed",
704
- uc: "upper",
705
- lc: "lower",
706
- cap: "capitalize",
707
- rep: "repeat",
708
- rev: "reverse",
709
- pad: "padStart",
710
- null: "nullIfEmpty"
711
- };
712
- function validateNumberString(value) {
713
- if (!value || isNaN(Number(value))) {
714
- return false;
715
- }
716
- return true;
711
+ var _cache = /* @__PURE__ */ new Map();
712
+ function clearPathInfoCacheForTooling() {
713
+ _cache.clear();
717
714
  }
718
- var eq = (options, literals) => {
719
- const opt = options?.[0] ?? optionsRequired("eq");
720
- const literal2 = literals !== void 0 && literals.length > 0 ? literals[0] : opt;
721
- return (value) => {
722
- if (typeof value === "number") {
723
- if (!validateNumberString(opt)) {
724
- optionMustBeNumber("eq");
725
- }
726
- return value === Number(opt);
727
- }
728
- if (typeof value === "string") {
729
- return value === opt;
730
- }
731
- return value === literal2;
732
- };
733
- };
734
- var ne = (options, literals) => {
735
- const opt = options?.[0] ?? optionsRequired("ne");
736
- const literal2 = literals !== void 0 && literals.length > 0 ? literals[0] : opt;
737
- return (value) => {
738
- if (typeof value === "number") {
739
- if (!validateNumberString(opt)) {
740
- optionMustBeNumber("ne");
741
- }
742
- return value !== Number(opt);
743
- }
744
- if (typeof value === "string") {
745
- return value !== opt;
746
- }
747
- return value !== literal2;
748
- };
749
- };
750
- var not = (_options) => {
751
- return (value) => {
752
- if (typeof value !== "boolean") {
753
- valueMustBeBoolean("not");
754
- }
755
- return !value;
756
- };
757
- };
758
- var lt = (options) => {
759
- const opt = options?.[0] ?? optionsRequired("lt");
760
- if (!validateNumberString(opt)) {
761
- optionMustBeNumber("lt");
762
- }
763
- return (value) => {
764
- if (typeof value !== "number") {
765
- valueMustBeNumber("lt");
766
- }
767
- return value < Number(opt);
768
- };
769
- };
770
- var le = (options) => {
771
- const opt = options?.[0] ?? optionsRequired("le");
772
- if (!validateNumberString(opt)) {
773
- optionMustBeNumber("le");
715
+ var id = 0;
716
+ function getPathInfo(path) {
717
+ let pathInfo = _cache.get(path);
718
+ if (typeof pathInfo !== "undefined") {
719
+ return pathInfo;
774
720
  }
775
- return (value) => {
776
- if (typeof value !== "number") {
777
- valueMustBeNumber("le");
778
- }
779
- return value <= Number(opt);
780
- };
781
- };
782
- var gt = (options) => {
783
- const opt = options?.[0] ?? optionsRequired("gt");
784
- if (!validateNumberString(opt)) {
785
- optionMustBeNumber("gt");
721
+ if (path.indexOf(RECURSION_WILDCARD) !== -1) {
722
+ raiseError(`[wcs/recursion-unsupported] "${path}" uses "${RECURSION_WILDCARD}", which is not accepted here. It is only meaningful in a $recursion declaration, in a recursive getter key, and in the path argument of $getAll / $setAll \u2014 and only when the state declares a $recursion anchor.`);
786
723
  }
787
- return (value) => {
788
- if (typeof value !== "number") {
789
- valueMustBeNumber("gt");
790
- }
791
- return value > Number(opt);
792
- };
793
- };
794
- var ge = (options) => {
795
- const opt = options?.[0] ?? optionsRequired("ge");
796
- if (!validateNumberString(opt)) {
797
- optionMustBeNumber("ge");
724
+ const segmentCount = countSegments(path);
725
+ if (segmentCount > MAX_PATH_SEGMENTS) {
726
+ raiseError(`[wcs/binding-syntax] "${path}" has ${segmentCount} path segments \u2014 the limit is ${MAX_PATH_SEGMENTS}. Every prefix of a path is interned, so the cost grows with the square of the depth.`);
798
727
  }
799
- return (value) => {
800
- if (typeof value !== "number") {
801
- valueMustBeNumber("ge");
728
+ pathInfo = Object.freeze(new PathInfo(path));
729
+ _cache.set(path, pathInfo);
730
+ return pathInfo;
731
+ }
732
+ function countSegments(path) {
733
+ let count = 1;
734
+ for (let i = 0; i < path.length; i++) {
735
+ if (path[i] === DELIMITER) {
736
+ count++;
802
737
  }
803
- return value >= Number(opt);
804
- };
805
- };
806
- var inc = (options) => {
807
- const opt = options?.[0] ?? optionsRequired("add");
808
- if (!validateNumberString(opt)) {
809
- optionMustBeNumber("add");
810
738
  }
811
- return (value) => {
812
- if (typeof value !== "number") {
813
- valueMustBeNumber("add");
739
+ return count;
740
+ }
741
+ var PathInfo = class {
742
+ id = ++id;
743
+ path;
744
+ segments;
745
+ lastSegment;
746
+ cumulativePaths;
747
+ cumulativePathSet;
748
+ cumulativePathInfos;
749
+ cumulativePathInfoSet;
750
+ parentPath;
751
+ wildcardPaths;
752
+ wildcardPathSet;
753
+ indexByWildcardPath;
754
+ wildcardPathInfos;
755
+ wildcardPathInfoSet;
756
+ wildcardParentPaths;
757
+ wildcardParentPathSet;
758
+ wildcardParentPathInfos;
759
+ wildcardParentPathInfoSet;
760
+ wildcardPositions;
761
+ lastWildcardPath;
762
+ lastWildcardInfo;
763
+ wildcardCount;
764
+ parentPathInfo;
765
+ constructor(path) {
766
+ const getPattern = (_path) => {
767
+ return path === _path ? this : getPathInfo(_path);
768
+ };
769
+ const segments = path.split(".");
770
+ const cumulativePaths = [];
771
+ const cumulativePathInfos = [];
772
+ const wildcardPaths = [];
773
+ const indexByWildcardPath = {};
774
+ const wildcardPathInfos = [];
775
+ const wildcardParentPaths = [];
776
+ const wildcardParentPathInfos = [];
777
+ const wildcardPositions = [];
778
+ let currentPatternPath = "", prevPatternPath = "";
779
+ let wildcardCount = 0;
780
+ for (let i = 0; i < segments.length; i++) {
781
+ currentPatternPath += segments[i];
782
+ if (segments[i] === WILDCARD) {
783
+ wildcardPaths.push(currentPatternPath);
784
+ indexByWildcardPath[currentPatternPath] = wildcardCount;
785
+ wildcardPathInfos.push(getPattern(currentPatternPath));
786
+ wildcardParentPaths.push(prevPatternPath);
787
+ wildcardParentPathInfos.push(getPattern(prevPatternPath));
788
+ wildcardPositions.push(i);
789
+ wildcardCount++;
790
+ }
791
+ cumulativePaths.push(currentPatternPath);
792
+ cumulativePathInfos.push(getPattern(currentPatternPath));
793
+ prevPatternPath = currentPatternPath;
794
+ currentPatternPath += ".";
814
795
  }
815
- return value + Number(opt);
816
- };
817
- };
818
- var dec = (options) => {
819
- const opt = options?.[0] ?? optionsRequired("sub");
820
- if (!validateNumberString(opt)) {
821
- optionMustBeNumber("sub");
796
+ const lastWildcardPath = wildcardPaths.length > 0 ? wildcardPaths[wildcardPaths.length - 1] : null;
797
+ const parentPath = cumulativePaths.length > 1 ? cumulativePaths[cumulativePaths.length - 2] : null;
798
+ this.path = path;
799
+ this.segments = segments;
800
+ this.lastSegment = segments[segments.length - 1];
801
+ this.cumulativePaths = cumulativePaths;
802
+ this.cumulativePathSet = new Set(cumulativePaths);
803
+ this.cumulativePathInfos = cumulativePathInfos;
804
+ this.cumulativePathInfoSet = new Set(cumulativePathInfos);
805
+ this.wildcardPaths = wildcardPaths;
806
+ this.wildcardPathSet = new Set(wildcardPaths);
807
+ this.indexByWildcardPath = indexByWildcardPath;
808
+ this.wildcardPathInfos = wildcardPathInfos;
809
+ this.wildcardPathInfoSet = new Set(wildcardPathInfos);
810
+ this.wildcardParentPaths = wildcardParentPaths;
811
+ this.wildcardParentPathSet = new Set(wildcardParentPaths);
812
+ this.wildcardParentPathInfos = wildcardParentPathInfos;
813
+ this.wildcardParentPathInfoSet = new Set(wildcardParentPathInfos);
814
+ this.wildcardPositions = wildcardPositions;
815
+ this.lastWildcardPath = lastWildcardPath;
816
+ this.lastWildcardInfo = lastWildcardPath ? getPattern(lastWildcardPath) : null;
817
+ this.parentPath = parentPath;
818
+ this.parentPathInfo = parentPath ? getPattern(parentPath) : null;
819
+ this.wildcardCount = wildcardCount;
822
820
  }
823
- return (value) => {
824
- if (typeof value !== "number") {
825
- valueMustBeNumber("sub");
826
- }
827
- return value - Number(opt);
828
- };
829
821
  };
830
- var mul = (options) => {
831
- const opt = options?.[0] ?? optionsRequired("mul");
832
- if (!validateNumberString(opt)) {
833
- optionMustBeNumber("mul");
822
+ var LINT_HINT = " Validate statically: npx @wcstack/lint <file>.";
823
+ var STRUCTURAL_BINDING_TYPE_SET = /* @__PURE__ */ new Set([
824
+ "if",
825
+ "elseif",
826
+ "else",
827
+ "for"
828
+ ]);
829
+ var resolvedByKey = /* @__PURE__ */ new Map();
830
+ function clearFilterResolutionCache() {
831
+ resolvedByKey.clear();
832
+ }
833
+ function finalizeArg(text, firstQuoteStart, lastQuoteEnd) {
834
+ const startLimit = firstQuoteStart === -1 ? text.length : firstQuoteStart;
835
+ let start = 0;
836
+ while (start < startLimit && /\s/.test(text[start])) {
837
+ start++;
834
838
  }
835
- return (value) => {
836
- if (typeof value !== "number") {
837
- valueMustBeNumber("mul");
838
- }
839
- return value * Number(opt);
840
- };
841
- };
842
- var div = (options) => {
843
- const opt = options?.[0] ?? optionsRequired("div");
844
- if (!validateNumberString(opt)) {
845
- optionMustBeNumber("div");
839
+ const endLimit = lastQuoteEnd === -1 ? 0 : lastQuoteEnd;
840
+ let end = text.length;
841
+ while (end > endLimit && /\s/.test(text[end - 1])) {
842
+ end--;
846
843
  }
847
- return (value) => {
848
- if (typeof value !== "number") {
849
- valueMustBeNumber("div");
850
- }
851
- return value / Number(opt);
844
+ return text.slice(start, end);
845
+ }
846
+ var NUMBER_LITERAL = /^[+-]?(\d+\.?\d*|\.\d+)([eE][+-]?\d+)?$/;
847
+ function toLiteral(text, quoted) {
848
+ if (quoted)
849
+ return text;
850
+ if (text === "true")
851
+ return true;
852
+ if (text === "false")
853
+ return false;
854
+ if (text === "null")
855
+ return null;
856
+ return NUMBER_LITERAL.test(text) ? Number(text) : text;
857
+ }
858
+ function parseFilterArgsWithLiterals(argsText) {
859
+ const args = [];
860
+ const literals = [];
861
+ let current2 = "";
862
+ let inQuote = null;
863
+ let hasQuote = false;
864
+ let firstQuoteStart = -1;
865
+ let lastQuoteEnd = -1;
866
+ const flush = () => {
867
+ const arg = finalizeArg(current2, firstQuoteStart, lastQuoteEnd);
868
+ args.push(arg);
869
+ literals.push(toLiteral(arg, hasQuote));
870
+ current2 = "";
871
+ hasQuote = false;
872
+ firstQuoteStart = -1;
873
+ lastQuoteEnd = -1;
852
874
  };
853
- };
854
- var mod = (options) => {
855
- const opt = options?.[0] ?? optionsRequired("mod");
856
- if (!validateNumberString(opt)) {
857
- optionMustBeNumber("mod");
858
- }
859
- return (value) => {
860
- if (typeof value !== "number") {
861
- valueMustBeNumber("mod");
875
+ for (let i = 0; i < argsText.length; i++) {
876
+ const char = argsText[i];
877
+ if (inQuote) {
878
+ if (char === inQuote) {
879
+ inQuote = null;
880
+ } else {
881
+ if (firstQuoteStart === -1) {
882
+ firstQuoteStart = current2.length;
883
+ }
884
+ current2 += char;
885
+ lastQuoteEnd = current2.length;
886
+ }
887
+ } else if (char === '"' || char === "'") {
888
+ inQuote = char;
889
+ hasQuote = true;
890
+ } else if (char === ",") {
891
+ flush();
892
+ } else {
893
+ current2 += char;
862
894
  }
863
- return value % Number(opt);
864
- };
865
- };
866
- var abs = (_options) => {
867
- return (value) => {
868
- if (typeof value !== "number") {
869
- valueMustBeNumber("abs");
895
+ }
896
+ if (inQuote !== null) {
897
+ raiseError(`[wcs/binding-syntax] unterminated ${inQuote} quote in the filter arguments "(${argsText})". Close the quote.${LINT_HINT}`);
898
+ }
899
+ const last = finalizeArg(current2, firstQuoteStart, lastQuoteEnd);
900
+ if (last || hasQuote) {
901
+ args.push(last);
902
+ literals.push(toLiteral(last, hasQuote));
903
+ }
904
+ return { args, literals };
905
+ }
906
+ var trimFn = (s) => s.trim();
907
+ var isQuote = (c) => c === "'" || c === '"';
908
+ function indexOfOutsideQuotes(text, char) {
909
+ let quote = null;
910
+ for (let i = 0; i < text.length; i++) {
911
+ const c = text[i];
912
+ if (quote !== null) {
913
+ if (c === quote)
914
+ quote = null;
915
+ } else if (isQuote(c)) {
916
+ quote = c;
917
+ } else if (c === char) {
918
+ return i;
870
919
  }
871
- return Math.abs(value);
872
- };
873
- };
874
- var clamp = (options) => {
875
- const opt1 = options?.[0] ?? optionsRequired("clamp");
876
- if (!validateNumberString(opt1)) {
877
- optionMustBeNumber("clamp");
878
920
  }
879
- const opt2 = options?.[1] ?? optionsRequired("clamp");
880
- if (!validateNumberString(opt2)) {
881
- optionMustBeNumber("clamp");
921
+ return -1;
922
+ }
923
+ function lastIndexOfOutsideQuotes(text, char) {
924
+ let quote = null;
925
+ let found = -1;
926
+ for (let i = 0; i < text.length; i++) {
927
+ const c = text[i];
928
+ if (quote !== null) {
929
+ if (c === quote)
930
+ quote = null;
931
+ } else if (isQuote(c)) {
932
+ quote = c;
933
+ } else if (c === char) {
934
+ found = i;
935
+ }
882
936
  }
883
- const min = Number(opt1);
884
- const max = Number(opt2);
885
- return (value) => {
886
- if (typeof value !== "number") {
887
- valueMustBeNumber("clamp");
937
+ return found;
938
+ }
939
+ function splitOutsideQuotes(text, separator) {
940
+ const parts = [];
941
+ let quote = null;
942
+ let start = 0;
943
+ for (let i = 0; i < text.length; i++) {
944
+ const c = text[i];
945
+ if (quote !== null) {
946
+ if (c === quote)
947
+ quote = null;
948
+ } else if (isQuote(c)) {
949
+ quote = c;
950
+ } else if (c === separator) {
951
+ parts.push(text.slice(start, i));
952
+ start = i + 1;
888
953
  }
889
- return Math.min(Math.max(value, min), max);
890
- };
891
- };
892
- var fix = (options) => {
893
- const opt = options?.[0] ?? "0";
894
- if (!validateNumberString(opt)) {
895
- optionMustBeNumber("toFixed");
896
954
  }
897
- return (value) => {
898
- if (typeof value !== "number") {
899
- valueMustBeNumber("toFixed");
955
+ parts.push(text.slice(start));
956
+ return parts;
957
+ }
958
+ function clearFilterFnCacheForTooling() {
959
+ clearFilterResolutionCache();
960
+ }
961
+ function parseFilters(filterTextList, filterIOType, sourceText) {
962
+ const source = sourceText ?? filterTextList.join("|");
963
+ return filterTextList.map((filterText) => {
964
+ const openParenIndex = indexOfOutsideQuotes(filterText, "(");
965
+ let closeParenIndex = lastIndexOfOutsideQuotes(filterText, ")");
966
+ if (closeParenIndex === -1) {
967
+ closeParenIndex = filterText.lastIndexOf(")");
900
968
  }
901
- return value.toFixed(Number(opt));
902
- };
903
- };
904
- var locale = (options) => {
905
- const explicit = options?.[0];
906
- return (value) => {
907
- if (typeof value !== "number") {
908
- valueMustBeNumber("locale");
969
+ if (openParenIndex !== -1 && closeParenIndex === -1) {
970
+ raiseError(`[wcs/binding-syntax] Invalid filter format: missing closing parenthesis in "${filterText}".${LINT_HINT}`);
909
971
  }
910
- return value.toLocaleString(explicit ?? config.locale);
911
- };
912
- };
913
- var uc = (_options) => {
914
- return (value) => {
915
- return String(value).toUpperCase();
916
- };
917
- };
918
- var lc = (_options) => {
919
- return (value) => {
920
- return String(value).toLowerCase();
921
- };
922
- };
923
- var cap = (_options) => {
924
- return (value) => {
925
- const v = String(value);
926
- if (v.length === 0) {
927
- return v;
972
+ if (closeParenIndex !== -1 && openParenIndex === -1) {
973
+ raiseError(`[wcs/binding-syntax] Invalid filter format: missing opening parenthesis in "${filterText}".${LINT_HINT}`);
928
974
  }
929
- if (v.length === 1) {
930
- return v.toUpperCase();
975
+ if (closeParenIndex !== -1 && closeParenIndex < openParenIndex) {
976
+ raiseError(`[wcs/binding-syntax] Invalid filter format: ")" comes before "(" in "${filterText}".${LINT_HINT}`);
931
977
  }
932
- return v.charAt(0).toUpperCase() + v.slice(1);
933
- };
934
- };
935
- var trim = (_options) => {
936
- return (value) => {
937
- return String(value).trim();
938
- };
939
- };
940
- var slice = (options) => {
941
- const numberedOpts = [];
942
- const opt1 = options?.[0] ?? optionsRequired("slice");
943
- if (!validateNumberString(opt1)) {
944
- optionMustBeNumber("slice");
945
- }
946
- numberedOpts.push(Number(opt1));
947
- const opt2 = options?.[1];
948
- if (typeof opt2 !== "undefined") {
949
- if (!validateNumberString(opt2)) {
950
- optionMustBeNumber("slice");
978
+ if (closeParenIndex !== -1 && filterText.slice(closeParenIndex + 1).trim().length > 0) {
979
+ const trailing = filterText.slice(closeParenIndex + 1).trim();
980
+ raiseError(`[wcs/binding-syntax] "${filterText}": unexpected "${trailing}" after the filter's closing ")" \u2014 separate filters with "|" (write "${filterText.slice(0, closeParenIndex + 1)}|${trailing}").${LINT_HINT}`);
981
+ }
982
+ const filterName = (openParenIndex === -1 ? filterText : filterText.substring(0, openParenIndex)).trim();
983
+ if (filterName.length === 0) {
984
+ raiseError(`[wcs/binding-syntax] an empty filter in "${source}" \u2014 remove the extra "|" or name the filter.${LINT_HINT}`);
985
+ }
986
+ if (filterName.includes(MODIFIER_SEPARATOR)) {
987
+ const [name, modifiers] = filterName.split(MODIFIER_SEPARATOR);
988
+ raiseError(filterIOType === "input" ? `[wcs/binding-syntax] "${filterName}" is not a filter name: a modifier list "${MODIFIER_SEPARATOR}${modifiers}" comes before the input filters, not inside one \u2014 write "<property>${MODIFIER_SEPARATOR}${modifiers}|${name}".${LINT_HINT}` : `[wcs/binding-syntax] "${filterName}" is not a filter name: "${MODIFIER_SEPARATOR}" cannot appear in one. Modifiers belong on the left side of the binding, before the ":" \u2014 write "${name}" here.${LINT_HINT}`);
989
+ }
990
+ if (openParenIndex === -1) {
991
+ return { filterName, args: [], literals: [] };
992
+ }
993
+ const argsText = filterText.substring(openParenIndex + 1, closeParenIndex);
994
+ return { filterName, ...parseFilterArgsWithLiterals(argsText) };
995
+ });
996
+ }
997
+ var cacheFilterInfos$1 = /* @__PURE__ */ new Map();
998
+ function clearPropPartCacheForTooling() {
999
+ cacheFilterInfos$1.clear();
1000
+ }
1001
+ function parsePropPart(propPart) {
1002
+ const pos = indexOfOutsideQuotes(propPart, FILTER_SEPARATOR);
1003
+ let propText = "";
1004
+ let filterTexts = [];
1005
+ let filtersText = "";
1006
+ let filters = [];
1007
+ if (pos !== -1) {
1008
+ propText = propPart.slice(0, pos).trim();
1009
+ filtersText = propPart.slice(pos + 1).trim();
1010
+ if (cacheFilterInfos$1.has(filtersText)) {
1011
+ filters = cacheFilterInfos$1.get(filtersText);
1012
+ } else {
1013
+ filterTexts = splitOutsideQuotes(filtersText, FILTER_SEPARATOR).map(trimFn);
1014
+ filters = parseFilters(filterTexts, "input", propPart);
1015
+ cacheFilterInfos$1.set(filtersText, filters);
951
1016
  }
952
- numberedOpts.push(Number(opt2));
1017
+ } else {
1018
+ propText = propPart.trim();
953
1019
  }
954
- return (value) => {
955
- return String(value).slice(...numberedOpts);
956
- };
957
- };
958
- var substr = (options) => {
959
- const opt1 = options?.[0] ?? optionsRequired("substr");
960
- if (!validateNumberString(opt1)) {
961
- optionMustBeNumber("substr");
1020
+ const modifierParts = propText.split(MODIFIER_SEPARATOR).map(trimFn);
1021
+ if (modifierParts.length > 2) {
1022
+ raiseError(`[wcs/binding-syntax] "${propText}": a binding takes one modifier list after a single "${MODIFIER_SEPARATOR}" \u2014 write "${modifierParts[0]}${MODIFIER_SEPARATOR}${modifierParts.slice(1).join(",")}".${LINT_HINT}`);
962
1023
  }
963
- const opt2 = options?.[1] ?? optionsRequired("substr");
964
- if (!validateNumberString(opt2)) {
965
- optionMustBeNumber("substr");
1024
+ const [propName, propModifiersText] = modifierParts;
1025
+ const propSegments = propName.split(DELIMITER).map(trimFn);
1026
+ const isExplicitProperty = propSegments.length > 1 && propSegments[0] === "";
1027
+ if (!isExplicitProperty && (propName.length === 0 || propSegments.some((segment) => segment.length === 0))) {
1028
+ raiseError(`[wcs/binding-syntax] "${propPart}": the left side of a binding must name a property \u2014 write "<property>: <path>" (modifiers and input filters come after the name).${LINT_HINT}`);
966
1029
  }
967
- return (value) => {
968
- return String(value).substr(Number(opt1), Number(opt2));
1030
+ const propModifiers = propModifiersText ? propModifiersText.split(",").map(trimFn) : [];
1031
+ return {
1032
+ propName,
1033
+ propSegments,
1034
+ propModifiers,
1035
+ inFilters: filters
969
1036
  };
970
- };
971
- var pad = (options) => {
972
- const opt1 = options?.[0] ?? optionsRequired("padStart");
973
- if (!validateNumberString(opt1)) {
974
- optionMustBeNumber("padStart");
1037
+ }
1038
+ var cacheFilterInfos = /* @__PURE__ */ new Map();
1039
+ function clearStatePartCacheForTooling() {
1040
+ cacheFilterInfos.clear();
1041
+ }
1042
+ function parseStatePart(statePart) {
1043
+ const pos = indexOfOutsideQuotes(statePart, FILTER_SEPARATOR);
1044
+ let stateAndPath = "";
1045
+ let filterTexts = [];
1046
+ let filtersText = "";
1047
+ let filters = [];
1048
+ if (pos !== -1) {
1049
+ stateAndPath = statePart.slice(0, pos).trim();
1050
+ filtersText = statePart.slice(pos + 1).trim();
1051
+ if (cacheFilterInfos.has(filtersText)) {
1052
+ filters = cacheFilterInfos.get(filtersText);
1053
+ } else {
1054
+ filterTexts = splitOutsideQuotes(filtersText, FILTER_SEPARATOR).map(trimFn);
1055
+ filters = parseFilters(filterTexts, "output", statePart);
1056
+ cacheFilterInfos.set(filtersText, filters);
1057
+ }
1058
+ } else {
1059
+ stateAndPath = statePart.trim();
975
1060
  }
976
- const opt2 = options?.[1] ?? "0";
977
- return (value) => {
978
- return String(value).padStart(Number(opt1), opt2);
979
- };
980
- };
981
- var padEnd = (options) => {
982
- const opt1 = options?.[0] ?? optionsRequired("padEnd");
983
- if (!validateNumberString(opt1)) {
984
- optionMustBeNumber("padEnd");
1061
+ if (stateAndPath.indexOf("@") !== -1) {
1062
+ raiseError(`"${stateAndPath}": 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 by its path prefix instead.`);
985
1063
  }
986
- const opt2 = options?.[1] ?? " ";
987
- return (value) => {
988
- return String(value).padEnd(Number(opt1), opt2);
989
- };
990
- };
991
- var rep = (options) => {
992
- const opt = options?.[0] ?? optionsRequired("repeat");
993
- if (!validateNumberString(opt)) {
994
- optionMustBeNumber("repeat");
1064
+ const statePathName = stateAndPath;
1065
+ const isLoopRelative = statePathName.startsWith(DELIMITER);
1066
+ const body = isLoopRelative ? statePathName.slice(DELIMITER.length) : statePathName;
1067
+ const hasEmptySegment = body.length > 0 && body.split(DELIMITER).some((segment) => segment.length === 0);
1068
+ if (hasEmptySegment || !isLoopRelative && body.length === 0) {
1069
+ raiseError(`[wcs/binding-syntax] "${statePart}": the right side of a binding must name a state path \u2014 write "<property>: <path>" (a path segment cannot be empty; "." alone and a leading "." are the loop-relative shorthand).${LINT_HINT}`);
995
1070
  }
996
- return (value) => {
997
- return String(value).repeat(Number(opt));
998
- };
999
- };
1000
- var rev = (_options) => {
1001
- return (value) => {
1002
- return String(value).split("").reverse().join("");
1003
- };
1004
- };
1005
- var int = (_options) => {
1006
- return (value) => {
1007
- return parseInt(String(value), 10);
1008
- };
1009
- };
1010
- var float = (_options) => {
1011
- return (value) => {
1012
- return parseFloat(String(value));
1013
- };
1014
- };
1015
- var round = (options) => {
1016
- const opt = options?.[0] ?? "0";
1017
- if (!validateNumberString(opt)) {
1018
- optionMustBeNumber("round");
1019
- }
1020
- return (value) => {
1021
- if (typeof value !== "number") {
1022
- valueMustBeNumber("round");
1023
- }
1024
- const optValue = Math.pow(10, Number(opt));
1025
- return Math.round(value * optValue) / optValue;
1071
+ const pathInfo = getPathInfo(statePathName);
1072
+ return {
1073
+ statePathName,
1074
+ statePathInfo: pathInfo,
1075
+ outFilters: filters
1026
1076
  };
1027
- };
1028
- var floor = (options) => {
1029
- const opt = options?.[0] ?? "0";
1030
- if (!validateNumberString(opt)) {
1031
- optionMustBeNumber("floor");
1032
- }
1033
- return (value) => {
1034
- if (typeof value !== "number") {
1035
- valueMustBeNumber("floor");
1077
+ }
1078
+ var KEYWORDS_WITHOUT_MODIFIERS = /* @__PURE__ */ new Set([ELSE_KEYWORD, "if", "elseif", "for", SPREAD_PROP]);
1079
+ var EXPLICIT_PROPERTY_REJECTED_HEADS = /* @__PURE__ */ new Set([
1080
+ CLASS_NAMESPACE,
1081
+ ATTR_NAMESPACE,
1082
+ STYLE_NAMESPACE,
1083
+ COMMAND_NAMESPACE,
1084
+ EVENT_TOKEN_NAMESPACE,
1085
+ VOLUME_INJECTION_PROP
1086
+ ]);
1087
+ function splitBindTexts(bindText) {
1088
+ return splitOutsideQuotes(bindText, BINDING_SEPARATOR);
1089
+ }
1090
+ function parseBindTextsForElement(bindText) {
1091
+ const [...bindTexts] = splitBindTexts(bindText).map(trimFn).filter((s) => s.length > 0);
1092
+ const results = bindTexts.map((bindText2) => {
1093
+ const separatorIndex = indexOfOutsideQuotes(bindText2, PROP_VALUE_SEPARATOR);
1094
+ if (separatorIndex === -1) {
1095
+ raiseError(`[wcs/binding-syntax] Invalid bindText: "${bindText2}". Missing ':' separator between propPart and statePart.${LINT_HINT}`);
1036
1096
  }
1037
- const optValue = Math.pow(10, Number(opt));
1038
- return Math.floor(value * optValue) / optValue;
1039
- };
1040
- };
1041
- var ceil = (options) => {
1042
- const opt = options?.[0] ?? "0";
1043
- if (!validateNumberString(opt)) {
1044
- optionMustBeNumber("ceil");
1045
- }
1046
- return (value) => {
1047
- if (typeof value !== "number") {
1048
- valueMustBeNumber("ceil");
1097
+ const propPart = bindText2.slice(0, separatorIndex).trim();
1098
+ const statePart = bindText2.slice(separatorIndex + 1).trim();
1099
+ const keyword = propPart.split(MODIFIER_SEPARATOR)[0].split(FILTER_SEPARATOR)[0].trim();
1100
+ if (keyword !== propPart && KEYWORDS_WITHOUT_MODIFIERS.has(keyword)) {
1101
+ raiseError(`[wcs/binding-syntax] "${bindText2}": "${keyword}" takes no modifiers or filters on its left side \u2014 write "${keyword}:".${LINT_HINT}`);
1049
1102
  }
1050
- const optValue = Math.pow(10, Number(opt));
1051
- return Math.ceil(value * optValue) / optValue;
1052
- };
1053
- };
1054
- var percent = (options) => {
1055
- const opt = options?.[0] ?? "0";
1056
- if (!validateNumberString(opt)) {
1057
- optionMustBeNumber("percent");
1058
- }
1059
- return (value) => {
1060
- if (typeof value !== "number") {
1061
- valueMustBeNumber("percent");
1103
+ if (propPart === ELSE_KEYWORD) {
1104
+ if (statePart.length > 0) {
1105
+ raiseError(`[wcs/binding-syntax] "${bindText2}": "else" takes no value \u2014 write "else:".${LINT_HINT}`);
1106
+ }
1107
+ const pathInfo = getPathInfo("#else");
1108
+ return {
1109
+ propName: ELSE_KEYWORD,
1110
+ propSegments: [ELSE_KEYWORD],
1111
+ propModifiers: [],
1112
+ statePathName: "#else",
1113
+ statePathInfo: pathInfo,
1114
+ inFilters: [],
1115
+ outFilters: [],
1116
+ bindingType: "else"
1117
+ };
1118
+ } else if (propPart === SPREAD_PROP) {
1119
+ if (statePart.length === 0) {
1120
+ raiseError(`[wcs/binding-syntax] Invalid spread binding "${bindText2}": spread target path is required.${LINT_HINT}`);
1121
+ }
1122
+ const stateResult = parseStatePart(statePart);
1123
+ if (stateResult.outFilters.length > 0) {
1124
+ raiseError(`[wcs/binding-syntax] Invalid spread binding "${bindText2}": filters are not allowed on spread targets.${LINT_HINT}`);
1125
+ }
1126
+ return {
1127
+ propName: SPREAD_PROP,
1128
+ propSegments: [SPREAD_PROP],
1129
+ propModifiers: [],
1130
+ inFilters: [],
1131
+ ...stateResult,
1132
+ bindingType: "spread"
1133
+ };
1134
+ } else if (propPart === "if" || propPart === "elseif" || propPart === "for") {
1135
+ const stateResult = parseStatePart(statePart);
1136
+ return {
1137
+ propName: propPart,
1138
+ propSegments: [propPart],
1139
+ propModifiers: [],
1140
+ inFilters: [],
1141
+ ...stateResult,
1142
+ bindingType: propPart
1143
+ };
1144
+ } else if (keyword === "radio" || keyword === "checkbox") {
1145
+ const stateResult = parseStatePart(statePart);
1146
+ const propResult = parsePropPart(propPart);
1147
+ return {
1148
+ ...propResult,
1149
+ ...stateResult,
1150
+ bindingType: keyword
1151
+ };
1152
+ } else {
1153
+ const stateResult = parseStatePart(statePart);
1154
+ const propResult = parsePropPart(propPart);
1155
+ if (propResult.propSegments[0] === "" && propResult.propSegments.length > 1) {
1156
+ const propSegments = propResult.propSegments.slice(1);
1157
+ if (propSegments.includes("") || EXPLICIT_PROPERTY_REJECTED_HEADS.has(propSegments[0])) {
1158
+ raiseError(`[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}`);
1159
+ }
1160
+ return {
1161
+ ...propResult,
1162
+ propName: propSegments.join(DELIMITER),
1163
+ propSegments,
1164
+ ...stateResult,
1165
+ bindingType: "prop"
1166
+ };
1167
+ }
1168
+ if (propResult.propSegments[0] === EVENT_TOKEN_NAMESPACE) {
1169
+ return {
1170
+ ...propResult,
1171
+ ...stateResult,
1172
+ bindingType: "event"
1173
+ };
1174
+ }
1175
+ if (propResult.propSegments[0].startsWith(EVENT_PROP_PREFIX)) {
1176
+ return {
1177
+ ...propResult,
1178
+ ...stateResult,
1179
+ bindingType: "event"
1180
+ };
1181
+ } else {
1182
+ return {
1183
+ ...propResult,
1184
+ ...stateResult,
1185
+ bindingType: "prop"
1186
+ };
1187
+ }
1062
1188
  }
1063
- return `${(value * 100).toFixed(Number(opt))}%`;
1064
- };
1065
- };
1066
- var unit = (options) => {
1067
- const opt = options?.[0] ?? optionsRequired("unit");
1068
- return (value) => {
1069
- if (value === null || typeof value === "undefined") {
1070
- return value;
1189
+ });
1190
+ if (results.length > 1) {
1191
+ const isIncludeSingleBinding = results.some((r) => STRUCTURAL_BINDING_TYPE_SET.has(r.bindingType));
1192
+ if (isIncludeSingleBinding) {
1193
+ raiseError(`[wcs/template-syntax] Invalid bindText: "${bindText}". 'if', 'elseif', 'else', and 'for' bindings must be single binding. Put the structural binding alone in its own data-wcs (e.g. <template data-wcs="for: items">).${LINT_HINT}`);
1071
1194
  }
1072
- return String(value) + opt;
1195
+ }
1196
+ return results;
1197
+ }
1198
+ function parseBindTextForEmbeddedNode(bindText) {
1199
+ const stateResult = parseStatePart(bindText);
1200
+ return {
1201
+ propName: "textContent",
1202
+ propSegments: ["textContent"],
1203
+ propModifiers: [],
1204
+ inFilters: [],
1205
+ ...stateResult,
1206
+ bindingType: "text"
1073
1207
  };
1074
- };
1075
- var join = (options) => {
1076
- const opt = options?.[0] ?? ", ";
1077
- return (value) => {
1078
- if (!Array.isArray(value)) {
1079
- valueMustBeArray("join");
1208
+ }
1209
+ function clearParserCaches() {
1210
+ clearPathInfoCacheForTooling();
1211
+ clearPropPartCacheForTooling();
1212
+ clearStatePartCacheForTooling();
1213
+ clearFilterFnCacheForTooling();
1214
+ }
1215
+
1216
+ // src/language/htmlParse.ts
1217
+ function findStartTagRegions(html) {
1218
+ const out = [];
1219
+ let i = 0;
1220
+ while (i < html.length) {
1221
+ const lt2 = html.indexOf("<", i);
1222
+ if (lt2 === -1) break;
1223
+ const next = html[lt2 + 1];
1224
+ if (html.startsWith("<!--", lt2)) {
1225
+ const close = html.indexOf("-->", lt2 + 4);
1226
+ i = close === -1 ? html.length : close + 3;
1227
+ continue;
1080
1228
  }
1081
- return value.join(opt);
1082
- };
1083
- };
1084
- var truncate = (options) => {
1085
- const opt1 = options?.[0] ?? optionsRequired("truncate");
1086
- if (!validateNumberString(opt1)) {
1087
- optionMustBeNumber("truncate");
1088
- }
1089
- const maxLength = Number(opt1);
1090
- const suffix = options?.[1] ?? "\u2026";
1091
- return (value) => {
1092
- const v = String(value);
1093
- if (v.length <= maxLength) {
1094
- return v;
1229
+ if (next === "!" || next === "?" || next === "/") {
1230
+ const close = html.indexOf(">", lt2 + 1);
1231
+ i = close === -1 ? html.length : close + 1;
1232
+ continue;
1095
1233
  }
1096
- return v.slice(0, maxLength) + suffix;
1097
- };
1098
- };
1099
- var date = (options) => {
1100
- const explicit = options?.[0];
1101
- return (value) => {
1102
- if (!(value instanceof Date)) {
1103
- valueMustBeDate("date");
1234
+ if (next === void 0 || !/[A-Za-z]/.test(next)) {
1235
+ i = lt2 + 1;
1236
+ continue;
1104
1237
  }
1105
- return value.toLocaleDateString(explicit ?? config.locale);
1106
- };
1107
- };
1108
- var time = (options) => {
1109
- const explicit = options?.[0];
1110
- return (value) => {
1111
- if (!(value instanceof Date)) {
1112
- valueMustBeDate("time");
1238
+ let n = lt2 + 1;
1239
+ while (n < html.length && /[^\s/>]/.test(html[n])) n++;
1240
+ const tagName = html.slice(lt2 + 1, n).toLowerCase();
1241
+ let j = n;
1242
+ let quote = null;
1243
+ while (j < html.length) {
1244
+ const c = html[j];
1245
+ if (quote !== null) {
1246
+ if (c === quote) quote = null;
1247
+ } else if (c === '"' || c === "'") {
1248
+ quote = c;
1249
+ } else if (c === ">") {
1250
+ break;
1251
+ }
1252
+ j++;
1113
1253
  }
1114
- return value.toLocaleTimeString(explicit ?? config.locale);
1115
- };
1116
- };
1117
- var datetime = (options) => {
1118
- const explicit = options?.[0];
1119
- return (value) => {
1120
- if (!(value instanceof Date)) {
1121
- valueMustBeDate("datetime");
1254
+ const gt2 = j < html.length ? j : html.length;
1255
+ const selfClosing = html[gt2 - 1] === "/";
1256
+ out.push({ tagName, start: n, end: selfClosing ? gt2 - 1 : gt2 });
1257
+ i = gt2 + 1;
1258
+ if (tagName === "script" || tagName === "style") {
1259
+ const close = html.toLowerCase().indexOf(`</${tagName}`, i);
1260
+ i = close === -1 ? html.length : close;
1122
1261
  }
1123
- return value.toLocaleString(explicit ?? config.locale);
1124
- };
1125
- };
1126
- var ymd = (options) => {
1127
- const opt = options?.[0] ?? "-";
1128
- return (value) => {
1129
- if (!(value instanceof Date)) {
1130
- valueMustBeDate("ymd");
1262
+ }
1263
+ return out;
1264
+ }
1265
+ function parseWcsScriptBlocks(html, stateTagName = "wcs-state") {
1266
+ const blocks = [];
1267
+ let pos = 0;
1268
+ const len = html.length;
1269
+ while (pos < len) {
1270
+ if (html.startsWith("<!--", pos)) {
1271
+ const commentEnd = html.indexOf("-->", pos + 4);
1272
+ if (commentEnd === -1) break;
1273
+ pos = commentEnd + 3;
1274
+ continue;
1131
1275
  }
1132
- const year = value.getFullYear().toString();
1133
- const month = (value.getMonth() + 1).toString().padStart(2, "0");
1134
- const day = value.getDate().toString().padStart(2, "0");
1135
- return `${year}${opt}${month}${opt}${day}`;
1136
- };
1137
- };
1138
- var hms = (options) => {
1139
- const opt = options?.[0] ?? ":";
1140
- return (value) => {
1141
- if (!(value instanceof Date)) {
1142
- valueMustBeDate("hms");
1276
+ const wcsMatch = matchOpenTag(html, pos, stateTagName);
1277
+ if (wcsMatch === null) {
1278
+ pos++;
1279
+ continue;
1143
1280
  }
1144
- const hours = value.getHours().toString().padStart(2, "0");
1145
- const minutes = value.getMinutes().toString().padStart(2, "0");
1146
- const seconds = value.getSeconds().toString().padStart(2, "0");
1147
- return `${hours}${opt}${minutes}${opt}${seconds}`;
1148
- };
1149
- };
1150
- var falsy = (_options) => {
1151
- return (value) => !value;
1281
+ const mountPath = extractAttribute(wcsMatch.tagContent, "mount");
1282
+ pos = wcsMatch.end;
1283
+ const wcsCloseIdx = findCloseTag(html, pos, stateTagName);
1284
+ const wcsEnd = wcsCloseIdx === -1 ? len : wcsCloseIdx;
1285
+ while (pos < wcsEnd) {
1286
+ if (html.startsWith("<!--", pos)) {
1287
+ const commentEnd = html.indexOf("-->", pos + 4);
1288
+ if (commentEnd === -1) break;
1289
+ pos = commentEnd + 3;
1290
+ continue;
1291
+ }
1292
+ const scriptMatch = matchOpenTag(html, pos, "script");
1293
+ if (scriptMatch === null) {
1294
+ pos++;
1295
+ continue;
1296
+ }
1297
+ const typeAttr = extractAttribute(scriptMatch.tagContent, "type");
1298
+ if (typeAttr?.toLowerCase() !== "module") {
1299
+ pos = scriptMatch.end;
1300
+ continue;
1301
+ }
1302
+ const contentStart = scriptMatch.end;
1303
+ const scriptCloseIdx = findCloseTag(html, contentStart, "script");
1304
+ if (scriptCloseIdx === -1) {
1305
+ pos = contentStart;
1306
+ break;
1307
+ }
1308
+ const contentEnd = scriptCloseIdx;
1309
+ blocks.push({
1310
+ contentStart,
1311
+ contentEnd,
1312
+ content: html.slice(contentStart, contentEnd),
1313
+ mountPath
1314
+ });
1315
+ pos = html.indexOf(">", scriptCloseIdx) + 1;
1316
+ if (pos === 0) break;
1317
+ }
1318
+ pos = wcsEnd;
1319
+ if (wcsCloseIdx !== -1) {
1320
+ const closeEnd = html.indexOf(">", wcsCloseIdx);
1321
+ if (closeEnd !== -1) pos = closeEnd + 1;
1322
+ }
1323
+ }
1324
+ return blocks;
1325
+ }
1326
+ function parseWcsStateElements(html, stateTagName = "wcs-state") {
1327
+ const elements = [];
1328
+ let pos = 0;
1329
+ const len = html.length;
1330
+ while (pos < len) {
1331
+ if (html.startsWith("<!--", pos)) {
1332
+ const commentEnd = html.indexOf("-->", pos + 4);
1333
+ if (commentEnd === -1) break;
1334
+ pos = commentEnd + 3;
1335
+ continue;
1336
+ }
1337
+ const wcsMatch = matchOpenTag(html, pos, stateTagName);
1338
+ if (wcsMatch === null) {
1339
+ pos++;
1340
+ continue;
1341
+ }
1342
+ const mountPath = extractAttribute(wcsMatch.tagContent, "mount");
1343
+ const bindComponent = parseAttributeNames(wcsMatch.tagContent).has("bind-component");
1344
+ const jsonAttr = extractAttribute(wcsMatch.tagContent, "json") ?? void 0;
1345
+ const stateAttr = extractAttribute(wcsMatch.tagContent, "state") ?? void 0;
1346
+ const srcAttr = extractAttribute(wcsMatch.tagContent, "src") ?? void 0;
1347
+ const tagStart = pos;
1348
+ const tagEnd = wcsMatch.end;
1349
+ pos = wcsMatch.end;
1350
+ const scriptBlocks = [];
1351
+ const wcsCloseIdx = findCloseTag(html, pos, stateTagName);
1352
+ const wcsEnd = wcsCloseIdx === -1 ? len : wcsCloseIdx;
1353
+ while (pos < wcsEnd) {
1354
+ if (html.startsWith("<!--", pos)) {
1355
+ const commentEnd = html.indexOf("-->", pos + 4);
1356
+ if (commentEnd === -1) break;
1357
+ pos = commentEnd + 3;
1358
+ continue;
1359
+ }
1360
+ const scriptMatch = matchOpenTag(html, pos, "script");
1361
+ if (scriptMatch === null) {
1362
+ pos++;
1363
+ continue;
1364
+ }
1365
+ const typeAttr = extractAttribute(scriptMatch.tagContent, "type");
1366
+ if (typeAttr?.toLowerCase() !== "module") {
1367
+ pos = scriptMatch.end;
1368
+ continue;
1369
+ }
1370
+ const contentStart = scriptMatch.end;
1371
+ const scriptCloseIdx = findCloseTag(html, contentStart, "script");
1372
+ if (scriptCloseIdx === -1) {
1373
+ pos = contentStart;
1374
+ break;
1375
+ }
1376
+ scriptBlocks.push({
1377
+ contentStart,
1378
+ contentEnd: scriptCloseIdx,
1379
+ content: html.slice(contentStart, scriptCloseIdx),
1380
+ mountPath
1381
+ });
1382
+ pos = html.indexOf(">", scriptCloseIdx) + 1;
1383
+ if (pos === 0) break;
1384
+ }
1385
+ elements.push({ mountPath, bindComponent, jsonAttr, stateAttr, srcAttr, scriptBlocks, tagStart, tagEnd });
1386
+ pos = wcsEnd;
1387
+ if (wcsCloseIdx !== -1) {
1388
+ const closeEnd = html.indexOf(">", wcsCloseIdx);
1389
+ if (closeEnd !== -1) pos = closeEnd + 1;
1390
+ }
1391
+ }
1392
+ return elements;
1393
+ }
1394
+ function findScriptJsonById(html, id2) {
1395
+ let pos = 0;
1396
+ const len = html.length;
1397
+ while (pos < len) {
1398
+ if (html.startsWith("<!--", pos)) {
1399
+ const commentEnd = html.indexOf("-->", pos + 4);
1400
+ if (commentEnd === -1) break;
1401
+ pos = commentEnd + 3;
1402
+ continue;
1403
+ }
1404
+ const scriptMatch = matchOpenTag(html, pos, "script");
1405
+ if (scriptMatch === null) {
1406
+ pos++;
1407
+ continue;
1408
+ }
1409
+ const typeAttr = extractAttribute(scriptMatch.tagContent, "type");
1410
+ const idAttr = extractAttribute(scriptMatch.tagContent, "id");
1411
+ if (typeAttr?.toLowerCase() === "application/json" && idAttr === id2) {
1412
+ const contentStart = scriptMatch.end;
1413
+ const scriptCloseIdx = findCloseTag(html, contentStart, "script");
1414
+ if (scriptCloseIdx === -1) return null;
1415
+ return html.slice(contentStart, scriptCloseIdx);
1416
+ }
1417
+ pos = scriptMatch.end;
1418
+ }
1419
+ return null;
1420
+ }
1421
+ function matchOpenTag(html, pos, tagName) {
1422
+ if (html[pos] !== "<") return null;
1423
+ const nameStart = pos + 1;
1424
+ const nameEnd = nameStart + tagName.length;
1425
+ if (nameEnd > html.length) return null;
1426
+ const slice2 = html.slice(nameStart, nameEnd);
1427
+ if (slice2.toLowerCase() !== tagName.toLowerCase()) return null;
1428
+ const charAfter = html[nameEnd];
1429
+ if (charAfter !== ">" && charAfter !== " " && charAfter !== " " && charAfter !== "\n" && charAfter !== "\r" && charAfter !== "/") {
1430
+ return null;
1431
+ }
1432
+ let i = nameEnd;
1433
+ let inSingleQuote = false;
1434
+ let inDoubleQuote = false;
1435
+ while (i < html.length) {
1436
+ const ch = html[i];
1437
+ if (inSingleQuote) {
1438
+ if (ch === "'") inSingleQuote = false;
1439
+ } else if (inDoubleQuote) {
1440
+ if (ch === '"') inDoubleQuote = false;
1441
+ } else if (ch === "'") {
1442
+ inSingleQuote = true;
1443
+ } else if (ch === '"') {
1444
+ inDoubleQuote = true;
1445
+ } else if (ch === ">") {
1446
+ return {
1447
+ start: pos,
1448
+ end: i + 1,
1449
+ tagContent: html.slice(nameEnd, i)
1450
+ };
1451
+ }
1452
+ i++;
1453
+ }
1454
+ return null;
1455
+ }
1456
+ function findCloseTag(html, startPos, tagName) {
1457
+ const pattern = "</" + tagName;
1458
+ const patternLower = pattern.toLowerCase();
1459
+ const htmlLower = html.toLowerCase();
1460
+ let pos = startPos;
1461
+ while (pos < html.length) {
1462
+ const idx = htmlLower.indexOf(patternLower, pos);
1463
+ if (idx === -1) return -1;
1464
+ const afterIdx = idx + pattern.length;
1465
+ if (afterIdx < html.length) {
1466
+ const ch = html[afterIdx];
1467
+ if (ch === ">" || ch === " " || ch === " " || ch === "\n" || ch === "\r") {
1468
+ return idx;
1469
+ }
1470
+ }
1471
+ pos = idx + 1;
1472
+ }
1473
+ return -1;
1474
+ }
1475
+ function parseAttributeNames(tagContent) {
1476
+ const names = /* @__PURE__ */ new Set();
1477
+ const attribute = /([^\s"'<>/=]+)(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^\s"'<>`=]+))?/g;
1478
+ let match;
1479
+ while ((match = attribute.exec(tagContent)) !== null) {
1480
+ names.add(match[1].toLowerCase());
1481
+ }
1482
+ return names;
1483
+ }
1484
+ function extractAttribute(tagContent, attrName) {
1485
+ const regex = new RegExp(
1486
+ `(?:^|\\s)${attrName}\\s*=\\s*(?:"([^"]*)"|'([^']*)'|(\\S+))`,
1487
+ "i"
1488
+ );
1489
+ const match = tagContent.match(regex);
1490
+ if (!match) return null;
1491
+ return match[1] ?? match[2] ?? match[3] ?? null;
1492
+ }
1493
+
1494
+ // src/core/parser/quoteAware.ts
1495
+ var isQuote2 = (c) => c === "'" || c === '"';
1496
+ function indexOfOutsideQuotes2(text, char) {
1497
+ let quote = null;
1498
+ for (let i = 0; i < text.length; i++) {
1499
+ const c = text[i];
1500
+ if (quote !== null) {
1501
+ if (c === quote) quote = null;
1502
+ } else if (isQuote2(c)) {
1503
+ quote = c;
1504
+ } else if (c === char) {
1505
+ return i;
1506
+ }
1507
+ }
1508
+ return -1;
1509
+ }
1510
+ function splitOutsideQuotes2(text, separator) {
1511
+ const parts = [];
1512
+ let quote = null;
1513
+ let start = 0;
1514
+ for (let i = 0; i < text.length; i++) {
1515
+ const c = text[i];
1516
+ if (quote !== null) {
1517
+ if (c === quote) quote = null;
1518
+ } else if (isQuote2(c)) {
1519
+ quote = c;
1520
+ } else if (c === separator) {
1521
+ parts.push(text.slice(start, i));
1522
+ start = i + 1;
1523
+ }
1524
+ }
1525
+ parts.push(text.slice(start));
1526
+ return parts;
1527
+ }
1528
+
1529
+ // ../state/dist/manifest.esm.js
1530
+ var _config = {
1531
+ bindAttributeName: "data-wcs",
1532
+ tagNames: {
1533
+ state: "wcs-state"
1534
+ },
1535
+ locale: "en"
1536
+ };
1537
+ var config = _config;
1538
+ var DELIMITER2 = ".";
1539
+ var WILDCARD2 = "*";
1540
+ var MAX_WILDCARD_DEPTH2 = 128;
1541
+ var BINDING_SEPARATOR2 = ";";
1542
+ var PROP_VALUE_SEPARATOR2 = ":";
1543
+ var MODIFIER_SEPARATOR2 = "#";
1544
+ var FILTER_SEPARATOR2 = "|";
1545
+ var MODIFIER_PREVENT = "prevent";
1546
+ var MODIFIER_STOP = "stop";
1547
+ var MODIFIER_READONLY = "ro";
1548
+ var MODIFIER_FLAGS = Object.freeze([
1549
+ MODIFIER_PREVENT,
1550
+ MODIFIER_STOP,
1551
+ MODIFIER_READONLY
1552
+ ]);
1553
+ var MODIFIER_KEY_INIT = "init";
1554
+ var MODIFIER_KEY_SYNC = "sync";
1555
+ var MODIFIER_KEYS = Object.freeze([
1556
+ MODIFIER_KEY_INIT,
1557
+ MODIFIER_KEY_SYNC
1558
+ ]);
1559
+ var ELSE_KEYWORD2 = "else";
1560
+ var SPREAD_PROP2 = "...";
1561
+ var EVENT_PROP_PREFIX2 = "on";
1562
+ var EVENT_TOKEN_NAMESPACE2 = "eventToken";
1563
+ var COMMAND_NAMESPACE2 = "command";
1564
+ var CLASS_NAMESPACE2 = "class";
1565
+ var ATTR_NAMESPACE2 = "attr";
1566
+ var STYLE_NAMESPACE2 = "style";
1567
+ var INDEX_PARAM_PREFIX2 = "$";
1568
+ var tmpIndexByIndexName2 = {};
1569
+ for (let i = 0; i < MAX_WILDCARD_DEPTH2; i++) {
1570
+ tmpIndexByIndexName2[`${INDEX_PARAM_PREFIX2}${i + 1}`] = i;
1571
+ }
1572
+ Object.freeze(tmpIndexByIndexName2);
1573
+ var STATE_CONNECTED_CALLBACK_NAME = "$connectedCallback";
1574
+ var STATE_DISCONNECTED_CALLBACK_NAME = "$disconnectedCallback";
1575
+ var STATE_RENDERED_CALLBACK_NAME = "$renderedCallback";
1576
+ var STATE_ERROR_CALLBACK_NAME = "$errorCallback";
1577
+ var WEBCOMPONENT_STATE_READY_CALLBACK_NAME = "$stateReadyCallback";
1578
+ var STATE_BINDABLES_NAME = "$bindables";
1579
+ var STATE_COMMANDS_NAME = "$commands";
1580
+ var STATE_COMMAND_TOKENS_NAME = "$commandTokens";
1581
+ var STATE_COMMAND_NAMESPACE_NAME = "$command";
1582
+ var STATE_EVENT_TOKENS_NAME = "$eventTokens";
1583
+ var STATE_ON_NAME = "$on";
1584
+ var STATE_STREAM_NAME = "$stream";
1585
+ var STATE_WATCH_NAME = "$watch";
1586
+ var STATE_SCAN_NAME = "$scan";
1587
+ var STATE_RECURSION_NAME = "$recursion";
1588
+ var STATE_LIST_KEYS_NAME = "$listKeys";
1589
+ var STATE_STREAM_STATUS_NAMESPACE_NAME = "$streamStatus";
1590
+ var STATE_STREAM_ERROR_NAMESPACE_NAME = "$streamError";
1591
+ function raiseError2(message) {
1592
+ throw new Error(`[@wcstack/state] ${message}`);
1593
+ }
1594
+ var DECLARATION_ALIASES = {
1595
+ $updatedCallback: STATE_RENDERED_CALLBACK_NAME,
1596
+ $streams: STATE_STREAM_NAME
1597
+ };
1598
+ function optionsRequired(fnName) {
1599
+ raiseError2(`filter ${fnName} requires at least one option`);
1600
+ }
1601
+ function optionMustBeNumber(fnName) {
1602
+ raiseError2(`filter ${fnName} requires a number as option`);
1603
+ }
1604
+ function valueMustBeNumber(fnName) {
1605
+ raiseError2(`filter ${fnName} requires a number value`);
1606
+ }
1607
+ function valueMustBeDate(fnName) {
1608
+ raiseError2(`filter ${fnName} requires a date value`);
1609
+ }
1610
+ function valueMustBeArray(fnName) {
1611
+ raiseError2(`filter ${fnName} requires an array value`);
1612
+ }
1613
+ var builtinFilterAliases = {
1614
+ inc: "add",
1615
+ dec: "sub",
1616
+ fix: "toFixed",
1617
+ uc: "upper",
1618
+ lc: "lower",
1619
+ cap: "capitalize",
1620
+ rep: "repeat",
1621
+ rev: "reverse",
1622
+ pad: "padStart",
1623
+ null: "nullIfEmpty"
1624
+ };
1625
+ function validateNumberString(value) {
1626
+ if (!value || isNaN(Number(value))) {
1627
+ return false;
1628
+ }
1629
+ return true;
1630
+ }
1631
+ function numberOption(value, fnName) {
1632
+ if (!validateNumberString(value)) {
1633
+ optionMustBeNumber(fnName);
1634
+ }
1635
+ return Number(value);
1636
+ }
1637
+ function requiredNumberOption(options, index, fnName) {
1638
+ return numberOption(options?.[index] ?? optionsRequired(fnName), fnName);
1639
+ }
1640
+ var nullishPassthrough = (factory) => (options) => {
1641
+ const filterFn = factory(options);
1642
+ return (value) => value == null ? value : filterFn(value);
1643
+ };
1644
+ var eq = (options, literals) => {
1645
+ const opt = options?.[0] ?? optionsRequired("eq");
1646
+ const literal2 = literals !== void 0 && literals.length > 0 ? literals[0] : opt;
1647
+ return (value) => {
1648
+ if (typeof value === "number") {
1649
+ if (typeof literal2 !== "string") {
1650
+ return value === literal2;
1651
+ }
1652
+ if (!validateNumberString(opt)) {
1653
+ optionMustBeNumber("eq");
1654
+ }
1655
+ return value === Number(opt);
1656
+ }
1657
+ if (typeof value === "string") {
1658
+ return value === opt;
1659
+ }
1660
+ return value === literal2;
1661
+ };
1662
+ };
1663
+ var ne = (options, literals) => {
1664
+ const opt = options?.[0] ?? optionsRequired("ne");
1665
+ const literal2 = literals !== void 0 && literals.length > 0 ? literals[0] : opt;
1666
+ return (value) => {
1667
+ if (typeof value === "number") {
1668
+ if (typeof literal2 !== "string") {
1669
+ return value !== literal2;
1670
+ }
1671
+ if (!validateNumberString(opt)) {
1672
+ optionMustBeNumber("ne");
1673
+ }
1674
+ return value !== Number(opt);
1675
+ }
1676
+ if (typeof value === "string") {
1677
+ return value !== opt;
1678
+ }
1679
+ return value !== literal2;
1680
+ };
1681
+ };
1682
+ var not = (_options) => {
1683
+ return (value) => !value;
1684
+ };
1685
+ var lt = (options) => {
1686
+ const opt = requiredNumberOption(options, 0, "lt");
1687
+ return (value) => {
1688
+ if (typeof value !== "number") {
1689
+ valueMustBeNumber("lt");
1690
+ }
1691
+ return value < opt;
1692
+ };
1693
+ };
1694
+ var le = (options) => {
1695
+ const opt = requiredNumberOption(options, 0, "le");
1696
+ return (value) => {
1697
+ if (typeof value !== "number") {
1698
+ valueMustBeNumber("le");
1699
+ }
1700
+ return value <= opt;
1701
+ };
1702
+ };
1703
+ var gt = (options) => {
1704
+ const opt = requiredNumberOption(options, 0, "gt");
1705
+ return (value) => {
1706
+ if (typeof value !== "number") {
1707
+ valueMustBeNumber("gt");
1708
+ }
1709
+ return value > opt;
1710
+ };
1711
+ };
1712
+ var ge = (options) => {
1713
+ const opt = requiredNumberOption(options, 0, "ge");
1714
+ return (value) => {
1715
+ if (typeof value !== "number") {
1716
+ valueMustBeNumber("ge");
1717
+ }
1718
+ return value >= opt;
1719
+ };
1720
+ };
1721
+ var inc = (options) => {
1722
+ const opt = requiredNumberOption(options, 0, "add");
1723
+ return (value) => {
1724
+ if (typeof value !== "number") {
1725
+ valueMustBeNumber("add");
1726
+ }
1727
+ return value + opt;
1728
+ };
1729
+ };
1730
+ var dec = (options) => {
1731
+ const opt = requiredNumberOption(options, 0, "sub");
1732
+ return (value) => {
1733
+ if (typeof value !== "number") {
1734
+ valueMustBeNumber("sub");
1735
+ }
1736
+ return value - opt;
1737
+ };
1738
+ };
1739
+ var mul = (options) => {
1740
+ const opt = requiredNumberOption(options, 0, "mul");
1741
+ return (value) => {
1742
+ if (typeof value !== "number") {
1743
+ valueMustBeNumber("mul");
1744
+ }
1745
+ return value * opt;
1746
+ };
1747
+ };
1748
+ var div = (options) => {
1749
+ const opt = requiredNumberOption(options, 0, "div");
1750
+ return (value) => {
1751
+ if (typeof value !== "number") {
1752
+ valueMustBeNumber("div");
1753
+ }
1754
+ return value / opt;
1755
+ };
1756
+ };
1757
+ var mod = (options) => {
1758
+ const opt = requiredNumberOption(options, 0, "mod");
1759
+ return (value) => {
1760
+ if (typeof value !== "number") {
1761
+ valueMustBeNumber("mod");
1762
+ }
1763
+ return value % opt;
1764
+ };
1765
+ };
1766
+ var abs = (_options) => {
1767
+ return (value) => {
1768
+ if (typeof value !== "number") {
1769
+ valueMustBeNumber("abs");
1770
+ }
1771
+ return Math.abs(value);
1772
+ };
1773
+ };
1774
+ var clamp = (options) => {
1775
+ const min = requiredNumberOption(options, 0, "clamp");
1776
+ const max = requiredNumberOption(options, 1, "clamp");
1777
+ return (value) => {
1778
+ if (typeof value !== "number") {
1779
+ valueMustBeNumber("clamp");
1780
+ }
1781
+ return Math.min(Math.max(value, min), max);
1782
+ };
1783
+ };
1784
+ var fix = (options) => {
1785
+ const opt = numberOption(options?.[0] ?? "0", "toFixed");
1786
+ return (value) => {
1787
+ if (typeof value !== "number") {
1788
+ valueMustBeNumber("toFixed");
1789
+ }
1790
+ return value.toFixed(opt);
1791
+ };
1792
+ };
1793
+ var locale = (options) => {
1794
+ const explicit = options?.[0];
1795
+ return (value) => {
1796
+ if (typeof value !== "number") {
1797
+ valueMustBeNumber("locale");
1798
+ }
1799
+ return value.toLocaleString(explicit ?? config.locale);
1800
+ };
1801
+ };
1802
+ var uc = (_options) => {
1803
+ return (value) => {
1804
+ return String(value).toUpperCase();
1805
+ };
1806
+ };
1807
+ var lc = (_options) => {
1808
+ return (value) => {
1809
+ return String(value).toLowerCase();
1810
+ };
1811
+ };
1812
+ var cap = (_options) => {
1813
+ return (value) => {
1814
+ const v = String(value);
1815
+ if (v.length === 0) {
1816
+ return v;
1817
+ }
1818
+ if (v.length === 1) {
1819
+ return v.toUpperCase();
1820
+ }
1821
+ return v.charAt(0).toUpperCase() + v.slice(1);
1822
+ };
1823
+ };
1824
+ var trim = (_options) => {
1825
+ return (value) => {
1826
+ return String(value).trim();
1827
+ };
1828
+ };
1829
+ var slice = (options) => {
1830
+ const numberedOpts = [requiredNumberOption(options, 0, "slice")];
1831
+ const opt2 = options?.[1];
1832
+ if (typeof opt2 !== "undefined") {
1833
+ numberedOpts.push(numberOption(opt2, "slice"));
1834
+ }
1835
+ return (value) => {
1836
+ return String(value).slice(...numberedOpts);
1837
+ };
1838
+ };
1839
+ var substr = (options) => {
1840
+ const opt1 = requiredNumberOption(options, 0, "substr");
1841
+ const opt2 = requiredNumberOption(options, 1, "substr");
1842
+ return (value) => {
1843
+ return String(value).substr(opt1, opt2);
1844
+ };
1845
+ };
1846
+ var pad = (options) => {
1847
+ const opt1 = requiredNumberOption(options, 0, "padStart");
1848
+ const opt2 = options?.[1] ?? "0";
1849
+ return (value) => {
1850
+ return String(value).padStart(opt1, opt2);
1851
+ };
1852
+ };
1853
+ var padEnd = (options) => {
1854
+ const opt1 = requiredNumberOption(options, 0, "padEnd");
1855
+ const opt2 = options?.[1] ?? " ";
1856
+ return (value) => {
1857
+ return String(value).padEnd(opt1, opt2);
1858
+ };
1859
+ };
1860
+ var rep = (options) => {
1861
+ const opt = requiredNumberOption(options, 0, "repeat");
1862
+ return (value) => {
1863
+ return String(value).repeat(opt);
1864
+ };
1865
+ };
1866
+ var rev = (_options) => {
1867
+ return (value) => {
1868
+ return String(value).split("").reverse().join("");
1869
+ };
1870
+ };
1871
+ var int = (_options) => {
1872
+ return (value) => {
1873
+ return parseInt(String(value), 10);
1874
+ };
1875
+ };
1876
+ var float = (_options) => {
1877
+ return (value) => {
1878
+ return parseFloat(String(value));
1879
+ };
1880
+ };
1881
+ var round = (options) => {
1882
+ const optValue = Math.pow(10, numberOption(options?.[0] ?? "0", "round"));
1883
+ return (value) => {
1884
+ if (typeof value !== "number") {
1885
+ valueMustBeNumber("round");
1886
+ }
1887
+ return Math.round(value * optValue) / optValue;
1888
+ };
1889
+ };
1890
+ var floor = (options) => {
1891
+ const optValue = Math.pow(10, numberOption(options?.[0] ?? "0", "floor"));
1892
+ return (value) => {
1893
+ if (typeof value !== "number") {
1894
+ valueMustBeNumber("floor");
1895
+ }
1896
+ return Math.floor(value * optValue) / optValue;
1897
+ };
1898
+ };
1899
+ var ceil = (options) => {
1900
+ const optValue = Math.pow(10, numberOption(options?.[0] ?? "0", "ceil"));
1901
+ return (value) => {
1902
+ if (typeof value !== "number") {
1903
+ valueMustBeNumber("ceil");
1904
+ }
1905
+ return Math.ceil(value * optValue) / optValue;
1906
+ };
1907
+ };
1908
+ var percent = (options) => {
1909
+ const opt = numberOption(options?.[0] ?? "0", "percent");
1910
+ return (value) => {
1911
+ if (typeof value !== "number") {
1912
+ valueMustBeNumber("percent");
1913
+ }
1914
+ return `${(value * 100).toFixed(opt)}%`;
1915
+ };
1916
+ };
1917
+ var unit = (options) => {
1918
+ const opt = options?.[0] ?? optionsRequired("unit");
1919
+ return (value) => {
1920
+ return String(value) + opt;
1921
+ };
1922
+ };
1923
+ var join = (options) => {
1924
+ const opt = options?.[0] ?? ", ";
1925
+ return (value) => {
1926
+ if (!Array.isArray(value)) {
1927
+ valueMustBeArray("join");
1928
+ }
1929
+ return value.join(opt);
1930
+ };
1931
+ };
1932
+ var truncate = (options) => {
1933
+ const maxLength = requiredNumberOption(options, 0, "truncate");
1934
+ const suffix = options?.[1] ?? "\u2026";
1935
+ return (value) => {
1936
+ const v = String(value);
1937
+ if (v.length <= maxLength) {
1938
+ return v;
1939
+ }
1940
+ return v.slice(0, maxLength) + suffix;
1941
+ };
1942
+ };
1943
+ var date = (options) => {
1944
+ const explicit = options?.[0];
1945
+ return (value) => {
1946
+ if (!(value instanceof Date)) {
1947
+ valueMustBeDate("date");
1948
+ }
1949
+ return value.toLocaleDateString(explicit ?? config.locale);
1950
+ };
1951
+ };
1952
+ var time = (options) => {
1953
+ const explicit = options?.[0];
1954
+ return (value) => {
1955
+ if (!(value instanceof Date)) {
1956
+ valueMustBeDate("time");
1957
+ }
1958
+ return value.toLocaleTimeString(explicit ?? config.locale);
1959
+ };
1960
+ };
1961
+ var datetime = (options) => {
1962
+ const explicit = options?.[0];
1963
+ return (value) => {
1964
+ if (!(value instanceof Date)) {
1965
+ valueMustBeDate("datetime");
1966
+ }
1967
+ return value.toLocaleString(explicit ?? config.locale);
1968
+ };
1969
+ };
1970
+ var ymd = (options) => {
1971
+ const opt = options?.[0] ?? "-";
1972
+ return (value) => {
1973
+ if (!(value instanceof Date)) {
1974
+ valueMustBeDate("ymd");
1975
+ }
1976
+ const year = value.getFullYear().toString();
1977
+ const month = (value.getMonth() + 1).toString().padStart(2, "0");
1978
+ const day = value.getDate().toString().padStart(2, "0");
1979
+ return `${year}${opt}${month}${opt}${day}`;
1980
+ };
1981
+ };
1982
+ var hms = (options) => {
1983
+ const opt = options?.[0] ?? ":";
1984
+ return (value) => {
1985
+ if (!(value instanceof Date)) {
1986
+ valueMustBeDate("hms");
1987
+ }
1988
+ const hours = value.getHours().toString().padStart(2, "0");
1989
+ const minutes = value.getMinutes().toString().padStart(2, "0");
1990
+ const seconds = value.getSeconds().toString().padStart(2, "0");
1991
+ return `${hours}${opt}${minutes}${opt}${seconds}`;
1992
+ };
1993
+ };
1994
+ var falsy = (_options) => {
1995
+ return (value) => !value;
1152
1996
  };
1153
1997
  var truthy = (_options) => {
1154
1998
  return (value) => !!value;
@@ -1205,17 +2049,18 @@ var builtinFilters = {
1205
2049
  "clamp": clamp,
1206
2050
  "toFixed": fix,
1207
2051
  "locale": locale,
1208
- "upper": uc,
1209
- "lower": lc,
1210
- "capitalize": cap,
1211
- "trim": trim,
1212
- "slice": slice,
1213
- "substr": substr,
1214
- "padStart": pad,
1215
- "padEnd": padEnd,
1216
- "repeat": rep,
1217
- "reverse": rev,
1218
- "truncate": truncate,
2052
+ // `String(value)` で組み立てる族は、値が無いときに素通しする(要件 B8 — nullishPassthrough を参照)
2053
+ "upper": nullishPassthrough(uc),
2054
+ "lower": nullishPassthrough(lc),
2055
+ "capitalize": nullishPassthrough(cap),
2056
+ "trim": nullishPassthrough(trim),
2057
+ "slice": nullishPassthrough(slice),
2058
+ "substr": nullishPassthrough(substr),
2059
+ "padStart": nullishPassthrough(pad),
2060
+ "padEnd": nullishPassthrough(padEnd),
2061
+ "repeat": nullishPassthrough(rep),
2062
+ "reverse": nullishPassthrough(rev),
2063
+ "truncate": nullishPassthrough(truncate),
1219
2064
  "join": join,
1220
2065
  "int": int,
1221
2066
  "float": float,
@@ -1223,7 +2068,7 @@ var builtinFilters = {
1223
2068
  "floor": floor,
1224
2069
  "ceil": ceil,
1225
2070
  "percent": percent,
1226
- "unit": unit,
2071
+ "unit": nullishPassthrough(unit),
1227
2072
  "date": date,
1228
2073
  "time": time,
1229
2074
  "datetime": datetime,
@@ -1243,7 +2088,9 @@ var builtinFilterMeta = {
1243
2088
  // 比較・論理
1244
2089
  eq: { description: "\u7B49\u3057\u3044\u304B\u6BD4\u8F03", hasArgs: true, resultType: "boolean", acceptTypes: "any", minArgs: 1, maxArgs: 1, argTypes: ["any"] },
1245
2090
  ne: { description: "\u7570\u306A\u308B\u304B\u6BD4\u8F03", hasArgs: true, resultType: "boolean", acceptTypes: "any", minArgs: 1, maxArgs: 1, argTypes: ["any"] },
1246
- not: { description: "\u30D6\u30FC\u30EB\u5024\u3092\u53CD\u8EE2", hasArgs: false, resultType: "boolean", acceptTypes: ["boolean"], minArgs: 0, maxArgs: 0 },
2091
+ // `not` は真偽性(truthy / falsy)の反転。`if:` が `Boolean()` で寄せるのと同じ規則で、
2092
+ // `else:` はこのフィルタを足した束縛として組み立てられる(structural/createNotFilter.ts)
2093
+ not: { description: "\u771F\u507D\u6027\u3092\u53CD\u8EE2\uFF08falsy \u2192 true\uFF09", hasArgs: false, resultType: "boolean", acceptTypes: "any", minArgs: 0, maxArgs: 0 },
1247
2094
  lt: { description: "\u3088\u308A\u5C0F\u3055\u3044\u304B", hasArgs: true, resultType: "boolean", acceptTypes: ["number", "string"], minArgs: 1, maxArgs: 1, argTypes: ["number"] },
1248
2095
  le: { description: "\u4EE5\u4E0B\u304B", hasArgs: true, resultType: "boolean", acceptTypes: ["number", "string"], minArgs: 1, maxArgs: 1, argTypes: ["number"] },
1249
2096
  gt: { description: "\u3088\u308A\u5927\u304D\u3044\u304B", hasArgs: true, resultType: "boolean", acceptTypes: ["number", "string"], minArgs: 1, maxArgs: 1, argTypes: ["number"] },
@@ -1265,13 +2112,15 @@ var builtinFilterMeta = {
1265
2112
  capitalize: { description: "\u5148\u982D\u6587\u5B57\u3092\u5927\u6587\u5B57\u306B", hasArgs: false, resultType: "string", acceptTypes: ["string"], minArgs: 0, maxArgs: 0 },
1266
2113
  trim: { description: "\u524D\u5F8C\u306E\u7A7A\u767D\u3092\u524A\u9664", hasArgs: false, resultType: "string", acceptTypes: ["string"], minArgs: 0, maxArgs: 0 },
1267
2114
  slice: { description: "\u90E8\u5206\u6587\u5B57\u5217 (start[,end])", hasArgs: true, resultType: "string", acceptTypes: ["string"], minArgs: 1, maxArgs: 2, argTypes: ["number", "number"] },
1268
- substr: { description: "\u90E8\u5206\u6587\u5B57\u5217 (pos,len)", hasArgs: true, resultType: "string", acceptTypes: ["string"], minArgs: 1, maxArgs: 2, argTypes: ["number", "number"] },
1269
- padStart: { description: "\u5148\u982D\u3092\u57CB\u3081\u308B (length[,char])", hasArgs: true, resultType: "string", acceptTypes: ["string"], minArgs: 1, maxArgs: 2, argTypes: ["number", "string"] },
1270
- padEnd: { description: "\u672B\u5C3E\u3092\u57CB\u3081\u308B (length[,char])", hasArgs: true, resultType: "string", acceptTypes: ["string"], minArgs: 1, maxArgs: 2, argTypes: ["number", "string"] },
2115
+ // 長さは省略できない(実装が両方読む)。minArgs: 1 だった頃は補完・lint が `substr(0)` を
2116
+ // 通し、実行時にだけ落ちていた
2117
+ substr: { description: "\u90E8\u5206\u6587\u5B57\u5217 (pos,len)", hasArgs: true, resultType: "string", acceptTypes: ["string"], minArgs: 2, maxArgs: 2, argTypes: ["number", "number"] },
2118
+ padStart: { description: "\u5148\u982D\u3092\u57CB\u3081\u308B (length[,char]\u3002char \u306E\u65E2\u5B9A\u306F 0 \u2014 JS \u306E\u65E2\u5B9A\u306F\u7A7A\u767D\u306A\u306E\u3067\u6CE8\u610F)", hasArgs: true, resultType: "string", acceptTypes: ["string"], minArgs: 1, maxArgs: 2, argTypes: ["number", "string"] },
2119
+ padEnd: { description: "\u672B\u5C3E\u3092\u57CB\u3081\u308B (length[,char]\u3002char \u306E\u65E2\u5B9A\u306F\u7A7A\u767D \u2014 JS \u3068\u540C\u3058)", hasArgs: true, resultType: "string", acceptTypes: ["string"], minArgs: 1, maxArgs: 2, argTypes: ["number", "string"] },
1271
2120
  repeat: { description: "\u7E70\u308A\u8FD4\u3057 (count)", hasArgs: true, resultType: "string", acceptTypes: ["string"], minArgs: 1, maxArgs: 1, argTypes: ["number"] },
1272
2121
  reverse: { description: "\u6587\u5B57\u9806\u3092\u53CD\u8EE2", hasArgs: false, resultType: "string", acceptTypes: ["string"], minArgs: 0, maxArgs: 0 },
1273
- truncate: { description: "\u5207\u308A\u8A70\u3081\u3066\u7701\u7565\u8A18\u53F7 (length[,suffix])", hasArgs: true, resultType: "string", acceptTypes: ["string"], minArgs: 1, maxArgs: 2, argTypes: ["number", "string"] },
1274
- join: { description: "\u914D\u5217\u3092\u9023\u7D50 ([separator])", hasArgs: true, resultType: "string", acceptTypes: ["array"], minArgs: 0, maxArgs: 1, argTypes: ["string"] },
2122
+ truncate: { description: "\u5207\u308A\u8A70\u3081\u3066\u7701\u7565\u8A18\u53F7 (length[,suffix]\u3002suffix \u306E\u65E2\u5B9A\u306F \u2026 \u2014 U+2026 \u306E 1 \u6587\u5B57)", hasArgs: true, resultType: "string", acceptTypes: ["string"], minArgs: 1, maxArgs: 2, argTypes: ["number", "string"] },
2123
+ join: { description: "\u914D\u5217\u3092\u9023\u7D50 ([separator]\u3002\u65E2\u5B9A\u306F\u30AB\u30F3\u30DE + \u7A7A\u767D)", hasArgs: true, resultType: "string", acceptTypes: ["array"], minArgs: 0, maxArgs: 1, argTypes: ["string"] },
1275
2124
  // 数値パース・丸め
1276
2125
  int: { description: "\u6574\u6570\u306B\u30D1\u30FC\u30B9", hasArgs: false, resultType: "number", acceptTypes: ["string", "number"], minArgs: 0, maxArgs: 0 },
1277
2126
  float: { description: "\u6D6E\u52D5\u5C0F\u6570\u70B9\u6570\u306B\u30D1\u30FC\u30B9", hasArgs: false, resultType: "number", acceptTypes: ["string", "number"], minArgs: 0, maxArgs: 0 },
@@ -1283,117 +2132,70 @@ var builtinFilterMeta = {
1283
2132
  // それらは既に string を返すため(builtinFilters.ts の unit を参照)
1284
2133
  unit: { description: "\u5358\u4F4D\uFF08\u63A5\u5C3E\u8F9E\uFF09\u3092\u4ED8\u52A0", hasArgs: true, resultType: "string", acceptTypes: ["number", "string"], minArgs: 1, maxArgs: 1, argTypes: ["string"] },
1285
2134
  // 日付・時刻
1286
- date: { description: "\u30ED\u30B1\u30FC\u30EB\u5F62\u5F0F\u306E\u65E5\u4ED8", hasArgs: false, resultType: "string", acceptTypes: "any", minArgs: 0, maxArgs: 0 },
1287
- time: { description: "\u30ED\u30B1\u30FC\u30EB\u5F62\u5F0F\u306E\u6642\u523B", hasArgs: false, resultType: "string", acceptTypes: "any", minArgs: 0, maxArgs: 0 },
1288
- datetime: { description: "\u30ED\u30B1\u30FC\u30EB\u5F62\u5F0F\u306E\u65E5\u6642", hasArgs: false, resultType: "string", acceptTypes: "any", minArgs: 0, maxArgs: 0 },
1289
- ymd: { description: "YYYY-MM-DD \u5F62\u5F0F", hasArgs: true, resultType: "string", acceptTypes: "any", minArgs: 0, maxArgs: 1, argTypes: ["string"] },
1290
- hms: { description: "HH:MM:SS \u5F62\u5F0F", hasArgs: true, resultType: "string", acceptTypes: "any", minArgs: 0, maxArgs: 1, argTypes: ["string"] },
2135
+ // `locale` と同じくロケールを 1 つ受ける(`date(ja-JP)`)。実装は最初からこれを読んでいたが、
2136
+ // メタデータ側が maxArgs: 0 だったため lint と補完が正しい書き方を誤りとして報告していた
2137
+ date: { description: "\u30ED\u30B1\u30FC\u30EB\u5F62\u5F0F\u306E\u65E5\u4ED8 ([locale]\u3002\u65E2\u5B9A\u306F config.locale)", hasArgs: true, resultType: "string", acceptTypes: "any", minArgs: 0, maxArgs: 1, argTypes: ["string"] },
2138
+ time: { description: "\u30ED\u30B1\u30FC\u30EB\u5F62\u5F0F\u306E\u6642\u523B ([locale]\u3002\u65E2\u5B9A\u306F config.locale)", hasArgs: true, resultType: "string", acceptTypes: "any", minArgs: 0, maxArgs: 1, argTypes: ["string"] },
2139
+ datetime: { description: "\u30ED\u30B1\u30FC\u30EB\u5F62\u5F0F\u306E\u65E5\u6642 ([locale]\u3002\u65E2\u5B9A\u306F config.locale)", hasArgs: true, resultType: "string", acceptTypes: "any", minArgs: 0, maxArgs: 1, argTypes: ["string"] },
2140
+ ymd: { description: "YYYY-MM-DD \u5F62\u5F0F ([separator]\u3002\u65E2\u5B9A\u306F -)", hasArgs: true, resultType: "string", acceptTypes: "any", minArgs: 0, maxArgs: 1, argTypes: ["string"] },
2141
+ hms: { description: "HH:MM:SS \u5F62\u5F0F ([separator]\u3002\u65E2\u5B9A\u306F :)", hasArgs: true, resultType: "string", acceptTypes: "any", minArgs: 0, maxArgs: 1, argTypes: ["string"] },
1291
2142
  // 真偽値・変換
1292
2143
  falsy: { description: "\u507D\u5024\u304B\u5224\u5B9A", hasArgs: false, resultType: "boolean", acceptTypes: "any", minArgs: 0, maxArgs: 0 },
1293
2144
  truthy: { description: "\u771F\u5024\u304B\u5224\u5B9A", hasArgs: false, resultType: "boolean", acceptTypes: "any", minArgs: 0, maxArgs: 0 },
1294
2145
  defaults: { description: "\u507D\u5024\u306E\u5834\u5408\u30C7\u30D5\u30A9\u30EB\u30C8\u5024", hasArgs: true, resultType: "passthrough", acceptTypes: "any", minArgs: 1, maxArgs: 1, argTypes: ["any"] },
1295
2146
  coalesce: { description: "null / undefined \u306E\u5834\u5408\u30C7\u30D5\u30A9\u30EB\u30C8\u5024", hasArgs: true, resultType: "passthrough", acceptTypes: "any", minArgs: 1, maxArgs: 1, argTypes: ["any"] },
1296
- boolean: { description: "\u30D6\u30FC\u30EB\u5024\u306B\u5909\u63DB", hasArgs: false, resultType: "boolean", acceptTypes: "any", minArgs: 0, maxArgs: 0 },
1297
- number: { description: "\u6570\u5024\u306B\u5909\u63DB", hasArgs: false, resultType: "number", acceptTypes: "any", minArgs: 0, maxArgs: 0 },
1298
- string: { description: "\u6587\u5B57\u5217\u306B\u5909\u63DB", hasArgs: false, resultType: "string", acceptTypes: "any", minArgs: 0, maxArgs: 0 },
1299
- nullIfEmpty: { description: "\u7A7A\u6587\u5B57\u5217\u3092null\u306B\u5909\u63DB", hasArgs: false, resultType: "passthrough", acceptTypes: ["string"], minArgs: 0, maxArgs: 0 }
1300
- };
1301
- var STRUCTURAL_BINDING_TYPE_SET = /* @__PURE__ */ new Set([
1302
- "if",
1303
- "elseif",
1304
- "else",
1305
- "for"
1306
- ]);
1307
- var DELIMITER = ".";
1308
- var WILDCARD = "*";
1309
- var MAX_WILDCARD_DEPTH = 128;
1310
- var BINDING_SEPARATOR = ";";
1311
- var PROP_VALUE_SEPARATOR = ":";
1312
- var MODIFIER_SEPARATOR = "#";
1313
- var FILTER_SEPARATOR = "|";
1314
- var MODIFIER_PREVENT = "prevent";
1315
- var MODIFIER_STOP = "stop";
1316
- var MODIFIER_READONLY = "ro";
1317
- var MODIFIER_FLAGS = Object.freeze([
1318
- MODIFIER_PREVENT,
1319
- MODIFIER_STOP,
1320
- MODIFIER_READONLY
1321
- ]);
1322
- var MODIFIER_KEY_INIT = "init";
1323
- var MODIFIER_KEY_SYNC = "sync";
1324
- var MODIFIER_KEYS = Object.freeze([
1325
- MODIFIER_KEY_INIT,
1326
- MODIFIER_KEY_SYNC
1327
- ]);
1328
- var ELSE_KEYWORD = "else";
1329
- var SPREAD_PROP = "...";
1330
- var EVENT_PROP_PREFIX = "on";
1331
- var EVENT_TOKEN_NAMESPACE = "eventToken";
1332
- var COMMAND_NAMESPACE = "command";
1333
- var CLASS_NAMESPACE = "class";
1334
- var ATTR_NAMESPACE = "attr";
1335
- var STYLE_NAMESPACE = "style";
1336
- var INDEX_PARAM_PREFIX = "$";
1337
- var tmpIndexByIndexName = {};
1338
- for (let i = 0; i < MAX_WILDCARD_DEPTH; i++) {
1339
- tmpIndexByIndexName[`${INDEX_PARAM_PREFIX}${i + 1}`] = i;
1340
- }
1341
- Object.freeze(tmpIndexByIndexName);
1342
- var STATE_CONNECTED_CALLBACK_NAME = "$connectedCallback";
1343
- var STATE_DISCONNECTED_CALLBACK_NAME = "$disconnectedCallback";
1344
- var STATE_UPDATED_CALLBACK_NAME = "$renderedCallback";
1345
- var STATE_ERROR_CALLBACK_NAME = "$errorCallback";
1346
- var WEBCOMPONENT_STATE_READY_CALLBACK_NAME = "$stateReadyCallback";
1347
- var STATE_BINDABLES_NAME = "$bindables";
1348
- var STATE_COMMANDS_NAME = "$commands";
1349
- var STATE_COMMAND_TOKENS_NAME = "$commandTokens";
1350
- var STATE_COMMAND_NAMESPACE_NAME = "$command";
1351
- var STATE_EVENT_TOKENS_NAME = "$eventTokens";
1352
- var STATE_ON_NAME = "$on";
1353
- var STATE_STREAMS_NAME = "$stream";
1354
- var STATE_WATCH_NAME = "$watch";
1355
- var STATE_SCAN_NAME = "$scan";
1356
- var STATE_RECURSION_NAME = "$recursion";
1357
- var STATE_LIST_KEYS_NAME = "$listKeys";
1358
- var STATE_STREAM_STATUS_NAMESPACE_NAME = "$streamStatus";
1359
- var STATE_STREAM_ERROR_NAMESPACE_NAME = "$streamError";
1360
- var WCS_MANIFEST_VERSION = 1;
2147
+ boolean: { description: "\u30D6\u30FC\u30EB\u5024\u306B\u5909\u63DB", hasArgs: false, resultType: "boolean", acceptTypes: "any", minArgs: 0, maxArgs: 0 },
2148
+ number: { description: "\u6570\u5024\u306B\u5909\u63DB", hasArgs: false, resultType: "number", acceptTypes: "any", minArgs: 0, maxArgs: 0 },
2149
+ string: { description: "\u6587\u5B57\u5217\u306B\u5909\u63DB", hasArgs: false, resultType: "string", acceptTypes: "any", minArgs: 0, maxArgs: 0 },
2150
+ nullIfEmpty: { description: "\u7A7A\u6587\u5B57\u5217\u3092null\u306B\u5909\u63DB", hasArgs: false, resultType: "passthrough", acceptTypes: ["string"], minArgs: 0, maxArgs: 0 }
2151
+ };
2152
+ var STRUCTURAL_BINDING_TYPE_SET2 = /* @__PURE__ */ new Set([
2153
+ "if",
2154
+ "elseif",
2155
+ "else",
2156
+ "for"
2157
+ ]);
2158
+ var WCS_MANIFEST_VERSION = 2;
2159
+ var STATE_API_ALIASES = Object.freeze({
2160
+ $trackDependency: "$dependOn",
2161
+ $untrackDependency: "$untracked"
2162
+ });
1361
2163
  function getWcsManifest() {
1362
2164
  return {
1363
2165
  version: WCS_MANIFEST_VERSION,
1364
2166
  syntax: {
1365
2167
  bindAttribute: config.bindAttributeName,
1366
2168
  tagName: config.tagNames.state,
1367
- pathDelimiter: DELIMITER,
1368
- wildcard: WILDCARD,
2169
+ pathDelimiter: DELIMITER2,
2170
+ wildcard: WILDCARD2,
1369
2171
  delimiters: {
1370
- binding: BINDING_SEPARATOR,
1371
- propValue: PROP_VALUE_SEPARATOR,
1372
- modifier: MODIFIER_SEPARATOR,
1373
- filter: FILTER_SEPARATOR
2172
+ binding: BINDING_SEPARATOR2,
2173
+ propValue: PROP_VALUE_SEPARATOR2,
2174
+ modifier: MODIFIER_SEPARATOR2,
2175
+ filter: FILTER_SEPARATOR2
1374
2176
  },
1375
2177
  // 正本 STRUCTURAL_BINDING_TYPE_SET から導出(手書きの二重定義を排除)。
1376
- structuralDirectives: Array.from(STRUCTURAL_BINDING_TYPE_SET),
2178
+ structuralDirectives: Array.from(STRUCTURAL_BINDING_TYPE_SET2),
1377
2179
  modifiers: {
1378
2180
  flags: MODIFIER_FLAGS,
1379
2181
  keyValue: MODIFIER_KEYS,
1380
- eventNamePrefix: EVENT_PROP_PREFIX
2182
+ eventNamePrefix: EVENT_PROP_PREFIX2
1381
2183
  },
1382
2184
  indexParam: {
1383
- prefix: INDEX_PARAM_PREFIX,
1384
- maxDepth: MAX_WILDCARD_DEPTH
2185
+ prefix: INDEX_PARAM_PREFIX2,
2186
+ maxDepth: MAX_WILDCARD_DEPTH2
1385
2187
  },
1386
2188
  bindingTypes: {
1387
- elseKeyword: ELSE_KEYWORD,
1388
- spread: SPREAD_PROP,
1389
- eventPropertyPrefix: EVENT_PROP_PREFIX,
1390
- explicitPropertyPrefix: DELIMITER,
2189
+ elseKeyword: ELSE_KEYWORD2,
2190
+ spread: SPREAD_PROP2,
2191
+ eventPropertyPrefix: EVENT_PROP_PREFIX2,
2192
+ explicitPropertyPrefix: DELIMITER2,
1391
2193
  propNamespaces: {
1392
- eventToken: EVENT_TOKEN_NAMESPACE,
1393
- command: COMMAND_NAMESPACE,
1394
- class: CLASS_NAMESPACE,
1395
- attr: ATTR_NAMESPACE,
1396
- style: STYLE_NAMESPACE
2194
+ eventToken: EVENT_TOKEN_NAMESPACE2,
2195
+ command: COMMAND_NAMESPACE2,
2196
+ class: CLASS_NAMESPACE2,
2197
+ attr: ATTR_NAMESPACE2,
2198
+ style: STYLE_NAMESPACE2
1397
2199
  }
1398
2200
  }
1399
2201
  },
@@ -1401,10 +2203,12 @@ function getWcsManifest() {
1401
2203
  filters: Object.keys(outputBuiltinFilters),
1402
2204
  filterMeta: builtinFilterMeta,
1403
2205
  filterAliases: builtinFilterAliases,
2206
+ declarationAliases: DECLARATION_ALIASES,
2207
+ apiAliases: STATE_API_ALIASES,
1404
2208
  reservedLifecycle: [
1405
2209
  STATE_CONNECTED_CALLBACK_NAME,
1406
2210
  STATE_DISCONNECTED_CALLBACK_NAME,
1407
- STATE_UPDATED_CALLBACK_NAME,
2211
+ STATE_RENDERED_CALLBACK_NAME,
1408
2212
  STATE_ERROR_CALLBACK_NAME,
1409
2213
  WEBCOMPONENT_STATE_READY_CALLBACK_NAME
1410
2214
  ],
@@ -1415,7 +2219,7 @@ function getWcsManifest() {
1415
2219
  STATE_COMMAND_NAMESPACE_NAME,
1416
2220
  STATE_EVENT_TOKENS_NAME,
1417
2221
  STATE_ON_NAME,
1418
- STATE_STREAMS_NAME,
2222
+ STATE_STREAM_NAME,
1419
2223
  STATE_WATCH_NAME,
1420
2224
  STATE_SCAN_NAME,
1421
2225
  STATE_LIST_KEYS_NAME,
@@ -1439,16 +2243,16 @@ var STRUCTURAL_DIRECTIVE_INFO = {
1439
2243
  elseif: { description: "else-if \u6761\u4EF6 (<template>)", insertColon: true },
1440
2244
  else: { description: "else \u30D6\u30ED\u30C3\u30AF (<template>)", insertColon: false }
1441
2245
  };
1442
- var STRUCTURAL_DIRECTIVES = [...STRUCTURAL_BINDING_TYPE_SET].map((name) => ({
2246
+ var STRUCTURAL_DIRECTIVES = [...STRUCTURAL_BINDING_TYPE_SET2].map((name) => ({
1443
2247
  name,
1444
2248
  ...STRUCTURAL_DIRECTIVE_INFO[name]
1445
2249
  }));
1446
2250
 
1447
2251
  // src/service/recursionPaths.ts
1448
- var RECURSION_WILDCARD = "**";
2252
+ var RECURSION_WILDCARD2 = "**";
1449
2253
  var RECURSION_KEY = "$recursion";
1450
2254
  function hasRecursionWildcard(path) {
1451
- return path.indexOf(RECURSION_WILDCARD) !== -1;
2255
+ return path.indexOf(RECURSION_WILDCARD2) !== -1;
1452
2256
  }
1453
2257
  function checkNodePath(path) {
1454
2258
  if (typeof path !== "string" || path.length === 0) return "empty";
@@ -1459,7 +2263,7 @@ function checkNodePath(path) {
1459
2263
  if (path.indexOf("#") !== -1) return "reservedMount";
1460
2264
  for (let i = 0; i < segments.length - 1; i++) {
1461
2265
  if (segments[i] === "*") return "midWildcard";
1462
- if (segments[i] === RECURSION_WILDCARD) return "nestedRecursion";
2266
+ if (segments[i] === RECURSION_WILDCARD2) return "nestedRecursion";
1463
2267
  if (!isNaN(Number(segments[i]))) return "indexSegment";
1464
2268
  }
1465
2269
  return null;
@@ -1468,7 +2272,7 @@ function makeRecursionSpec(anchor, repeat) {
1468
2272
  return Object.freeze({
1469
2273
  anchor,
1470
2274
  repeat,
1471
- recursiveAnchor: anchor.slice(0, anchor.lastIndexOf(".")) + "." + RECURSION_WILDCARD,
2275
+ recursiveAnchor: anchor.slice(0, anchor.lastIndexOf(".")) + "." + RECURSION_WILDCARD2,
1472
2276
  anchorList: anchor.slice(0, anchor.lastIndexOf(".")),
1473
2277
  repeatList: repeat.slice(0, repeat.lastIndexOf("."))
1474
2278
  });
@@ -1544,8 +2348,8 @@ function collectRecursionSpecs(candidates) {
1544
2348
  const out = [];
1545
2349
  for (const candidate of candidates) {
1546
2350
  if (candidate.kind !== "recursionAnchor" || typeof candidate.repeat !== "string") continue;
1547
- if (!candidate.path.endsWith("." + RECURSION_WILDCARD)) continue;
1548
- const anchor = candidate.path.slice(0, candidate.path.length - RECURSION_WILDCARD.length) + "*";
2351
+ if (!candidate.path.endsWith("." + RECURSION_WILDCARD2)) continue;
2352
+ const anchor = candidate.path.slice(0, candidate.path.length - RECURSION_WILDCARD2.length) + "*";
1549
2353
  if (out.some((spec) => spec.anchor === anchor && spec.repeat === candidate.repeat)) continue;
1550
2354
  out.push(makeRecursionSpec(anchor, candidate.repeat));
1551
2355
  }
@@ -1725,6 +2529,26 @@ function analyzeWatchEntries(scriptContent) {
1725
2529
  definitelyNotFunction: entry.kind === "data" && isNonFunctionLiteral(entry.value)
1726
2530
  }));
1727
2531
  }
2532
+ function analyzeWatchHandlerSources(scriptContent) {
2533
+ const root = locateDefaultExportObject(scriptContent);
2534
+ if (!root) return [];
2535
+ const prop = parseTopLevelProperties(root.content).find((p) => p.name === RESERVED_WATCH_KEY);
2536
+ if (!prop || prop.kind !== "data" || !prop.value || !isObjectLiteral(prop.value) || prop.valueStart === void 0) {
2537
+ return [];
2538
+ }
2539
+ const leading = prop.value.length - prop.value.trimStart().length;
2540
+ const innerStart = root.start + prop.valueStart + leading + 1;
2541
+ const out = [];
2542
+ for (const entry of parseTopLevelProperties(extractObjectContent(prop.value))) {
2543
+ if (entry.value === void 0 || entry.valueStart === void 0) continue;
2544
+ out.push({
2545
+ kind: entry.kind === "data" ? "value" : "body",
2546
+ text: entry.value,
2547
+ start: innerStart + entry.valueStart
2548
+ });
2549
+ }
2550
+ return out;
2551
+ }
1728
2552
  function analyzeListKeyEntries(scriptContent) {
1729
2553
  return analyzeObjectEntries(scriptContent, RESERVED_LIST_KEYS_KEY).map((entry) => ({ key: entry.key, start: entry.start, end: entry.end }));
1730
2554
  }
@@ -2591,271 +3415,42 @@ function pushSchemaValuePaths(path, node, paths, defs, depth) {
2591
3415
  const typeHint = schemaTypeHint(nodes);
2592
3416
  paths.push(withHint({ path, kind: "data", fromSchema: true }, typeHint));
2593
3417
  const items = nodes.map((n) => n.items).find((i) => i !== void 0 && i !== null && typeof i === "object");
2594
- const isArray2 = items !== void 0 || (typeHint?.split("|").includes("array") ?? false);
2595
- if (isArray2) {
2596
- const itemNodes = items !== void 0 ? derefSchemaNodes(items, defs) : [];
2597
- paths.push(withHint({ path: `${path}.*`, kind: "list", fromSchema: true }, schemaTypeHint(itemNodes)));
2598
- paths.push({ path: `${path}.length`, kind: "data", typeHint: "number", fromSchema: true });
2599
- if (depth >= MAX_OBJECT_NEST_DEPTH) return;
2600
- const seen = /* @__PURE__ */ new Set();
2601
- for (const n of itemNodes) {
2602
- for (const [childKey, childNode] of Object.entries(n.properties ?? {})) {
2603
- if (seen.has(childKey)) continue;
2604
- seen.add(childKey);
2605
- pushSchemaValuePaths(`${path}.*.${childKey}`, childNode, paths, defs, depth + 1);
2606
- }
2607
- }
2608
- return;
2609
- }
2610
- if (nodes.some((n) => n.properties !== void 0)) {
2611
- collectSchemaObjectPaths(node, path, paths, defs, depth + 1);
2612
- }
2613
- }
2614
- function withHint(candidate, typeHint) {
2615
- return typeHint === void 0 ? candidate : { ...candidate, typeHint };
2616
- }
2617
-
2618
- // src/language/htmlParse.ts
2619
- function parseWcsScriptBlocks(html, stateTagName = "wcs-state") {
2620
- const blocks = [];
2621
- let pos = 0;
2622
- const len = html.length;
2623
- while (pos < len) {
2624
- if (html.startsWith("<!--", pos)) {
2625
- const commentEnd = html.indexOf("-->", pos + 4);
2626
- if (commentEnd === -1) break;
2627
- pos = commentEnd + 3;
2628
- continue;
2629
- }
2630
- const wcsMatch = matchOpenTag(html, pos, stateTagName);
2631
- if (wcsMatch === null) {
2632
- pos++;
2633
- continue;
2634
- }
2635
- const mountPath = extractAttribute(wcsMatch.tagContent, "mount");
2636
- pos = wcsMatch.end;
2637
- const wcsCloseIdx = findCloseTag(html, pos, stateTagName);
2638
- const wcsEnd = wcsCloseIdx === -1 ? len : wcsCloseIdx;
2639
- while (pos < wcsEnd) {
2640
- if (html.startsWith("<!--", pos)) {
2641
- const commentEnd = html.indexOf("-->", pos + 4);
2642
- if (commentEnd === -1) break;
2643
- pos = commentEnd + 3;
2644
- continue;
2645
- }
2646
- const scriptMatch = matchOpenTag(html, pos, "script");
2647
- if (scriptMatch === null) {
2648
- pos++;
2649
- continue;
2650
- }
2651
- const typeAttr = extractAttribute(scriptMatch.tagContent, "type");
2652
- if (typeAttr?.toLowerCase() !== "module") {
2653
- pos = scriptMatch.end;
2654
- continue;
2655
- }
2656
- const contentStart = scriptMatch.end;
2657
- const scriptCloseIdx = findCloseTag(html, contentStart, "script");
2658
- if (scriptCloseIdx === -1) {
2659
- pos = contentStart;
2660
- break;
2661
- }
2662
- const contentEnd = scriptCloseIdx;
2663
- blocks.push({
2664
- contentStart,
2665
- contentEnd,
2666
- content: html.slice(contentStart, contentEnd),
2667
- mountPath
2668
- });
2669
- pos = html.indexOf(">", scriptCloseIdx) + 1;
2670
- if (pos === 0) break;
2671
- }
2672
- pos = wcsEnd;
2673
- if (wcsCloseIdx !== -1) {
2674
- const closeEnd = html.indexOf(">", wcsCloseIdx);
2675
- if (closeEnd !== -1) pos = closeEnd + 1;
2676
- }
2677
- }
2678
- return blocks;
2679
- }
2680
- function parseWcsStateElements(html, stateTagName = "wcs-state") {
2681
- const elements = [];
2682
- let pos = 0;
2683
- const len = html.length;
2684
- while (pos < len) {
2685
- if (html.startsWith("<!--", pos)) {
2686
- const commentEnd = html.indexOf("-->", pos + 4);
2687
- if (commentEnd === -1) break;
2688
- pos = commentEnd + 3;
2689
- continue;
2690
- }
2691
- const wcsMatch = matchOpenTag(html, pos, stateTagName);
2692
- if (wcsMatch === null) {
2693
- pos++;
2694
- continue;
2695
- }
2696
- const mountPath = extractAttribute(wcsMatch.tagContent, "mount");
2697
- const bindComponent = parseAttributeNames(wcsMatch.tagContent).has("bind-component");
2698
- const jsonAttr = extractAttribute(wcsMatch.tagContent, "json") ?? void 0;
2699
- const stateAttr = extractAttribute(wcsMatch.tagContent, "state") ?? void 0;
2700
- const srcAttr = extractAttribute(wcsMatch.tagContent, "src") ?? void 0;
2701
- const tagStart = pos;
2702
- const tagEnd = wcsMatch.end;
2703
- pos = wcsMatch.end;
2704
- const scriptBlocks = [];
2705
- const wcsCloseIdx = findCloseTag(html, pos, stateTagName);
2706
- const wcsEnd = wcsCloseIdx === -1 ? len : wcsCloseIdx;
2707
- while (pos < wcsEnd) {
2708
- if (html.startsWith("<!--", pos)) {
2709
- const commentEnd = html.indexOf("-->", pos + 4);
2710
- if (commentEnd === -1) break;
2711
- pos = commentEnd + 3;
2712
- continue;
2713
- }
2714
- const scriptMatch = matchOpenTag(html, pos, "script");
2715
- if (scriptMatch === null) {
2716
- pos++;
2717
- continue;
2718
- }
2719
- const typeAttr = extractAttribute(scriptMatch.tagContent, "type");
2720
- if (typeAttr?.toLowerCase() !== "module") {
2721
- pos = scriptMatch.end;
2722
- continue;
2723
- }
2724
- const contentStart = scriptMatch.end;
2725
- const scriptCloseIdx = findCloseTag(html, contentStart, "script");
2726
- if (scriptCloseIdx === -1) {
2727
- pos = contentStart;
2728
- break;
2729
- }
2730
- scriptBlocks.push({
2731
- contentStart,
2732
- contentEnd: scriptCloseIdx,
2733
- content: html.slice(contentStart, scriptCloseIdx),
2734
- mountPath
2735
- });
2736
- pos = html.indexOf(">", scriptCloseIdx) + 1;
2737
- if (pos === 0) break;
2738
- }
2739
- elements.push({ mountPath, bindComponent, jsonAttr, stateAttr, srcAttr, scriptBlocks, tagStart, tagEnd });
2740
- pos = wcsEnd;
2741
- if (wcsCloseIdx !== -1) {
2742
- const closeEnd = html.indexOf(">", wcsCloseIdx);
2743
- if (closeEnd !== -1) pos = closeEnd + 1;
2744
- }
2745
- }
2746
- return elements;
2747
- }
2748
- function findScriptJsonById(html, id2) {
2749
- let pos = 0;
2750
- const len = html.length;
2751
- while (pos < len) {
2752
- if (html.startsWith("<!--", pos)) {
2753
- const commentEnd = html.indexOf("-->", pos + 4);
2754
- if (commentEnd === -1) break;
2755
- pos = commentEnd + 3;
2756
- continue;
2757
- }
2758
- const scriptMatch = matchOpenTag(html, pos, "script");
2759
- if (scriptMatch === null) {
2760
- pos++;
2761
- continue;
2762
- }
2763
- const typeAttr = extractAttribute(scriptMatch.tagContent, "type");
2764
- const idAttr = extractAttribute(scriptMatch.tagContent, "id");
2765
- if (typeAttr?.toLowerCase() === "application/json" && idAttr === id2) {
2766
- const contentStart = scriptMatch.end;
2767
- const scriptCloseIdx = findCloseTag(html, contentStart, "script");
2768
- if (scriptCloseIdx === -1) return null;
2769
- return html.slice(contentStart, scriptCloseIdx);
2770
- }
2771
- pos = scriptMatch.end;
2772
- }
2773
- return null;
2774
- }
2775
- function matchOpenTag(html, pos, tagName) {
2776
- if (html[pos] !== "<") return null;
2777
- const nameStart = pos + 1;
2778
- const nameEnd = nameStart + tagName.length;
2779
- if (nameEnd > html.length) return null;
2780
- const slice2 = html.slice(nameStart, nameEnd);
2781
- if (slice2.toLowerCase() !== tagName.toLowerCase()) return null;
2782
- const charAfter = html[nameEnd];
2783
- if (charAfter !== ">" && charAfter !== " " && charAfter !== " " && charAfter !== "\n" && charAfter !== "\r" && charAfter !== "/") {
2784
- return null;
2785
- }
2786
- let i = nameEnd;
2787
- let inSingleQuote = false;
2788
- let inDoubleQuote = false;
2789
- while (i < html.length) {
2790
- const ch = html[i];
2791
- if (inSingleQuote) {
2792
- if (ch === "'") inSingleQuote = false;
2793
- } else if (inDoubleQuote) {
2794
- if (ch === '"') inDoubleQuote = false;
2795
- } else if (ch === "'") {
2796
- inSingleQuote = true;
2797
- } else if (ch === '"') {
2798
- inDoubleQuote = true;
2799
- } else if (ch === ">") {
2800
- return {
2801
- start: pos,
2802
- end: i + 1,
2803
- tagContent: html.slice(nameEnd, i)
2804
- };
2805
- }
2806
- i++;
2807
- }
2808
- return null;
2809
- }
2810
- function findCloseTag(html, startPos, tagName) {
2811
- const pattern = "</" + tagName;
2812
- const patternLower = pattern.toLowerCase();
2813
- const htmlLower = html.toLowerCase();
2814
- let pos = startPos;
2815
- while (pos < html.length) {
2816
- const idx = htmlLower.indexOf(patternLower, pos);
2817
- if (idx === -1) return -1;
2818
- const afterIdx = idx + pattern.length;
2819
- if (afterIdx < html.length) {
2820
- const ch = html[afterIdx];
2821
- if (ch === ">" || ch === " " || ch === " " || ch === "\n" || ch === "\r") {
2822
- return idx;
3418
+ const isArray2 = items !== void 0 || (typeHint?.split("|").includes("array") ?? false);
3419
+ if (isArray2) {
3420
+ const itemNodes = items !== void 0 ? derefSchemaNodes(items, defs) : [];
3421
+ paths.push(withHint({ path: `${path}.*`, kind: "list", fromSchema: true }, schemaTypeHint(itemNodes)));
3422
+ paths.push({ path: `${path}.length`, kind: "data", typeHint: "number", fromSchema: true });
3423
+ if (depth >= MAX_OBJECT_NEST_DEPTH) return;
3424
+ const seen = /* @__PURE__ */ new Set();
3425
+ for (const n of itemNodes) {
3426
+ for (const [childKey, childNode] of Object.entries(n.properties ?? {})) {
3427
+ if (seen.has(childKey)) continue;
3428
+ seen.add(childKey);
3429
+ pushSchemaValuePaths(`${path}.*.${childKey}`, childNode, paths, defs, depth + 1);
2823
3430
  }
2824
3431
  }
2825
- pos = idx + 1;
3432
+ return;
2826
3433
  }
2827
- return -1;
2828
- }
2829
- function parseAttributeNames(tagContent) {
2830
- const names = /* @__PURE__ */ new Set();
2831
- const attribute = /([^\s"'<>/=]+)(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^\s"'<>`=]+))?/g;
2832
- let match;
2833
- while ((match = attribute.exec(tagContent)) !== null) {
2834
- names.add(match[1].toLowerCase());
3434
+ if (nodes.some((n) => n.properties !== void 0)) {
3435
+ collectSchemaObjectPaths(node, path, paths, defs, depth + 1);
2835
3436
  }
2836
- return names;
2837
3437
  }
2838
- function extractAttribute(tagContent, attrName) {
2839
- const regex = new RegExp(
2840
- `(?:^|\\s)${attrName}\\s*=\\s*(?:"([^"]*)"|'([^']*)'|(\\S+))`,
2841
- "i"
2842
- );
2843
- const match = tagContent.match(regex);
2844
- if (!match) return null;
2845
- return match[1] ?? match[2] ?? match[3] ?? null;
3438
+ function withHint(candidate, typeHint) {
3439
+ return typeHint === void 0 ? candidate : { ...candidate, typeHint };
2846
3440
  }
2847
3441
 
2848
3442
  // src/service/statePathResolver.ts
2849
3443
  function getStatePathsFromHtml(html, stateTagName = "wcs-state", fileReader) {
2850
3444
  const elements = parseWcsStateElements(html, stateTagName);
2851
3445
  const allPaths = [];
3446
+ const mountPoints = /* @__PURE__ */ new Set();
2852
3447
  for (const element of elements) {
2853
- const paths = resolveElementPaths(element, html, fileReader);
3448
+ const paths = resolveElementPaths(element, html, mountPoints, fileReader);
2854
3449
  allPaths.push(...paths);
2855
3450
  }
2856
3451
  return allPaths;
2857
3452
  }
2858
- function resolveElementPaths(element, html, fileReader) {
3453
+ function resolveElementPaths(element, html, mountPoints, fileReader) {
2859
3454
  const raw = resolveElementPathsRaw(element, html, fileReader);
2860
3455
  if (element.mountPath === null) return raw;
2861
3456
  const prefix = element.mountPath + ".";
@@ -2865,6 +3460,15 @@ function resolveElementPaths(element, html, fileReader) {
2865
3460
  if (p.kind === "method" || p.kind === "eventToken") continue;
2866
3461
  out.push({ ...p, path: prefix + p.path });
2867
3462
  }
3463
+ if (out.length > 0) {
3464
+ const segments = element.mountPath.split(".");
3465
+ for (let i = 1; i <= segments.length; i++) {
3466
+ const path = segments.slice(0, i).join(".");
3467
+ if (mountPoints.has(path)) continue;
3468
+ mountPoints.add(path);
3469
+ out.push({ path, kind: "data", typeHint: "object" });
3470
+ }
3471
+ }
2868
3472
  return out;
2869
3473
  }
2870
3474
  function resolveElementPathsRaw(element, html, fileReader) {
@@ -2976,7 +3580,7 @@ function countWildcardSegments(path) {
2976
3580
  }
2977
3581
  function forPathOf(raw) {
2978
3582
  let path = raw.trim();
2979
- const pipe = path.indexOf("|");
3583
+ const pipe = indexOfOutsideQuotes2(path, "|");
2980
3584
  if (pipe !== -1) path = path.slice(0, pipe).trim();
2981
3585
  const at2 = path.indexOf("@");
2982
3586
  if (at2 !== -1) path = path.slice(0, at2).trim();
@@ -3107,7 +3711,10 @@ var ja = {
3107
3711
  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`,
3108
3712
  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`,
3109
3713
  nameAlias: (written, canonical) => `"${written}" \u306F "${canonical}" \u306E\u65E7\u540D\u3067\u3059\u30023.x \u306E\u9593\u306F\u52D5\u304D\u307E\u3059\u304C 4.0 \u3067\u5916\u308C\u308B\u306E\u3067\u3001"${canonical}" \u3068\u66F8\u3044\u3066\u304F\u3060\u3055\u3044\uFF08@wcstack/state 3.2\uFF09`,
3110
- 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`,
3714
+ declarationAliasRead: (alias, canonical) => `"${alias}" \u306E\u8AAD\u307F\u51FA\u3057\u306F 3.x \u3067\u3082\u52D5\u304D\u307E\u305B\u3093\u3002"${alias}" \u306F "${canonical}" \u306E\u65E7\u540D\u3067\u3001\u30E9\u30F3\u30BF\u30A4\u30E0\u306F\u8AAD\u307F\u8FBC\u307F\u6642\u306B "${canonical}" \u3078\u5199\u3057\u3001\u65E7\u540D\u306E\u81EA\u524D\u30D7\u30ED\u30D1\u30C6\u30A3\u3092\u524A\u9664\u3059\u308B\u305F\u3081\u3001this["${alias}"] \u306F undefined \u306B\u306A\u308A\u307E\u3059\u3002"${canonical}" \u3092\u8AAD\u3093\u3067\u304F\u3060\u3055\u3044\uFF08@wcstack/state 3.2\uFF09`,
3715
+ declarationAliasReadUncertain: (alias, canonical) => `"${alias}" \u306E\u8AAD\u307F\u51FA\u3057\u306F\u65E7\u540D\u306E\u307E\u307E\u306B\u3067\u304D\u307E\u305B\u3093\u3002"${alias}" \u306F "${canonical}" \u306E\u65E7\u540D\u3067\u3001\u30E9\u30F3\u30BF\u30A4\u30E0\u306F\u8AAD\u307F\u8FBC\u307F\u6642\u306B "${canonical}" \u3078\u5199\u3057\u307E\u3059\u3002\u65E7\u540D\u304C\u81EA\u524D\u30D7\u30ED\u30D1\u30C6\u30A3\u306A\u3089\u524A\u9664\u3055\u308C\u308B\u306E\u3067 this["${alias}"] \u306F undefined \u306B\u306A\u308A\u3001class \u306E\u30D7\u30ED\u30C8\u30BF\u30A4\u30D7\u306B\u7F6E\u3044\u305F\u30E1\u30BD\u30C3\u30C9 / \u30A2\u30AF\u30BB\u30B5\u306A\u3089\u4ECA\u306F\u8AAD\u3081\u307E\u3059\u304C 4.0 \u3067\u5916\u308C\u307E\u3059\u3002\u3069\u3061\u3089\u306E\u5F62\u3067\u3082 "${canonical}" \u3092\u8AAD\u3093\u3067\u304F\u3060\u3055\u3044\uFF08@wcstack/state 3.2\uFF09`,
3716
+ declarationAlias: (alias, canonical) => `\u3053\u306E state \u306F "${alias}" \u3068 "${canonical}" \u3092\u4E21\u65B9\u5BA3\u8A00\u3057\u3066\u3044\u307E\u3059\u3002"${alias}" \u306F "${canonical}" \u306E\u65E7\u540D\uFF083.x \u306E\u9593\u306F\u52D5\u304D\u307E\u3059\uFF09\u306A\u306E\u3067\u3001"${canonical}" \u3060\u3051\u3092\u6B8B\u3057\u3066\u304F\u3060\u3055\u3044\uFF08\u30E9\u30F3\u30BF\u30A4\u30E0\u306F\u8AAD\u307F\u8FBC\u307F\u6642\u306B throw \u3057\u307E\u3059\uFF09`,
3717
+ onPrefixedMember: (member, tag, modifiers) => `"${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}${modifiers ? `#${modifiers}` : ""}:" \u3068\u66F8\u3044\u3066\u304F\u3060\u3055\u3044\uFF08@wcstack/state 3.1\uFF09`,
3111
3718
  tagCommandUnknown: (name, tag, declared) => `"${name}" \u306F <${tag}> \u306E command \u3067\u306F\u3042\u308A\u307E\u305B\u3093\uFF08\u5BA3\u8A00\u6E08\u307F: ${declared}\uFF09`,
3112
3719
  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`,
3113
3720
  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`,
@@ -3145,6 +3752,8 @@ var ja = {
3145
3752
  return `this["${p}"] \u3078\u306E\u4EE3\u5165\u306B "**" \u306F\u4F7F\u3048\u307E\u305B\u3093\uFF08\u518D\u5E30 setter \u306F\u521D\u7248\u3067\u306F\u6301\u3066\u305A\u3001\u4EE3\u5165\u306F\u5C55\u958B\u5F8C\u306E\u5177\u4F53\u30D1\u30B9\u306B\u3057\u304B\u6210\u7ACB\u3057\u307E\u305B\u3093\uFF09\u3002$setAll("${p}", [], value) \u3067\u5168\u6DF1\u3055\u3078\u30D6\u30ED\u30FC\u30C9\u30AD\u30E3\u30B9\u30C8\u3059\u308B\u304B\u3001\u5177\u4F53\u30D1\u30B9\u3078\u66F8\u3044\u3066\u304F\u3060\u3055\u3044`;
3146
3753
  case "postUpdate":
3147
3754
  return `$postUpdate("${p}") \u306B "**" \u306F\u6E21\u305B\u307E\u305B\u3093\u3002\u901A\u77E5\u306F\u5C55\u958B\u5F8C\u306E\u5177\u4F53\u30D1\u30B9\uFF08\u56FA\u5B9A\u672C\u6570\u306E "*"\uFF09\u306B\u5BFE\u3057\u3066\u306E\u307F\u6210\u7ACB\u3057\u307E\u3059`;
3755
+ case "dependOn":
3756
+ return `$dependOn("${p}") \u306B "**" \u306F\u6E21\u305B\u307E\u305B\u3093\u3002\u4F9D\u5B58\u306E\u767B\u9332\u306F\u5C55\u958B\u5F8C\u306E\u5177\u4F53\u30D1\u30B9\uFF08\u56FA\u5B9A\u672C\u6570\u306E "*"\uFF09\u306B\u5BFE\u3057\u3066\u306E\u307F\u6210\u7ACB\u3057\u307E\u3059`;
3148
3757
  case "trackDependency":
3149
3758
  return `$trackDependency("${p}") \u306B "**" \u306F\u6E21\u305B\u307E\u305B\u3093\u3002\u4F9D\u5B58\u306E\u767B\u9332\u306F\u5C55\u958B\u5F8C\u306E\u5177\u4F53\u30D1\u30B9\uFF08\u56FA\u5B9A\u672C\u6570\u306E "*"\uFF09\u306B\u5BFE\u3057\u3066\u306E\u307F\u6210\u7ACB\u3057\u307E\u3059`;
3150
3759
  case "listKeys":
@@ -3298,7 +3907,10 @@ var en = {
3298
3907
  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.`,
3299
3908
  tagMemberUnknown: (prop, tag) => `"${prop}" is not a wcBindable member of <${tag}> (bindings to unknown members are silently ignored)`,
3300
3909
  nameAlias: (written, canonical) => `"${written}" is the old name of "${canonical}". It works through 3.x and goes in 4.0 \u2014 write "${canonical}" (@wcstack/state 3.2)`,
3301
- 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)`,
3910
+ declarationAliasRead: (alias, canonical) => `Reading "${alias}" does not work, not even in 3.x. "${alias}" is the old name of "${canonical}": the runtime maps it to "${canonical}" at load time and deletes the old own property, so this["${alias}"] is undefined. Read "${canonical}" instead (@wcstack/state 3.2)`,
3911
+ declarationAliasReadUncertain: (alias, canonical) => `Reading "${alias}" cannot stay on the old name. "${alias}" is the old name of "${canonical}": the runtime maps it to "${canonical}" at load time. If the old name is an own property it is deleted, so this["${alias}"] is undefined; if it sits on a class prototype (a method or accessor) it still reads today but goes in 4.0. Either way, read "${canonical}" (@wcstack/state 3.2)`,
3912
+ declarationAlias: (alias, canonical) => `The state declares both "${alias}" and "${canonical}". "${alias}" is the old name of "${canonical}" (it works through 3.x) \u2014 keep "${canonical}" (the runtime throws at load time)`,
3913
+ onPrefixedMember: (member, tag, modifiers) => `"${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}${modifiers ? `#${modifiers}` : ""}:" to bind the property (@wcstack/state 3.1)`,
3302
3914
  tagCommandUnknown: (name, tag, declared) => `"${name}" is not a command of <${tag}> (declared: ${declared})`,
3303
3915
  spreadNoBindable: (tag) => `'...' (spread) requires <${tag}> to expose a valid wcBindable declaration \u2014 this tag declares none, so the runtime raises an error`,
3304
3916
  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})`,
@@ -3336,6 +3948,8 @@ var en = {
3336
3948
  return `this["${p}"] = \u2026 cannot use "**" (there are no recursive setters in this version; an assignment only resolves against an expanded concrete path). Broadcast with $setAll("${p}", [], value), or write a concrete path`;
3337
3949
  case "postUpdate":
3338
3950
  return `$postUpdate("${p}") cannot take "**" \u2014 a notification is defined against a concrete path (a fixed number of "*")`;
3951
+ case "dependOn":
3952
+ return `$dependOn("${p}") cannot take "**" \u2014 a dependency is registered against a concrete path (a fixed number of "*")`;
3339
3953
  case "trackDependency":
3340
3954
  return `$trackDependency("${p}") cannot take "**" \u2014 a dependency is registered against a concrete path (a fixed number of "*")`;
3341
3955
  case "listKeys":
@@ -3437,9 +4051,9 @@ function validateBindings(html, attrName, stateTagName = "wcs-state", locale2, f
3437
4051
  if (nonEmptyCount > 1) {
3438
4052
  let scanPos = 0;
3439
4053
  for (const b of bindings) {
3440
- const colon = b.indexOf(":");
4054
+ const colon = indexOfOutsideQuotes2(b, ":");
3441
4055
  const prop = (colon === -1 ? b : b.slice(0, colon)).trim();
3442
- if (STRUCTURAL_BINDING_TYPE_SET.has(prop)) {
4056
+ if (STRUCTURAL_BINDING_TYPE_SET2.has(prop)) {
3443
4057
  const leading = b.length - b.trimStart().length;
3444
4058
  diagnostics.push({
3445
4059
  code: WcsDiagnosticCode.TemplateSyntax,
@@ -3648,778 +4262,345 @@ function validateBindings(html, attrName, stateTagName = "wcs-state", locale2, f
3648
4262
  const pathTrimmed = parsed.path.trim();
3649
4263
  if (pathTrimmed && !pathTrimmed.startsWith(".") && !isLiteral(pathTrimmed)) {
3650
4264
  const chainDiags = validateFilterChainTypes(
3651
- pathTrimmed,
3652
- parsed.filters,
3653
- scopedPaths,
3654
- bindingStart,
3655
- msgs
3656
- );
3657
- diagnostics.push(...chainDiags);
3658
- }
3659
- }
3660
- }
3661
- for (const filter of parsed.inputFilters) {
3662
- diagnostics.push(...validateFilterUsage(filter, bindingStart, msgs));
3663
- }
3664
- if (parsed.path && scopedPaths.length > 0) {
3665
- const pathTrimmed = parsed.path.trim();
3666
- if (pathTrimmed && !pathTrimmed.startsWith(".") && !isLiteral(pathTrimmed)) {
3667
- const resultType = resolveResultType(pathTrimmed, parsed.filters, scopedPaths);
3668
- if (resultType !== null) {
3669
- const typeReq = getExpectedType(
3670
- parsed.property,
3671
- () => isNegatedByElseChain(getStructuralTemplates(), attr.valueStart)
3672
- );
3673
- if (typeReq && resultType !== typeReq.expected) {
3674
- const pathOffset = binding.indexOf(parsed.path);
3675
- const pathStart = bindingStart + pathOffset;
3676
- const schemaDefinite = typeReq.expected === "array" && parsed.filters.length === 0 && applicationSchema !== void 0 && scopedPaths.some((p) => p.path === pathTrimmed && p.fromSchema === true);
3677
- diagnostics.push({
3678
- code: schemaDefinite ? WcsDiagnosticCode.PathTypeMismatch : WcsDiagnosticCode.BindingTypeExpectation,
3679
- start: pathStart,
3680
- end: pathStart + pathTrimmed.length,
3681
- message: schemaDefinite ? msgs.pathTypeMismatch(pathTrimmed, typeReq.label, typeReq.expected, resultType) : msgs.typeExpectation(typeReq.label, typeReq.expected, resultType),
3682
- severity: schemaDefinite ? "error" : typeReq.severity
3683
- });
3684
- }
3685
- }
3686
- }
3687
- }
3688
- pos += binding.length + 1;
3689
- }
3690
- }
3691
- return diagnostics;
3692
- }
3693
- function findAllBindAttributes(html, attrName) {
3694
- const attrs = [];
3695
- const escaped = attrName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3696
- const regex = new RegExp(`${escaped}\\s*=\\s*(["'])`, "gi");
3697
- let match;
3698
- while ((match = regex.exec(html)) !== null) {
3699
- const quote = match[1];
3700
- const valueStart = match.index + match[0].length;
3701
- const valueEnd = html.indexOf(quote, valueStart);
3702
- if (valueEnd === -1) continue;
3703
- attrs.push({
3704
- value: html.slice(valueStart, valueEnd),
3705
- valueStart
3706
- });
3707
- }
3708
- return attrs;
3709
- }
3710
- function splitBindingExpressions(value) {
3711
- const result = [];
3712
- let current2 = "";
3713
- let parenDepth = 0;
3714
- for (const ch of value) {
3715
- if (ch === "(") parenDepth++;
3716
- else if (ch === ")") parenDepth = Math.max(0, parenDepth - 1);
3717
- else if (ch === ";" && parenDepth === 0) {
3718
- result.push(current2);
3719
- current2 = "";
3720
- continue;
3721
- }
3722
- current2 += ch;
3723
- }
3724
- result.push(current2);
3725
- return result;
3726
- }
3727
- function parseBindingExpression(expr) {
3728
- const colonIndex = expr.indexOf(":");
3729
- if (colonIndex === -1) {
3730
- return { property: expr.trim(), path: null, filters: [], inputFilters: [] };
3731
- }
3732
- const rawProp = expr.slice(0, colonIndex);
3733
- const propSegments = splitByPipe(rawProp);
3734
- const property = propSegments[0].trim();
3735
- const inputFilters = parseFilterSegments(expr, propSegments.slice(1), propSegments[0].length + 1);
3736
- const afterColon = expr.slice(colonIndex + 1);
3737
- const segments = splitByPipe(afterColon);
3738
- const pathSegment = segments[0] || "";
3739
- const filterSegments = segments.slice(1);
3740
- const atIndex = pathSegment.indexOf("@");
3741
- const path = atIndex !== -1 ? pathSegment.slice(0, atIndex) : pathSegment;
3742
- const filters = parseFilterSegments(expr, filterSegments, colonIndex + 1 + pathSegment.length + 1);
3743
- return { property, path: path.trim() || null, filters, inputFilters };
3744
- }
3745
- function parseFilterSegments(expr, segments, searchStart) {
3746
- const filters = [];
3747
- let filterSearchStart = searchStart;
3748
- for (const seg of segments) {
3749
- const trimmed = seg.trim();
3750
- const filterMatch = trimmed.match(/^(\w+)(?:\(([^)]*)\))?/);
3751
- if (filterMatch) {
3752
- const nameOffset = expr.indexOf(trimmed, filterSearchStart);
3753
- const args = filterMatch[2] !== void 0 ? filterMatch[2].split(",").map((a) => a.trim()).filter((a) => a !== "") : [];
3754
- filters.push({
3755
- name: filterMatch[1],
3756
- offset: nameOffset >= 0 ? nameOffset : filterSearchStart,
3757
- args,
3758
- argsOffset: nameOffset >= 0 ? nameOffset + filterMatch[1].length : filterSearchStart
3759
- });
3760
- }
3761
- filterSearchStart += seg.length + 1;
3762
- }
3763
- return filters;
3764
- }
3765
- function validateFilterUsage(filter, bindingStart, msgs) {
3766
- const diagnostics = [];
3767
- const canonical = canonicalFilterName(filter.name);
3768
- const info = filterMap.get(canonical);
3769
- if (info && canonical !== filter.name) {
3770
- diagnostics.push({
3771
- code: WcsDiagnosticCode.NameAlias,
3772
- start: bindingStart + filter.offset,
3773
- end: bindingStart + filter.offset + filter.name.length,
3774
- message: msgs.nameAlias(filter.name, canonical),
3775
- severity: "info"
3776
- });
3777
- }
3778
- if (!info) {
3779
- diagnostics.push({
3780
- code: WcsDiagnosticCode.FilterUnknown,
3781
- start: bindingStart + filter.offset,
3782
- end: bindingStart + filter.offset + filter.name.length,
3783
- message: msgs.filterUnknown(filter.name),
3784
- severity: "warning"
3785
- });
3786
- return diagnostics;
3787
- }
3788
- const argCount = filter.args.length;
3789
- if (argCount < info.minArgs) {
3790
- diagnostics.push({
3791
- code: WcsDiagnosticCode.FilterArity,
3792
- start: bindingStart + filter.offset,
3793
- end: bindingStart + filter.offset + filter.name.length,
3794
- message: msgs.filterMinArgs(filter.name, info.minArgs, argCount),
3795
- severity: "error"
3796
- });
3797
- } else if (argCount > info.maxArgs) {
3798
- diagnostics.push({
3799
- code: WcsDiagnosticCode.FilterArity,
3800
- start: bindingStart + filter.offset,
3801
- end: bindingStart + filter.offset + filter.name.length,
3802
- message: msgs.filterMaxArgs(filter.name, info.maxArgs, argCount),
3803
- severity: "error"
3804
- });
3805
- }
3806
- if (info.argTypes && argCount > 0) {
3807
- for (let i = 0; i < Math.min(argCount, info.argTypes.length); i++) {
3808
- const expectedArgType = info.argTypes[i];
3809
- if (expectedArgType === "any") continue;
3810
- const actualArgType = inferArgType(filter.args[i]);
3811
- if (actualArgType !== expectedArgType) {
3812
- diagnostics.push({
3813
- code: WcsDiagnosticCode.FilterArgType,
3814
- start: bindingStart + filter.argsOffset,
3815
- end: bindingStart + filter.argsOffset + filter.name.length,
3816
- message: msgs.filterArgType(filter.name, i + 1, expectedArgType, filter.args[i], actualArgType),
3817
- severity: "warning"
3818
- });
3819
- }
3820
- }
3821
- }
3822
- return diagnostics;
3823
- }
3824
- function splitByPipe(value) {
3825
- const result = [];
3826
- let current2 = "";
3827
- let parenDepth = 0;
3828
- for (const ch of value) {
3829
- if (ch === "(") parenDepth++;
3830
- else if (ch === ")") parenDepth = Math.max(0, parenDepth - 1);
3831
- else if (ch === "|" && parenDepth === 0) {
3832
- result.push(current2);
3833
- current2 = "";
3834
- continue;
3835
- }
3836
- current2 += ch;
3837
- }
3838
- result.push(current2);
3839
- return result;
3840
- }
3841
- function validatePathExistence(checkPath, displayPath, scopedPaths, scopedPathSet, commandNames, msgs) {
3842
- if (/^\$\d+$/.test(checkPath)) return null;
3843
- if (checkPath.startsWith("$command.")) {
3844
- if (commandNames.size > 0 && !commandNames.has(checkPath)) {
3845
- return msgs.commandTokenUndeclared(displayPath);
3846
- }
3847
- return null;
3848
- }
3849
- if (checkPath.startsWith("$streamStatus.") || checkPath.startsWith("$streamError.")) {
3850
- const prefix = checkPath.startsWith("$streamStatus.") ? "$streamStatus." : "$streamError.";
3851
- const hasNamespace = scopedPaths.some((p) => p.path.startsWith(prefix));
3852
- if (hasNamespace && !scopedPathSet.has(checkPath)) {
3853
- return msgs.streamPathMissing(displayPath);
3854
- }
3855
- return null;
3856
- }
3857
- if (!scopedPathSet.has(checkPath) && !matchesRecursionCandidates(scopedPaths, checkPath, scopedPathSet)) {
3858
- return msgs.pathMissing(displayPath);
3859
- }
3860
- return null;
3861
- }
3862
- function matchesRecursionCandidates(scopedPaths, checkPath, scopedPathSet) {
3863
- const specs = collectRecursionSpecs(scopedPaths);
3864
- if (specs.length === 0) return false;
3865
- return matchesRecursion(specs, checkPath, (candidate) => scopedPathSet.has(candidate));
3866
- }
3867
- function toMissingVerdict(message) {
3868
- return message ? { code: WcsDiagnosticCode.BindingPathMissing, message, severity: "warning" } : null;
3869
- }
3870
- function validateSchemaPathExistence(checkPath, displayPath, scopedPaths, scopedPathSet, commandNames, schema, msgs) {
3871
- if (checkPath.startsWith("$")) {
3872
- return toMissingVerdict(validatePathExistence(checkPath, displayPath, scopedPaths, scopedPathSet, commandNames, msgs));
3873
- }
3874
- if (scopedPathSet.has(checkPath)) return null;
3875
- if (matchesRecursionCandidates(scopedPaths, checkPath, scopedPathSet)) return null;
3876
- const resolution = resolveSchemaPath(schema, schema.$defs ?? {}, checkPath.split("."));
3877
- if (resolution.kind === "nonexistent") {
3878
- return { code: WcsDiagnosticCode.PathNonexistent, message: msgs.pathNonexistent(displayPath), severity: "error" };
3879
- }
3880
- return null;
3881
- }
3882
- function collectStructuralTemplates(html, attrName) {
3883
- const escaped = attrName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3884
- const attrRegex = new RegExp(`${escaped}\\s*=\\s*(["'])`, "i");
3885
- const tagRegex = /<template(?:\s[^>]*)?>|<\/template\s*>/gi;
3886
- const templates = [];
3887
- let depth = 0;
3888
- let match;
3889
- while ((match = tagRegex.exec(html)) !== null) {
3890
- if (match[0].startsWith("</")) {
3891
- depth = Math.max(0, depth - 1);
3892
- continue;
3893
- }
3894
- const attrMatch = attrRegex.exec(match[0]);
3895
- if (attrMatch) {
3896
- const quote = attrMatch[1];
3897
- const valueStart = match.index + attrMatch.index + attrMatch[0].length;
3898
- const valueEnd = html.indexOf(quote, valueStart);
3899
- if (valueEnd !== -1) {
3900
- const first = splitBindingExpressions(html.slice(valueStart, valueEnd))[0] ?? "";
3901
- const prop = first.split(":")[0].replace(/#.*$/, "").trim();
3902
- const type = prop === "if" || prop === "elseif" || prop === "else" ? prop : "other";
3903
- templates.push({ valueStart, depth, type });
4265
+ pathTrimmed,
4266
+ parsed.filters,
4267
+ scopedPaths,
4268
+ bindingStart,
4269
+ msgs
4270
+ );
4271
+ diagnostics.push(...chainDiags);
4272
+ }
4273
+ }
3904
4274
  }
3905
- }
3906
- depth++;
3907
- }
3908
- return templates;
3909
- }
3910
- function isNegatedByElseChain(templates, valueStart) {
3911
- const index = templates.findIndex((t) => t.valueStart === valueStart);
3912
- if (index === -1) return false;
3913
- const selfDepth = templates[index].depth;
3914
- for (let i = index + 1; i < templates.length; i++) {
3915
- const next = templates[i];
3916
- if (next.depth > selfDepth) continue;
3917
- if (next.depth < selfDepth) return false;
3918
- if (next.type === "elseif" || next.type === "else") return true;
3919
- if (next.type === "if") return false;
3920
- }
3921
- return false;
3922
- }
3923
- function getExpectedType(property, isNegatedIf) {
3924
- const prop = property.replace(/#.*$/, "");
3925
- if (prop === "for") {
3926
- return { label: "for", expected: "array", severity: "error" };
3927
- }
3928
- if (prop === "if" || prop === "elseif") {
3929
- if (!isNegatedIf()) return null;
3930
- return { label: prop, expected: "boolean", severity: "warning" };
3931
- }
3932
- if (prop.startsWith("class.")) {
3933
- return { label: prop, expected: "boolean", severity: "warning" };
3934
- }
3935
- if (prop.startsWith("attr.")) {
3936
- return { label: prop, expected: "string", severity: "warning" };
3937
- }
3938
- if (prop.startsWith("style.")) {
3939
- return { label: prop, expected: "string", severity: "warning" };
3940
- }
3941
- return null;
3942
- }
3943
- function validateFilterChainTypes(path, filters, statePaths, bindingStart, msgs) {
3944
- const diagnostics = [];
3945
- const pathInfo = statePaths.find((p) => p.path === path);
3946
- if (!pathInfo?.typeHint) return diagnostics;
3947
- let currentType = pathInfo.typeHint;
3948
- for (const filter of filters) {
3949
- const info = filterMap.get(filter.name);
3950
- if (!info) break;
3951
- if (info.acceptTypes !== "any") {
3952
- const currentTypes = currentType.split("|");
3953
- const hasMatch = currentTypes.some((t) => info.acceptTypes.includes(t));
3954
- if (!hasMatch) {
3955
- diagnostics.push({
3956
- code: WcsDiagnosticCode.FilterInputType,
3957
- start: bindingStart + filter.offset,
3958
- end: bindingStart + filter.offset + filter.name.length,
3959
- message: msgs.filterInputType(filter.name, info.acceptTypes.join("|"), currentType),
3960
- severity: "warning"
3961
- });
4275
+ for (const filter of parsed.inputFilters) {
4276
+ diagnostics.push(...validateFilterUsage(filter, bindingStart, msgs));
3962
4277
  }
3963
- }
3964
- if (info.resultType !== "passthrough") {
3965
- currentType = info.resultType;
4278
+ if (parsed.path && scopedPaths.length > 0) {
4279
+ const pathTrimmed = parsed.path.trim();
4280
+ if (pathTrimmed && !pathTrimmed.startsWith(".") && !isLiteral(pathTrimmed)) {
4281
+ const resultType = resolveResultType(pathTrimmed, parsed.filters, scopedPaths);
4282
+ if (resultType !== null) {
4283
+ const typeReq = getExpectedType(
4284
+ parsed.property,
4285
+ () => isNegatedByElseChain(getStructuralTemplates(), attr.valueStart)
4286
+ );
4287
+ if (typeReq && resultType !== typeReq.expected) {
4288
+ const pathOffset = binding.indexOf(parsed.path);
4289
+ const pathStart = bindingStart + pathOffset;
4290
+ const schemaDefinite = typeReq.expected === "array" && parsed.filters.length === 0 && applicationSchema !== void 0 && scopedPaths.some((p) => p.path === pathTrimmed && p.fromSchema === true);
4291
+ diagnostics.push({
4292
+ code: schemaDefinite ? WcsDiagnosticCode.PathTypeMismatch : WcsDiagnosticCode.BindingTypeExpectation,
4293
+ start: pathStart,
4294
+ end: pathStart + pathTrimmed.length,
4295
+ message: schemaDefinite ? msgs.pathTypeMismatch(pathTrimmed, typeReq.label, typeReq.expected, resultType) : msgs.typeExpectation(typeReq.label, typeReq.expected, resultType),
4296
+ severity: schemaDefinite ? "error" : typeReq.severity
4297
+ });
4298
+ }
4299
+ }
4300
+ }
4301
+ }
4302
+ pos += binding.length + 1;
3966
4303
  }
3967
4304
  }
3968
4305
  return diagnostics;
3969
4306
  }
3970
- function resolveResultType(path, filters, statePaths) {
3971
- const pathInfo = statePaths.find((p) => p.path === path);
3972
- if (!pathInfo?.typeHint) return null;
3973
- let currentType = pathInfo.typeHint;
3974
- for (const filter of filters) {
3975
- const info = filterMap.get(filter.name);
3976
- if (!info) return null;
3977
- if (info.resultType === "passthrough") continue;
3978
- currentType = info.resultType;
4307
+ function findAllBindAttributes(html, attrName) {
4308
+ const attrs = [];
4309
+ const escaped = attrName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
4310
+ const regex = new RegExp(`(?:^|[\\s/])${escaped}\\s*=\\s*(["'])`, "gi");
4311
+ for (const tag of findStartTagRegions(html)) {
4312
+ const attrsText = html.slice(tag.start, tag.end);
4313
+ regex.lastIndex = 0;
4314
+ let match;
4315
+ while ((match = regex.exec(attrsText)) !== null) {
4316
+ const quote = match[1];
4317
+ const valueStart = tag.start + match.index + match[0].length;
4318
+ const valueEnd = html.indexOf(quote, valueStart);
4319
+ if (valueEnd === -1 || valueEnd > tag.end) continue;
4320
+ attrs.push({ value: html.slice(valueStart, valueEnd), valueStart });
4321
+ regex.lastIndex = valueEnd - tag.start;
4322
+ }
3979
4323
  }
3980
- return currentType;
3981
- }
3982
- function inferArgType(arg) {
3983
- const v = arg.trim();
3984
- if (/^-?\d+(\.\d+)?$/.test(v)) return "number";
3985
- return "string";
4324
+ return attrs;
3986
4325
  }
3987
- function isLiteral(value) {
3988
- return /^-?\d/.test(value) || /^["'`]/.test(value) || value === "true" || value === "false" || value === "null";
4326
+ function splitBindingExpressions(value) {
4327
+ return splitBindTexts(value);
3989
4328
  }
3990
-
3991
- // src/service/stateTypeValidator.ts
3992
- function validateStateTypes(html, stateTagName = "wcs-state", locale2) {
3993
- const msgs = getMessages(locale2);
3994
- const blocks = parseWcsScriptBlocks(html, stateTagName);
3995
- const diagnostics = [];
3996
- for (const block of blocks) {
3997
- const props = findJsDocTypedProperties(block.content);
3998
- for (const prop of props) {
3999
- if (!isValueCompatible(prop.declaredTypes, prop.valueType)) {
4000
- const absStart = block.contentStart + prop.valueOffset;
4001
- const absEnd = absStart + prop.valueLength;
4002
- diagnostics.push({
4003
- start: absStart,
4004
- end: absEnd,
4005
- message: msgs.typeAnnotationIncompatible(prop.valueType, prop.rawType),
4006
- severity: "warning"
4007
- });
4008
- }
4009
- }
4329
+ function parseBindingExpression(expr) {
4330
+ const colonIndex = indexOfOutsideQuotes2(expr, ":");
4331
+ if (colonIndex === -1) {
4332
+ return { property: expr.trim(), path: null, filters: [], inputFilters: [] };
4010
4333
  }
4011
- return diagnostics;
4334
+ const rawProp = expr.slice(0, colonIndex);
4335
+ const propSegments = splitByPipe(rawProp);
4336
+ const property = propSegments[0].trim();
4337
+ const inputFilters = parseFilterSegments(expr, propSegments.slice(1), propSegments[0].length + 1);
4338
+ const afterColon = expr.slice(colonIndex + 1);
4339
+ const segments = splitByPipe(afterColon);
4340
+ const pathSegment = segments[0] || "";
4341
+ const filterSegments = segments.slice(1);
4342
+ const atIndex = pathSegment.indexOf("@");
4343
+ const path = atIndex !== -1 ? pathSegment.slice(0, atIndex) : pathSegment;
4344
+ const filters = parseFilterSegments(expr, filterSegments, colonIndex + 1 + pathSegment.length + 1);
4345
+ return { property, path: path.trim() || null, filters, inputFilters };
4012
4346
  }
4013
- function findJsDocTypedProperties(script) {
4014
- const results = [];
4015
- const regex = /\/\*\*\s*@type\s*\{([^}]+)\}\s*\*\/\s*(?:"([^"]+)"|'([^']+)'|(\w+))\s*:\s*/g;
4016
- let match;
4017
- while ((match = regex.exec(script)) !== null) {
4018
- const rawType = match[1].trim();
4019
- const name = match[2] ?? match[3] ?? match[4];
4020
- const valueStart = match.index + match[0].length;
4021
- const valueText = extractValue(script, valueStart);
4022
- const valueType = inferValueType(valueText);
4023
- if (valueType) {
4024
- const declaredTypes = rawType.split("|").map((t) => normalizeType(t.trim()));
4025
- results.push({
4347
+ function parseFilterSegments(expr, segments, searchStart) {
4348
+ const filters = [];
4349
+ let filterSearchStart = searchStart;
4350
+ for (const seg of segments) {
4351
+ const trimmed = seg.trim();
4352
+ const open = trimmed.indexOf("(");
4353
+ const close = trimmed.lastIndexOf(")");
4354
+ const namePart = open === -1 ? trimmed : trimmed.slice(0, open);
4355
+ const nameMatch = /^\w+/.exec(namePart.trim());
4356
+ if (nameMatch !== null) {
4357
+ const name = nameMatch[0];
4358
+ const nameOffset = expr.indexOf(trimmed, filterSearchStart);
4359
+ const args = open !== -1 && close > open ? parseFilterArgsText(trimmed.slice(open + 1, close)) : [];
4360
+ filters.push({
4026
4361
  name,
4027
- rawType,
4028
- declaredTypes,
4029
- valueType,
4030
- valueOffset: valueStart,
4031
- valueLength: valueText.length
4362
+ offset: nameOffset >= 0 ? nameOffset : filterSearchStart,
4363
+ args,
4364
+ argsOffset: nameOffset >= 0 ? nameOffset + name.length : filterSearchStart
4032
4365
  });
4033
4366
  }
4367
+ filterSearchStart += seg.length + 1;
4034
4368
  }
4035
- return results;
4036
- }
4037
- function extractValue(script, start) {
4038
- let depth = 0;
4039
- let inString = null;
4040
- let i = start;
4041
- while (i < script.length) {
4042
- const ch = script[i];
4043
- if (inString) {
4044
- if (ch === inString && script[i - 1] !== "\\") inString = null;
4045
- } else if (ch === '"' || ch === "'" || ch === "`") {
4046
- inString = ch;
4047
- } else if (ch === "{" || ch === "[" || ch === "(") {
4048
- depth++;
4049
- } else if (ch === "}" || ch === "]" || ch === ")") {
4050
- if (depth === 0) break;
4051
- depth--;
4052
- } else if ((ch === "," || ch === "\n") && depth === 0) {
4053
- break;
4054
- }
4055
- i++;
4056
- }
4057
- return script.slice(start, i).trim();
4058
- }
4059
- function inferValueType(value) {
4060
- const v = value.replace(/,\s*$/, "").trim();
4061
- if (v === "null") return "null";
4062
- if (v === "undefined") return "null";
4063
- if (v === "true" || v === "false") return "boolean";
4064
- if (/^-?\d+\.\d/.test(v)) return "number";
4065
- if (/^-?\d/.test(v)) return "number";
4066
- if (/^["'`]/.test(v)) return "string";
4067
- if (v.startsWith("[")) return "array";
4068
- if (v.startsWith("{")) return "object";
4069
- return null;
4070
- }
4071
- function normalizeType(type) {
4072
- const lower = type.toLowerCase();
4073
- if (lower === "null" || lower === "undefined") return "null";
4074
- if (lower === "string") return "string";
4075
- if (lower === "number") return "number";
4076
- if (lower === "boolean") return "boolean";
4077
- if (lower.endsWith("[]") || lower.startsWith("array")) return "array";
4078
- if (lower === "object") return "object";
4079
- return type;
4080
- }
4081
- function isValueCompatible(declaredTypes, valueType) {
4082
- return declaredTypes.includes(valueType);
4083
- }
4084
-
4085
- // src/service/scriptPatterns.ts
4086
- var ID = String.raw`[\w$]+`;
4087
- var SUB = String.raw`\s*(?:\?\.)?\s*\[(?!\s*["'])[^\[\]]+\]`;
4088
- var DOT_SEG = String.raw`\s*\??\.\s*${ID}`;
4089
- var CHAIN = String.raw`(?:${DOT_SEG}|${SUB})*`;
4090
- var BRACKETS_ONLY = String.raw`(?:${SUB})+`;
4091
- var CHAIN_ONE_PLUS = String.raw`(?:${DOT_SEG}|${SUB})+`;
4092
- var ROOT_DOT = String.raw`\bthis\s*\??\.\s*(${ID})`;
4093
- var ROOT_BRACKET = String.raw`\bthis\s*(?:\?\.)?\s*\[\s*["']([^"']+)["']\s*\]`;
4094
- var ASSIGN_TAIL = String.raw`\s*(?:(?:\*\*|<<|>>>|>>|&&|\|\||\?\?|[+\-*/%&|^])?=(?!=)|\+\+|--)`;
4095
- var PRE_INCDEC = String.raw`(?:\+\+|--)\s*`;
4096
- function chainToDotted(chain) {
4097
- const token = new RegExp(String.raw`\s*(?:\??\.\s*(${ID})|(?:\?\.)?\s*\[([^\[\]]+)\])`, "g");
4098
- let out = "";
4099
- let match;
4100
- while ((match = token.exec(chain)) !== null) {
4101
- if (match[1] !== void 0) {
4102
- out += `.${match[1]}`;
4103
- } else {
4104
- const key = match[2].trim();
4105
- out += /^\d+$/.test(key) ? `.${key}` : `.<${key}>`;
4106
- }
4107
- }
4108
- return out;
4109
- }
4110
- function hasDotSegment(chain) {
4111
- return /[.]/.test(chain.replace(/\s*(?:\?\.)?\s*\[[^\[\]]+\]/g, ""));
4369
+ return filters;
4112
4370
  }
4113
- function isApiRoot(root) {
4114
- return root.startsWith("$");
4371
+ function parseFilterArgsText(argsText) {
4372
+ const args = splitOutsideQuotes2(argsText, ",").map((a) => a.trim());
4373
+ if (args[args.length - 1] === "") args.pop();
4374
+ return args;
4115
4375
  }
4116
-
4117
- // src/service/nestedAssignValidator.ts
4118
- var NESTED_ASSIGN = new RegExp(`${ROOT_DOT}(${CHAIN_ONE_PLUS})${ASSIGN_TAIL}`, "g");
4119
- var PRE_NESTED_INCDEC = new RegExp(`${PRE_INCDEC}${ROOT_DOT}(${CHAIN_ONE_PLUS})`, "g");
4120
- function validateNestedAssigns(html, stateTagName = "wcs-state", locale2) {
4121
- const msgs = getMessages(locale2);
4122
- const blocks = parseWcsScriptBlocks(html, stateTagName);
4376
+ function validateFilterUsage(filter, bindingStart, msgs) {
4123
4377
  const diagnostics = [];
4124
- for (const block of blocks) {
4125
- findNestedAssigns(block.content, block.contentStart, msgs, diagnostics);
4378
+ const canonical = canonicalFilterName(filter.name);
4379
+ const info = filterMap.get(canonical);
4380
+ if (info && canonical !== filter.name) {
4381
+ diagnostics.push({
4382
+ code: WcsDiagnosticCode.NameAlias,
4383
+ start: bindingStart + filter.offset,
4384
+ end: bindingStart + filter.offset + filter.name.length,
4385
+ message: msgs.nameAlias(filter.name, canonical),
4386
+ severity: "info"
4387
+ });
4126
4388
  }
4127
- return diagnostics;
4128
- }
4129
- function findNestedAssigns(script, baseOffset, msgs, out) {
4130
- for (const regex of [NESTED_ASSIGN, PRE_NESTED_INCDEC]) {
4131
- regex.lastIndex = 0;
4132
- let match;
4133
- while ((match = regex.exec(script)) !== null) {
4134
- const [full, topProp, chainPart] = match;
4135
- if (isApiRoot(topProp)) continue;
4136
- if (!hasDotSegment(chainPart)) continue;
4137
- const suggestedPath = topProp + chainToDotted(chainPart);
4138
- const start = baseOffset + match.index;
4139
- out.push({
4140
- start,
4141
- end: start + full.length,
4142
- message: msgs.nestedAssign(suggestedPath),
4143
- severity: "error"
4144
- });
4389
+ if (!info) {
4390
+ diagnostics.push({
4391
+ code: WcsDiagnosticCode.FilterUnknown,
4392
+ start: bindingStart + filter.offset,
4393
+ end: bindingStart + filter.offset + filter.name.length,
4394
+ message: msgs.filterUnknown(filter.name),
4395
+ severity: "warning"
4396
+ });
4397
+ return diagnostics;
4398
+ }
4399
+ const argCount = filter.args.length;
4400
+ if (argCount < info.minArgs) {
4401
+ diagnostics.push({
4402
+ code: WcsDiagnosticCode.FilterArity,
4403
+ start: bindingStart + filter.offset,
4404
+ end: bindingStart + filter.offset + filter.name.length,
4405
+ message: msgs.filterMinArgs(filter.name, info.minArgs, argCount),
4406
+ severity: "error"
4407
+ });
4408
+ } else if (argCount > info.maxArgs) {
4409
+ diagnostics.push({
4410
+ code: WcsDiagnosticCode.FilterArity,
4411
+ start: bindingStart + filter.offset,
4412
+ end: bindingStart + filter.offset + filter.name.length,
4413
+ message: msgs.filterMaxArgs(filter.name, info.maxArgs, argCount),
4414
+ severity: "error"
4415
+ });
4416
+ }
4417
+ if (info.argTypes && argCount > 0) {
4418
+ for (let i = 0; i < Math.min(argCount, info.argTypes.length); i++) {
4419
+ const expectedArgType = info.argTypes[i];
4420
+ if (expectedArgType === "any") continue;
4421
+ const actualArgType = inferArgType(filter.args[i]);
4422
+ if (actualArgType !== expectedArgType) {
4423
+ diagnostics.push({
4424
+ code: WcsDiagnosticCode.FilterArgType,
4425
+ start: bindingStart + filter.argsOffset,
4426
+ end: bindingStart + filter.argsOffset + filter.name.length,
4427
+ message: msgs.filterArgType(filter.name, i + 1, expectedArgType, filter.args[i], actualArgType),
4428
+ severity: "warning"
4429
+ });
4430
+ }
4145
4431
  }
4146
4432
  }
4433
+ return diagnostics;
4147
4434
  }
4148
-
4149
- // src/service/arrayMutationValidator.ts
4150
- var DESTRUCTIVE_METHODS = "push|pop|shift|unshift|splice|sort|reverse|fill|copyWithin";
4151
- var ALTERNATIVES = {
4152
- push: (a) => `${a} = ${a}.concat(item)`,
4153
- unshift: (a) => `${a} = [item, ...${a}]`,
4154
- pop: (a) => `${a} = ${a}.slice(0, -1)`,
4155
- shift: (a) => `${a} = ${a}.slice(1)`,
4156
- splice: (a) => `${a} = ${a}.toSpliced(...)`,
4157
- sort: (a) => `${a} = ${a}.toSorted(...)`,
4158
- reverse: (a) => `${a} = ${a}.toReversed()`,
4159
- fill: (a) => `${a} = ${a}.map(...)`,
4160
- copyWithin: (a) => `${a} = ${a}.map(...)`
4161
- };
4162
- var METHOD_TAIL = String.raw`\s*\??\.\s*(${DESTRUCTIVE_METHODS})(?=\s*\()`;
4163
- var DOT_ROOT_CALL = new RegExp(`${ROOT_DOT}(${CHAIN})${METHOD_TAIL}`, "g");
4164
- var BRACKET_ROOT_CALL = new RegExp(`${ROOT_BRACKET}(${CHAIN})${METHOD_TAIL}`, "g");
4165
- var DOT_INDEX_ASSIGN = new RegExp(`${ROOT_DOT}(${BRACKETS_ONLY})${ASSIGN_TAIL}`, "g");
4166
- var BRACKET_INDEX_ASSIGN = new RegExp(`${ROOT_BRACKET}(${BRACKETS_ONLY})${ASSIGN_TAIL}`, "g");
4167
- var PRE_DOT_INDEX = new RegExp(`${PRE_INCDEC}${ROOT_DOT}(${BRACKETS_ONLY})`, "g");
4168
- var PRE_BRACKET_INDEX = new RegExp(`${PRE_INCDEC}${ROOT_BRACKET}(${BRACKETS_ONLY})`, "g");
4169
- function toAccessor(path) {
4170
- return /^[A-Za-z_]\w*$/.test(path) ? `this.${path}` : `this["${path}"]`;
4435
+ function splitByPipe(value) {
4436
+ return splitOutsideQuotes2(value, "|");
4171
4437
  }
4172
- function validateArrayMutations(html, stateTagName = "wcs-state", locale2) {
4173
- const msgs = getMessages(locale2);
4174
- const blocks = parseWcsScriptBlocks(html, stateTagName);
4175
- const diagnostics = [];
4176
- for (const block of blocks) {
4177
- findDestructiveCalls(block.content, block.contentStart, msgs, diagnostics);
4178
- findIndexAssigns(block.content, block.contentStart, msgs, diagnostics);
4438
+ function validatePathExistence(checkPath, displayPath, scopedPaths, scopedPathSet, commandNames, msgs) {
4439
+ if (/^\$\d+$/.test(checkPath)) return null;
4440
+ if (checkPath.startsWith("$command.")) {
4441
+ if (commandNames.size > 0 && !commandNames.has(checkPath)) {
4442
+ return msgs.commandTokenUndeclared(displayPath);
4443
+ }
4444
+ return null;
4179
4445
  }
4180
- return diagnostics;
4181
- }
4182
- function findDestructiveCalls(script, baseOffset, msgs, out) {
4183
- for (const regex of [DOT_ROOT_CALL, BRACKET_ROOT_CALL]) {
4184
- regex.lastIndex = 0;
4185
- let match;
4186
- while ((match = regex.exec(script)) !== null) {
4187
- const [full, root, chain, method] = match;
4188
- if (isApiRoot(root)) continue;
4189
- const statePath = root + chainToDotted(chain);
4190
- const start = baseOffset + match.index;
4191
- out.push({
4192
- code: WcsDiagnosticCode.ArrayMutation,
4193
- start,
4194
- end: start + full.length,
4195
- message: msgs.arrayMutation(method, ALTERNATIVES[method](toAccessor(statePath))),
4196
- severity: "error",
4197
- statePath
4198
- });
4446
+ if (checkPath.startsWith("$streamStatus.") || checkPath.startsWith("$streamError.")) {
4447
+ const prefix = checkPath.startsWith("$streamStatus.") ? "$streamStatus." : "$streamError.";
4448
+ const hasNamespace = scopedPaths.some((p) => p.path.startsWith(prefix));
4449
+ if (hasNamespace && !scopedPathSet.has(checkPath)) {
4450
+ return msgs.streamPathMissing(displayPath);
4199
4451
  }
4452
+ return null;
4200
4453
  }
4454
+ if (!scopedPathSet.has(checkPath) && !matchesRecursionCandidates(scopedPaths, checkPath, scopedPathSet)) {
4455
+ return msgs.pathMissing(displayPath);
4456
+ }
4457
+ return null;
4201
4458
  }
4202
- function findIndexAssigns(script, baseOffset, msgs, out) {
4203
- for (const regex of [DOT_INDEX_ASSIGN, BRACKET_INDEX_ASSIGN, PRE_DOT_INDEX, PRE_BRACKET_INDEX]) {
4204
- regex.lastIndex = 0;
4205
- let match;
4206
- while ((match = regex.exec(script)) !== null) {
4207
- const [full, root, chain] = match;
4208
- if (isApiRoot(root)) continue;
4209
- const suggestedPath = root + chainToDotted(chain);
4210
- const start = baseOffset + match.index;
4211
- out.push({
4212
- code: WcsDiagnosticCode.ArrayIndexAssign,
4213
- start,
4214
- end: start + full.length,
4215
- message: msgs.arrayIndexAssign(suggestedPath),
4216
- severity: "error",
4217
- statePath: suggestedPath
4218
- });
4219
- }
4459
+ function matchesRecursionCandidates(scopedPaths, checkPath, scopedPathSet) {
4460
+ const specs = collectRecursionSpecs(scopedPaths);
4461
+ if (specs.length === 0) return false;
4462
+ return matchesRecursion(specs, checkPath, (candidate) => scopedPathSet.has(candidate));
4463
+ }
4464
+ function toMissingVerdict(message) {
4465
+ return message ? { code: WcsDiagnosticCode.BindingPathMissing, message, severity: "warning" } : null;
4466
+ }
4467
+ function validateSchemaPathExistence(checkPath, displayPath, scopedPaths, scopedPathSet, commandNames, schema, msgs) {
4468
+ if (checkPath.startsWith("$")) {
4469
+ return toMissingVerdict(validatePathExistence(checkPath, displayPath, scopedPaths, scopedPathSet, commandNames, msgs));
4470
+ }
4471
+ if (scopedPathSet.has(checkPath)) return null;
4472
+ if (matchesRecursionCandidates(scopedPaths, checkPath, scopedPathSet)) return null;
4473
+ const resolution = resolveSchemaPath(schema, schema.$defs ?? {}, checkPath.split("."));
4474
+ if (resolution.kind === "nonexistent") {
4475
+ return { code: WcsDiagnosticCode.PathNonexistent, message: msgs.pathNonexistent(displayPath), severity: "error" };
4220
4476
  }
4477
+ return null;
4221
4478
  }
4222
-
4223
- // src/service/templateSyntax.ts
4224
- function findAllMustacheSyntax(html) {
4225
- const results = [];
4226
- const regex = /\{\{\s*(.+?)\s*\}\}/g;
4479
+ function collectStructuralTemplates(html, attrName) {
4480
+ const escaped = attrName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
4481
+ const attrRegex = new RegExp(`${escaped}\\s*=\\s*(["'])`, "i");
4482
+ const tagRegex = /<template(?:\s[^>]*)?>|<\/template\s*>/gi;
4483
+ const templates = [];
4484
+ let depth = 0;
4227
4485
  let match;
4228
- while ((match = regex.exec(html)) !== null) {
4229
- if (isInsideTag(html, match.index, "script") || isInsideTag(html, match.index, "style")) {
4486
+ while ((match = tagRegex.exec(html)) !== null) {
4487
+ if (match[0].startsWith("</")) {
4488
+ depth = Math.max(0, depth - 1);
4230
4489
  continue;
4231
4490
  }
4232
- const expr = match[1];
4233
- const exprStart = match.index + match[0].indexOf(expr);
4234
- results.push({
4235
- kind: "mustache",
4236
- expression: expr,
4237
- exprStart,
4238
- exprEnd: exprStart + expr.length,
4239
- matchStart: match.index,
4240
- matchEnd: match.index + match[0].length,
4241
- insideTemplate: isInsideTag(html, match.index, "template")
4242
- });
4491
+ const attrMatch = attrRegex.exec(match[0]);
4492
+ if (attrMatch) {
4493
+ const quote = attrMatch[1];
4494
+ const valueStart = match.index + attrMatch.index + attrMatch[0].length;
4495
+ const valueEnd = html.indexOf(quote, valueStart);
4496
+ if (valueEnd !== -1) {
4497
+ const first = splitBindingExpressions(html.slice(valueStart, valueEnd))[0] ?? "";
4498
+ const firstColon = indexOfOutsideQuotes2(first, ":");
4499
+ const prop = (firstColon === -1 ? first : first.slice(0, firstColon)).replace(/#.*$/, "").trim();
4500
+ const type = prop === "if" || prop === "elseif" || prop === "else" ? prop : "other";
4501
+ templates.push({ valueStart, depth, type });
4502
+ }
4503
+ }
4504
+ depth++;
4243
4505
  }
4244
- return results;
4506
+ return templates;
4245
4507
  }
4246
- function findAllCommentBindings(html, commentTextPrefix = "wcs-text") {
4247
- const results = [];
4248
- const escaped = commentTextPrefix.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
4249
- const regex = new RegExp(`<!--\\s*@@\\s*(?:${escaped})?\\s*:\\s*(.+?)\\s*-->`, "g");
4250
- let match;
4251
- while ((match = regex.exec(html)) !== null) {
4252
- const expr = match[1];
4253
- if (!expr) continue;
4254
- const exprStart = match.index + match[0].indexOf(expr);
4255
- results.push({
4256
- kind: "comment",
4257
- expression: expr,
4258
- exprStart,
4259
- exprEnd: exprStart + expr.length,
4260
- matchStart: match.index,
4261
- matchEnd: match.index + match[0].length,
4262
- insideTemplate: isInsideTag(html, match.index, "template")
4263
- });
4508
+ function isNegatedByElseChain(templates, valueStart) {
4509
+ const index = templates.findIndex((t) => t.valueStart === valueStart);
4510
+ if (index === -1) return false;
4511
+ const selfDepth = templates[index].depth;
4512
+ for (let i = index + 1; i < templates.length; i++) {
4513
+ const next = templates[i];
4514
+ if (next.depth > selfDepth) continue;
4515
+ if (next.depth < selfDepth) return false;
4516
+ if (next.type === "elseif" || next.type === "else") return true;
4517
+ if (next.type === "if") return false;
4518
+ }
4519
+ return false;
4520
+ }
4521
+ function getExpectedType(property, isNegatedIf) {
4522
+ const prop = property.replace(/#.*$/, "");
4523
+ if (prop === "for") {
4524
+ return { label: "for", expected: "array", severity: "error" };
4525
+ }
4526
+ if (prop === "if" || prop === "elseif") {
4527
+ if (!isNegatedIf()) return null;
4528
+ return { label: prop, expected: "boolean", severity: "warning" };
4529
+ }
4530
+ if (prop.startsWith("class.")) {
4531
+ return { label: prop, expected: "boolean", severity: "warning" };
4532
+ }
4533
+ if (prop.startsWith("attr.")) {
4534
+ return { label: prop, expected: "string", severity: "warning" };
4264
4535
  }
4265
- return results;
4266
- }
4267
- function isInsideTag(html, offset2, tagName) {
4268
- const tagRegex = new RegExp(`<(/?)${tagName}[\\s>]`, "gi");
4269
- let depth = 0;
4270
- let match;
4271
- while ((match = tagRegex.exec(html)) !== null) {
4272
- if (match.index > offset2) break;
4273
- if (match[1]) {
4274
- depth = Math.max(0, depth - 1);
4275
- } else {
4276
- depth++;
4277
- }
4536
+ if (prop.startsWith("style.")) {
4537
+ return { label: prop, expected: "string", severity: "warning" };
4278
4538
  }
4279
- return depth > 0;
4539
+ return null;
4280
4540
  }
4281
-
4282
- // src/service/templateSyntaxValidator.ts
4283
- function validateTemplateSyntax(html, stateTagName, bindAttrName = "data-wcs", locale2, fileReader, applicationSchema) {
4541
+ function validateFilterChainTypes(path, filters, statePaths, bindingStart, msgs) {
4284
4542
  const diagnostics = [];
4285
- const msgs = getMessages(locale2);
4286
- const allPaths = mergeSchemaCandidates(getStatePathsFromHtml(html, stateTagName, fileReader), applicationSchema);
4287
- const defaultSchema = applicationSchema;
4288
- const missingVerdict = (path, displayPath, pathSet2, scoped) => {
4289
- if (hasRecursionWildcard(path)) {
4290
- return {
4291
- code: WcsDiagnosticCode.RecursionUnsupported,
4292
- severity: "error",
4293
- message: msgs.recursionUnsupported(path, "binding")
4294
- };
4295
- }
4296
- if (isValidTemplatePath(path, pathSet2, scoped)) return null;
4297
- if (defaultSchema !== void 0 && !path.startsWith("$")) {
4298
- const resolution = resolveSchemaPath(defaultSchema, defaultSchema.$defs ?? {}, path.split("."));
4299
- return resolution.kind === "nonexistent" ? { code: WcsDiagnosticCode.PathNonexistent, severity: "error", message: msgs.pathNonexistent(displayPath) } : null;
4300
- }
4301
- return { code: WcsDiagnosticCode.BindingPathMissing, severity: "warning", message: msgs.pathMissing(displayPath) };
4302
- };
4303
- if (allPaths.length === 0) return diagnostics;
4304
- const defaultPaths = allPaths;
4305
- const pathSet = new Set(defaultPaths.map((p) => p.path));
4306
- const filterNameSet = new Set(BUILTIN_FILTERS.map((f) => f.name));
4307
- const mustaches = findAllMustacheSyntax(html);
4308
- const comments = findAllCommentBindings(html);
4309
- for (const item of [...mustaches, ...comments]) {
4310
- if (item.kind === "comment") {
4311
- diagnostics.push({
4312
- code: WcsDiagnosticCode.TemplateSyntax,
4313
- start: item.matchStart,
4314
- end: item.matchEnd,
4315
- message: msgs.wcsTextInfo(item.expression),
4316
- severity: "info"
4317
- });
4318
- }
4319
- if (item.kind === "mustache" && !item.insideTemplate) {
4320
- diagnostics.push({
4321
- code: WcsDiagnosticCode.TemplateSyntax,
4322
- start: item.matchStart,
4323
- end: item.matchEnd,
4324
- message: msgs.moustacheFouc(item.expression),
4325
- severity: "info"
4326
- });
4327
- }
4328
- if (!item.expression) continue;
4329
- const parts = item.expression.split("|");
4330
- const pathPart = (parts[0] || "").trim();
4331
- if (pathPart.includes("@")) continue;
4332
- const insideFor = item.insideTemplate && isInsideForTemplate(html, item.matchStart, bindAttrName);
4333
- if (pathPart && !/^-?\d|^["'`]|^true$|^false$|^null$/.test(pathPart)) {
4334
- if (!insideFor && pathPart.includes("*")) {
4335
- diagnostics.push({
4336
- code: WcsDiagnosticCode.TemplateSyntax,
4337
- start: item.exprStart,
4338
- end: item.exprStart + pathPart.length,
4339
- message: msgs.patternPathOutsideFor(pathPart),
4340
- severity: "warning"
4341
- });
4342
- }
4343
- if (!insideFor && pathPart.startsWith(".")) {
4344
- diagnostics.push({
4345
- code: WcsDiagnosticCode.TemplateSyntax,
4346
- start: item.exprStart,
4347
- end: item.exprStart + pathPart.length,
4348
- message: msgs.omittedPathOutsideFor(pathPart),
4349
- severity: "warning"
4350
- });
4351
- }
4352
- if (insideFor && !pathPart.startsWith(".")) {
4353
- const indexMatch = /^\$(\d+)$/.exec(pathPart);
4354
- const needed = indexMatch !== null ? Number(indexMatch[1]) : pathPart.includes("*") ? countWildcardSegments(pathPart) : 0;
4355
- if (needed > 0) {
4356
- const available = getAvailableWildcardRank(html, item.matchStart, bindAttrName);
4357
- if (available > 0 && needed > available) {
4358
- diagnostics.push({
4359
- code: WcsDiagnosticCode.WildcardRank,
4360
- start: item.exprStart,
4361
- end: item.exprStart + pathPart.length,
4362
- message: msgs.wildcardRank(`"${pathPart}"`, needed, available),
4363
- severity: "warning"
4364
- });
4365
- }
4366
- }
4367
- }
4368
- if (/\.\d+\.|\.\d+$/.test(pathPart)) {
4543
+ const pathInfo = statePaths.find((p) => p.path === path);
4544
+ if (!pathInfo?.typeHint) return diagnostics;
4545
+ let currentType = pathInfo.typeHint;
4546
+ for (const filter of filters) {
4547
+ const info = filterMap.get(canonicalFilterName(filter.name));
4548
+ if (!info) break;
4549
+ if (info.acceptTypes !== "any") {
4550
+ const currentTypes = currentType.split("|");
4551
+ const hasMatch = currentTypes.some((t) => info.acceptTypes.includes(t));
4552
+ if (!hasMatch) {
4369
4553
  diagnostics.push({
4370
- code: WcsDiagnosticCode.TemplateSyntax,
4371
- start: item.exprStart,
4372
- end: item.exprStart + pathPart.length,
4373
- message: msgs.resolvedPathInUi(pathPart),
4554
+ code: WcsDiagnosticCode.FilterInputType,
4555
+ start: bindingStart + filter.offset,
4556
+ end: bindingStart + filter.offset + filter.name.length,
4557
+ message: msgs.filterInputType(filter.name, info.acceptTypes.join("|"), currentType),
4374
4558
  severity: "warning"
4375
4559
  });
4376
4560
  }
4377
- if (pathPart.startsWith(".")) {
4378
- const forPath = insideFor ? getInnermostForPath(html, item.matchStart, bindAttrName) : null;
4379
- if (forPath && !forPath.startsWith(".")) {
4380
- const expandedPath = pathPart === "." ? `${forPath}.*` : `${forPath}.*.${pathPart.slice(1)}`;
4381
- const verdict = missingVerdict(expandedPath, pathPart, pathSet, defaultPaths);
4382
- if (verdict) {
4383
- diagnostics.push({
4384
- code: verdict.code,
4385
- start: item.exprStart,
4386
- end: item.exprStart + pathPart.length,
4387
- message: verdict.message + msgs.expansionSuffix(expandedPath),
4388
- severity: verdict.severity
4389
- });
4390
- }
4391
- }
4392
- } else {
4393
- const verdict = missingVerdict(pathPart, pathPart, pathSet, defaultPaths);
4394
- if (verdict) {
4395
- diagnostics.push({
4396
- code: verdict.code,
4397
- start: item.exprStart,
4398
- end: item.exprStart + pathPart.length,
4399
- message: verdict.message,
4400
- severity: verdict.severity
4401
- });
4402
- }
4403
- }
4404
4561
  }
4405
- for (let i = 1; i < parts.length; i++) {
4406
- const filterName = parts[i].trim().replace(/\(.*$/, "");
4407
- const filterOffset = item.expression.indexOf(parts[i]) + (parts[i].length - parts[i].trimStart().length);
4408
- const canonical = canonicalFilterName(filterName);
4409
- if (filterName && canonical !== filterName && filterNameSet.has(canonical)) {
4410
- diagnostics.push({
4411
- code: WcsDiagnosticCode.NameAlias,
4412
- start: item.exprStart + filterOffset,
4413
- end: item.exprStart + filterOffset + filterName.length,
4414
- message: msgs.nameAlias(filterName, canonical),
4415
- severity: "info"
4416
- });
4417
- } else if (filterName && !filterNameSet.has(filterName)) {
4562
+ if (info.resultType !== "passthrough") {
4563
+ currentType = info.resultType;
4564
+ }
4565
+ }
4566
+ return diagnostics;
4567
+ }
4568
+ function resolveResultType(path, filters, statePaths) {
4569
+ const pathInfo = statePaths.find((p) => p.path === path);
4570
+ if (!pathInfo?.typeHint) return null;
4571
+ let currentType = pathInfo.typeHint;
4572
+ for (const filter of filters) {
4573
+ const info = filterMap.get(canonicalFilterName(filter.name));
4574
+ if (!info) return null;
4575
+ if (info.resultType === "passthrough") continue;
4576
+ currentType = info.resultType;
4577
+ }
4578
+ return currentType;
4579
+ }
4580
+ function inferArgType(arg) {
4581
+ const v = arg.trim();
4582
+ if (/^-?\d+(\.\d+)?$/.test(v)) return "number";
4583
+ return "string";
4584
+ }
4585
+ function isLiteral(value) {
4586
+ return /^-?\d/.test(value) || /^["'`]/.test(value) || value === "true" || value === "false" || value === "null";
4587
+ }
4588
+
4589
+ // src/service/stateTypeValidator.ts
4590
+ function validateStateTypes(html, stateTagName = "wcs-state", locale2) {
4591
+ const msgs = getMessages(locale2);
4592
+ const blocks = parseWcsScriptBlocks(html, stateTagName);
4593
+ const diagnostics = [];
4594
+ for (const block of blocks) {
4595
+ const props = findJsDocTypedProperties(block.content);
4596
+ for (const prop of props) {
4597
+ if (!isValueCompatible(prop.declaredTypes, prop.valueType)) {
4598
+ const absStart = block.contentStart + prop.valueOffset;
4599
+ const absEnd = absStart + prop.valueLength;
4418
4600
  diagnostics.push({
4419
- code: WcsDiagnosticCode.FilterUnknown,
4420
- start: item.exprStart + filterOffset,
4421
- end: item.exprStart + filterOffset + filterName.length,
4422
- message: msgs.filterUnknown(filterName),
4601
+ start: absStart,
4602
+ end: absEnd,
4603
+ message: msgs.typeAnnotationIncompatible(prop.valueType, prop.rawType),
4423
4604
  severity: "warning"
4424
4605
  });
4425
4606
  }
@@ -4427,490 +4608,446 @@ function validateTemplateSyntax(html, stateTagName, bindAttrName = "data-wcs", l
4427
4608
  }
4428
4609
  return diagnostics;
4429
4610
  }
4430
- function isValidTemplatePath(path, pathSet, scopedPaths) {
4431
- if (/^\$\d+$/.test(path)) return true;
4432
- if (path.startsWith("$streamStatus.") || path.startsWith("$streamError.")) {
4433
- const prefix = path.startsWith("$streamStatus.") ? "$streamStatus." : "$streamError.";
4434
- const hasNamespace = scopedPaths.some((p) => p.path.startsWith(prefix));
4435
- return !hasNamespace || pathSet.has(path);
4611
+ function findJsDocTypedProperties(script) {
4612
+ const results = [];
4613
+ const regex = /\/\*\*\s*@type\s*\{([^}]+)\}\s*\*\/\s*(?:"([^"]+)"|'([^']+)'|(\w+))\s*:\s*/g;
4614
+ let match;
4615
+ while ((match = regex.exec(script)) !== null) {
4616
+ const rawType = match[1].trim();
4617
+ const name = match[2] ?? match[3] ?? match[4];
4618
+ const valueStart = match.index + match[0].length;
4619
+ const valueText = extractValue(script, valueStart);
4620
+ const valueType = inferValueType(valueText);
4621
+ if (valueType) {
4622
+ const declaredTypes = rawType.split("|").map((t) => normalizeType(t.trim()));
4623
+ results.push({
4624
+ name,
4625
+ rawType,
4626
+ declaredTypes,
4627
+ valueType,
4628
+ valueOffset: valueStart,
4629
+ valueLength: valueText.length
4630
+ });
4631
+ }
4436
4632
  }
4437
- return pathSet.has(path) || matchesRecursionCandidates(scopedPaths, path, pathSet);
4633
+ return results;
4438
4634
  }
4439
-
4440
- // ../state/dist/parser.esm.js
4441
- var DELIMITER2 = ".";
4442
- var WILDCARD2 = "*";
4443
- var MAX_WILDCARD_DEPTH2 = 128;
4444
- var BINDING_SEPARATOR2 = ";";
4445
- var PROP_VALUE_SEPARATOR2 = ":";
4446
- var MODIFIER_SEPARATOR2 = "#";
4447
- var FILTER_SEPARATOR2 = "|";
4448
- var ELSE_KEYWORD2 = "else";
4449
- var SPREAD_PROP2 = "...";
4450
- var EVENT_PROP_PREFIX2 = "on";
4451
- var EVENT_TOKEN_NAMESPACE2 = "eventToken";
4452
- var COMMAND_NAMESPACE2 = "command";
4453
- var CLASS_NAMESPACE2 = "class";
4454
- var ATTR_NAMESPACE2 = "attr";
4455
- var STYLE_NAMESPACE2 = "style";
4456
- var INDEX_PARAM_PREFIX2 = "$";
4457
- var tmpIndexByIndexName2 = {};
4458
- for (let i = 0; i < MAX_WILDCARD_DEPTH2; i++) {
4459
- tmpIndexByIndexName2[`${INDEX_PARAM_PREFIX2}${i + 1}`] = i;
4635
+ function extractValue(script, start) {
4636
+ let depth = 0;
4637
+ let inString = null;
4638
+ let i = start;
4639
+ while (i < script.length) {
4640
+ const ch = script[i];
4641
+ if (inString) {
4642
+ if (ch === inString && script[i - 1] !== "\\") inString = null;
4643
+ } else if (ch === '"' || ch === "'" || ch === "`") {
4644
+ inString = ch;
4645
+ } else if (ch === "{" || ch === "[" || ch === "(") {
4646
+ depth++;
4647
+ } else if (ch === "}" || ch === "]" || ch === ")") {
4648
+ if (depth === 0) break;
4649
+ depth--;
4650
+ } else if ((ch === "," || ch === "\n") && depth === 0) {
4651
+ break;
4652
+ }
4653
+ i++;
4654
+ }
4655
+ return script.slice(start, i).trim();
4460
4656
  }
4461
- Object.freeze(tmpIndexByIndexName2);
4462
- var RECURSION_WILDCARD2 = "**";
4463
- function raiseError2(message) {
4464
- throw new Error(`[@wcstack/state] ${message}`);
4657
+ function inferValueType(value) {
4658
+ const v = value.replace(/,\s*$/, "").trim();
4659
+ if (v === "null") return "null";
4660
+ if (v === "undefined") return "null";
4661
+ if (v === "true" || v === "false") return "boolean";
4662
+ if (/^-?\d+\.\d/.test(v)) return "number";
4663
+ if (/^-?\d/.test(v)) return "number";
4664
+ if (/^["'`]/.test(v)) return "string";
4665
+ if (v.startsWith("[")) return "array";
4666
+ if (v.startsWith("{")) return "object";
4667
+ return null;
4465
4668
  }
4466
- var _cache = /* @__PURE__ */ new Map();
4467
- function clearPathInfoCacheForTooling() {
4468
- _cache.clear();
4669
+ function normalizeType(type) {
4670
+ const lower = type.toLowerCase();
4671
+ if (lower === "null" || lower === "undefined") return "null";
4672
+ if (lower === "string") return "string";
4673
+ if (lower === "number") return "number";
4674
+ if (lower === "boolean") return "boolean";
4675
+ if (lower.endsWith("[]") || lower.startsWith("array")) return "array";
4676
+ if (lower === "object") return "object";
4677
+ return type;
4469
4678
  }
4470
- var id = 0;
4471
- function getPathInfo(path) {
4472
- let pathInfo = _cache.get(path);
4473
- if (typeof pathInfo !== "undefined") {
4474
- return pathInfo;
4475
- }
4476
- if (path.indexOf(RECURSION_WILDCARD2) !== -1) {
4477
- raiseError2(`[wcs/recursion-unsupported] "${path}" uses "${RECURSION_WILDCARD2}", which is not accepted here. It is only meaningful in a $recursion declaration, in a recursive getter key, and in the path argument of $getAll / $setAll \u2014 and only when the state declares a $recursion anchor.`);
4478
- }
4479
- pathInfo = Object.freeze(new PathInfo(path));
4480
- _cache.set(path, pathInfo);
4481
- return pathInfo;
4679
+ function isValueCompatible(declaredTypes, valueType) {
4680
+ return declaredTypes.includes(valueType);
4482
4681
  }
4483
- var PathInfo = class {
4484
- id = ++id;
4485
- path;
4486
- segments;
4487
- lastSegment;
4488
- cumulativePaths;
4489
- cumulativePathSet;
4490
- cumulativePathInfos;
4491
- cumulativePathInfoSet;
4492
- parentPath;
4493
- wildcardPaths;
4494
- wildcardPathSet;
4495
- indexByWildcardPath;
4496
- wildcardPathInfos;
4497
- wildcardPathInfoSet;
4498
- wildcardParentPaths;
4499
- wildcardParentPathSet;
4500
- wildcardParentPathInfos;
4501
- wildcardParentPathInfoSet;
4502
- wildcardPositions;
4503
- lastWildcardPath;
4504
- lastWildcardInfo;
4505
- wildcardCount;
4506
- parentPathInfo;
4507
- constructor(path) {
4508
- const getPattern = (_path) => {
4509
- return path === _path ? this : getPathInfo(_path);
4510
- };
4511
- const segments = path.split(".");
4512
- const cumulativePaths = [];
4513
- const cumulativePathInfos = [];
4514
- const wildcardPaths = [];
4515
- const indexByWildcardPath = {};
4516
- const wildcardPathInfos = [];
4517
- const wildcardParentPaths = [];
4518
- const wildcardParentPathInfos = [];
4519
- const wildcardPositions = [];
4520
- let currentPatternPath = "", prevPatternPath = "";
4521
- let wildcardCount = 0;
4522
- for (let i = 0; i < segments.length; i++) {
4523
- currentPatternPath += segments[i];
4524
- if (segments[i] === WILDCARD2) {
4525
- wildcardPaths.push(currentPatternPath);
4526
- indexByWildcardPath[currentPatternPath] = wildcardCount;
4527
- wildcardPathInfos.push(getPattern(currentPatternPath));
4528
- wildcardParentPaths.push(prevPatternPath);
4529
- wildcardParentPathInfos.push(getPattern(prevPatternPath));
4530
- wildcardPositions.push(i);
4531
- wildcardCount++;
4532
- }
4533
- cumulativePaths.push(currentPatternPath);
4534
- cumulativePathInfos.push(getPattern(currentPatternPath));
4535
- prevPatternPath = currentPatternPath;
4536
- currentPatternPath += ".";
4682
+
4683
+ // src/service/scriptPatterns.ts
4684
+ var ID = String.raw`[\w$]+`;
4685
+ var SUB = String.raw`\s*(?:\?\.)?\s*\[(?!\s*["'])[^\[\]]+\]`;
4686
+ var DOT_SEG = String.raw`\s*\??\.\s*${ID}`;
4687
+ var CHAIN = String.raw`(?:${DOT_SEG}|${SUB})*`;
4688
+ var BRACKETS_ONLY = String.raw`(?:${SUB})+`;
4689
+ var CHAIN_ONE_PLUS = String.raw`(?:${DOT_SEG}|${SUB})+`;
4690
+ var ROOT_DOT = String.raw`\bthis\s*\??\.\s*(${ID})`;
4691
+ var ROOT_BRACKET = String.raw`\bthis\s*(?:\?\.)?\s*\[\s*["']([^"']+)["']\s*\]`;
4692
+ var ASSIGN_TAIL = String.raw`\s*(?:(?:\*\*|<<|>>>|>>|&&|\|\||\?\?|[+\-*/%&|^])?=(?!=)|\+\+|--)`;
4693
+ var PRE_INCDEC = String.raw`(?:\+\+|--)\s*`;
4694
+ function chainToDotted(chain) {
4695
+ const token = new RegExp(String.raw`\s*(?:\??\.\s*(${ID})|(?:\?\.)?\s*\[([^\[\]]+)\])`, "g");
4696
+ let out = "";
4697
+ let match;
4698
+ while ((match = token.exec(chain)) !== null) {
4699
+ if (match[1] !== void 0) {
4700
+ out += `.${match[1]}`;
4701
+ } else {
4702
+ const key = match[2].trim();
4703
+ out += /^\d+$/.test(key) ? `.${key}` : `.<${key}>`;
4537
4704
  }
4538
- const lastWildcardPath = wildcardPaths.length > 0 ? wildcardPaths[wildcardPaths.length - 1] : null;
4539
- const parentPath = cumulativePaths.length > 1 ? cumulativePaths[cumulativePaths.length - 2] : null;
4540
- this.path = path;
4541
- this.segments = segments;
4542
- this.lastSegment = segments[segments.length - 1];
4543
- this.cumulativePaths = cumulativePaths;
4544
- this.cumulativePathSet = new Set(cumulativePaths);
4545
- this.cumulativePathInfos = cumulativePathInfos;
4546
- this.cumulativePathInfoSet = new Set(cumulativePathInfos);
4547
- this.wildcardPaths = wildcardPaths;
4548
- this.wildcardPathSet = new Set(wildcardPaths);
4549
- this.indexByWildcardPath = indexByWildcardPath;
4550
- this.wildcardPathInfos = wildcardPathInfos;
4551
- this.wildcardPathInfoSet = new Set(wildcardPathInfos);
4552
- this.wildcardParentPaths = wildcardParentPaths;
4553
- this.wildcardParentPathSet = new Set(wildcardParentPaths);
4554
- this.wildcardParentPathInfos = wildcardParentPathInfos;
4555
- this.wildcardParentPathInfoSet = new Set(wildcardParentPathInfos);
4556
- this.wildcardPositions = wildcardPositions;
4557
- this.lastWildcardPath = lastWildcardPath;
4558
- this.lastWildcardInfo = lastWildcardPath ? getPattern(lastWildcardPath) : null;
4559
- this.parentPath = parentPath;
4560
- this.parentPathInfo = parentPath ? getPattern(parentPath) : null;
4561
- this.wildcardCount = wildcardCount;
4562
4705
  }
4563
- };
4564
- var LINT_HINT = " Validate statically: npx @wcstack/lint <file>.";
4565
- var STRUCTURAL_BINDING_TYPE_SET2 = /* @__PURE__ */ new Set([
4566
- "if",
4567
- "elseif",
4568
- "else",
4569
- "for"
4570
- ]);
4571
- var resolvedByKey = /* @__PURE__ */ new Map();
4572
- function clearFilterResolutionCache() {
4573
- resolvedByKey.clear();
4706
+ return out;
4574
4707
  }
4575
- function finalizeArg(text, firstQuoteStart, lastQuoteEnd) {
4576
- const startLimit = firstQuoteStart === -1 ? text.length : firstQuoteStart;
4577
- let start = 0;
4578
- while (start < startLimit && /\s/.test(text[start])) {
4579
- start++;
4580
- }
4581
- const endLimit = lastQuoteEnd === -1 ? 0 : lastQuoteEnd;
4582
- let end = text.length;
4583
- while (end > endLimit && /\s/.test(text[end - 1])) {
4584
- end--;
4708
+ function hasDotSegment(chain) {
4709
+ return /[.]/.test(chain.replace(/\s*(?:\?\.)?\s*\[[^\[\]]+\]/g, ""));
4710
+ }
4711
+ function isApiRoot(root) {
4712
+ return root.startsWith("$");
4713
+ }
4714
+ function execAllMasked(pattern, script) {
4715
+ const masked = maskCommentsAndStrings(script);
4716
+ const regex = new RegExp(pattern, "gd");
4717
+ const out = [];
4718
+ let match;
4719
+ while ((match = regex.exec(masked)) !== null) {
4720
+ const indices = match.indices;
4721
+ out.push({
4722
+ index: match.index,
4723
+ length: match[0].length,
4724
+ groups: indices.slice(1).map((pair) => pair === void 0 ? void 0 : script.slice(pair[0], pair[1]))
4725
+ });
4585
4726
  }
4586
- return text.slice(start, end);
4727
+ return out;
4587
4728
  }
4588
- var NUMBER_LITERAL = /^[+-]?(\d+\.?\d*|\.\d+)([eE][+-]?\d+)?$/;
4589
- function toLiteral(text, quoted) {
4590
- if (quoted)
4591
- return text;
4592
- if (text === "true")
4593
- return true;
4594
- if (text === "false")
4595
- return false;
4596
- if (text === "null")
4597
- return null;
4598
- return NUMBER_LITERAL.test(text) ? Number(text) : text;
4729
+
4730
+ // src/service/nestedAssignValidator.ts
4731
+ var NESTED_ASSIGN = `${ROOT_DOT}(${CHAIN_ONE_PLUS})${ASSIGN_TAIL}`;
4732
+ var PRE_NESTED_INCDEC = `${PRE_INCDEC}${ROOT_DOT}(${CHAIN_ONE_PLUS})`;
4733
+ function validateNestedAssigns(html, stateTagName = "wcs-state", locale2) {
4734
+ const msgs = getMessages(locale2);
4735
+ const blocks = parseWcsScriptBlocks(html, stateTagName);
4736
+ const diagnostics = [];
4737
+ for (const block of blocks) {
4738
+ findNestedAssigns(block.content, block.contentStart, msgs, diagnostics);
4739
+ }
4740
+ return diagnostics;
4599
4741
  }
4600
- function parseFilterArgsWithLiterals(argsText) {
4601
- const args = [];
4602
- const literals = [];
4603
- let current2 = "";
4604
- let inQuote = null;
4605
- let hasQuote = false;
4606
- let firstQuoteStart = -1;
4607
- let lastQuoteEnd = -1;
4608
- const flush = () => {
4609
- const arg = finalizeArg(current2, firstQuoteStart, lastQuoteEnd);
4610
- args.push(arg);
4611
- literals.push(toLiteral(arg, hasQuote));
4612
- current2 = "";
4613
- hasQuote = false;
4614
- firstQuoteStart = -1;
4615
- lastQuoteEnd = -1;
4616
- };
4617
- for (let i = 0; i < argsText.length; i++) {
4618
- const char = argsText[i];
4619
- if (inQuote) {
4620
- if (char === inQuote) {
4621
- inQuote = null;
4622
- } else {
4623
- if (firstQuoteStart === -1) {
4624
- firstQuoteStart = current2.length;
4625
- }
4626
- current2 += char;
4627
- lastQuoteEnd = current2.length;
4628
- }
4629
- } else if (char === '"' || char === "'") {
4630
- inQuote = char;
4631
- hasQuote = true;
4632
- } else if (char === ",") {
4633
- flush();
4634
- } else {
4635
- current2 += char;
4742
+ function findNestedAssigns(script, baseOffset, msgs, out) {
4743
+ for (const pattern of [NESTED_ASSIGN, PRE_NESTED_INCDEC]) {
4744
+ for (const match of execAllMasked(pattern, script)) {
4745
+ const [topProp, chainPart] = match.groups;
4746
+ if (topProp === void 0 || chainPart === void 0) continue;
4747
+ if (isApiRoot(topProp)) continue;
4748
+ if (!hasDotSegment(chainPart)) continue;
4749
+ const suggestedPath = topProp + chainToDotted(chainPart);
4750
+ const start = baseOffset + match.index;
4751
+ out.push({
4752
+ start,
4753
+ end: start + match.length,
4754
+ message: msgs.nestedAssign(suggestedPath),
4755
+ severity: "error"
4756
+ });
4636
4757
  }
4637
4758
  }
4638
- if (inQuote !== null) {
4639
- raiseError2(`[wcs/binding-syntax] unterminated ${inQuote} quote in the filter arguments "(${argsText})". Close the quote.${LINT_HINT}`);
4640
- }
4641
- const last = finalizeArg(current2, firstQuoteStart, lastQuoteEnd);
4642
- if (last || hasQuote) {
4643
- args.push(last);
4644
- literals.push(toLiteral(last, hasQuote));
4645
- }
4646
- return { args, literals };
4647
4759
  }
4648
- function clearFilterFnCacheForTooling() {
4649
- clearFilterResolutionCache();
4760
+
4761
+ // src/service/arrayMutationValidator.ts
4762
+ var DESTRUCTIVE_METHODS = "push|pop|shift|unshift|splice|sort|reverse|fill|copyWithin";
4763
+ var ALTERNATIVES = {
4764
+ push: (a) => `${a} = ${a}.concat(item)`,
4765
+ unshift: (a) => `${a} = [item, ...${a}]`,
4766
+ pop: (a) => `${a} = ${a}.slice(0, -1)`,
4767
+ shift: (a) => `${a} = ${a}.slice(1)`,
4768
+ splice: (a) => `${a} = ${a}.toSpliced(...)`,
4769
+ sort: (a) => `${a} = ${a}.toSorted(...)`,
4770
+ reverse: (a) => `${a} = ${a}.toReversed()`,
4771
+ fill: (a) => `${a} = ${a}.map(...)`,
4772
+ copyWithin: (a) => `${a} = ${a}.map(...)`
4773
+ };
4774
+ var METHOD_TAIL = String.raw`\s*\??\.\s*(${DESTRUCTIVE_METHODS})(?=\s*\()`;
4775
+ var DOT_ROOT_CALL = `${ROOT_DOT}(${CHAIN})${METHOD_TAIL}`;
4776
+ var BRACKET_ROOT_CALL = `${ROOT_BRACKET}(${CHAIN})${METHOD_TAIL}`;
4777
+ var DOT_INDEX_ASSIGN = `${ROOT_DOT}(${BRACKETS_ONLY})${ASSIGN_TAIL}`;
4778
+ var BRACKET_INDEX_ASSIGN = `${ROOT_BRACKET}(${BRACKETS_ONLY})${ASSIGN_TAIL}`;
4779
+ var PRE_DOT_INDEX = `${PRE_INCDEC}${ROOT_DOT}(${BRACKETS_ONLY})`;
4780
+ var PRE_BRACKET_INDEX = `${PRE_INCDEC}${ROOT_BRACKET}(${BRACKETS_ONLY})`;
4781
+ function toAccessor(path) {
4782
+ return /^[A-Za-z_]\w*$/.test(path) ? `this.${path}` : `this["${path}"]`;
4650
4783
  }
4651
- function parseFilters(filterTextList, _filterIOType) {
4652
- return filterTextList.map((filterText) => {
4653
- const openParenIndex = filterText.indexOf("(");
4654
- const closeParenIndex = filterText.lastIndexOf(")");
4655
- if (openParenIndex !== -1 && closeParenIndex === -1) {
4656
- raiseError2(`Invalid filter format: missing closing parenthesis in "${filterText}"`);
4657
- }
4658
- if (closeParenIndex !== -1 && openParenIndex === -1) {
4659
- raiseError2(`Invalid filter format: missing opening parenthesis in "${filterText}"`);
4660
- }
4661
- const filterName = (openParenIndex === -1 ? filterText : filterText.substring(0, openParenIndex)).trim();
4662
- if (filterName.length === 0) {
4663
- raiseError2(`[wcs/binding-syntax] an empty filter in "${filterTextList.join("|")}" \u2014 remove the extra "|" or name the filter.${LINT_HINT}`);
4664
- }
4665
- if (openParenIndex === -1) {
4666
- return { filterName, args: [], literals: [] };
4667
- }
4668
- const argsText = filterText.substring(openParenIndex + 1, closeParenIndex);
4669
- return { filterName, ...parseFilterArgsWithLiterals(argsText) };
4670
- });
4784
+ function validateArrayMutations(html, stateTagName = "wcs-state", locale2) {
4785
+ const msgs = getMessages(locale2);
4786
+ const blocks = parseWcsScriptBlocks(html, stateTagName);
4787
+ const diagnostics = [];
4788
+ for (const block of blocks) {
4789
+ findDestructiveCalls(block.content, block.contentStart, msgs, diagnostics);
4790
+ findIndexAssigns(block.content, block.contentStart, msgs, diagnostics);
4791
+ }
4792
+ return diagnostics;
4671
4793
  }
4672
- var trimFn = (s) => s.trim();
4673
- var isQuote = (c) => c === "'" || c === '"';
4674
- function indexOfOutsideQuotes(text, char) {
4675
- let quote = null;
4676
- for (let i = 0; i < text.length; i++) {
4677
- const c = text[i];
4678
- if (quote !== null) {
4679
- if (c === quote)
4680
- quote = null;
4681
- } else if (isQuote(c)) {
4682
- quote = c;
4683
- } else if (c === char) {
4684
- return i;
4794
+ function findDestructiveCalls(script, baseOffset, msgs, out) {
4795
+ for (const pattern of [DOT_ROOT_CALL, BRACKET_ROOT_CALL]) {
4796
+ for (const match of execAllMasked(pattern, script)) {
4797
+ const [root, chain, method] = match.groups;
4798
+ if (root === void 0 || chain === void 0 || method === void 0) continue;
4799
+ if (isApiRoot(root)) continue;
4800
+ const statePath = root + chainToDotted(chain);
4801
+ const start = baseOffset + match.index;
4802
+ out.push({
4803
+ code: WcsDiagnosticCode.ArrayMutation,
4804
+ start,
4805
+ end: start + match.length,
4806
+ message: msgs.arrayMutation(method, ALTERNATIVES[method](toAccessor(statePath))),
4807
+ severity: "error",
4808
+ statePath
4809
+ });
4685
4810
  }
4686
4811
  }
4687
- return -1;
4688
4812
  }
4689
- function splitOutsideQuotes(text, separator) {
4690
- const parts = [];
4691
- let quote = null;
4692
- let start = 0;
4693
- for (let i = 0; i < text.length; i++) {
4694
- const c = text[i];
4695
- if (quote !== null) {
4696
- if (c === quote)
4697
- quote = null;
4698
- } else if (isQuote(c)) {
4699
- quote = c;
4700
- } else if (c === separator) {
4701
- parts.push(text.slice(start, i));
4702
- start = i + 1;
4813
+ function findIndexAssigns(script, baseOffset, msgs, out) {
4814
+ for (const pattern of [DOT_INDEX_ASSIGN, BRACKET_INDEX_ASSIGN, PRE_DOT_INDEX, PRE_BRACKET_INDEX]) {
4815
+ for (const match of execAllMasked(pattern, script)) {
4816
+ const [root, chain] = match.groups;
4817
+ if (root === void 0 || chain === void 0) continue;
4818
+ if (isApiRoot(root)) continue;
4819
+ const suggestedPath = root + chainToDotted(chain);
4820
+ const start = baseOffset + match.index;
4821
+ out.push({
4822
+ code: WcsDiagnosticCode.ArrayIndexAssign,
4823
+ start,
4824
+ end: start + match.length,
4825
+ message: msgs.arrayIndexAssign(suggestedPath),
4826
+ severity: "error",
4827
+ statePath: suggestedPath
4828
+ });
4703
4829
  }
4704
4830
  }
4705
- parts.push(text.slice(start));
4706
- return parts;
4707
- }
4708
- var cacheFilterInfos$1 = /* @__PURE__ */ new Map();
4709
- function clearPropPartCacheForTooling() {
4710
- cacheFilterInfos$1.clear();
4711
4831
  }
4712
- function parsePropPart(propPart) {
4713
- const pos = indexOfOutsideQuotes(propPart, FILTER_SEPARATOR2);
4714
- let propText = "";
4715
- let filterTexts = [];
4716
- let filtersText = "";
4717
- let filters = [];
4718
- if (pos !== -1) {
4719
- propText = propPart.slice(0, pos).trim();
4720
- filtersText = propPart.slice(pos + 1).trim();
4721
- if (cacheFilterInfos$1.has(filtersText)) {
4722
- filters = cacheFilterInfos$1.get(filtersText);
4723
- } else {
4724
- filterTexts = splitOutsideQuotes(filtersText, FILTER_SEPARATOR2).map(trimFn);
4725
- filters = parseFilters(filterTexts);
4726
- cacheFilterInfos$1.set(filtersText, filters);
4832
+
4833
+ // src/service/templateSyntax.ts
4834
+ function findAllMustacheSyntax(html) {
4835
+ const results = [];
4836
+ const regex = /\{\{\s*(.+?)\s*\}\}/g;
4837
+ let match;
4838
+ while ((match = regex.exec(html)) !== null) {
4839
+ if (isInsideTag(html, match.index, "script") || isInsideTag(html, match.index, "style")) {
4840
+ continue;
4727
4841
  }
4728
- } else {
4729
- propText = propPart.trim();
4730
- }
4731
- const modifierParts = propText.split(MODIFIER_SEPARATOR2).map(trimFn);
4732
- if (modifierParts.length > 2) {
4733
- 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}`);
4842
+ const expr = match[1];
4843
+ const exprStart = match.index + match[0].indexOf(expr);
4844
+ results.push({
4845
+ kind: "mustache",
4846
+ expression: expr,
4847
+ exprStart,
4848
+ exprEnd: exprStart + expr.length,
4849
+ matchStart: match.index,
4850
+ matchEnd: match.index + match[0].length,
4851
+ insideTemplate: isInsideTag(html, match.index, "template")
4852
+ });
4734
4853
  }
4735
- const [propName, propModifiersText] = modifierParts;
4736
- const propSegments = propName.split(DELIMITER2).map(trimFn);
4737
- const propModifiers = propModifiersText ? propModifiersText.split(",").map(trimFn) : [];
4738
- return {
4739
- propName,
4740
- propSegments,
4741
- propModifiers,
4742
- inFilters: filters
4743
- };
4854
+ return results;
4744
4855
  }
4745
- var cacheFilterInfos = /* @__PURE__ */ new Map();
4746
- function clearStatePartCacheForTooling() {
4747
- cacheFilterInfos.clear();
4856
+ function findAllCommentBindings(html, commentTextPrefix = "wcs-text") {
4857
+ const results = [];
4858
+ const escaped = commentTextPrefix.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
4859
+ const regex = new RegExp(`<!--\\s*@@\\s*(?:${escaped})?\\s*:\\s*(.+?)\\s*-->`, "g");
4860
+ let match;
4861
+ while ((match = regex.exec(html)) !== null) {
4862
+ const expr = match[1];
4863
+ if (!expr) continue;
4864
+ const exprStart = match.index + match[0].indexOf(expr);
4865
+ results.push({
4866
+ kind: "comment",
4867
+ expression: expr,
4868
+ exprStart,
4869
+ exprEnd: exprStart + expr.length,
4870
+ matchStart: match.index,
4871
+ matchEnd: match.index + match[0].length,
4872
+ insideTemplate: isInsideTag(html, match.index, "template")
4873
+ });
4874
+ }
4875
+ return results;
4748
4876
  }
4749
- function parseStatePart(statePart) {
4750
- const pos = indexOfOutsideQuotes(statePart, FILTER_SEPARATOR2);
4751
- let stateAndPath = "";
4752
- let filterTexts = [];
4753
- let filtersText = "";
4754
- let filters = [];
4755
- if (pos !== -1) {
4756
- stateAndPath = statePart.slice(0, pos).trim();
4757
- filtersText = statePart.slice(pos + 1).trim();
4758
- if (cacheFilterInfos.has(filtersText)) {
4759
- filters = cacheFilterInfos.get(filtersText);
4877
+ function isInsideTag(html, offset2, tagName) {
4878
+ const tagRegex = new RegExp(`<(/?)${tagName}[\\s>]`, "gi");
4879
+ let depth = 0;
4880
+ let match;
4881
+ while ((match = tagRegex.exec(html)) !== null) {
4882
+ if (match.index > offset2) break;
4883
+ if (match[1]) {
4884
+ depth = Math.max(0, depth - 1);
4760
4885
  } else {
4761
- filterTexts = splitOutsideQuotes(filtersText, FILTER_SEPARATOR2).map(trimFn);
4762
- filters = parseFilters(filterTexts);
4763
- cacheFilterInfos.set(filtersText, filters);
4886
+ depth++;
4764
4887
  }
4765
- } else {
4766
- stateAndPath = statePart.trim();
4767
- }
4768
- if (stateAndPath.indexOf("@") !== -1) {
4769
- raiseError2(`"${stateAndPath}": 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 by its path prefix instead.`);
4770
4888
  }
4771
- const statePathName = stateAndPath;
4772
- const pathInfo = getPathInfo(statePathName);
4773
- return {
4774
- statePathName,
4775
- statePathInfo: pathInfo,
4776
- outFilters: filters
4777
- };
4778
- }
4779
- var KEYWORDS_WITHOUT_MODIFIERS = /* @__PURE__ */ new Set([ELSE_KEYWORD2, "if", "elseif", "for", SPREAD_PROP2]);
4780
- var EXPLICIT_PROPERTY_REJECTED_HEADS = /* @__PURE__ */ new Set([
4781
- CLASS_NAMESPACE2,
4782
- ATTR_NAMESPACE2,
4783
- STYLE_NAMESPACE2,
4784
- COMMAND_NAMESPACE2,
4785
- EVENT_TOKEN_NAMESPACE2
4786
- ]);
4787
- function splitBindTexts(bindText) {
4788
- return splitOutsideQuotes(bindText, BINDING_SEPARATOR2);
4889
+ return depth > 0;
4789
4890
  }
4790
- function parseBindTextsForElement(bindText) {
4791
- const [...bindTexts] = splitBindTexts(bindText).map(trimFn).filter((s) => s.length > 0);
4792
- const results = bindTexts.map((bindText2) => {
4793
- const separatorIndex = bindText2.indexOf(PROP_VALUE_SEPARATOR2);
4794
- if (separatorIndex === -1) {
4795
- raiseError2(`Invalid bindText: "${bindText2}". Missing ':' separator between propPart and statePart.`);
4891
+
4892
+ // src/service/templateSyntaxValidator.ts
4893
+ function validateTemplateSyntax(html, stateTagName, bindAttrName = "data-wcs", locale2, fileReader, applicationSchema) {
4894
+ const diagnostics = [];
4895
+ const msgs = getMessages(locale2);
4896
+ const allPaths = mergeSchemaCandidates(getStatePathsFromHtml(html, stateTagName, fileReader), applicationSchema);
4897
+ const defaultSchema = applicationSchema;
4898
+ const missingVerdict = (path, displayPath, pathSet2, scoped) => {
4899
+ if (hasRecursionWildcard(path)) {
4900
+ return {
4901
+ code: WcsDiagnosticCode.RecursionUnsupported,
4902
+ severity: "error",
4903
+ message: msgs.recursionUnsupported(path, "binding")
4904
+ };
4905
+ }
4906
+ if (isValidTemplatePath(path, pathSet2, scoped)) return null;
4907
+ if (defaultSchema !== void 0 && !path.startsWith("$")) {
4908
+ const resolution = resolveSchemaPath(defaultSchema, defaultSchema.$defs ?? {}, path.split("."));
4909
+ return resolution.kind === "nonexistent" ? { code: WcsDiagnosticCode.PathNonexistent, severity: "error", message: msgs.pathNonexistent(displayPath) } : null;
4910
+ }
4911
+ return { code: WcsDiagnosticCode.BindingPathMissing, severity: "warning", message: msgs.pathMissing(displayPath) };
4912
+ };
4913
+ if (allPaths.length === 0) return diagnostics;
4914
+ const defaultPaths = allPaths;
4915
+ const pathSet = new Set(defaultPaths.map((p) => p.path));
4916
+ const filterNameSet = new Set(BUILTIN_FILTERS.map((f) => f.name));
4917
+ const mustaches = findAllMustacheSyntax(html);
4918
+ const comments = findAllCommentBindings(html);
4919
+ for (const item of [...mustaches, ...comments]) {
4920
+ if (item.kind === "comment") {
4921
+ diagnostics.push({
4922
+ code: WcsDiagnosticCode.TemplateSyntax,
4923
+ start: item.matchStart,
4924
+ end: item.matchEnd,
4925
+ message: msgs.wcsTextInfo(item.expression),
4926
+ severity: "info"
4927
+ });
4796
4928
  }
4797
- const propPart = bindText2.slice(0, separatorIndex).trim();
4798
- const statePart = bindText2.slice(separatorIndex + 1).trim();
4799
- const keyword = propPart.split(MODIFIER_SEPARATOR2)[0].split(FILTER_SEPARATOR2)[0].trim();
4800
- if (keyword !== propPart && KEYWORDS_WITHOUT_MODIFIERS.has(keyword)) {
4801
- raiseError2(`[wcs/binding-syntax] "${bindText2}": "${keyword}" takes no modifiers or filters on its left side \u2014 write "${keyword}:".${LINT_HINT}`);
4929
+ if (item.kind === "mustache" && !item.insideTemplate) {
4930
+ diagnostics.push({
4931
+ code: WcsDiagnosticCode.TemplateSyntax,
4932
+ start: item.matchStart,
4933
+ end: item.matchEnd,
4934
+ message: msgs.moustacheFouc(item.expression),
4935
+ severity: "info"
4936
+ });
4802
4937
  }
4803
- if (propPart === ELSE_KEYWORD2) {
4804
- if (statePart.length > 0) {
4805
- raiseError2(`[wcs/binding-syntax] "${bindText2}": "else" takes no value \u2014 write "else:".${LINT_HINT}`);
4806
- }
4807
- const pathInfo = getPathInfo("#else");
4808
- return {
4809
- propName: ELSE_KEYWORD2,
4810
- propSegments: [ELSE_KEYWORD2],
4811
- propModifiers: [],
4812
- statePathName: "#else",
4813
- statePathInfo: pathInfo,
4814
- inFilters: [],
4815
- outFilters: [],
4816
- bindingType: "else"
4817
- };
4818
- } else if (propPart === SPREAD_PROP2) {
4819
- const stateResult = parseStatePart(statePart);
4820
- if (stateResult.outFilters.length > 0) {
4821
- raiseError2(`Invalid spread binding "${bindText2}": filters are not allowed on spread targets.`);
4938
+ if (!item.expression) continue;
4939
+ const parts = splitOutsideQuotes2(item.expression, "|");
4940
+ const pathPart = (parts[0] || "").trim();
4941
+ if (pathPart.includes("@")) continue;
4942
+ const insideFor = item.insideTemplate && isInsideForTemplate(html, item.matchStart, bindAttrName);
4943
+ if (pathPart && !/^-?\d|^["'`]|^true$|^false$|^null$/.test(pathPart)) {
4944
+ if (!insideFor && pathPart.includes("*")) {
4945
+ diagnostics.push({
4946
+ code: WcsDiagnosticCode.TemplateSyntax,
4947
+ start: item.exprStart,
4948
+ end: item.exprStart + pathPart.length,
4949
+ message: msgs.patternPathOutsideFor(pathPart),
4950
+ severity: "warning"
4951
+ });
4822
4952
  }
4823
- if (stateResult.statePathName.length === 0) {
4824
- raiseError2(`Invalid spread binding "${bindText2}": spread target path is required.`);
4953
+ if (!insideFor && pathPart.startsWith(".")) {
4954
+ diagnostics.push({
4955
+ code: WcsDiagnosticCode.TemplateSyntax,
4956
+ start: item.exprStart,
4957
+ end: item.exprStart + pathPart.length,
4958
+ message: msgs.omittedPathOutsideFor(pathPart),
4959
+ severity: "warning"
4960
+ });
4825
4961
  }
4826
- return {
4827
- propName: SPREAD_PROP2,
4828
- propSegments: [SPREAD_PROP2],
4829
- propModifiers: [],
4830
- inFilters: [],
4831
- ...stateResult,
4832
- bindingType: "spread"
4833
- };
4834
- } else if (propPart === "if" || propPart === "elseif" || propPart === "for") {
4835
- const stateResult = parseStatePart(statePart);
4836
- return {
4837
- propName: propPart,
4838
- propSegments: [propPart],
4839
- propModifiers: [],
4840
- inFilters: [],
4841
- ...stateResult,
4842
- bindingType: propPart
4843
- };
4844
- } else if (keyword === "radio" || keyword === "checkbox") {
4845
- const stateResult = parseStatePart(statePart);
4846
- const propResult = parsePropPart(propPart);
4847
- return {
4848
- ...propResult,
4849
- ...stateResult,
4850
- bindingType: keyword
4851
- };
4852
- } else {
4853
- const stateResult = parseStatePart(statePart);
4854
- const propResult = parsePropPart(propPart);
4855
- if (propResult.propSegments[0] === "" && propResult.propSegments.length > 1) {
4856
- const propSegments = propResult.propSegments.slice(1);
4857
- if (propSegments.includes("") || EXPLICIT_PROPERTY_REJECTED_HEADS.has(propSegments[0])) {
4858
- 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}`);
4962
+ if (insideFor && !pathPart.startsWith(".")) {
4963
+ const indexMatch = /^\$(\d+)$/.exec(pathPart);
4964
+ const needed = indexMatch !== null ? Number(indexMatch[1]) : pathPart.includes("*") ? countWildcardSegments(pathPart) : 0;
4965
+ if (needed > 0) {
4966
+ const available = getAvailableWildcardRank(html, item.matchStart, bindAttrName);
4967
+ if (available > 0 && needed > available) {
4968
+ diagnostics.push({
4969
+ code: WcsDiagnosticCode.WildcardRank,
4970
+ start: item.exprStart,
4971
+ end: item.exprStart + pathPart.length,
4972
+ message: msgs.wildcardRank(`"${pathPart}"`, needed, available),
4973
+ severity: "warning"
4974
+ });
4975
+ }
4859
4976
  }
4860
- return {
4861
- ...propResult,
4862
- propName: propSegments.join(DELIMITER2),
4863
- propSegments,
4864
- ...stateResult,
4865
- bindingType: "prop"
4866
- };
4867
4977
  }
4868
- if (propResult.propSegments[0] === EVENT_TOKEN_NAMESPACE2) {
4869
- return {
4870
- ...propResult,
4871
- ...stateResult,
4872
- bindingType: "event"
4873
- };
4978
+ if (/\.\d+\.|\.\d+$/.test(pathPart)) {
4979
+ diagnostics.push({
4980
+ code: WcsDiagnosticCode.TemplateSyntax,
4981
+ start: item.exprStart,
4982
+ end: item.exprStart + pathPart.length,
4983
+ message: msgs.resolvedPathInUi(pathPart),
4984
+ severity: "warning"
4985
+ });
4874
4986
  }
4875
- if (propResult.propSegments[0].startsWith(EVENT_PROP_PREFIX2)) {
4876
- return {
4877
- ...propResult,
4878
- ...stateResult,
4879
- bindingType: "event"
4880
- };
4987
+ if (pathPart.startsWith(".")) {
4988
+ const forPath = insideFor ? getInnermostForPath(html, item.matchStart, bindAttrName) : null;
4989
+ if (forPath && !forPath.startsWith(".")) {
4990
+ const expandedPath = pathPart === "." ? `${forPath}.*` : `${forPath}.*.${pathPart.slice(1)}`;
4991
+ const verdict = missingVerdict(expandedPath, pathPart, pathSet, defaultPaths);
4992
+ if (verdict) {
4993
+ diagnostics.push({
4994
+ code: verdict.code,
4995
+ start: item.exprStart,
4996
+ end: item.exprStart + pathPart.length,
4997
+ message: verdict.message + msgs.expansionSuffix(expandedPath),
4998
+ severity: verdict.severity
4999
+ });
5000
+ }
5001
+ }
4881
5002
  } else {
4882
- return {
4883
- ...propResult,
4884
- ...stateResult,
4885
- bindingType: "prop"
4886
- };
5003
+ const verdict = missingVerdict(pathPart, pathPart, pathSet, defaultPaths);
5004
+ if (verdict) {
5005
+ diagnostics.push({
5006
+ code: verdict.code,
5007
+ start: item.exprStart,
5008
+ end: item.exprStart + pathPart.length,
5009
+ message: verdict.message,
5010
+ severity: verdict.severity
5011
+ });
5012
+ }
4887
5013
  }
4888
5014
  }
4889
- });
4890
- if (results.length > 1) {
4891
- const isIncludeSingleBinding = results.some((r) => STRUCTURAL_BINDING_TYPE_SET2.has(r.bindingType));
4892
- if (isIncludeSingleBinding) {
4893
- raiseError2(`[wcs/template-syntax] Invalid bindText: "${bindText}". 'if', 'elseif', 'else', and 'for' bindings must be single binding. Put the structural binding alone in its own data-wcs (e.g. <template data-wcs="for: items">).${LINT_HINT}`);
5015
+ let segmentStart = parts[0].length + 1;
5016
+ for (let i = 1; i < parts.length; i++) {
5017
+ const segment = parts[i];
5018
+ const filterName = segment.trim().replace(/\(.*$/, "");
5019
+ const filterOffset = segmentStart + (segment.length - segment.trimStart().length);
5020
+ segmentStart += segment.length + 1;
5021
+ const canonical = canonicalFilterName(filterName);
5022
+ if (filterName && canonical !== filterName && filterNameSet.has(canonical)) {
5023
+ diagnostics.push({
5024
+ code: WcsDiagnosticCode.NameAlias,
5025
+ start: item.exprStart + filterOffset,
5026
+ end: item.exprStart + filterOffset + filterName.length,
5027
+ message: msgs.nameAlias(filterName, canonical),
5028
+ severity: "info"
5029
+ });
5030
+ } else if (filterName && !filterNameSet.has(filterName)) {
5031
+ diagnostics.push({
5032
+ code: WcsDiagnosticCode.FilterUnknown,
5033
+ start: item.exprStart + filterOffset,
5034
+ end: item.exprStart + filterOffset + filterName.length,
5035
+ message: msgs.filterUnknown(filterName),
5036
+ severity: "warning"
5037
+ });
5038
+ }
4894
5039
  }
4895
5040
  }
4896
- return results;
4897
- }
4898
- function parseBindTextForEmbeddedNode(bindText) {
4899
- const stateResult = parseStatePart(bindText);
4900
- return {
4901
- propName: "textContent",
4902
- propSegments: ["textContent"],
4903
- propModifiers: [],
4904
- inFilters: [],
4905
- ...stateResult,
4906
- bindingType: "text"
4907
- };
5041
+ return diagnostics;
4908
5042
  }
4909
- function clearParserCaches() {
4910
- clearPathInfoCacheForTooling();
4911
- clearPropPartCacheForTooling();
4912
- clearStatePartCacheForTooling();
4913
- clearFilterFnCacheForTooling();
5043
+ function isValidTemplatePath(path, pathSet, scopedPaths) {
5044
+ if (/^\$\d+$/.test(path)) return true;
5045
+ if (path.startsWith("$streamStatus.") || path.startsWith("$streamError.")) {
5046
+ const prefix = path.startsWith("$streamStatus.") ? "$streamStatus." : "$streamError.";
5047
+ const hasNamespace = scopedPaths.some((p) => p.path.startsWith(prefix));
5048
+ return !hasNamespace || pathSet.has(path);
5049
+ }
5050
+ return pathSet.has(path) || matchesRecursionCandidates(scopedPaths, path, pathSet);
4914
5051
  }
4915
5052
 
4916
5053
  // src/core/parser/positionalParser.ts
@@ -4933,7 +5070,7 @@ function parseEmbeddedTextWithPositions(expression) {
4933
5070
  if (parsed === null) {
4934
5071
  return { exprRange, exprText: expression, parsed, error, propRange: null, pathRange: null };
4935
5072
  }
4936
- const firstPipe = expression.indexOf(delimiters.filter);
5073
+ const firstPipe = indexOfOutsideQuotes2(expression, delimiters.filter);
4937
5074
  const pathScopeEnd = firstPipe === -1 ? expression.length : firstPipe;
4938
5075
  const pathLocal = locate(expression, parsed.statePathName, 0, pathScopeEnd);
4939
5076
  return {
@@ -4967,14 +5104,14 @@ function parseBindTextWithPositions(bindText) {
4967
5104
  results.push({ exprRange, exprText: expr, parsed, error, propRange: null, pathRange: null });
4968
5105
  continue;
4969
5106
  }
4970
- const colon = expr.indexOf(delimiters.propValue);
5107
+ const colon = indexOfOutsideQuotes2(expr, delimiters.propValue);
4971
5108
  const propEndLimit = colon === -1 ? expr.length : colon;
4972
5109
  const propLocal = locate(expr, parsed.propName, 0, propEndLimit);
4973
5110
  let pathLocal = null;
4974
5111
  if (colon !== -1) {
4975
5112
  const stateBase = colon + 1;
4976
- const firstPipe = expr.indexOf(delimiters.filter, stateBase);
4977
- const pathScopeEnd = firstPipe === -1 ? expr.length : firstPipe;
5113
+ const rhsPipe = indexOfOutsideQuotes2(expr.slice(stateBase), delimiters.filter);
5114
+ const pathScopeEnd = rhsPipe === -1 ? expr.length : stateBase + rhsPipe;
4978
5115
  pathLocal = locate(expr, parsed.statePathName, stateBase, pathScopeEnd);
4979
5116
  }
4980
5117
  const lift = (range) => range === null ? null : { start: exprStart + range.start, end: exprStart + range.end };
@@ -5016,13 +5153,13 @@ function validateBindingSyntax(html, bindAttrName = "data-wcs", locale2) {
5016
5153
  });
5017
5154
  }
5018
5155
  }
5019
- for (const mustache of findAllMustacheSyntax(html)) {
5020
- const detail = bindingSyntaxDetail(parseEmbeddedTextWithPositions(mustache.expression).error);
5156
+ for (const item of [...findAllMustacheSyntax(html), ...findAllCommentBindings(html)]) {
5157
+ const detail = bindingSyntaxDetail(parseEmbeddedTextWithPositions(item.expression).error);
5021
5158
  if (detail === null) continue;
5022
5159
  diagnostics.push({
5023
5160
  code: WcsDiagnosticCode.BindingSyntax,
5024
- start: mustache.exprStart,
5025
- end: mustache.exprStart + mustache.expression.length,
5161
+ start: item.exprStart,
5162
+ end: item.exprStart + item.expression.length,
5026
5163
  message: msgs.bindingSyntax(detail),
5027
5164
  severity: "error"
5028
5165
  });
@@ -6317,6 +6454,13 @@ var DOM_COMMON_PROPERTIES = /* @__PURE__ */ new Set([
6317
6454
  "className"
6318
6455
  ]);
6319
6456
  var STRUCTURAL_DIRECTIVES2 = /* @__PURE__ */ new Set(["for", "if", "elseif", "else"]);
6457
+ var EXPLICIT_PROPERTY_REJECTED_HEADS2 = /* @__PURE__ */ new Set([
6458
+ ...Object.values(getWcsManifest().syntax.bindingTypes.propNamespaces),
6459
+ "state"
6460
+ ]);
6461
+ var EXPLICIT_PROPERTY_REJECTED_HEADS_RE = new RegExp(
6462
+ `^(?:${[...EXPLICIT_PROPERTY_REJECTED_HEADS2].map((w) => w.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|")})(\\.|$)`
6463
+ );
6320
6464
  var EMPTYISH_SEEDS = /* @__PURE__ */ new Set(["''", '""', "``", "null", "[]", "{}"]);
6321
6465
  function validateIoNodes(html, bindAttribute = "data-wcs", stateTagName = "wcs-state", locale2, fileReader) {
6322
6466
  const diagnostics = [];
@@ -6383,7 +6527,8 @@ function validateBindingAgainstContract(tagName, contract, parsed, property, sta
6383
6527
  const explicit = property.startsWith(".") && property !== "...";
6384
6528
  if (explicit) {
6385
6529
  property = property.slice(1);
6386
- if (/^(class|style|attr|command|eventToken)(\.|$)/.test(property)) return;
6530
+ if (property.length === 0 || property.startsWith(".")) return;
6531
+ if (EXPLICIT_PROPERTY_REJECTED_HEADS_RE.test(property)) return;
6387
6532
  }
6388
6533
  if (property === "...") return;
6389
6534
  if (STRUCTURAL_DIRECTIVES2.has(property)) return;
@@ -6397,7 +6542,7 @@ function validateBindingAgainstContract(tagName, contract, parsed, property, sta
6397
6542
  severity: "warning",
6398
6543
  tag: tagName,
6399
6544
  member: property,
6400
- message: msgs.onPrefixedMember(property, tagName)
6545
+ message: msgs.onPrefixedMember(property, tagName, modifiers)
6401
6546
  });
6402
6547
  }
6403
6548
  return;
@@ -7168,12 +7313,12 @@ function createApiCallRegex(apis) {
7168
7313
  }
7169
7314
 
7170
7315
  // src/service/recursionValidator.ts
7171
- var RECURSION_APIS = ["getAll", "setAll", "resolve", "postUpdate", "trackDependency"];
7316
+ var RECURSION_APIS = ["getAll", "setAll", "resolve", "postUpdate", "dependOn", "trackDependency"];
7172
7317
  var UNSUPPORTED_API_SITE = {
7173
7318
  $resolve: "resolve",
7174
7319
  $postUpdate: "postUpdate",
7175
- $trackDependency: "trackDependency",
7176
- $dependOn: "trackDependency"
7320
+ $dependOn: "dependOn",
7321
+ $trackDependency: "trackDependency"
7177
7322
  };
7178
7323
  var BRACKET_ASSIGNMENT = new RegExp(`${ROOT_BRACKET}${ASSIGN_TAIL}`, "g");
7179
7324
  var PRE_BRACKET_INCDEC = new RegExp(`${PRE_INCDEC}${ROOT_BRACKET}`, "g");
@@ -7431,12 +7576,12 @@ function validateListKeys(script, offset2, msgs, out) {
7431
7576
  }
7432
7577
  }
7433
7578
  function validateApiCalls(script, offset2, spec, getterSuffixes, undeclared, msgs, out) {
7434
- const scan = blankComments(script);
7579
+ const scan = maskCommentsAndStrings(script);
7435
7580
  const regex = createApiCallRegex(RECURSION_APIS);
7436
7581
  let match;
7437
7582
  while ((match = regex.exec(scan)) !== null) {
7438
7583
  const api = `$${match[1]}`;
7439
- const parsed = splitCallArgs(scan, match.index + match[0].length);
7584
+ const parsed = splitCallArgs(script, match.index + match[0].length);
7440
7585
  if (parsed === null) continue;
7441
7586
  regex.lastIndex = parsed.end;
7442
7587
  if (parsed.args.length === 0) continue;
@@ -7589,19 +7734,10 @@ function isFunctionLiteral(arg) {
7589
7734
 
7590
7735
  // src/service/namedStateValidator.ts
7591
7736
  function findStateSelector(expr, embedded = false) {
7592
- const colon = embedded ? -1 : expr.indexOf(":");
7737
+ const colon = embedded ? -1 : indexOfOutsideQuotes2(expr, ":");
7593
7738
  const from = colon + 1;
7594
- let depth = 0;
7595
- let end = expr.length;
7596
- for (let i = from; i < expr.length; i++) {
7597
- const ch = expr[i];
7598
- if (ch === "(") depth++;
7599
- else if (ch === ")") depth = Math.max(0, depth - 1);
7600
- else if (ch === "|" && depth === 0) {
7601
- end = i;
7602
- break;
7603
- }
7604
- }
7739
+ const pipe = indexOfOutsideQuotes2(expr.slice(from), "|");
7740
+ const end = pipe === -1 ? expr.length : from + pipe;
7605
7741
  const at2 = expr.indexOf("@", from);
7606
7742
  if (at2 === -1 || at2 >= end) return null;
7607
7743
  const raw = expr.slice(at2 + 1, end);
@@ -13703,6 +13839,64 @@ function visitDestructure(pattern, prefix, scope, out) {
13703
13839
  emit(out, segments, { form: "destructure", callee: false, written: false }, property.start, property.end);
13704
13840
  }
13705
13841
  }
13842
+ function collectThisMemberRefs(body, names) {
13843
+ let program;
13844
+ try {
13845
+ program = parse3(PREFIX + body + SUFFIX, { ecmaVersion: "latest", sourceType: "module" });
13846
+ } catch {
13847
+ return null;
13848
+ }
13849
+ const fn = unwrapWrapper(program);
13850
+ if (fn === null) return null;
13851
+ return walkThisMembers(fn.body, names, PREFIX.length);
13852
+ }
13853
+ var VALUE_PREFIX = "(async function* () {\n(";
13854
+ var VALUE_SUFFIX = ")\n})";
13855
+ function collectThisMemberRefsInValue(value, names) {
13856
+ let program;
13857
+ try {
13858
+ program = parse3(VALUE_PREFIX + value + VALUE_SUFFIX, { ecmaVersion: "latest", sourceType: "module" });
13859
+ } catch {
13860
+ return null;
13861
+ }
13862
+ const wrapper = unwrapWrapper(program);
13863
+ if (wrapper === null || wrapper.body.body.length !== 1) return null;
13864
+ const statement = wrapper.body.body[0];
13865
+ if (statement.type !== "ExpressionStatement") return null;
13866
+ const expression = statement.expression;
13867
+ if (expression.type !== "FunctionExpression") return [];
13868
+ return walkThisMembers(expression.body, names, VALUE_PREFIX.length);
13869
+ }
13870
+ function walkThisMembers(root, names, offsetShift) {
13871
+ const out = [];
13872
+ const walk = (node, thisIsState) => {
13873
+ if (isClassNode(node)) return;
13874
+ if (isFunctionNode(node)) {
13875
+ const inner = node.type === "ArrowFunctionExpression" ? thisIsState : false;
13876
+ forEachChild(node, (child) => walk(child, inner));
13877
+ return;
13878
+ }
13879
+ if (thisIsState && node.type === "MemberExpression" && node.object.type === "ThisExpression") {
13880
+ const hit = thisMemberName(node);
13881
+ if (hit !== null && names.has(hit.name)) {
13882
+ out.push({ name: hit.name, start: hit.start - offsetShift, end: hit.end - offsetShift });
13883
+ }
13884
+ }
13885
+ forEachChild(node, (child) => walk(child, thisIsState));
13886
+ };
13887
+ walk(root, true);
13888
+ return out;
13889
+ }
13890
+ function thisMemberName(node) {
13891
+ const property = node.property;
13892
+ if (!node.computed && property.type === "Identifier") {
13893
+ return { name: property.name, start: property.start, end: property.end };
13894
+ }
13895
+ if (node.computed && property.type === "Literal" && typeof property.value === "string") {
13896
+ return { name: property.value, start: property.start + 1, end: property.end - 1 };
13897
+ }
13898
+ return null;
13899
+ }
13706
13900
 
13707
13901
  // src/core/index/referenceIndex.ts
13708
13902
  function buildReferenceIndex(html, options = {}) {
@@ -13815,10 +14009,30 @@ var STATE_UPDATED_CALLBACKS = /* @__PURE__ */ new Set(["$renderedCallback", "$up
13815
14009
  var OLD_API_NAMES = { $trackDependency: "$dependOn", $untrackDependency: "$untracked" };
13816
14010
  var OLD_DECLARATION_KEYS = { $streams: "$stream", $updatedCallback: "$renderedCallback" };
13817
14011
  var OLD_API_CALL = /\.\s*(\$trackDependency|\$untrackDependency)\b/g;
13818
- var OLD_DECLARATION_KEY = /(^|[{,\s])(\$streams|\$updatedCallback)(?=\s*[:(])/g;
14012
+ var OLD_DECLARATION_MEMBER = /\.\s*(\$streams|\$updatedCallback)\b/g;
14013
+ var OLD_DECLARATION_MEMBER_NAMES = new Set(Object.keys(OLD_DECLARATION_KEYS));
14014
+ var OLD_DECLARATION_KEY = /(^|[{,;\s])(\$streams|\$updatedCallback)(?=\s*(?:[:(]|=(?!=)))/g;
14015
+ function collectDeclarationAliasReads(script) {
14016
+ if (!hasDefaultExportObject(script)) return null;
14017
+ const out = [];
14018
+ const take = (refs, base) => {
14019
+ if (refs === null) return false;
14020
+ for (const ref2 of refs) out.push({ name: ref2.name, start: base + ref2.start });
14021
+ return true;
14022
+ };
14023
+ for (const callable of analyzeCallableBodies(script)) {
14024
+ if (!take(collectThisMemberRefs(callable.body, OLD_DECLARATION_MEMBER_NAMES), callable.bodyStart)) return null;
14025
+ }
14026
+ for (const handler of analyzeWatchHandlerSources(script)) {
14027
+ const refs = handler.kind === "body" ? collectThisMemberRefs(handler.text, OLD_DECLARATION_MEMBER_NAMES) : collectThisMemberRefsInValue(handler.text, OLD_DECLARATION_MEMBER_NAMES);
14028
+ if (!take(refs, handler.start)) return null;
14029
+ }
14030
+ out.sort((a, b) => a.start - b.start);
14031
+ return out;
14032
+ }
13819
14033
  function validateNameAliases(script, scriptStart, locale2) {
13820
14034
  const msgs = getMessages(locale2);
13821
- const scan = blankComments(script);
14035
+ const scan = maskCommentsAndStrings(script);
13822
14036
  const out = [];
13823
14037
  const push2 = (name, canonical, offset2) => {
13824
14038
  out.push({
@@ -13829,14 +14043,55 @@ function validateNameAliases(script, scriptStart, locale2) {
13829
14043
  severity: "info"
13830
14044
  });
13831
14045
  };
14046
+ const pushRead = (name, offset2, shape) => {
14047
+ const canonical = OLD_DECLARATION_KEYS[name];
14048
+ out.push({
14049
+ code: WcsDiagnosticCode.DeclarationAliasRead,
14050
+ start: scriptStart + offset2,
14051
+ end: scriptStart + offset2 + name.length,
14052
+ message: shape === "own" ? msgs.declarationAliasRead(name, canonical) : msgs.declarationAliasReadUncertain(name, canonical),
14053
+ severity: shape === "own" ? "warning" : "info"
14054
+ });
14055
+ };
13832
14056
  OLD_API_CALL.lastIndex = 0;
13833
14057
  let match;
13834
14058
  while ((match = OLD_API_CALL.exec(scan)) !== null) {
13835
- push2(match[1], OLD_API_NAMES[match[1]], match.index + match[0].length - match[1].length);
14059
+ const written = match[1];
14060
+ push2(written, OLD_API_NAMES[written], match.index + match[0].length - written.length);
14061
+ }
14062
+ const astReads = collectDeclarationAliasReads(script);
14063
+ if (astReads !== null) {
14064
+ for (const read of astReads) pushRead(read.name, read.start, "own");
14065
+ } else {
14066
+ OLD_DECLARATION_MEMBER.lastIndex = 0;
14067
+ while ((match = OLD_DECLARATION_MEMBER.exec(scan)) !== null) {
14068
+ const written = match[1];
14069
+ pushRead(written, match.index + match[0].length - written.length, "unknown");
14070
+ }
14071
+ }
14072
+ const spans = analyzeDeclarationSpans(script);
14073
+ if (spans.length === 0) {
14074
+ OLD_DECLARATION_KEY.lastIndex = 0;
14075
+ while ((match = OLD_DECLARATION_KEY.exec(scan)) !== null) {
14076
+ push2(match[2], OLD_DECLARATION_KEYS[match[2]], match.index + match[1].length);
14077
+ }
14078
+ return out;
13836
14079
  }
13837
- OLD_DECLARATION_KEY.lastIndex = 0;
13838
- while ((match = OLD_DECLARATION_KEY.exec(scan)) !== null) {
13839
- push2(match[2], OLD_DECLARATION_KEYS[match[2]], match.index + match[1].length);
14080
+ const declared = new Set(spans.map((span) => span.name));
14081
+ for (const span of spans) {
14082
+ const canonical = OLD_DECLARATION_KEYS[span.name];
14083
+ if (canonical === void 0) continue;
14084
+ if (declared.has(canonical)) {
14085
+ out.push({
14086
+ code: WcsDiagnosticCode.DeclarationAlias,
14087
+ start: scriptStart + span.start,
14088
+ end: scriptStart + span.end,
14089
+ message: msgs.declarationAlias(span.name, canonical),
14090
+ severity: "error"
14091
+ });
14092
+ continue;
14093
+ }
14094
+ push2(span.name, canonical, span.start);
13840
14095
  }
13841
14096
  return out;
13842
14097
  }
@@ -13844,9 +14099,10 @@ var API_CALL = /\.\s*\$(getAll|setAll|resolve)\s*\(/g;
13844
14099
  function validateIndexArity(script, scriptStart, locale2) {
13845
14100
  const msgs = getMessages(locale2);
13846
14101
  const out = [];
14102
+ const scan = maskCommentsAndStrings(script);
13847
14103
  API_CALL.lastIndex = 0;
13848
14104
  let match;
13849
- while ((match = API_CALL.exec(script)) !== null) {
14105
+ while ((match = API_CALL.exec(scan)) !== null) {
13850
14106
  const api = `$${match[1]}`;
13851
14107
  const parsed = splitCallArgs(script, match.index + match[0].length);
13852
14108
  if (parsed === null) continue;
@@ -13996,18 +14252,21 @@ function collectNestedWriteRoots(html, stateTagName, bindAttrName, blocks) {
13996
14252
  }
13997
14253
  for (const block of blocks) {
13998
14254
  if (block.mountPath !== null) addPrefixes(block.mountPath, true);
13999
- const scan = blankComments(block.content);
14255
+ const scan = maskCommentsAndStrings(block.content);
14000
14256
  for (const regex of [BRACKET_WRITE, PRE_BRACKET_INCDEC2]) {
14001
14257
  regex.lastIndex = 0;
14002
14258
  let match;
14003
- while ((match = regex.exec(scan)) !== null) addPrefixes(match[1], false);
14259
+ while ((match = regex.exec(scan)) !== null) {
14260
+ const keyStart = match.index + match[0].search(/["']/) + 1;
14261
+ addPrefixes(block.content.slice(keyStart, keyStart + match[1].length), false);
14262
+ }
14004
14263
  }
14005
14264
  API_CALL.lastIndex = 0;
14006
14265
  let call;
14007
14266
  while ((call = API_CALL.exec(scan)) !== null) {
14008
14267
  const api = call[1];
14009
14268
  if (api === "getAll") continue;
14010
- const parsed = splitCallArgs(scan, call.index + call[0].length);
14269
+ const parsed = splitCallArgs(block.content, call.index + call[0].length);
14011
14270
  if (parsed === null) continue;
14012
14271
  API_CALL.lastIndex = parsed.end;
14013
14272
  const path = parsed.args.length > 0 ? literalString(parsed.args[0]) : null;