@wcstack/lint 1.32.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.ja.md +31 -3
- package/README.md +31 -3
- package/dist/cli.cjs +998 -567
- package/package.json +1 -1
package/dist/cli.cjs
CHANGED
|
@@ -96,6 +96,9 @@ var WcsDiagnosticCode = {
|
|
|
96
96
|
// 同名 tag / filter の後勝ち禁止(§5-3)。override:true が無い再定義もこの collision で表す。
|
|
97
97
|
ManifestTagCollision: "wcs/manifest-tag-collision",
|
|
98
98
|
ManifestFilterCollision: "wcs/manifest-filter-collision",
|
|
99
|
+
// 同名 state の stateSchema が複数の application artifact に宣言されている(§5-3 の
|
|
100
|
+
// application 版・D8)。勝者なし: その state は未宣言扱い(schema 検証は沈黙)。
|
|
101
|
+
ManifestStateCollision: "wcs/manifest-state-collision",
|
|
99
102
|
// 明示 override:true(§5-4)。衝突ではなく意図的な shadow の告知(info)。
|
|
100
103
|
ManifestOverride: "wcs/manifest-override",
|
|
101
104
|
// --- sidecar vs live declaration drift ---
|
|
@@ -168,7 +171,15 @@ var WcsDiagnosticCode = {
|
|
|
168
171
|
// router/auto があるのに <base href> がない(SPA の basename 誤導出)。
|
|
169
172
|
BaseHrefMissing: "wcs/base-href-missing",
|
|
170
173
|
// @wcstack/signals と /dom エントリの同一ページ混在(リアクティブコア二重化)。
|
|
171
|
-
SignalsDualEntry: "wcs/signals-dual-entry"
|
|
174
|
+
SignalsDualEntry: "wcs/signals-dual-entry",
|
|
175
|
+
// --- deprecations ---
|
|
176
|
+
// 名前付き State(`<wcs-state name>` / `path@name`)。v2 でマウント(`mount=` と接頭辞付きパス)に
|
|
177
|
+
// 置き換わる(docs/state-mount-design.md D16)。1.x では warning、v2 では parse error と同時に error。
|
|
178
|
+
NamedStateDeprecated: "wcs/named-state-deprecated",
|
|
179
|
+
// --- volume mount ---
|
|
180
|
+
// `<wcs-state mount="...">` の値が runtime の validateVolumeMountPath で raise する形
|
|
181
|
+
// (空・空セグメント・ワイルドカード・予約文字 $ # @)。runtime と同条件・同文言(v2)。
|
|
182
|
+
MountPathInvalid: "wcs/mount-path-invalid"
|
|
172
183
|
};
|
|
173
184
|
function sortDiagnostics(diagnostics) {
|
|
174
185
|
const severityRank = { error: 0, warning: 1, info: 2 };
|
|
@@ -784,7 +795,6 @@ var MAX_WILDCARD_DEPTH = 128;
|
|
|
784
795
|
var BINDING_SEPARATOR = ";";
|
|
785
796
|
var PROP_VALUE_SEPARATOR = ":";
|
|
786
797
|
var MODIFIER_SEPARATOR = "#";
|
|
787
|
-
var STATE_NAME_SEPARATOR = "@";
|
|
788
798
|
var FILTER_SEPARATOR = "|";
|
|
789
799
|
var MODIFIER_PREVENT = "prevent";
|
|
790
800
|
var MODIFIER_STOP = "stop";
|
|
@@ -842,7 +852,6 @@ function getWcsManifest() {
|
|
|
842
852
|
binding: BINDING_SEPARATOR,
|
|
843
853
|
propValue: PROP_VALUE_SEPARATOR,
|
|
844
854
|
modifier: MODIFIER_SEPARATOR,
|
|
845
|
-
stateName: STATE_NAME_SEPARATOR,
|
|
846
855
|
filter: FILTER_SEPARATOR
|
|
847
856
|
},
|
|
848
857
|
// 正本 STRUCTURAL_BINDING_TYPE_SET から導出(手書きの二重定義を排除)。
|
|
@@ -909,233 +918,13 @@ var STRUCTURAL_DIRECTIVES = [...STRUCTURAL_BINDING_TYPE_SET].map((name) => ({
|
|
|
909
918
|
...STRUCTURAL_DIRECTIVE_INFO[name]
|
|
910
919
|
}));
|
|
911
920
|
|
|
912
|
-
// src/language/htmlParse.ts
|
|
913
|
-
function parseWcsScriptBlocks(html, stateTagName = "wcs-state") {
|
|
914
|
-
const blocks = [];
|
|
915
|
-
let pos = 0;
|
|
916
|
-
const len = html.length;
|
|
917
|
-
while (pos < len) {
|
|
918
|
-
if (html.startsWith("<!--", pos)) {
|
|
919
|
-
const commentEnd = html.indexOf("-->", pos + 4);
|
|
920
|
-
if (commentEnd === -1) break;
|
|
921
|
-
pos = commentEnd + 3;
|
|
922
|
-
continue;
|
|
923
|
-
}
|
|
924
|
-
const wcsMatch = matchOpenTag(html, pos, stateTagName);
|
|
925
|
-
if (wcsMatch === null) {
|
|
926
|
-
pos++;
|
|
927
|
-
continue;
|
|
928
|
-
}
|
|
929
|
-
const stateName = extractAttribute(wcsMatch.tagContent, "name") ?? "default";
|
|
930
|
-
pos = wcsMatch.end;
|
|
931
|
-
const wcsCloseIdx = findCloseTag(html, pos, stateTagName);
|
|
932
|
-
const wcsEnd = wcsCloseIdx === -1 ? len : wcsCloseIdx;
|
|
933
|
-
while (pos < wcsEnd) {
|
|
934
|
-
if (html.startsWith("<!--", pos)) {
|
|
935
|
-
const commentEnd = html.indexOf("-->", pos + 4);
|
|
936
|
-
if (commentEnd === -1) break;
|
|
937
|
-
pos = commentEnd + 3;
|
|
938
|
-
continue;
|
|
939
|
-
}
|
|
940
|
-
const scriptMatch = matchOpenTag(html, pos, "script");
|
|
941
|
-
if (scriptMatch === null) {
|
|
942
|
-
pos++;
|
|
943
|
-
continue;
|
|
944
|
-
}
|
|
945
|
-
const typeAttr = extractAttribute(scriptMatch.tagContent, "type");
|
|
946
|
-
if (typeAttr?.toLowerCase() !== "module") {
|
|
947
|
-
pos = scriptMatch.end;
|
|
948
|
-
continue;
|
|
949
|
-
}
|
|
950
|
-
const contentStart = scriptMatch.end;
|
|
951
|
-
const scriptCloseIdx = findCloseTag(html, contentStart, "script");
|
|
952
|
-
if (scriptCloseIdx === -1) {
|
|
953
|
-
pos = contentStart;
|
|
954
|
-
break;
|
|
955
|
-
}
|
|
956
|
-
const contentEnd = scriptCloseIdx;
|
|
957
|
-
blocks.push({
|
|
958
|
-
contentStart,
|
|
959
|
-
contentEnd,
|
|
960
|
-
content: html.slice(contentStart, contentEnd),
|
|
961
|
-
stateName
|
|
962
|
-
});
|
|
963
|
-
pos = html.indexOf(">", scriptCloseIdx) + 1;
|
|
964
|
-
if (pos === 0) break;
|
|
965
|
-
}
|
|
966
|
-
pos = wcsEnd;
|
|
967
|
-
if (wcsCloseIdx !== -1) {
|
|
968
|
-
const closeEnd = html.indexOf(">", wcsCloseIdx);
|
|
969
|
-
if (closeEnd !== -1) pos = closeEnd + 1;
|
|
970
|
-
}
|
|
971
|
-
}
|
|
972
|
-
return blocks;
|
|
973
|
-
}
|
|
974
|
-
function parseWcsStateElements(html, stateTagName = "wcs-state") {
|
|
975
|
-
const elements = [];
|
|
976
|
-
let pos = 0;
|
|
977
|
-
const len = html.length;
|
|
978
|
-
while (pos < len) {
|
|
979
|
-
if (html.startsWith("<!--", pos)) {
|
|
980
|
-
const commentEnd = html.indexOf("-->", pos + 4);
|
|
981
|
-
if (commentEnd === -1) break;
|
|
982
|
-
pos = commentEnd + 3;
|
|
983
|
-
continue;
|
|
984
|
-
}
|
|
985
|
-
const wcsMatch = matchOpenTag(html, pos, stateTagName);
|
|
986
|
-
if (wcsMatch === null) {
|
|
987
|
-
pos++;
|
|
988
|
-
continue;
|
|
989
|
-
}
|
|
990
|
-
const stateName = extractAttribute(wcsMatch.tagContent, "name") ?? "default";
|
|
991
|
-
const jsonAttr = extractAttribute(wcsMatch.tagContent, "json") ?? void 0;
|
|
992
|
-
const stateAttr = extractAttribute(wcsMatch.tagContent, "state") ?? void 0;
|
|
993
|
-
const srcAttr = extractAttribute(wcsMatch.tagContent, "src") ?? void 0;
|
|
994
|
-
const tagStart = pos;
|
|
995
|
-
const tagEnd = wcsMatch.end;
|
|
996
|
-
pos = wcsMatch.end;
|
|
997
|
-
const scriptBlocks = [];
|
|
998
|
-
const wcsCloseIdx = findCloseTag(html, pos, stateTagName);
|
|
999
|
-
const wcsEnd = wcsCloseIdx === -1 ? len : wcsCloseIdx;
|
|
1000
|
-
while (pos < wcsEnd) {
|
|
1001
|
-
if (html.startsWith("<!--", pos)) {
|
|
1002
|
-
const commentEnd = html.indexOf("-->", pos + 4);
|
|
1003
|
-
if (commentEnd === -1) break;
|
|
1004
|
-
pos = commentEnd + 3;
|
|
1005
|
-
continue;
|
|
1006
|
-
}
|
|
1007
|
-
const scriptMatch = matchOpenTag(html, pos, "script");
|
|
1008
|
-
if (scriptMatch === null) {
|
|
1009
|
-
pos++;
|
|
1010
|
-
continue;
|
|
1011
|
-
}
|
|
1012
|
-
const typeAttr = extractAttribute(scriptMatch.tagContent, "type");
|
|
1013
|
-
if (typeAttr?.toLowerCase() !== "module") {
|
|
1014
|
-
pos = scriptMatch.end;
|
|
1015
|
-
continue;
|
|
1016
|
-
}
|
|
1017
|
-
const contentStart = scriptMatch.end;
|
|
1018
|
-
const scriptCloseIdx = findCloseTag(html, contentStart, "script");
|
|
1019
|
-
if (scriptCloseIdx === -1) {
|
|
1020
|
-
pos = contentStart;
|
|
1021
|
-
break;
|
|
1022
|
-
}
|
|
1023
|
-
scriptBlocks.push({
|
|
1024
|
-
contentStart,
|
|
1025
|
-
contentEnd: scriptCloseIdx,
|
|
1026
|
-
content: html.slice(contentStart, scriptCloseIdx),
|
|
1027
|
-
stateName
|
|
1028
|
-
});
|
|
1029
|
-
pos = html.indexOf(">", scriptCloseIdx) + 1;
|
|
1030
|
-
if (pos === 0) break;
|
|
1031
|
-
}
|
|
1032
|
-
elements.push({ stateName, jsonAttr, stateAttr, srcAttr, scriptBlocks, tagStart, tagEnd });
|
|
1033
|
-
pos = wcsEnd;
|
|
1034
|
-
if (wcsCloseIdx !== -1) {
|
|
1035
|
-
const closeEnd = html.indexOf(">", wcsCloseIdx);
|
|
1036
|
-
if (closeEnd !== -1) pos = closeEnd + 1;
|
|
1037
|
-
}
|
|
1038
|
-
}
|
|
1039
|
-
return elements;
|
|
1040
|
-
}
|
|
1041
|
-
function findScriptJsonById(html, id2) {
|
|
1042
|
-
let pos = 0;
|
|
1043
|
-
const len = html.length;
|
|
1044
|
-
while (pos < len) {
|
|
1045
|
-
if (html.startsWith("<!--", pos)) {
|
|
1046
|
-
const commentEnd = html.indexOf("-->", pos + 4);
|
|
1047
|
-
if (commentEnd === -1) break;
|
|
1048
|
-
pos = commentEnd + 3;
|
|
1049
|
-
continue;
|
|
1050
|
-
}
|
|
1051
|
-
const scriptMatch = matchOpenTag(html, pos, "script");
|
|
1052
|
-
if (scriptMatch === null) {
|
|
1053
|
-
pos++;
|
|
1054
|
-
continue;
|
|
1055
|
-
}
|
|
1056
|
-
const typeAttr = extractAttribute(scriptMatch.tagContent, "type");
|
|
1057
|
-
const idAttr = extractAttribute(scriptMatch.tagContent, "id");
|
|
1058
|
-
if (typeAttr?.toLowerCase() === "application/json" && idAttr === id2) {
|
|
1059
|
-
const contentStart = scriptMatch.end;
|
|
1060
|
-
const scriptCloseIdx = findCloseTag(html, contentStart, "script");
|
|
1061
|
-
if (scriptCloseIdx === -1) return null;
|
|
1062
|
-
return html.slice(contentStart, scriptCloseIdx);
|
|
1063
|
-
}
|
|
1064
|
-
pos = scriptMatch.end;
|
|
1065
|
-
}
|
|
1066
|
-
return null;
|
|
1067
|
-
}
|
|
1068
|
-
function matchOpenTag(html, pos, tagName) {
|
|
1069
|
-
if (html[pos] !== "<") return null;
|
|
1070
|
-
const nameStart = pos + 1;
|
|
1071
|
-
const nameEnd = nameStart + tagName.length;
|
|
1072
|
-
if (nameEnd > html.length) return null;
|
|
1073
|
-
const slice3 = html.slice(nameStart, nameEnd);
|
|
1074
|
-
if (slice3.toLowerCase() !== tagName.toLowerCase()) return null;
|
|
1075
|
-
const charAfter = html[nameEnd];
|
|
1076
|
-
if (charAfter !== ">" && charAfter !== " " && charAfter !== " " && charAfter !== "\n" && charAfter !== "\r" && charAfter !== "/") {
|
|
1077
|
-
return null;
|
|
1078
|
-
}
|
|
1079
|
-
let i = nameEnd;
|
|
1080
|
-
let inSingleQuote = false;
|
|
1081
|
-
let inDoubleQuote = false;
|
|
1082
|
-
while (i < html.length) {
|
|
1083
|
-
const ch = html[i];
|
|
1084
|
-
if (inSingleQuote) {
|
|
1085
|
-
if (ch === "'") inSingleQuote = false;
|
|
1086
|
-
} else if (inDoubleQuote) {
|
|
1087
|
-
if (ch === '"') inDoubleQuote = false;
|
|
1088
|
-
} else if (ch === "'") {
|
|
1089
|
-
inSingleQuote = true;
|
|
1090
|
-
} else if (ch === '"') {
|
|
1091
|
-
inDoubleQuote = true;
|
|
1092
|
-
} else if (ch === ">") {
|
|
1093
|
-
return {
|
|
1094
|
-
start: pos,
|
|
1095
|
-
end: i + 1,
|
|
1096
|
-
tagContent: html.slice(nameEnd, i)
|
|
1097
|
-
};
|
|
1098
|
-
}
|
|
1099
|
-
i++;
|
|
1100
|
-
}
|
|
1101
|
-
return null;
|
|
1102
|
-
}
|
|
1103
|
-
function findCloseTag(html, startPos, tagName) {
|
|
1104
|
-
const pattern = "</" + tagName;
|
|
1105
|
-
const patternLower = pattern.toLowerCase();
|
|
1106
|
-
const htmlLower = html.toLowerCase();
|
|
1107
|
-
let pos = startPos;
|
|
1108
|
-
while (pos < html.length) {
|
|
1109
|
-
const idx = htmlLower.indexOf(patternLower, pos);
|
|
1110
|
-
if (idx === -1) return -1;
|
|
1111
|
-
const afterIdx = idx + pattern.length;
|
|
1112
|
-
if (afterIdx < html.length) {
|
|
1113
|
-
const ch = html[afterIdx];
|
|
1114
|
-
if (ch === ">" || ch === " " || ch === " " || ch === "\n" || ch === "\r") {
|
|
1115
|
-
return idx;
|
|
1116
|
-
}
|
|
1117
|
-
}
|
|
1118
|
-
pos = idx + 1;
|
|
1119
|
-
}
|
|
1120
|
-
return -1;
|
|
1121
|
-
}
|
|
1122
|
-
function extractAttribute(tagContent, attrName) {
|
|
1123
|
-
const regex = new RegExp(
|
|
1124
|
-
`(?:^|\\s)${attrName}\\s*=\\s*(?:"([^"]*)"|'([^']*)'|(\\S+))`,
|
|
1125
|
-
"i"
|
|
1126
|
-
);
|
|
1127
|
-
const match = tagContent.match(regex);
|
|
1128
|
-
if (!match) return null;
|
|
1129
|
-
return match[1] ?? match[2] ?? match[3] ?? null;
|
|
1130
|
-
}
|
|
1131
|
-
|
|
1132
921
|
// src/service/stateAnalyzer.ts
|
|
1133
922
|
var RESERVED_STREAMS_KEY = "$streams";
|
|
1134
923
|
var RESERVED_COMMAND_TOKENS_KEY = "$commandTokens";
|
|
1135
924
|
var RESERVED_EVENT_TOKENS_KEY = "$eventTokens";
|
|
1136
925
|
var RESERVED_LIST_KEYS_KEY = "$listKeys";
|
|
1137
926
|
var RESERVED_WATCH_KEY = "$watch";
|
|
1138
|
-
function analyzeStatePaths(scriptContent
|
|
927
|
+
function analyzeStatePaths(scriptContent) {
|
|
1139
928
|
const objectContent = extractDefaultExportObject(scriptContent);
|
|
1140
929
|
if (!objectContent) return [];
|
|
1141
930
|
const paths = [];
|
|
@@ -1144,27 +933,27 @@ function analyzeStatePaths(scriptContent, stateName = "default") {
|
|
|
1144
933
|
const pendingListKeys = [];
|
|
1145
934
|
for (const prop of topLevelProps) {
|
|
1146
935
|
if (prop.name.startsWith("$")) {
|
|
1147
|
-
collectReservedKeyPaths(prop, paths, pendingStreamValues, pendingListKeys
|
|
936
|
+
collectReservedKeyPaths(prop, paths, pendingStreamValues, pendingListKeys);
|
|
1148
937
|
continue;
|
|
1149
938
|
}
|
|
1150
939
|
if (prop.kind === "method") {
|
|
1151
|
-
paths.push({ path: prop.name, kind: "method"
|
|
940
|
+
paths.push({ path: prop.name, kind: "method" });
|
|
1152
941
|
continue;
|
|
1153
942
|
}
|
|
1154
943
|
if (prop.kind === "getter") {
|
|
1155
|
-
if (!paths.some((p) => p.
|
|
1156
|
-
paths.push({ path: prop.name, kind: "computed"
|
|
944
|
+
if (!paths.some((p) => p.path === prop.name)) {
|
|
945
|
+
paths.push({ path: prop.name, kind: "computed" });
|
|
1157
946
|
}
|
|
1158
947
|
continue;
|
|
1159
948
|
}
|
|
1160
|
-
pushDataPropertyPaths(prop, paths
|
|
949
|
+
pushDataPropertyPaths(prop, paths);
|
|
1161
950
|
}
|
|
1162
951
|
for (const streamValue of pendingStreamValues) {
|
|
1163
|
-
if (paths.some((p) => p.
|
|
1164
|
-
pushDataPropertyPaths(streamValue, paths
|
|
952
|
+
if (paths.some((p) => p.path === streamValue.name)) continue;
|
|
953
|
+
pushDataPropertyPaths(streamValue, paths);
|
|
1165
954
|
}
|
|
1166
955
|
for (const listKeyEntry of pendingListKeys) {
|
|
1167
|
-
pushListKeyPaths(listKeyEntry, paths
|
|
956
|
+
pushListKeyPaths(listKeyEntry, paths);
|
|
1168
957
|
}
|
|
1169
958
|
return paths;
|
|
1170
959
|
}
|
|
@@ -1251,7 +1040,7 @@ function findNonObjectWatch(scriptContent) {
|
|
|
1251
1040
|
if (!isArrowFunction && !isWholeLiteral) return null;
|
|
1252
1041
|
return span;
|
|
1253
1042
|
}
|
|
1254
|
-
function collectReservedKeyPaths(prop, paths, pendingStreamValues, pendingListKeys
|
|
1043
|
+
function collectReservedKeyPaths(prop, paths, pendingStreamValues, pendingListKeys) {
|
|
1255
1044
|
if (prop.name === RESERVED_STREAMS_KEY && prop.kind === "data" && prop.value && isObjectLiteral(prop.value)) {
|
|
1256
1045
|
const entries = parseTopLevelProperties(extractObjectContent(prop.value));
|
|
1257
1046
|
for (const entry of entries) {
|
|
@@ -1263,20 +1052,20 @@ function collectReservedKeyPaths(prop, paths, pendingStreamValues, pendingListKe
|
|
|
1263
1052
|
value: initial?.value,
|
|
1264
1053
|
typeHint: initial?.typeHint
|
|
1265
1054
|
});
|
|
1266
|
-
paths.push({ path: `$streamStatus.${entry.name}`, kind: "data", typeHint: "string"
|
|
1267
|
-
paths.push({ path: `$streamError.${entry.name}`, kind: "data"
|
|
1055
|
+
paths.push({ path: `$streamStatus.${entry.name}`, kind: "data", typeHint: "string" });
|
|
1056
|
+
paths.push({ path: `$streamError.${entry.name}`, kind: "data" });
|
|
1268
1057
|
}
|
|
1269
1058
|
return;
|
|
1270
1059
|
}
|
|
1271
1060
|
if (prop.name === RESERVED_COMMAND_TOKENS_KEY && prop.value) {
|
|
1272
1061
|
for (const name of extractStringArrayItems(prop.value)) {
|
|
1273
|
-
paths.push({ path: `$command.${name}`, kind: "command"
|
|
1062
|
+
paths.push({ path: `$command.${name}`, kind: "command" });
|
|
1274
1063
|
}
|
|
1275
1064
|
return;
|
|
1276
1065
|
}
|
|
1277
1066
|
if (prop.name === RESERVED_EVENT_TOKENS_KEY && prop.value) {
|
|
1278
1067
|
for (const name of extractStringArrayItems(prop.value)) {
|
|
1279
|
-
paths.push({ path: name, kind: "eventToken"
|
|
1068
|
+
paths.push({ path: name, kind: "eventToken" });
|
|
1280
1069
|
}
|
|
1281
1070
|
return;
|
|
1282
1071
|
}
|
|
@@ -1288,22 +1077,22 @@ function collectReservedKeyPaths(prop, paths, pendingStreamValues, pendingListKe
|
|
|
1288
1077
|
return;
|
|
1289
1078
|
}
|
|
1290
1079
|
}
|
|
1291
|
-
function pushListKeyPaths(entry, paths
|
|
1080
|
+
function pushListKeyPaths(entry, paths) {
|
|
1292
1081
|
const listPath = entry.name;
|
|
1293
1082
|
const segments = listPath.split(".");
|
|
1294
1083
|
if (listPath.length === 0 || segments.some((s) => s.length === 0) || segments[segments.length - 1] === "*") {
|
|
1295
1084
|
return;
|
|
1296
1085
|
}
|
|
1297
|
-
const has = (path) => paths.some((p) => p.
|
|
1298
|
-
if (!has(listPath)) paths.push({ path: listPath, kind: "data", typeHint: "array"
|
|
1299
|
-
if (!has(`${listPath}.*`)) paths.push({ path: `${listPath}.*`, kind: "list"
|
|
1086
|
+
const has = (path) => paths.some((p) => p.path === path);
|
|
1087
|
+
if (!has(listPath)) paths.push({ path: listPath, kind: "data", typeHint: "array" });
|
|
1088
|
+
if (!has(`${listPath}.*`)) paths.push({ path: `${listPath}.*`, kind: "list" });
|
|
1300
1089
|
if (!has(`${listPath}.length`)) {
|
|
1301
|
-
paths.push({ path: `${listPath}.length`, kind: "data", typeHint: "number"
|
|
1090
|
+
paths.push({ path: `${listPath}.length`, kind: "data", typeHint: "number" });
|
|
1302
1091
|
}
|
|
1303
1092
|
const keyField = extractStringLiteralValue(entry.value);
|
|
1304
1093
|
if (keyField === null || keyField.includes(".") || keyField.includes("*")) return;
|
|
1305
1094
|
if (!has(`${listPath}.*.${keyField}`)) {
|
|
1306
|
-
paths.push({ path: `${listPath}.*.${keyField}`, kind: "data"
|
|
1095
|
+
paths.push({ path: `${listPath}.*.${keyField}`, kind: "data" });
|
|
1307
1096
|
}
|
|
1308
1097
|
}
|
|
1309
1098
|
function extractStringLiteralValue(value) {
|
|
@@ -1326,17 +1115,17 @@ function extractStringArrayItems(value) {
|
|
|
1326
1115
|
return items;
|
|
1327
1116
|
}
|
|
1328
1117
|
var MAX_OBJECT_NEST_DEPTH = 5;
|
|
1329
|
-
function pushDataPropertyPaths(prop, paths
|
|
1330
|
-
pushDataPropertyPathsAt(prop.name, prop, paths,
|
|
1118
|
+
function pushDataPropertyPaths(prop, paths) {
|
|
1119
|
+
pushDataPropertyPathsAt(prop.name, prop, paths, 0);
|
|
1331
1120
|
}
|
|
1332
|
-
function pushDataPropertyPathsAt(path, prop, paths,
|
|
1333
|
-
paths.push({ path, kind: "data", typeHint: prop.typeHint, rawInitial: prop.value?.trim()
|
|
1121
|
+
function pushDataPropertyPathsAt(path, prop, paths, depth) {
|
|
1122
|
+
paths.push({ path, kind: "data", typeHint: prop.typeHint, rawInitial: prop.value?.trim() });
|
|
1334
1123
|
if (prop.value && isArrayLiteral(prop.value)) {
|
|
1335
|
-
paths.push({ path: `${path}.*`, kind: "list"
|
|
1336
|
-
paths.push({ path: `${path}.length`, kind: "data", typeHint: "number"
|
|
1124
|
+
paths.push({ path: `${path}.*`, kind: "list" });
|
|
1125
|
+
paths.push({ path: `${path}.length`, kind: "data", typeHint: "number" });
|
|
1337
1126
|
if (depth >= MAX_OBJECT_NEST_DEPTH) return;
|
|
1338
1127
|
for (const childProp of extractArrayElementDataProperties(prop.value)) {
|
|
1339
|
-
pushDataPropertyPathsAt(`${path}.*.${childProp.name}`, childProp, paths,
|
|
1128
|
+
pushDataPropertyPathsAt(`${path}.*.${childProp.name}`, childProp, paths, depth + 1);
|
|
1340
1129
|
}
|
|
1341
1130
|
return;
|
|
1342
1131
|
}
|
|
@@ -1345,11 +1134,11 @@ function pushDataPropertyPathsAt(path, prop, paths, stateName, depth) {
|
|
|
1345
1134
|
const childProps = parseTopLevelProperties(extractObjectContent(prop.value));
|
|
1346
1135
|
for (const childProp of childProps) {
|
|
1347
1136
|
if (childProp.kind !== "data") continue;
|
|
1348
|
-
pushDataPropertyPathsAt(`${path}.${childProp.name}`, childProp, paths,
|
|
1137
|
+
pushDataPropertyPathsAt(`${path}.${childProp.name}`, childProp, paths, depth + 1);
|
|
1349
1138
|
}
|
|
1350
1139
|
}
|
|
1351
1140
|
}
|
|
1352
|
-
function analyzeJsonPaths(jsonString
|
|
1141
|
+
function analyzeJsonPaths(jsonString) {
|
|
1353
1142
|
let data;
|
|
1354
1143
|
try {
|
|
1355
1144
|
data = JSON.parse(jsonString);
|
|
@@ -1358,31 +1147,31 @@ function analyzeJsonPaths(jsonString, stateName = "default") {
|
|
|
1358
1147
|
}
|
|
1359
1148
|
if (typeof data !== "object" || data === null || Array.isArray(data)) return [];
|
|
1360
1149
|
const paths = [];
|
|
1361
|
-
collectJsonPaths(data, "", paths,
|
|
1150
|
+
collectJsonPaths(data, "", paths, 0);
|
|
1362
1151
|
return paths;
|
|
1363
1152
|
}
|
|
1364
|
-
function collectJsonPaths(obj, prefix, paths,
|
|
1153
|
+
function collectJsonPaths(obj, prefix, paths, depth) {
|
|
1365
1154
|
if (depth >= MAX_OBJECT_NEST_DEPTH) return;
|
|
1366
1155
|
for (const [key, value] of Object.entries(obj)) {
|
|
1367
1156
|
if (prefix === "" && key.startsWith("$")) continue;
|
|
1368
1157
|
const path = prefix ? `${prefix}.${key}` : key;
|
|
1369
|
-
pushJsonValuePaths(path, value, paths,
|
|
1158
|
+
pushJsonValuePaths(path, value, paths, depth);
|
|
1370
1159
|
}
|
|
1371
1160
|
}
|
|
1372
|
-
function pushJsonValuePaths(path, value, paths,
|
|
1373
|
-
paths.push({ path, kind: "data", typeHint: inferJsonTypeHint(value)
|
|
1161
|
+
function pushJsonValuePaths(path, value, paths, depth) {
|
|
1162
|
+
paths.push({ path, kind: "data", typeHint: inferJsonTypeHint(value) });
|
|
1374
1163
|
if (Array.isArray(value)) {
|
|
1375
|
-
paths.push({ path: `${path}.*`, kind: "list"
|
|
1376
|
-
paths.push({ path: `${path}.length`, kind: "data", typeHint: "number"
|
|
1164
|
+
paths.push({ path: `${path}.*`, kind: "list" });
|
|
1165
|
+
paths.push({ path: `${path}.length`, kind: "data", typeHint: "number" });
|
|
1377
1166
|
if (depth >= MAX_OBJECT_NEST_DEPTH) return;
|
|
1378
1167
|
if (value.length > 0 && typeof value[0] === "object" && value[0] !== null && !Array.isArray(value[0])) {
|
|
1379
1168
|
const firstElement = value[0];
|
|
1380
1169
|
for (const [childKey, childValue] of Object.entries(firstElement)) {
|
|
1381
|
-
pushJsonValuePaths(`${path}.*.${childKey}`, childValue, paths,
|
|
1170
|
+
pushJsonValuePaths(`${path}.*.${childKey}`, childValue, paths, depth + 1);
|
|
1382
1171
|
}
|
|
1383
1172
|
}
|
|
1384
1173
|
} else if (typeof value === "object" && value !== null) {
|
|
1385
|
-
collectJsonPaths(value, path, paths,
|
|
1174
|
+
collectJsonPaths(value, path, paths, depth + 1);
|
|
1386
1175
|
}
|
|
1387
1176
|
}
|
|
1388
1177
|
function inferJsonTypeHint(value) {
|
|
@@ -1585,42 +1374,369 @@ function extractJsDocType(content, propIndex) {
|
|
|
1585
1374
|
const typeExpr = jsdocMatch[1].trim();
|
|
1586
1375
|
return normalizeJsDocType(typeExpr);
|
|
1587
1376
|
}
|
|
1588
|
-
function normalizeJsDocType(typeExpr) {
|
|
1589
|
-
const parts = typeExpr.split("|").map((p) => p.trim());
|
|
1590
|
-
const normalized = parts.map((p) => {
|
|
1591
|
-
const lower = p.toLowerCase();
|
|
1592
|
-
if (lower === "string") return "string";
|
|
1593
|
-
if (lower === "number") return "number";
|
|
1594
|
-
if (lower === "boolean") return "boolean";
|
|
1595
|
-
if (lower === "null") return "null";
|
|
1596
|
-
if (lower === "undefined") return "null";
|
|
1597
|
-
if (lower.endsWith("[]") || lower.startsWith("array")) return "array";
|
|
1598
|
-
if (lower === "object") return "object";
|
|
1377
|
+
function normalizeJsDocType(typeExpr) {
|
|
1378
|
+
const parts = typeExpr.split("|").map((p) => p.trim());
|
|
1379
|
+
const normalized = parts.map((p) => {
|
|
1380
|
+
const lower = p.toLowerCase();
|
|
1381
|
+
if (lower === "string") return "string";
|
|
1382
|
+
if (lower === "number") return "number";
|
|
1383
|
+
if (lower === "boolean") return "boolean";
|
|
1384
|
+
if (lower === "null") return "null";
|
|
1385
|
+
if (lower === "undefined") return "null";
|
|
1386
|
+
if (lower.endsWith("[]") || lower.startsWith("array")) return "array";
|
|
1387
|
+
if (lower === "object") return "object";
|
|
1388
|
+
return null;
|
|
1389
|
+
}).filter((p) => p !== null);
|
|
1390
|
+
if (normalized.length === 0) return void 0;
|
|
1391
|
+
const unique = [...new Set(normalized)].sort();
|
|
1392
|
+
return unique.join("|");
|
|
1393
|
+
}
|
|
1394
|
+
function isEscaped(text, i) {
|
|
1395
|
+
let backslashCount = 0;
|
|
1396
|
+
let j = i - 1;
|
|
1397
|
+
while (j >= 0 && text[j] === "\\") {
|
|
1398
|
+
backslashCount++;
|
|
1399
|
+
j--;
|
|
1400
|
+
}
|
|
1401
|
+
return backslashCount % 2 === 1;
|
|
1402
|
+
}
|
|
1403
|
+
function inferTypeHint(valueStart) {
|
|
1404
|
+
const v = valueStart.trim().replace(/,\s*$/, "");
|
|
1405
|
+
if (/^-?\d+\.\d/.test(v)) return "number";
|
|
1406
|
+
if (/^-?\d/.test(v)) return "number";
|
|
1407
|
+
if (/^["'`]/.test(v)) return "string";
|
|
1408
|
+
if (v === "true" || v === "false") return "boolean";
|
|
1409
|
+
if (v === "null") return "null";
|
|
1410
|
+
if (v.startsWith("[")) return "array";
|
|
1411
|
+
if (v.startsWith("{")) return "object";
|
|
1412
|
+
return void 0;
|
|
1413
|
+
}
|
|
1414
|
+
function analyzeSchemaPaths(schema) {
|
|
1415
|
+
const paths = [];
|
|
1416
|
+
const defs = schema.$defs ?? {};
|
|
1417
|
+
collectSchemaObjectPaths(schema, "", paths, defs, 0);
|
|
1418
|
+
return paths;
|
|
1419
|
+
}
|
|
1420
|
+
function mergeSchemaCandidates(candidates, applicationSchema) {
|
|
1421
|
+
if (applicationSchema === void 0) return candidates;
|
|
1422
|
+
const schemaCandidates = [];
|
|
1423
|
+
const schemaKeys = /* @__PURE__ */ new Set();
|
|
1424
|
+
for (const p of analyzeSchemaPaths(applicationSchema)) {
|
|
1425
|
+
schemaCandidates.push(p);
|
|
1426
|
+
schemaKeys.add(p.path);
|
|
1427
|
+
}
|
|
1428
|
+
const kept = candidates.filter((p) => !schemaKeys.has(p.path));
|
|
1429
|
+
return [...kept, ...schemaCandidates];
|
|
1430
|
+
}
|
|
1431
|
+
function derefSchemaNodes(node, defs) {
|
|
1432
|
+
const out = [];
|
|
1433
|
+
const stack = [{ node, chain: /* @__PURE__ */ new Set() }];
|
|
1434
|
+
while (stack.length > 0) {
|
|
1435
|
+
const { node: n, chain } = stack.pop();
|
|
1436
|
+
if (n === null || typeof n !== "object") continue;
|
|
1437
|
+
if (typeof n.$ref === "string") {
|
|
1438
|
+
const match = /^#\/\$defs\/(.+)$/.exec(n.$ref);
|
|
1439
|
+
if (match === null || chain.has(n.$ref)) continue;
|
|
1440
|
+
const target = defs[match[1].replace(/~1/g, "/").replace(/~0/g, "~")];
|
|
1441
|
+
if (target === void 0) continue;
|
|
1442
|
+
stack.push({ node: target, chain: /* @__PURE__ */ new Set([...chain, n.$ref]) });
|
|
1443
|
+
continue;
|
|
1444
|
+
}
|
|
1445
|
+
if (Array.isArray(n.anyOf)) {
|
|
1446
|
+
for (let i = n.anyOf.length - 1; i >= 0; i--) stack.push({ node: n.anyOf[i], chain });
|
|
1447
|
+
continue;
|
|
1448
|
+
}
|
|
1449
|
+
out.push(n);
|
|
1450
|
+
}
|
|
1451
|
+
return out;
|
|
1452
|
+
}
|
|
1453
|
+
function schemaTypeHint(nodes) {
|
|
1454
|
+
const hints = /* @__PURE__ */ new Set();
|
|
1455
|
+
for (const n of nodes) {
|
|
1456
|
+
const types = typeof n.type === "string" ? [n.type] : Array.isArray(n.type) ? n.type : [];
|
|
1457
|
+
if (types.length > 0) {
|
|
1458
|
+
for (const t of types) {
|
|
1459
|
+
if (t === "null") continue;
|
|
1460
|
+
hints.add(t === "integer" ? "number" : t);
|
|
1461
|
+
}
|
|
1462
|
+
continue;
|
|
1463
|
+
}
|
|
1464
|
+
if (Array.isArray(n.enum)) {
|
|
1465
|
+
for (const v of n.enum) {
|
|
1466
|
+
const h = inferJsonTypeHint(v);
|
|
1467
|
+
if (h !== void 0 && h !== "null") hints.add(h);
|
|
1468
|
+
}
|
|
1469
|
+
} else if (n.const !== void 0) {
|
|
1470
|
+
const h = inferJsonTypeHint(n.const);
|
|
1471
|
+
if (h !== void 0 && h !== "null") hints.add(h);
|
|
1472
|
+
} else if (n.properties !== void 0) {
|
|
1473
|
+
hints.add("object");
|
|
1474
|
+
} else if (n.items !== void 0) {
|
|
1475
|
+
hints.add("array");
|
|
1476
|
+
}
|
|
1477
|
+
}
|
|
1478
|
+
return hints.size === 0 ? void 0 : [...hints].join("|");
|
|
1479
|
+
}
|
|
1480
|
+
function collectSchemaObjectPaths(node, prefix, paths, defs, depth) {
|
|
1481
|
+
if (depth >= MAX_OBJECT_NEST_DEPTH) return;
|
|
1482
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1483
|
+
for (const n of derefSchemaNodes(node, defs)) {
|
|
1484
|
+
for (const [key, child] of Object.entries(n.properties ?? {})) {
|
|
1485
|
+
if (prefix === "" && key.startsWith("$")) continue;
|
|
1486
|
+
if (seen.has(key)) continue;
|
|
1487
|
+
seen.add(key);
|
|
1488
|
+
const path = prefix ? `${prefix}.${key}` : key;
|
|
1489
|
+
pushSchemaValuePaths(path, child, paths, defs, depth);
|
|
1490
|
+
}
|
|
1491
|
+
}
|
|
1492
|
+
}
|
|
1493
|
+
function pushSchemaValuePaths(path, node, paths, defs, depth) {
|
|
1494
|
+
const nodes = derefSchemaNodes(node, defs);
|
|
1495
|
+
const typeHint = schemaTypeHint(nodes);
|
|
1496
|
+
paths.push(withHint({ path, kind: "data", fromSchema: true }, typeHint));
|
|
1497
|
+
const items = nodes.map((n) => n.items).find((i) => i !== void 0 && i !== null && typeof i === "object");
|
|
1498
|
+
const isArray = items !== void 0 || (typeHint?.split("|").includes("array") ?? false);
|
|
1499
|
+
if (isArray) {
|
|
1500
|
+
const itemNodes = items !== void 0 ? derefSchemaNodes(items, defs) : [];
|
|
1501
|
+
paths.push(withHint({ path: `${path}.*`, kind: "list", fromSchema: true }, schemaTypeHint(itemNodes)));
|
|
1502
|
+
paths.push({ path: `${path}.length`, kind: "data", typeHint: "number", fromSchema: true });
|
|
1503
|
+
if (depth >= MAX_OBJECT_NEST_DEPTH) return;
|
|
1504
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1505
|
+
for (const n of itemNodes) {
|
|
1506
|
+
for (const [childKey, childNode] of Object.entries(n.properties ?? {})) {
|
|
1507
|
+
if (seen.has(childKey)) continue;
|
|
1508
|
+
seen.add(childKey);
|
|
1509
|
+
pushSchemaValuePaths(`${path}.*.${childKey}`, childNode, paths, defs, depth + 1);
|
|
1510
|
+
}
|
|
1511
|
+
}
|
|
1512
|
+
return;
|
|
1513
|
+
}
|
|
1514
|
+
if (nodes.some((n) => n.properties !== void 0)) {
|
|
1515
|
+
collectSchemaObjectPaths(node, path, paths, defs, depth + 1);
|
|
1516
|
+
}
|
|
1517
|
+
}
|
|
1518
|
+
function withHint(candidate, typeHint) {
|
|
1519
|
+
return typeHint === void 0 ? candidate : { ...candidate, typeHint };
|
|
1520
|
+
}
|
|
1521
|
+
|
|
1522
|
+
// src/language/htmlParse.ts
|
|
1523
|
+
function parseWcsScriptBlocks(html, stateTagName = "wcs-state") {
|
|
1524
|
+
const blocks = [];
|
|
1525
|
+
let pos = 0;
|
|
1526
|
+
const len = html.length;
|
|
1527
|
+
while (pos < len) {
|
|
1528
|
+
if (html.startsWith("<!--", pos)) {
|
|
1529
|
+
const commentEnd = html.indexOf("-->", pos + 4);
|
|
1530
|
+
if (commentEnd === -1) break;
|
|
1531
|
+
pos = commentEnd + 3;
|
|
1532
|
+
continue;
|
|
1533
|
+
}
|
|
1534
|
+
const wcsMatch = matchOpenTag(html, pos, stateTagName);
|
|
1535
|
+
if (wcsMatch === null) {
|
|
1536
|
+
pos++;
|
|
1537
|
+
continue;
|
|
1538
|
+
}
|
|
1539
|
+
const mountPath = extractAttribute(wcsMatch.tagContent, "mount");
|
|
1540
|
+
pos = wcsMatch.end;
|
|
1541
|
+
const wcsCloseIdx = findCloseTag(html, pos, stateTagName);
|
|
1542
|
+
const wcsEnd = wcsCloseIdx === -1 ? len : wcsCloseIdx;
|
|
1543
|
+
while (pos < wcsEnd) {
|
|
1544
|
+
if (html.startsWith("<!--", pos)) {
|
|
1545
|
+
const commentEnd = html.indexOf("-->", pos + 4);
|
|
1546
|
+
if (commentEnd === -1) break;
|
|
1547
|
+
pos = commentEnd + 3;
|
|
1548
|
+
continue;
|
|
1549
|
+
}
|
|
1550
|
+
const scriptMatch = matchOpenTag(html, pos, "script");
|
|
1551
|
+
if (scriptMatch === null) {
|
|
1552
|
+
pos++;
|
|
1553
|
+
continue;
|
|
1554
|
+
}
|
|
1555
|
+
const typeAttr = extractAttribute(scriptMatch.tagContent, "type");
|
|
1556
|
+
if (typeAttr?.toLowerCase() !== "module") {
|
|
1557
|
+
pos = scriptMatch.end;
|
|
1558
|
+
continue;
|
|
1559
|
+
}
|
|
1560
|
+
const contentStart = scriptMatch.end;
|
|
1561
|
+
const scriptCloseIdx = findCloseTag(html, contentStart, "script");
|
|
1562
|
+
if (scriptCloseIdx === -1) {
|
|
1563
|
+
pos = contentStart;
|
|
1564
|
+
break;
|
|
1565
|
+
}
|
|
1566
|
+
const contentEnd = scriptCloseIdx;
|
|
1567
|
+
blocks.push({
|
|
1568
|
+
contentStart,
|
|
1569
|
+
contentEnd,
|
|
1570
|
+
content: html.slice(contentStart, contentEnd),
|
|
1571
|
+
mountPath
|
|
1572
|
+
});
|
|
1573
|
+
pos = html.indexOf(">", scriptCloseIdx) + 1;
|
|
1574
|
+
if (pos === 0) break;
|
|
1575
|
+
}
|
|
1576
|
+
pos = wcsEnd;
|
|
1577
|
+
if (wcsCloseIdx !== -1) {
|
|
1578
|
+
const closeEnd = html.indexOf(">", wcsCloseIdx);
|
|
1579
|
+
if (closeEnd !== -1) pos = closeEnd + 1;
|
|
1580
|
+
}
|
|
1581
|
+
}
|
|
1582
|
+
return blocks;
|
|
1583
|
+
}
|
|
1584
|
+
function parseWcsStateElements(html, stateTagName = "wcs-state") {
|
|
1585
|
+
const elements = [];
|
|
1586
|
+
let pos = 0;
|
|
1587
|
+
const len = html.length;
|
|
1588
|
+
while (pos < len) {
|
|
1589
|
+
if (html.startsWith("<!--", pos)) {
|
|
1590
|
+
const commentEnd = html.indexOf("-->", pos + 4);
|
|
1591
|
+
if (commentEnd === -1) break;
|
|
1592
|
+
pos = commentEnd + 3;
|
|
1593
|
+
continue;
|
|
1594
|
+
}
|
|
1595
|
+
const wcsMatch = matchOpenTag(html, pos, stateTagName);
|
|
1596
|
+
if (wcsMatch === null) {
|
|
1597
|
+
pos++;
|
|
1598
|
+
continue;
|
|
1599
|
+
}
|
|
1600
|
+
const mountPath = extractAttribute(wcsMatch.tagContent, "mount");
|
|
1601
|
+
const jsonAttr = extractAttribute(wcsMatch.tagContent, "json") ?? void 0;
|
|
1602
|
+
const stateAttr = extractAttribute(wcsMatch.tagContent, "state") ?? void 0;
|
|
1603
|
+
const srcAttr = extractAttribute(wcsMatch.tagContent, "src") ?? void 0;
|
|
1604
|
+
const tagStart = pos;
|
|
1605
|
+
const tagEnd = wcsMatch.end;
|
|
1606
|
+
pos = wcsMatch.end;
|
|
1607
|
+
const scriptBlocks = [];
|
|
1608
|
+
const wcsCloseIdx = findCloseTag(html, pos, stateTagName);
|
|
1609
|
+
const wcsEnd = wcsCloseIdx === -1 ? len : wcsCloseIdx;
|
|
1610
|
+
while (pos < wcsEnd) {
|
|
1611
|
+
if (html.startsWith("<!--", pos)) {
|
|
1612
|
+
const commentEnd = html.indexOf("-->", pos + 4);
|
|
1613
|
+
if (commentEnd === -1) break;
|
|
1614
|
+
pos = commentEnd + 3;
|
|
1615
|
+
continue;
|
|
1616
|
+
}
|
|
1617
|
+
const scriptMatch = matchOpenTag(html, pos, "script");
|
|
1618
|
+
if (scriptMatch === null) {
|
|
1619
|
+
pos++;
|
|
1620
|
+
continue;
|
|
1621
|
+
}
|
|
1622
|
+
const typeAttr = extractAttribute(scriptMatch.tagContent, "type");
|
|
1623
|
+
if (typeAttr?.toLowerCase() !== "module") {
|
|
1624
|
+
pos = scriptMatch.end;
|
|
1625
|
+
continue;
|
|
1626
|
+
}
|
|
1627
|
+
const contentStart = scriptMatch.end;
|
|
1628
|
+
const scriptCloseIdx = findCloseTag(html, contentStart, "script");
|
|
1629
|
+
if (scriptCloseIdx === -1) {
|
|
1630
|
+
pos = contentStart;
|
|
1631
|
+
break;
|
|
1632
|
+
}
|
|
1633
|
+
scriptBlocks.push({
|
|
1634
|
+
contentStart,
|
|
1635
|
+
contentEnd: scriptCloseIdx,
|
|
1636
|
+
content: html.slice(contentStart, scriptCloseIdx),
|
|
1637
|
+
mountPath
|
|
1638
|
+
});
|
|
1639
|
+
pos = html.indexOf(">", scriptCloseIdx) + 1;
|
|
1640
|
+
if (pos === 0) break;
|
|
1641
|
+
}
|
|
1642
|
+
elements.push({ mountPath, jsonAttr, stateAttr, srcAttr, scriptBlocks, tagStart, tagEnd });
|
|
1643
|
+
pos = wcsEnd;
|
|
1644
|
+
if (wcsCloseIdx !== -1) {
|
|
1645
|
+
const closeEnd = html.indexOf(">", wcsCloseIdx);
|
|
1646
|
+
if (closeEnd !== -1) pos = closeEnd + 1;
|
|
1647
|
+
}
|
|
1648
|
+
}
|
|
1649
|
+
return elements;
|
|
1650
|
+
}
|
|
1651
|
+
function findScriptJsonById(html, id2) {
|
|
1652
|
+
let pos = 0;
|
|
1653
|
+
const len = html.length;
|
|
1654
|
+
while (pos < len) {
|
|
1655
|
+
if (html.startsWith("<!--", pos)) {
|
|
1656
|
+
const commentEnd = html.indexOf("-->", pos + 4);
|
|
1657
|
+
if (commentEnd === -1) break;
|
|
1658
|
+
pos = commentEnd + 3;
|
|
1659
|
+
continue;
|
|
1660
|
+
}
|
|
1661
|
+
const scriptMatch = matchOpenTag(html, pos, "script");
|
|
1662
|
+
if (scriptMatch === null) {
|
|
1663
|
+
pos++;
|
|
1664
|
+
continue;
|
|
1665
|
+
}
|
|
1666
|
+
const typeAttr = extractAttribute(scriptMatch.tagContent, "type");
|
|
1667
|
+
const idAttr = extractAttribute(scriptMatch.tagContent, "id");
|
|
1668
|
+
if (typeAttr?.toLowerCase() === "application/json" && idAttr === id2) {
|
|
1669
|
+
const contentStart = scriptMatch.end;
|
|
1670
|
+
const scriptCloseIdx = findCloseTag(html, contentStart, "script");
|
|
1671
|
+
if (scriptCloseIdx === -1) return null;
|
|
1672
|
+
return html.slice(contentStart, scriptCloseIdx);
|
|
1673
|
+
}
|
|
1674
|
+
pos = scriptMatch.end;
|
|
1675
|
+
}
|
|
1676
|
+
return null;
|
|
1677
|
+
}
|
|
1678
|
+
function matchOpenTag(html, pos, tagName) {
|
|
1679
|
+
if (html[pos] !== "<") return null;
|
|
1680
|
+
const nameStart = pos + 1;
|
|
1681
|
+
const nameEnd = nameStart + tagName.length;
|
|
1682
|
+
if (nameEnd > html.length) return null;
|
|
1683
|
+
const slice3 = html.slice(nameStart, nameEnd);
|
|
1684
|
+
if (slice3.toLowerCase() !== tagName.toLowerCase()) return null;
|
|
1685
|
+
const charAfter = html[nameEnd];
|
|
1686
|
+
if (charAfter !== ">" && charAfter !== " " && charAfter !== " " && charAfter !== "\n" && charAfter !== "\r" && charAfter !== "/") {
|
|
1599
1687
|
return null;
|
|
1600
|
-
}
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1688
|
+
}
|
|
1689
|
+
let i = nameEnd;
|
|
1690
|
+
let inSingleQuote = false;
|
|
1691
|
+
let inDoubleQuote = false;
|
|
1692
|
+
while (i < html.length) {
|
|
1693
|
+
const ch = html[i];
|
|
1694
|
+
if (inSingleQuote) {
|
|
1695
|
+
if (ch === "'") inSingleQuote = false;
|
|
1696
|
+
} else if (inDoubleQuote) {
|
|
1697
|
+
if (ch === '"') inDoubleQuote = false;
|
|
1698
|
+
} else if (ch === "'") {
|
|
1699
|
+
inSingleQuote = true;
|
|
1700
|
+
} else if (ch === '"') {
|
|
1701
|
+
inDoubleQuote = true;
|
|
1702
|
+
} else if (ch === ">") {
|
|
1703
|
+
return {
|
|
1704
|
+
start: pos,
|
|
1705
|
+
end: i + 1,
|
|
1706
|
+
tagContent: html.slice(nameEnd, i)
|
|
1707
|
+
};
|
|
1708
|
+
}
|
|
1709
|
+
i++;
|
|
1710
|
+
}
|
|
1711
|
+
return null;
|
|
1604
1712
|
}
|
|
1605
|
-
function
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1713
|
+
function findCloseTag(html, startPos, tagName) {
|
|
1714
|
+
const pattern = "</" + tagName;
|
|
1715
|
+
const patternLower = pattern.toLowerCase();
|
|
1716
|
+
const htmlLower = html.toLowerCase();
|
|
1717
|
+
let pos = startPos;
|
|
1718
|
+
while (pos < html.length) {
|
|
1719
|
+
const idx = htmlLower.indexOf(patternLower, pos);
|
|
1720
|
+
if (idx === -1) return -1;
|
|
1721
|
+
const afterIdx = idx + pattern.length;
|
|
1722
|
+
if (afterIdx < html.length) {
|
|
1723
|
+
const ch = html[afterIdx];
|
|
1724
|
+
if (ch === ">" || ch === " " || ch === " " || ch === "\n" || ch === "\r") {
|
|
1725
|
+
return idx;
|
|
1726
|
+
}
|
|
1727
|
+
}
|
|
1728
|
+
pos = idx + 1;
|
|
1611
1729
|
}
|
|
1612
|
-
return
|
|
1730
|
+
return -1;
|
|
1613
1731
|
}
|
|
1614
|
-
function
|
|
1615
|
-
const
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
if (
|
|
1621
|
-
|
|
1622
|
-
if (v.startsWith("{")) return "object";
|
|
1623
|
-
return void 0;
|
|
1732
|
+
function extractAttribute(tagContent, attrName) {
|
|
1733
|
+
const regex = new RegExp(
|
|
1734
|
+
`(?:^|\\s)${attrName}\\s*=\\s*(?:"([^"]*)"|'([^']*)'|(\\S+))`,
|
|
1735
|
+
"i"
|
|
1736
|
+
);
|
|
1737
|
+
const match = tagContent.match(regex);
|
|
1738
|
+
if (!match) return null;
|
|
1739
|
+
return match[1] ?? match[2] ?? match[3] ?? null;
|
|
1624
1740
|
}
|
|
1625
1741
|
|
|
1626
1742
|
// src/service/statePathResolver.ts
|
|
@@ -1634,33 +1750,45 @@ function getStatePathsFromHtml(html, stateTagName = "wcs-state", fileReader) {
|
|
|
1634
1750
|
return allPaths;
|
|
1635
1751
|
}
|
|
1636
1752
|
function resolveElementPaths(element, html, fileReader) {
|
|
1753
|
+
const raw = resolveElementPathsRaw(element, html, fileReader);
|
|
1754
|
+
if (element.mountPath === null) return raw;
|
|
1755
|
+
const prefix = element.mountPath + ".";
|
|
1756
|
+
const out = [];
|
|
1757
|
+
for (const p of raw) {
|
|
1758
|
+
if (p.path.startsWith("$")) continue;
|
|
1759
|
+
if (p.kind === "method" || p.kind === "eventToken") continue;
|
|
1760
|
+
out.push({ ...p, path: prefix + p.path });
|
|
1761
|
+
}
|
|
1762
|
+
return out;
|
|
1763
|
+
}
|
|
1764
|
+
function resolveElementPathsRaw(element, html, fileReader) {
|
|
1637
1765
|
if (element.stateAttr) {
|
|
1638
1766
|
const jsonContent = findScriptJsonById(html, element.stateAttr);
|
|
1639
1767
|
if (jsonContent) {
|
|
1640
|
-
const paths = analyzeJsonPaths(jsonContent
|
|
1768
|
+
const paths = analyzeJsonPaths(jsonContent);
|
|
1641
1769
|
if (paths.length > 0) return paths;
|
|
1642
1770
|
}
|
|
1643
1771
|
}
|
|
1644
1772
|
if (element.srcAttr && fileReader) {
|
|
1645
|
-
const paths = resolveSrcAttribute(element.srcAttr,
|
|
1773
|
+
const paths = resolveSrcAttribute(element.srcAttr, fileReader);
|
|
1646
1774
|
if (paths.length > 0) return paths;
|
|
1647
1775
|
}
|
|
1648
1776
|
if (element.jsonAttr) {
|
|
1649
|
-
const paths = analyzeJsonPaths(element.jsonAttr
|
|
1777
|
+
const paths = analyzeJsonPaths(element.jsonAttr);
|
|
1650
1778
|
if (paths.length > 0) return paths;
|
|
1651
1779
|
}
|
|
1652
1780
|
if (element.scriptBlocks.length > 0) {
|
|
1653
1781
|
return element.scriptBlocks.flatMap(
|
|
1654
|
-
(block) => analyzeStatePaths(block.content
|
|
1782
|
+
(block) => analyzeStatePaths(block.content)
|
|
1655
1783
|
);
|
|
1656
1784
|
}
|
|
1657
1785
|
return [];
|
|
1658
1786
|
}
|
|
1659
|
-
function resolveSrcAttribute(srcPath,
|
|
1787
|
+
function resolveSrcAttribute(srcPath, fileReader) {
|
|
1660
1788
|
if (srcPath.endsWith(".json")) {
|
|
1661
1789
|
const content = fileReader(srcPath);
|
|
1662
1790
|
if (content) {
|
|
1663
|
-
return analyzeJsonPaths(content
|
|
1791
|
+
return analyzeJsonPaths(content);
|
|
1664
1792
|
}
|
|
1665
1793
|
return [];
|
|
1666
1794
|
}
|
|
@@ -1668,18 +1796,18 @@ function resolveSrcAttribute(srcPath, stateName, fileReader) {
|
|
|
1668
1796
|
const tsPath = srcPath.replace(/\.js$/, ".ts");
|
|
1669
1797
|
const tsContent = fileReader(tsPath);
|
|
1670
1798
|
if (tsContent) {
|
|
1671
|
-
return analyzeStatePaths(tsContent
|
|
1799
|
+
return analyzeStatePaths(tsContent);
|
|
1672
1800
|
}
|
|
1673
1801
|
const jsContent = fileReader(srcPath);
|
|
1674
1802
|
if (jsContent) {
|
|
1675
|
-
return analyzeStatePaths(jsContent
|
|
1803
|
+
return analyzeStatePaths(jsContent);
|
|
1676
1804
|
}
|
|
1677
1805
|
return [];
|
|
1678
1806
|
}
|
|
1679
1807
|
if (srcPath.endsWith(".ts")) {
|
|
1680
1808
|
const content = fileReader(srcPath);
|
|
1681
1809
|
if (content) {
|
|
1682
|
-
return analyzeStatePaths(content
|
|
1810
|
+
return analyzeStatePaths(content);
|
|
1683
1811
|
}
|
|
1684
1812
|
return [];
|
|
1685
1813
|
}
|
|
@@ -1812,6 +1940,8 @@ var ja = {
|
|
|
1812
1940
|
commandTokenUndeclared: (t) => `\u30B3\u30DE\u30F3\u30C9\u30C8\u30FC\u30AF\u30F3 "${t}" \u306F $commandTokens \u306B\u5BA3\u8A00\u3055\u308C\u3066\u3044\u307E\u305B\u3093`,
|
|
1813
1941
|
streamPathMissing: (p) => `\u30D1\u30B9 "${p}" \u306F $streams \u5BA3\u8A00\u306B\u5B58\u5728\u3057\u307E\u305B\u3093`,
|
|
1814
1942
|
pathMissing: (p) => `\u30D1\u30B9 "${p}" \u306F\u72B6\u614B\u5B9A\u7FA9\u306B\u5B58\u5728\u3057\u307E\u305B\u3093`,
|
|
1943
|
+
pathNonexistent: (p) => `\u30D1\u30B9 "${p}" \u306F\u5BA3\u8A00\u3055\u308C\u305F stateSchema \u306B\u5B58\u5728\u3057\u307E\u305B\u3093`,
|
|
1944
|
+
pathTypeMismatch: (p, label, expected, actual) => `\u30D1\u30B9 "${p}" \u306F stateSchema \u4E0A\u3067 ${actual} \u578B\u3067\u3059\u304C\u3001${label} \u306B\u306F${JA_EXPECTED_LABEL[expected]}\u304C\u5FC5\u8981\u3067\u3059`,
|
|
1815
1945
|
expansionSuffix: (x) => `\uFF08\u5C55\u958B: ${x}\uFF09`,
|
|
1816
1946
|
patternPathOutsideFor: (p) => `\u30D1\u30BF\u30FC\u30F3\u30D1\u30B9 "${p}" \u306F <template for> \u306E\u5916\u5074\u3067\u306F\u4F7F\u7528\u3067\u304D\u307E\u305B\u3093`,
|
|
1817
1947
|
omittedPathOutsideFor: (p) => `\u7701\u7565\u30D1\u30B9 "${p}" \u306F <template for> \u306E\u5916\u5074\u3067\u306F\u4F7F\u7528\u3067\u304D\u307E\u305B\u3093`,
|
|
@@ -1851,7 +1981,21 @@ var ja = {
|
|
|
1851
1981
|
storageSeedClobber: (path, raw) => `<wcs-storage> \u306E value \u30D0\u30A4\u30F3\u30C9\u5148 "${path}" \u304C ${raw} \u3067\u30B7\u30FC\u30C9\u3055\u308C\u3066\u3044\u307E\u3059\u3002\u521D\u671F\u66F8\u304D\u623B\u3057\u304C\u4FDD\u5B58\u5024\u3092\u4E0A\u66F8\u304D\u3057\u307E\u3059 \u2014 undefined \u3067\u30B7\u30FC\u30C9\uFF08\`${path}: undefined\`\uFF09\u3059\u308B\u304B manual \u3092\u4ED8\u3051\u3066\u304F\u3060\u3055\u3044`,
|
|
1852
1982
|
devtoolsAfterState: () => `@wcstack/devtools/auto \u306F @wcstack/state/auto \u3088\u308A\u5148\u306B\u8AAD\u307F\u8FBC\u3093\u3067\u304F\u3060\u3055\u3044\uFF08\u5F8C\u3060\u3068\u914D\u7DDA\u53F0\u5E33\u304C\u30E9\u30A4\u30D6\u3067 captured \u3055\u308C\u307E\u305B\u3093\uFF09`,
|
|
1853
1983
|
baseHrefMissing: () => `@wcstack/router \u3092\u4F7F\u3046 SPA \u306B\u306F <head> \u5185\u306E <base href="/"> \u304C\u5FC5\u8981\u3067\u3059\uFF08\u7121\u3044\u3068\u30C7\u30A3\u30FC\u30D7\u30EA\u30F3\u30AF\u3067 basename \u304C\u8AA4\u5C0E\u51FA\u3055\u308C\u307E\u3059\uFF09`,
|
|
1854
|
-
signalsDualEntry: () => `@wcstack/signals \u3068 @wcstack/signals/dom \u304C\u540C\u4E00\u30DA\u30FC\u30B8\u304B\u3089 import \u3055\u308C\u3066\u3044\u307E\u3059\u3002CDN \u3067\u306F\u5404\u30A8\u30F3\u30C8\u30EA\u304C\u81EA\u5DF1\u5B8C\u7D50\u30D0\u30F3\u30C9\u30EB\u306E\u305F\u3081\u30EA\u30A2\u30AF\u30C6\u30A3\u30D6\u30B3\u30A2\u304C\u4E8C\u91CD\u5316\u3057\u3001\u5883\u754C\u3067\u53CD\u5FDC\u304C\u58CA\u308C\u307E\u3059 \u2014 \u3059\u3079\u3066 /dom \u30A8\u30F3\u30C8\u30EA\u304B\u3089 import \u3057\u3066\u304F\u3060\u3055\u3044
|
|
1984
|
+
signalsDualEntry: () => `@wcstack/signals \u3068 @wcstack/signals/dom \u304C\u540C\u4E00\u30DA\u30FC\u30B8\u304B\u3089 import \u3055\u308C\u3066\u3044\u307E\u3059\u3002CDN \u3067\u306F\u5404\u30A8\u30F3\u30C8\u30EA\u304C\u81EA\u5DF1\u5B8C\u7D50\u30D0\u30F3\u30C9\u30EB\u306E\u305F\u3081\u30EA\u30A2\u30AF\u30C6\u30A3\u30D6\u30B3\u30A2\u304C\u4E8C\u91CD\u5316\u3057\u3001\u5883\u754C\u3067\u53CD\u5FDC\u304C\u58CA\u308C\u307E\u3059 \u2014 \u3059\u3079\u3066 /dom \u30A8\u30F3\u30C8\u30EA\u304B\u3089 import \u3057\u3066\u304F\u3060\u3055\u3044`,
|
|
1985
|
+
namedStateAttrDeprecated: (name) => `name \u5C5E\u6027\u306F v2 \u3067\u64A4\u53BB\u3055\u308C\u307E\u3057\u305F\uFF081 root 1 \u30C4\u30EA\u30FC\uFF09\u3002\u30EB\u30FC\u30C8\u30C4\u30EA\u30FC\u3078\u306E\u30DE\u30A6\u30F3\u30C8 <wcs-state mount="${name}"> \u306B\u7F6E\u304D\u63DB\u3048\u3001\u30D1\u30B9\u306F "${name}.<path>" \u3067\u53C2\u7167\u3057\u3066\u304F\u3060\u3055\u3044\uFF08docs/state-mount-design.md \xA79\uFF09`,
|
|
1986
|
+
namedStatePathDeprecated: (name) => name === "default" ? `"@default" \u30BB\u30EC\u30AF\u30BF\u306F v2 \u3067\u64A4\u53BB\u3055\u308C\u307E\u3057\u305F\u3002"@default" \u3092\u5916\u3057\u3066\u304F\u3060\u3055\u3044\uFF08docs/state-mount-design.md \xA79\uFF09` : `"@name" \u30BB\u30EC\u30AF\u30BF\u306F v2 \u3067\u64A4\u53BB\u3055\u308C\u307E\u3057\u305F\uFF081 root 1 \u30C4\u30EA\u30FC\uFF09\u3002\u30DE\u30A6\u30F3\u30C8\u3057\u305F\u30C4\u30EA\u30FC\u3092 "${name}.<path>" \u3067\u53C2\u7167\u3057\u3066\u304F\u3060\u3055\u3044\uFF08docs/state-mount-design.md \xA79\uFF09`,
|
|
1987
|
+
mountPathInvalid: (problem, mountPath) => {
|
|
1988
|
+
switch (problem) {
|
|
1989
|
+
case "empty":
|
|
1990
|
+
return `"mount" \u306B\u306F\u7A7A\u3067\u306A\u3044\u30C4\u30EA\u30FC\u30D1\u30B9\u304C\u5FC5\u8981\u3067\u3059\uFF08runtime: "mount" requires a non-empty tree path.\uFF09`;
|
|
1991
|
+
case "emptySegment":
|
|
1992
|
+
return `"mount" \u30D1\u30B9 "${mountPath}" \u306B\u7A7A\u306E\u30BB\u30B0\u30E1\u30F3\u30C8\u304C\u3042\u308A\u307E\u3059\uFF08runtime: has an empty segment.\uFF09`;
|
|
1993
|
+
case "wildcard":
|
|
1994
|
+
return `"mount" \u30D1\u30B9 "${mountPath}" \u306F\u9759\u7684\u3067\u306A\u3051\u308C\u3070\u306A\u308A\u307E\u305B\u3093 \u2014 \u30EF\u30A4\u30EB\u30C9\u30AB\u30FC\u30C9\u306F\u4F7F\u3048\u307E\u305B\u3093\uFF08runtime: must be static.\uFF09`;
|
|
1995
|
+
default:
|
|
1996
|
+
return `"mount" \u30D1\u30B9 "${mountPath}" \u306B\u4E88\u7D04\u6587\u5B57\uFF08$, #, @\uFF09\u306F\u4F7F\u3048\u307E\u305B\u3093\uFF08runtime: must not use reserved characters.\uFF09`;
|
|
1997
|
+
}
|
|
1998
|
+
}
|
|
1855
1999
|
};
|
|
1856
2000
|
var EN_EXPECTED_LABEL = {
|
|
1857
2001
|
array: "an array-typed path",
|
|
@@ -1867,6 +2011,8 @@ var en = {
|
|
|
1867
2011
|
commandTokenUndeclared: (t) => `Command token "${t}" is not declared in $commandTokens`,
|
|
1868
2012
|
streamPathMissing: (p) => `Path "${p}" does not exist in the $streams declaration`,
|
|
1869
2013
|
pathMissing: (p) => `Path "${p}" does not exist in the state definition`,
|
|
2014
|
+
pathNonexistent: (p) => `Path "${p}" does not exist in the declared stateSchema`,
|
|
2015
|
+
pathTypeMismatch: (p, label, expected, actual) => `Path "${p}" is ${actual} in the stateSchema, but ${label} requires ${expected === "array" ? "an array" : expected === "boolean" ? "a boolean" : "a string"}`,
|
|
1870
2016
|
expansionSuffix: (x) => ` (expanded: ${x})`,
|
|
1871
2017
|
patternPathOutsideFor: (p) => `Pattern path "${p}" cannot be used outside a <template for>`,
|
|
1872
2018
|
omittedPathOutsideFor: (p) => `Shorthand path "${p}" cannot be used outside a <template for>`,
|
|
@@ -1906,25 +2052,225 @@ var en = {
|
|
|
1906
2052
|
storageSeedClobber: (path, raw) => `The <wcs-storage> value-bound slot "${path}" is seeded with ${raw}. The initial write-back overwrites the persisted value \u2014 seed it with undefined (\`${path}: undefined\`) or add manual`,
|
|
1907
2053
|
devtoolsAfterState: () => `Load @wcstack/devtools/auto BEFORE @wcstack/state/auto (otherwise the wiring ledger is not captured live)`,
|
|
1908
2054
|
baseHrefMissing: () => `An SPA using @wcstack/router needs <base href="/"> in <head> (without it, deep links misderive the basename)`,
|
|
1909
|
-
signalsDualEntry: () => `Both @wcstack/signals and @wcstack/signals/dom are imported on this page. On a CDN each entry is a self-contained bundle, so the reactive core is duplicated and reactivity breaks at the seam \u2014 import everything from the single /dom entry
|
|
2055
|
+
signalsDualEntry: () => `Both @wcstack/signals and @wcstack/signals/dom are imported on this page. On a CDN each entry is a self-contained bundle, so the reactive core is duplicated and reactivity breaks at the seam \u2014 import everything from the single /dom entry`,
|
|
2056
|
+
namedStateAttrDeprecated: (name) => `The "name" attribute was removed in v2 \u2014 there is a single state tree per root. Mount this state onto the tree instead: <wcs-state mount="${name}"> and read it as "${name}.<path>" (docs/state-mount-design.md \xA79)`,
|
|
2057
|
+
namedStatePathDeprecated: (name) => name === "default" ? `The "@default" selector was removed in v2 \u2014 drop it (docs/state-mount-design.md \xA79)` : `The "@name" selector was removed in v2 \u2014 there is a single state tree. Mount the named state onto the tree (<wcs-state mount="...">) and read it as "${name}.<path>" (docs/state-mount-design.md \xA79)`,
|
|
2058
|
+
mountPathInvalid: (problem, mountPath) => {
|
|
2059
|
+
switch (problem) {
|
|
2060
|
+
case "empty":
|
|
2061
|
+
return `"mount" requires a non-empty tree path.`;
|
|
2062
|
+
case "emptySegment":
|
|
2063
|
+
return `"mount" path "${mountPath}" has an empty segment.`;
|
|
2064
|
+
case "wildcard":
|
|
2065
|
+
return `"mount" path "${mountPath}" must be static (wildcards are not allowed).`;
|
|
2066
|
+
default:
|
|
2067
|
+
return `"mount" path "${mountPath}" must not use reserved characters ($, #, @).`;
|
|
2068
|
+
}
|
|
2069
|
+
}
|
|
1910
2070
|
};
|
|
1911
2071
|
var CATALOGS = { ja, en };
|
|
1912
2072
|
function getMessages(locale3) {
|
|
1913
2073
|
return CATALOGS[resolveLocale(locale3)];
|
|
1914
2074
|
}
|
|
1915
2075
|
|
|
2076
|
+
// src/core/sidecar/schemaSubset.ts
|
|
2077
|
+
var ALLOWED_SCHEMA_KEYWORDS = /* @__PURE__ */ new Set([
|
|
2078
|
+
"type",
|
|
2079
|
+
"properties",
|
|
2080
|
+
"required",
|
|
2081
|
+
"items",
|
|
2082
|
+
"enum",
|
|
2083
|
+
"const",
|
|
2084
|
+
"anyOf",
|
|
2085
|
+
"$defs",
|
|
2086
|
+
"$ref"
|
|
2087
|
+
]);
|
|
2088
|
+
var DiagnosticContext = class {
|
|
2089
|
+
constructor(spans) {
|
|
2090
|
+
this.spans = spans;
|
|
2091
|
+
}
|
|
2092
|
+
diagnostics = [];
|
|
2093
|
+
add(code, pointer2, message, severity, extra = {}, useKeySpan = false) {
|
|
2094
|
+
const span = this.spans.get(pointer2);
|
|
2095
|
+
const start = span === void 0 ? 0 : useKeySpan ? span.keyStart ?? span.start : span.start;
|
|
2096
|
+
const end = span === void 0 ? 0 : useKeySpan ? span.keyEnd ?? span.end : span.end;
|
|
2097
|
+
this.diagnostics.push({ code, start, end, message, severity, ...extra });
|
|
2098
|
+
}
|
|
2099
|
+
};
|
|
2100
|
+
function isSchemaObject(value) {
|
|
2101
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
2102
|
+
}
|
|
2103
|
+
function isSchemaMap(value) {
|
|
2104
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
2105
|
+
}
|
|
2106
|
+
function validateSchemaSubset(schema, pointerBase, ctx, rootDefs) {
|
|
2107
|
+
walkKeywords(schema, pointerBase, ctx, rootDefs);
|
|
2108
|
+
const safe = /* @__PURE__ */ new Set();
|
|
2109
|
+
detectCycles(schema, pointerBase, ctx, rootDefs, /* @__PURE__ */ new Set(), safe);
|
|
2110
|
+
for (const [name, def] of Object.entries(rootDefs)) {
|
|
2111
|
+
detectCycles(def, `${pointerBase}/$defs/${escape(name)}`, ctx, rootDefs, /* @__PURE__ */ new Set(), safe);
|
|
2112
|
+
}
|
|
2113
|
+
}
|
|
2114
|
+
function walkKeywords(node, ptr, ctx, rootDefs) {
|
|
2115
|
+
if (!isSchemaObject(node)) return;
|
|
2116
|
+
for (const keyword of Object.keys(node)) {
|
|
2117
|
+
if (!ALLOWED_SCHEMA_KEYWORDS.has(keyword)) {
|
|
2118
|
+
ctx.add(
|
|
2119
|
+
WcsDiagnosticCode.ManifestUnknownKeyword,
|
|
2120
|
+
`${ptr}/${escape(keyword)}`,
|
|
2121
|
+
`Unsupported schema keyword "${keyword}". Allowed: ${[...ALLOWED_SCHEMA_KEYWORDS].join(", ")}.`,
|
|
2122
|
+
"warning",
|
|
2123
|
+
{},
|
|
2124
|
+
true
|
|
2125
|
+
);
|
|
2126
|
+
}
|
|
2127
|
+
}
|
|
2128
|
+
if (typeof node.$ref === "string") {
|
|
2129
|
+
if (!node.$ref.startsWith("#/")) {
|
|
2130
|
+
ctx.add(
|
|
2131
|
+
WcsDiagnosticCode.ManifestExternalRef,
|
|
2132
|
+
`${ptr}/$ref`,
|
|
2133
|
+
`External $ref "${node.$ref}" is forbidden; only local "#/$defs/..." references are allowed.`,
|
|
2134
|
+
"error"
|
|
2135
|
+
);
|
|
2136
|
+
} else if (resolveLocalRef(node.$ref, rootDefs) === void 0) {
|
|
2137
|
+
ctx.add(
|
|
2138
|
+
WcsDiagnosticCode.ManifestRefUnresolved,
|
|
2139
|
+
`${ptr}/$ref`,
|
|
2140
|
+
`Unresolved local $ref "${node.$ref}".`,
|
|
2141
|
+
"error"
|
|
2142
|
+
);
|
|
2143
|
+
}
|
|
2144
|
+
}
|
|
2145
|
+
if (isSchemaMap(node.properties)) {
|
|
2146
|
+
for (const [name, child] of Object.entries(node.properties)) {
|
|
2147
|
+
walkKeywords(child, `${ptr}/properties/${escape(name)}`, ctx, rootDefs);
|
|
2148
|
+
}
|
|
2149
|
+
}
|
|
2150
|
+
if (node.items !== void 0 && isSchemaObject(node.items)) {
|
|
2151
|
+
walkKeywords(node.items, `${ptr}/items`, ctx, rootDefs);
|
|
2152
|
+
}
|
|
2153
|
+
if (Array.isArray(node.anyOf)) {
|
|
2154
|
+
node.anyOf.forEach((child, i) => walkKeywords(child, `${ptr}/anyOf/${i}`, ctx, rootDefs));
|
|
2155
|
+
}
|
|
2156
|
+
if (isSchemaMap(node.$defs)) {
|
|
2157
|
+
for (const [name, child] of Object.entries(node.$defs)) {
|
|
2158
|
+
walkKeywords(child, `${ptr}/$defs/${escape(name)}`, ctx, rootDefs);
|
|
2159
|
+
}
|
|
2160
|
+
}
|
|
2161
|
+
}
|
|
2162
|
+
function detectCycles(node, ptr, ctx, rootDefs, refStack, safe) {
|
|
2163
|
+
if (!isSchemaObject(node)) return;
|
|
2164
|
+
if (typeof node.$ref === "string") {
|
|
2165
|
+
const ref = node.$ref;
|
|
2166
|
+
if (!ref.startsWith("#/")) return;
|
|
2167
|
+
if (refStack.has(ref)) {
|
|
2168
|
+
ctx.add(WcsDiagnosticCode.ManifestRefCycle, `${ptr}/$ref`, `Cyclic $ref detected at "${ref}".`, "error");
|
|
2169
|
+
return;
|
|
2170
|
+
}
|
|
2171
|
+
if (safe.has(ref)) return;
|
|
2172
|
+
const target = resolveLocalRef(ref, rootDefs);
|
|
2173
|
+
if (target === void 0) return;
|
|
2174
|
+
refStack.add(ref);
|
|
2175
|
+
detectCycles(target, ptr, ctx, rootDefs, refStack, safe);
|
|
2176
|
+
refStack.delete(ref);
|
|
2177
|
+
safe.add(ref);
|
|
2178
|
+
return;
|
|
2179
|
+
}
|
|
2180
|
+
if (isSchemaMap(node.properties)) {
|
|
2181
|
+
for (const child of Object.values(node.properties)) detectCycles(child, ptr, ctx, rootDefs, refStack, safe);
|
|
2182
|
+
}
|
|
2183
|
+
if (node.items !== void 0 && isSchemaObject(node.items)) {
|
|
2184
|
+
detectCycles(node.items, ptr, ctx, rootDefs, refStack, safe);
|
|
2185
|
+
}
|
|
2186
|
+
if (Array.isArray(node.anyOf)) {
|
|
2187
|
+
for (const child of node.anyOf) detectCycles(child, ptr, ctx, rootDefs, refStack, safe);
|
|
2188
|
+
}
|
|
2189
|
+
}
|
|
2190
|
+
function resolveLocalRef(ref, rootDefs) {
|
|
2191
|
+
const match = /^#\/\$defs\/(.+)$/.exec(ref);
|
|
2192
|
+
if (match === null) return void 0;
|
|
2193
|
+
const name = match[1].replace(/~1/g, "/").replace(/~0/g, "~");
|
|
2194
|
+
return rootDefs[name];
|
|
2195
|
+
}
|
|
2196
|
+
function resolveSchemaPath(root, rootDefs, segments) {
|
|
2197
|
+
let current = root;
|
|
2198
|
+
for (let depth = 0; depth < segments.length; depth++) {
|
|
2199
|
+
const segment = segments[depth];
|
|
2200
|
+
const resolved = derefUnion(current, rootDefs);
|
|
2201
|
+
if (resolved.kind === "ref-error") return resolved;
|
|
2202
|
+
const candidates = resolved.nodes;
|
|
2203
|
+
if (segment === "*") {
|
|
2204
|
+
const items = firstDefined(candidates, (n) => isSchemaObject(n.items) ? n.items : void 0);
|
|
2205
|
+
if (items === void 0) {
|
|
2206
|
+
return { kind: "unknown" };
|
|
2207
|
+
}
|
|
2208
|
+
current = items;
|
|
2209
|
+
continue;
|
|
2210
|
+
}
|
|
2211
|
+
if (segment === "length" && candidates.some((n) => hasType(n, "array"))) {
|
|
2212
|
+
current = { type: "number" };
|
|
2213
|
+
continue;
|
|
2214
|
+
}
|
|
2215
|
+
const child = firstDefined(candidates, (n) => isSchemaMap(n.properties) ? n.properties[segment] : void 0);
|
|
2216
|
+
if (child !== void 0) {
|
|
2217
|
+
current = child;
|
|
2218
|
+
continue;
|
|
2219
|
+
}
|
|
2220
|
+
const anyObject = candidates.some((n) => hasType(n, "object") || isSchemaMap(n.properties));
|
|
2221
|
+
if (anyObject) {
|
|
2222
|
+
return { kind: "nonexistent", segment, depth };
|
|
2223
|
+
}
|
|
2224
|
+
return { kind: "unknown" };
|
|
2225
|
+
}
|
|
2226
|
+
const final = derefUnion(current, rootDefs);
|
|
2227
|
+
if (final.kind === "ref-error") return final;
|
|
2228
|
+
return { kind: "resolved", schema: final.nodes.length === 1 ? final.nodes[0] : current };
|
|
2229
|
+
}
|
|
2230
|
+
function derefUnion(node, rootDefs) {
|
|
2231
|
+
const out = [];
|
|
2232
|
+
const stack = [{ node, chain: /* @__PURE__ */ new Set() }];
|
|
2233
|
+
while (stack.length > 0) {
|
|
2234
|
+
const { node: n, chain } = stack.pop();
|
|
2235
|
+
if (typeof n.$ref === "string") {
|
|
2236
|
+
if (!n.$ref.startsWith("#/") || chain.has(n.$ref)) {
|
|
2237
|
+
return { kind: "ref-error", ref: n.$ref };
|
|
2238
|
+
}
|
|
2239
|
+
const target = resolveLocalRef(n.$ref, rootDefs);
|
|
2240
|
+
if (target === void 0) return { kind: "ref-error", ref: n.$ref };
|
|
2241
|
+
stack.push({ node: target, chain: /* @__PURE__ */ new Set([...chain, n.$ref]) });
|
|
2242
|
+
continue;
|
|
2243
|
+
}
|
|
2244
|
+
if (Array.isArray(n.anyOf)) {
|
|
2245
|
+
for (const branch of n.anyOf) stack.push({ node: branch, chain });
|
|
2246
|
+
continue;
|
|
2247
|
+
}
|
|
2248
|
+
out.push(n);
|
|
2249
|
+
}
|
|
2250
|
+
return { kind: "ok", nodes: out };
|
|
2251
|
+
}
|
|
2252
|
+
function firstDefined(nodes, pick) {
|
|
2253
|
+
for (const n of nodes) {
|
|
2254
|
+
const v = pick(n);
|
|
2255
|
+
if (v !== void 0) return v;
|
|
2256
|
+
}
|
|
2257
|
+
return void 0;
|
|
2258
|
+
}
|
|
2259
|
+
function hasType(node, t) {
|
|
2260
|
+
const type = node.type;
|
|
2261
|
+
if (type === void 0) return false;
|
|
2262
|
+
return Array.isArray(type) ? type.includes(t) : type === t;
|
|
2263
|
+
}
|
|
2264
|
+
function escape(key) {
|
|
2265
|
+
return key.replace(/~/g, "~0").replace(/\//g, "~1");
|
|
2266
|
+
}
|
|
2267
|
+
|
|
1916
2268
|
// src/service/bindingValidator.ts
|
|
1917
2269
|
var filterMap = new Map(BUILTIN_FILTERS.map((f) => [f.name, f]));
|
|
1918
|
-
function validateBindings(html, attrName, stateTagName = "wcs-state", locale3, fileReader) {
|
|
2270
|
+
function validateBindings(html, attrName, stateTagName = "wcs-state", locale3, fileReader, applicationSchema) {
|
|
1919
2271
|
const diagnostics = [];
|
|
1920
2272
|
const msgs = getMessages(locale3);
|
|
1921
|
-
const statePaths = getStatePathsFromHtml(html, stateTagName, fileReader);
|
|
1922
|
-
const pathsByState = /* @__PURE__ */ new Map();
|
|
1923
|
-
for (const p of statePaths) {
|
|
1924
|
-
const list = pathsByState.get(p.stateName) ?? [];
|
|
1925
|
-
list.push(p);
|
|
1926
|
-
pathsByState.set(p.stateName, list);
|
|
1927
|
-
}
|
|
2273
|
+
const statePaths = mergeSchemaCandidates(getStatePathsFromHtml(html, stateTagName, fileReader), applicationSchema);
|
|
1928
2274
|
const attrs = findAllBindAttributes(html, attrName);
|
|
1929
2275
|
let structuralTemplates = null;
|
|
1930
2276
|
const getStructuralTemplates = () => {
|
|
@@ -1957,7 +2303,7 @@ function validateBindings(html, attrName, stateTagName = "wcs-state", locale3, f
|
|
|
1957
2303
|
for (const binding of bindings) {
|
|
1958
2304
|
const bindingStart = attr.valueStart + pos;
|
|
1959
2305
|
const parsed = parseBindingExpression(binding);
|
|
1960
|
-
const scopedPaths =
|
|
2306
|
+
const scopedPaths = statePaths;
|
|
1961
2307
|
const scopedPathSet = new Set(scopedPaths.map((p) => p.path));
|
|
1962
2308
|
const propNoMod = parsed.property.replace(/#.*$/, "").trim();
|
|
1963
2309
|
if (propNoMod === "...") {
|
|
@@ -2041,16 +2387,17 @@ function validateBindings(html, attrName, stateTagName = "wcs-state", locale3, f
|
|
|
2041
2387
|
}
|
|
2042
2388
|
}
|
|
2043
2389
|
if (checkPath) {
|
|
2044
|
-
const
|
|
2045
|
-
|
|
2390
|
+
const schema = applicationSchema;
|
|
2391
|
+
const verdict = schema !== void 0 ? validateSchemaPathExistence(checkPath, pathTrimmed, scopedPaths, scopedPathSet, commandNames, schema, msgs) : toMissingVerdict(validatePathExistence(checkPath, pathTrimmed, scopedPaths, scopedPathSet, commandNames, msgs));
|
|
2392
|
+
if (verdict) {
|
|
2046
2393
|
const pathOffset = binding.indexOf(parsed.path);
|
|
2047
2394
|
const pathStart = bindingStart + pathOffset;
|
|
2048
2395
|
diagnostics.push({
|
|
2049
|
-
code:
|
|
2396
|
+
code: verdict.code,
|
|
2050
2397
|
start: pathStart,
|
|
2051
2398
|
end: pathStart + pathTrimmed.length,
|
|
2052
|
-
message: `${message}${pathTrimmed.startsWith(".") ? msgs.expansionSuffix(checkPath) : ""}`,
|
|
2053
|
-
severity:
|
|
2399
|
+
message: `${verdict.message}${pathTrimmed.startsWith(".") ? msgs.expansionSuffix(checkPath) : ""}`,
|
|
2400
|
+
severity: verdict.severity
|
|
2054
2401
|
});
|
|
2055
2402
|
}
|
|
2056
2403
|
}
|
|
@@ -2169,12 +2516,13 @@ function validateBindings(html, attrName, stateTagName = "wcs-state", locale3, f
|
|
|
2169
2516
|
if (typeReq && resultType !== typeReq.expected) {
|
|
2170
2517
|
const pathOffset = binding.indexOf(parsed.path);
|
|
2171
2518
|
const pathStart = bindingStart + pathOffset;
|
|
2519
|
+
const schemaDefinite = typeReq.expected === "array" && parsed.filters.length === 0 && applicationSchema !== void 0 && scopedPaths.some((p) => p.path === pathTrimmed && p.fromSchema === true);
|
|
2172
2520
|
diagnostics.push({
|
|
2173
|
-
code: WcsDiagnosticCode.BindingTypeExpectation,
|
|
2521
|
+
code: schemaDefinite ? WcsDiagnosticCode.PathTypeMismatch : WcsDiagnosticCode.BindingTypeExpectation,
|
|
2174
2522
|
start: pathStart,
|
|
2175
2523
|
end: pathStart + pathTrimmed.length,
|
|
2176
|
-
message: msgs.typeExpectation(typeReq.label, typeReq.expected, resultType),
|
|
2177
|
-
severity: typeReq.severity
|
|
2524
|
+
message: schemaDefinite ? msgs.pathTypeMismatch(pathTrimmed, typeReq.label, typeReq.expected, resultType) : msgs.typeExpectation(typeReq.label, typeReq.expected, resultType),
|
|
2525
|
+
severity: schemaDefinite ? "error" : typeReq.severity
|
|
2178
2526
|
});
|
|
2179
2527
|
}
|
|
2180
2528
|
}
|
|
@@ -2222,7 +2570,7 @@ function splitBindingExpressions(value) {
|
|
|
2222
2570
|
function parseBindingExpression(expr) {
|
|
2223
2571
|
const colonIndex = expr.indexOf(":");
|
|
2224
2572
|
if (colonIndex === -1) {
|
|
2225
|
-
return { property: expr.trim(), path: null,
|
|
2573
|
+
return { property: expr.trim(), path: null, filters: [], inputFilters: [] };
|
|
2226
2574
|
}
|
|
2227
2575
|
const rawProp = expr.slice(0, colonIndex);
|
|
2228
2576
|
const propSegments = splitByPipe(rawProp);
|
|
@@ -2234,9 +2582,8 @@ function parseBindingExpression(expr) {
|
|
|
2234
2582
|
const filterSegments = segments.slice(1);
|
|
2235
2583
|
const atIndex = pathSegment.indexOf("@");
|
|
2236
2584
|
const path = atIndex !== -1 ? pathSegment.slice(0, atIndex) : pathSegment;
|
|
2237
|
-
const targetState = atIndex !== -1 ? pathSegment.slice(atIndex + 1).trim() || "default" : "default";
|
|
2238
2585
|
const filters = parseFilterSegments(expr, filterSegments, colonIndex + 1 + pathSegment.length + 1);
|
|
2239
|
-
return { property, path: path.trim() || null,
|
|
2586
|
+
return { property, path: path.trim() || null, filters, inputFilters };
|
|
2240
2587
|
}
|
|
2241
2588
|
function parseFilterSegments(expr, segments, searchStart) {
|
|
2242
2589
|
const filters = [];
|
|
@@ -2345,6 +2692,20 @@ function validatePathExistence(checkPath, displayPath, scopedPaths, scopedPathSe
|
|
|
2345
2692
|
}
|
|
2346
2693
|
return null;
|
|
2347
2694
|
}
|
|
2695
|
+
function toMissingVerdict(message) {
|
|
2696
|
+
return message ? { code: WcsDiagnosticCode.BindingPathMissing, message, severity: "warning" } : null;
|
|
2697
|
+
}
|
|
2698
|
+
function validateSchemaPathExistence(checkPath, displayPath, scopedPaths, scopedPathSet, commandNames, schema, msgs) {
|
|
2699
|
+
if (checkPath.startsWith("$")) {
|
|
2700
|
+
return toMissingVerdict(validatePathExistence(checkPath, displayPath, scopedPaths, scopedPathSet, commandNames, msgs));
|
|
2701
|
+
}
|
|
2702
|
+
if (scopedPathSet.has(checkPath)) return null;
|
|
2703
|
+
const resolution = resolveSchemaPath(schema, schema.$defs ?? {}, checkPath.split("."));
|
|
2704
|
+
if (resolution.kind === "nonexistent") {
|
|
2705
|
+
return { code: WcsDiagnosticCode.PathNonexistent, message: msgs.pathNonexistent(displayPath), severity: "error" };
|
|
2706
|
+
}
|
|
2707
|
+
return null;
|
|
2708
|
+
}
|
|
2348
2709
|
function collectStructuralTemplates(html, attrName) {
|
|
2349
2710
|
const escaped = attrName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
2350
2711
|
const attrRegex = new RegExp(`${escaped}\\s*=\\s*(["'])`, "i");
|
|
@@ -2746,12 +3107,21 @@ function isInsideTag(html, offset, tagName) {
|
|
|
2746
3107
|
}
|
|
2747
3108
|
|
|
2748
3109
|
// src/service/templateSyntaxValidator.ts
|
|
2749
|
-
function validateTemplateSyntax(html, stateTagName, bindAttrName = "data-wcs", locale3, fileReader) {
|
|
3110
|
+
function validateTemplateSyntax(html, stateTagName, bindAttrName = "data-wcs", locale3, fileReader, applicationSchema) {
|
|
2750
3111
|
const diagnostics = [];
|
|
2751
3112
|
const msgs = getMessages(locale3);
|
|
2752
|
-
const allPaths = getStatePathsFromHtml(html, stateTagName, fileReader);
|
|
3113
|
+
const allPaths = mergeSchemaCandidates(getStatePathsFromHtml(html, stateTagName, fileReader), applicationSchema);
|
|
3114
|
+
const defaultSchema = applicationSchema;
|
|
3115
|
+
const missingVerdict = (path, displayPath, pathSet2, scoped) => {
|
|
3116
|
+
if (isValidTemplatePath(path, pathSet2, scoped)) return null;
|
|
3117
|
+
if (defaultSchema !== void 0 && !path.startsWith("$")) {
|
|
3118
|
+
const resolution = resolveSchemaPath(defaultSchema, defaultSchema.$defs ?? {}, path.split("."));
|
|
3119
|
+
return resolution.kind === "nonexistent" ? { code: WcsDiagnosticCode.PathNonexistent, severity: "error", message: msgs.pathNonexistent(displayPath) } : null;
|
|
3120
|
+
}
|
|
3121
|
+
return { code: WcsDiagnosticCode.BindingPathMissing, severity: "warning", message: msgs.pathMissing(displayPath) };
|
|
3122
|
+
};
|
|
2753
3123
|
if (allPaths.length === 0) return diagnostics;
|
|
2754
|
-
const defaultPaths = allPaths
|
|
3124
|
+
const defaultPaths = allPaths;
|
|
2755
3125
|
const pathSet = new Set(defaultPaths.map((p) => p.path));
|
|
2756
3126
|
const filterNameSet = new Set(BUILTIN_FILTERS.map((f) => f.name));
|
|
2757
3127
|
const mustaches = findAllMustacheSyntax(html);
|
|
@@ -2777,9 +3147,8 @@ function validateTemplateSyntax(html, stateTagName, bindAttrName = "data-wcs", l
|
|
|
2777
3147
|
}
|
|
2778
3148
|
if (!item.expression) continue;
|
|
2779
3149
|
const parts = item.expression.split("|");
|
|
2780
|
-
|
|
2781
|
-
|
|
2782
|
-
if (atIdx !== -1) pathPart = pathPart.slice(0, atIdx).trim();
|
|
3150
|
+
const pathPart = (parts[0] || "").trim();
|
|
3151
|
+
if (pathPart.includes("@")) continue;
|
|
2783
3152
|
const insideFor = item.insideTemplate && isInsideForTemplate(html, item.matchStart, bindAttrName);
|
|
2784
3153
|
if (pathPart && !/^-?\d|^["'`]|^true$|^false$|^null$/.test(pathPart)) {
|
|
2785
3154
|
if (!insideFor && pathPart.includes("*")) {
|
|
@@ -2800,7 +3169,7 @@ function validateTemplateSyntax(html, stateTagName, bindAttrName = "data-wcs", l
|
|
|
2800
3169
|
severity: "warning"
|
|
2801
3170
|
});
|
|
2802
3171
|
}
|
|
2803
|
-
if (insideFor && !pathPart.startsWith(".")
|
|
3172
|
+
if (insideFor && !pathPart.startsWith(".")) {
|
|
2804
3173
|
const indexMatch = /^\$(\d+)$/.exec(pathPart);
|
|
2805
3174
|
const needed = indexMatch !== null ? Number(indexMatch[1]) : pathPart.includes("*") ? countWildcardSegments(pathPart) : 0;
|
|
2806
3175
|
if (needed > 0) {
|
|
@@ -2829,24 +3198,28 @@ function validateTemplateSyntax(html, stateTagName, bindAttrName = "data-wcs", l
|
|
|
2829
3198
|
const forPath = insideFor ? getInnermostForPath(html, item.matchStart, bindAttrName) : null;
|
|
2830
3199
|
if (forPath && !forPath.startsWith(".")) {
|
|
2831
3200
|
const expandedPath = pathPart === "." ? `${forPath}.*` : `${forPath}.*.${pathPart.slice(1)}`;
|
|
2832
|
-
|
|
3201
|
+
const verdict = missingVerdict(expandedPath, pathPart, pathSet, defaultPaths);
|
|
3202
|
+
if (verdict) {
|
|
2833
3203
|
diagnostics.push({
|
|
2834
|
-
code:
|
|
3204
|
+
code: verdict.code,
|
|
2835
3205
|
start: item.exprStart,
|
|
2836
3206
|
end: item.exprStart + pathPart.length,
|
|
2837
|
-
message:
|
|
2838
|
-
severity:
|
|
3207
|
+
message: verdict.message + msgs.expansionSuffix(expandedPath),
|
|
3208
|
+
severity: verdict.severity
|
|
2839
3209
|
});
|
|
2840
3210
|
}
|
|
2841
3211
|
}
|
|
2842
|
-
} else
|
|
2843
|
-
|
|
2844
|
-
|
|
2845
|
-
|
|
2846
|
-
|
|
2847
|
-
|
|
2848
|
-
|
|
2849
|
-
|
|
3212
|
+
} else {
|
|
3213
|
+
const verdict = missingVerdict(pathPart, pathPart, pathSet, defaultPaths);
|
|
3214
|
+
if (verdict) {
|
|
3215
|
+
diagnostics.push({
|
|
3216
|
+
code: verdict.code,
|
|
3217
|
+
start: item.exprStart,
|
|
3218
|
+
end: item.exprStart + pathPart.length,
|
|
3219
|
+
message: verdict.message,
|
|
3220
|
+
severity: verdict.severity
|
|
3221
|
+
});
|
|
3222
|
+
}
|
|
2850
3223
|
}
|
|
2851
3224
|
}
|
|
2852
3225
|
for (let i = 1; i < parts.length; i++) {
|
|
@@ -3732,11 +4105,14 @@ var BUILTIN_TAGS = {
|
|
|
3732
4105
|
"wcs-raf": {
|
|
3733
4106
|
"package": "raf",
|
|
3734
4107
|
"hasWcBindable": true,
|
|
3735
|
-
"observedAttributes": [
|
|
4108
|
+
"observedAttributes": [
|
|
4109
|
+
"reduced-motion"
|
|
4110
|
+
],
|
|
3736
4111
|
"inputs": {
|
|
3737
4112
|
"once": "once",
|
|
3738
4113
|
"repeat": "repeat",
|
|
3739
4114
|
"manual": "manual",
|
|
4115
|
+
"reducedMotion": "reduced-motion",
|
|
3740
4116
|
"trigger": null
|
|
3741
4117
|
},
|
|
3742
4118
|
"properties": [
|
|
@@ -4255,7 +4631,7 @@ function validateBindingAgainstContract(tagName, contract, parsed, property, sta
|
|
|
4255
4631
|
return;
|
|
4256
4632
|
}
|
|
4257
4633
|
if (property === "trigger" && "trigger" in contract.inputs && parsed.path) {
|
|
4258
|
-
const cand = findDataSlot(getPaths(), parsed.path
|
|
4634
|
+
const cand = findDataSlot(getPaths(), parsed.path);
|
|
4259
4635
|
if (cand?.rawInitial === "true") {
|
|
4260
4636
|
diagnostics.push({
|
|
4261
4637
|
code: WcsDiagnosticCode.TriggerSeededTruthy,
|
|
@@ -4269,7 +4645,7 @@ function validateBindingAgainstContract(tagName, contract, parsed, property, sta
|
|
|
4269
4645
|
}
|
|
4270
4646
|
}
|
|
4271
4647
|
if (tagName === "wcs-storage" && property === "value" && !hasManual && parsed.path && !/(?:^|,)init=(?:element|auto)\b/.test(modifiers)) {
|
|
4272
|
-
const cand = findDataSlot(getPaths(), parsed.path
|
|
4648
|
+
const cand = findDataSlot(getPaths(), parsed.path);
|
|
4273
4649
|
if (cand?.rawInitial !== void 0 && EMPTYISH_SEEDS.has(normalizeSeed(cand.rawInitial))) {
|
|
4274
4650
|
diagnostics.push({
|
|
4275
4651
|
code: WcsDiagnosticCode.StorageSeedClobber,
|
|
@@ -4283,8 +4659,8 @@ function validateBindingAgainstContract(tagName, contract, parsed, property, sta
|
|
|
4283
4659
|
}
|
|
4284
4660
|
}
|
|
4285
4661
|
}
|
|
4286
|
-
function findDataSlot(paths, path
|
|
4287
|
-
return paths.find((c) => c.kind === "data" && c.path === path
|
|
4662
|
+
function findDataSlot(paths, path) {
|
|
4663
|
+
return paths.find((c) => c.kind === "data" && c.path === path);
|
|
4288
4664
|
}
|
|
4289
4665
|
function normalizeSeed(raw) {
|
|
4290
4666
|
const compact = raw.replace(/\s+/g, "");
|
|
@@ -4559,7 +4935,7 @@ function blankJsComments(code) {
|
|
|
4559
4935
|
}
|
|
4560
4936
|
|
|
4561
4937
|
// src/service/watchDeclarationValidator.ts
|
|
4562
|
-
var
|
|
4938
|
+
var STATE_NAME_SEPARATOR = "@";
|
|
4563
4939
|
function validateWatchDeclarations(html, stateTagName = "wcs-state", locale3) {
|
|
4564
4940
|
const msgs = getMessages(locale3);
|
|
4565
4941
|
const out = [];
|
|
@@ -4576,7 +4952,7 @@ function validateWatchDeclarations(html, stateTagName = "wcs-state", locale3) {
|
|
|
4576
4952
|
}
|
|
4577
4953
|
const entries = analyzeWatchEntries(block.content);
|
|
4578
4954
|
if (entries.length === 0) continue;
|
|
4579
|
-
const paths = analyzeStatePaths(block.content
|
|
4955
|
+
const paths = analyzeStatePaths(block.content);
|
|
4580
4956
|
const pathSet = new Set(paths.map((p) => p.path));
|
|
4581
4957
|
for (const entry of entries) {
|
|
4582
4958
|
const diagnostic = validateEntry(entry, pathSet, msgs);
|
|
@@ -4595,7 +4971,7 @@ function validateWatchDeclarations(html, stateTagName = "wcs-state", locale3) {
|
|
|
4595
4971
|
function validateEntry(entry, pathSet, msgs) {
|
|
4596
4972
|
const { key } = entry;
|
|
4597
4973
|
const invalid = (message) => ({ code: WcsDiagnosticCode.WatchDeclarationInvalid, message, severity: "error" });
|
|
4598
|
-
if (key.includes(
|
|
4974
|
+
if (key.includes(STATE_NAME_SEPARATOR)) {
|
|
4599
4975
|
return invalid(msgs.watchKeyCrossState(key));
|
|
4600
4976
|
}
|
|
4601
4977
|
if (key.startsWith("$")) {
|
|
@@ -4617,6 +4993,112 @@ function validateEntry(entry, pathSet, msgs) {
|
|
|
4617
4993
|
return null;
|
|
4618
4994
|
}
|
|
4619
4995
|
|
|
4996
|
+
// src/service/namedStateValidator.ts
|
|
4997
|
+
function findStateSelector(expr, embedded = false) {
|
|
4998
|
+
const colon = embedded ? -1 : expr.indexOf(":");
|
|
4999
|
+
const from = colon + 1;
|
|
5000
|
+
let depth = 0;
|
|
5001
|
+
let end = expr.length;
|
|
5002
|
+
for (let i = from; i < expr.length; i++) {
|
|
5003
|
+
const ch = expr[i];
|
|
5004
|
+
if (ch === "(") depth++;
|
|
5005
|
+
else if (ch === ")") depth = Math.max(0, depth - 1);
|
|
5006
|
+
else if (ch === "|" && depth === 0) {
|
|
5007
|
+
end = i;
|
|
5008
|
+
break;
|
|
5009
|
+
}
|
|
5010
|
+
}
|
|
5011
|
+
const at = expr.indexOf("@", from);
|
|
5012
|
+
if (at === -1 || at >= end) return null;
|
|
5013
|
+
const raw = expr.slice(at + 1, end);
|
|
5014
|
+
const name = raw.trim();
|
|
5015
|
+
const nameStart = at + 1 + (raw.length - raw.trimStart().length);
|
|
5016
|
+
return { start: at, end: name.length === 0 ? at + 1 : nameStart + name.length, name: name.length === 0 ? "default" : name };
|
|
5017
|
+
}
|
|
5018
|
+
function validateNamedState(html, attrName, stateTagName = "wcs-state", locale3) {
|
|
5019
|
+
const msgs = getMessages(locale3);
|
|
5020
|
+
const diagnostics = [];
|
|
5021
|
+
for (const element of parseWcsStateElements(html, stateTagName)) {
|
|
5022
|
+
const tagText = html.slice(element.tagStart, element.tagEnd);
|
|
5023
|
+
const match = /(?:^|\s)name\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s>]+))/i.exec(tagText);
|
|
5024
|
+
if (match === null) continue;
|
|
5025
|
+
const value = match[1] ?? match[2] ?? match[3] ?? "";
|
|
5026
|
+
const quoted = match[1] !== void 0 || match[2] !== void 0;
|
|
5027
|
+
const valueEnd = element.tagStart + match.index + match[0].length - (quoted ? 1 : 0);
|
|
5028
|
+
diagnostics.push({
|
|
5029
|
+
code: WcsDiagnosticCode.NamedStateDeprecated,
|
|
5030
|
+
start: valueEnd - value.length,
|
|
5031
|
+
end: valueEnd,
|
|
5032
|
+
message: msgs.namedStateAttrDeprecated(value),
|
|
5033
|
+
severity: "error"
|
|
5034
|
+
});
|
|
5035
|
+
}
|
|
5036
|
+
for (const attr of findAllBindAttributes(html, attrName)) {
|
|
5037
|
+
let pos = 0;
|
|
5038
|
+
for (const expr of splitBindingExpressions(attr.value)) {
|
|
5039
|
+
const selector = findStateSelector(expr);
|
|
5040
|
+
if (selector !== null) {
|
|
5041
|
+
diagnostics.push({
|
|
5042
|
+
code: WcsDiagnosticCode.NamedStateDeprecated,
|
|
5043
|
+
start: attr.valueStart + pos + selector.start,
|
|
5044
|
+
end: attr.valueStart + pos + selector.end,
|
|
5045
|
+
message: msgs.namedStatePathDeprecated(selector.name),
|
|
5046
|
+
severity: "error"
|
|
5047
|
+
});
|
|
5048
|
+
}
|
|
5049
|
+
pos += expr.length + 1;
|
|
5050
|
+
}
|
|
5051
|
+
}
|
|
5052
|
+
for (const item of [...findAllMustacheSyntax(html), ...findAllCommentBindings(html)]) {
|
|
5053
|
+
const selector = findStateSelector(item.expression, true);
|
|
5054
|
+
if (selector !== null) {
|
|
5055
|
+
diagnostics.push({
|
|
5056
|
+
code: WcsDiagnosticCode.NamedStateDeprecated,
|
|
5057
|
+
start: item.exprStart + selector.start,
|
|
5058
|
+
end: item.exprStart + selector.end,
|
|
5059
|
+
message: msgs.namedStatePathDeprecated(selector.name),
|
|
5060
|
+
severity: "error"
|
|
5061
|
+
});
|
|
5062
|
+
}
|
|
5063
|
+
}
|
|
5064
|
+
return diagnostics;
|
|
5065
|
+
}
|
|
5066
|
+
|
|
5067
|
+
// src/service/mountAttrValidator.ts
|
|
5068
|
+
function findMountPathProblem(mountPath) {
|
|
5069
|
+
if (mountPath.length === 0) return "empty";
|
|
5070
|
+
for (const segment of mountPath.split(".")) {
|
|
5071
|
+
if (segment.length === 0) return "emptySegment";
|
|
5072
|
+
if (segment === "*") return "wildcard";
|
|
5073
|
+
if (segment.includes("$") || segment.includes("#") || segment.includes("@")) return "reserved";
|
|
5074
|
+
}
|
|
5075
|
+
return null;
|
|
5076
|
+
}
|
|
5077
|
+
function validateMountAttributes(html, stateTagName = "wcs-state", locale3) {
|
|
5078
|
+
const msgs = getMessages(locale3);
|
|
5079
|
+
const diagnostics = [];
|
|
5080
|
+
for (const element of parseWcsStateElements(html, stateTagName)) {
|
|
5081
|
+
if (element.mountPath === null) continue;
|
|
5082
|
+
const problem = findMountPathProblem(element.mountPath);
|
|
5083
|
+
if (problem === null) continue;
|
|
5084
|
+
const tagText = html.slice(element.tagStart, element.tagEnd);
|
|
5085
|
+
const match = /(?:^|\s)mount\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s>]+))/i.exec(tagText);
|
|
5086
|
+
if (match === null) continue;
|
|
5087
|
+
const value = match[1] ?? match[2] ?? match[3] ?? "";
|
|
5088
|
+
const quoted = match[1] !== void 0 || match[2] !== void 0;
|
|
5089
|
+
const valueEnd = element.tagStart + match.index + match[0].length - (quoted ? 1 : 0);
|
|
5090
|
+
const attrStart = element.tagStart + match.index + (/^\s/.test(match[0]) ? 1 : 0);
|
|
5091
|
+
diagnostics.push({
|
|
5092
|
+
code: WcsDiagnosticCode.MountPathInvalid,
|
|
5093
|
+
start: value.length === 0 ? attrStart : valueEnd - value.length,
|
|
5094
|
+
end: valueEnd + (quoted && value.length === 0 ? 1 : 0),
|
|
5095
|
+
message: msgs.mountPathInvalid(problem, element.mountPath),
|
|
5096
|
+
severity: "error"
|
|
5097
|
+
});
|
|
5098
|
+
}
|
|
5099
|
+
return diagnostics;
|
|
5100
|
+
}
|
|
5101
|
+
|
|
4620
5102
|
// ../state/dist/parser.esm.js
|
|
4621
5103
|
var DELIMITER2 = ".";
|
|
4622
5104
|
var WILDCARD2 = "*";
|
|
@@ -4624,7 +5106,6 @@ var MAX_WILDCARD_DEPTH2 = 128;
|
|
|
4624
5106
|
var BINDING_SEPARATOR2 = ";";
|
|
4625
5107
|
var PROP_VALUE_SEPARATOR2 = ":";
|
|
4626
5108
|
var MODIFIER_SEPARATOR2 = "#";
|
|
4627
|
-
var STATE_NAME_SEPARATOR3 = "@";
|
|
4628
5109
|
var FILTER_SEPARATOR2 = "|";
|
|
4629
5110
|
var ELSE_KEYWORD2 = "else";
|
|
4630
5111
|
var SPREAD_PROP2 = "...";
|
|
@@ -5480,10 +5961,12 @@ function parseStatePart(statePart) {
|
|
|
5480
5961
|
} else {
|
|
5481
5962
|
stateAndPath = statePart.trim();
|
|
5482
5963
|
}
|
|
5483
|
-
|
|
5964
|
+
if (stateAndPath.indexOf("@") !== -1) {
|
|
5965
|
+
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.`);
|
|
5966
|
+
}
|
|
5967
|
+
const statePathName = stateAndPath;
|
|
5484
5968
|
const pathInfo = getPathInfo(statePathName);
|
|
5485
5969
|
return {
|
|
5486
|
-
stateName,
|
|
5487
5970
|
statePathName,
|
|
5488
5971
|
statePathInfo: pathInfo,
|
|
5489
5972
|
outFilters: filters
|
|
@@ -5506,7 +5989,6 @@ function parseBindTextsForElement(bindText) {
|
|
|
5506
5989
|
propModifiers: [],
|
|
5507
5990
|
statePathName: "#else",
|
|
5508
5991
|
statePathInfo: pathInfo,
|
|
5509
|
-
stateName: "",
|
|
5510
5992
|
inFilters: [],
|
|
5511
5993
|
outFilters: [],
|
|
5512
5994
|
bindingType: "else"
|
|
@@ -5606,24 +6088,18 @@ function parseEmbeddedTextWithPositions(expression) {
|
|
|
5606
6088
|
error = e.message;
|
|
5607
6089
|
}
|
|
5608
6090
|
if (parsed === null) {
|
|
5609
|
-
return { exprRange, exprText: expression, parsed, error, propRange: null, pathRange: null
|
|
6091
|
+
return { exprRange, exprText: expression, parsed, error, propRange: null, pathRange: null };
|
|
5610
6092
|
}
|
|
5611
6093
|
const firstPipe = expression.indexOf(delimiters.filter);
|
|
5612
6094
|
const pathScopeEnd = firstPipe === -1 ? expression.length : firstPipe;
|
|
5613
6095
|
const pathLocal = locate(expression, parsed.statePathName, 0, pathScopeEnd);
|
|
5614
|
-
let stateNameLocal = null;
|
|
5615
|
-
const at = expression.indexOf(delimiters.stateName);
|
|
5616
|
-
if (at !== -1 && at < pathScopeEnd) {
|
|
5617
|
-
stateNameLocal = locate(expression, parsed.stateName, at + 1, pathScopeEnd);
|
|
5618
|
-
}
|
|
5619
6096
|
return {
|
|
5620
6097
|
exprRange,
|
|
5621
6098
|
exprText: expression,
|
|
5622
6099
|
parsed,
|
|
5623
6100
|
error,
|
|
5624
6101
|
propRange: null,
|
|
5625
|
-
pathRange: pathLocal
|
|
5626
|
-
stateNameRange: stateNameLocal
|
|
6102
|
+
pathRange: pathLocal
|
|
5627
6103
|
};
|
|
5628
6104
|
}
|
|
5629
6105
|
function parseBindTextWithPositions(bindText) {
|
|
@@ -5645,23 +6121,18 @@ function parseBindTextWithPositions(bindText) {
|
|
|
5645
6121
|
error = e.message;
|
|
5646
6122
|
}
|
|
5647
6123
|
if (parsed === null) {
|
|
5648
|
-
results.push({ exprRange, exprText: expr, parsed, error, propRange: null, pathRange: null
|
|
6124
|
+
results.push({ exprRange, exprText: expr, parsed, error, propRange: null, pathRange: null });
|
|
5649
6125
|
continue;
|
|
5650
6126
|
}
|
|
5651
6127
|
const colon = expr.indexOf(delimiters.propValue);
|
|
5652
6128
|
const propEndLimit = colon === -1 ? expr.length : colon;
|
|
5653
6129
|
const propLocal = locate(expr, parsed.propName, 0, propEndLimit);
|
|
5654
6130
|
let pathLocal = null;
|
|
5655
|
-
let stateNameLocal = null;
|
|
5656
6131
|
if (colon !== -1) {
|
|
5657
6132
|
const stateBase = colon + 1;
|
|
5658
6133
|
const firstPipe = expr.indexOf(delimiters.filter, stateBase);
|
|
5659
6134
|
const pathScopeEnd = firstPipe === -1 ? expr.length : firstPipe;
|
|
5660
6135
|
pathLocal = locate(expr, parsed.statePathName, stateBase, pathScopeEnd);
|
|
5661
|
-
const at = expr.indexOf(delimiters.stateName, stateBase);
|
|
5662
|
-
if (at !== -1 && at < pathScopeEnd) {
|
|
5663
|
-
stateNameLocal = locate(expr, parsed.stateName, at + 1, pathScopeEnd);
|
|
5664
|
-
}
|
|
5665
6136
|
}
|
|
5666
6137
|
const lift = (range) => range === null ? null : { start: exprStart + range.start, end: exprStart + range.end };
|
|
5667
6138
|
results.push({
|
|
@@ -5670,17 +6141,13 @@ function parseBindTextWithPositions(bindText) {
|
|
|
5670
6141
|
parsed,
|
|
5671
6142
|
error,
|
|
5672
6143
|
propRange: lift(propLocal),
|
|
5673
|
-
pathRange: lift(pathLocal)
|
|
5674
|
-
stateNameRange: lift(stateNameLocal)
|
|
6144
|
+
pathRange: lift(pathLocal)
|
|
5675
6145
|
});
|
|
5676
6146
|
}
|
|
5677
6147
|
return results;
|
|
5678
6148
|
}
|
|
5679
6149
|
|
|
5680
6150
|
// src/core/index/referenceIndex.ts
|
|
5681
|
-
function keyOf(stateName, path) {
|
|
5682
|
-
return `${stateName}\0${path}`;
|
|
5683
|
-
}
|
|
5684
6151
|
function buildReferenceIndex(html, options = {}) {
|
|
5685
6152
|
clearParserCaches();
|
|
5686
6153
|
const bindAttribute = options.bindAttribute ?? "data-wcs";
|
|
@@ -5698,13 +6165,11 @@ function buildReferenceIndex(html, options = {}) {
|
|
|
5698
6165
|
occurrences.push({
|
|
5699
6166
|
source: "attribute",
|
|
5700
6167
|
kind: binding.parsed.propSegments[0] === "eventToken" ? "eventToken" : "path",
|
|
5701
|
-
stateName: binding.parsed.stateName,
|
|
5702
6168
|
path: binding.parsed.statePathName,
|
|
5703
6169
|
pathRange: lift(binding.pathRange),
|
|
5704
6170
|
exprRange: lift(binding.exprRange),
|
|
5705
6171
|
propName: binding.parsed.propName,
|
|
5706
6172
|
propRange: binding.propRange === null ? null : lift(binding.propRange),
|
|
5707
|
-
stateNameRange: binding.stateNameRange === null ? null : lift(binding.stateNameRange),
|
|
5708
6173
|
bindingType: binding.parsed.bindingType
|
|
5709
6174
|
});
|
|
5710
6175
|
}
|
|
@@ -5727,22 +6192,21 @@ function buildReferenceIndex(html, options = {}) {
|
|
|
5727
6192
|
occurrences.push({
|
|
5728
6193
|
source: match.kind,
|
|
5729
6194
|
kind: "path",
|
|
5730
|
-
stateName: binding.parsed.stateName,
|
|
5731
6195
|
path: binding.parsed.statePathName,
|
|
5732
6196
|
pathRange: shift(binding.pathRange),
|
|
5733
6197
|
exprRange: { start: match.exprStart, end: match.exprEnd },
|
|
5734
6198
|
propName: null,
|
|
5735
6199
|
propRange: null,
|
|
5736
|
-
stateNameRange: binding.stateNameRange === null ? null : shift(binding.stateNameRange),
|
|
5737
6200
|
bindingType: "text"
|
|
5738
6201
|
});
|
|
5739
6202
|
}
|
|
5740
6203
|
const declarations = [];
|
|
5741
6204
|
for (const block of parseWcsScriptBlocks(html, stateTagName)) {
|
|
6205
|
+
const prefix = block.mountPath === null ? "" : block.mountPath + ".";
|
|
5742
6206
|
for (const span of analyzeDeclarationSpans(block.content)) {
|
|
6207
|
+
if (prefix !== "" && span.name.startsWith("$")) continue;
|
|
5743
6208
|
declarations.push({
|
|
5744
|
-
|
|
5745
|
-
name: span.name,
|
|
6209
|
+
name: prefix + span.name,
|
|
5746
6210
|
kind: span.kind,
|
|
5747
6211
|
range: { start: block.contentStart + span.start, end: block.contentStart + span.end }
|
|
5748
6212
|
});
|
|
@@ -5751,7 +6215,7 @@ function buildReferenceIndex(html, options = {}) {
|
|
|
5751
6215
|
const byPath = /* @__PURE__ */ new Map();
|
|
5752
6216
|
for (const occurrence of occurrences) {
|
|
5753
6217
|
if (occurrence.kind !== "path") continue;
|
|
5754
|
-
const key =
|
|
6218
|
+
const key = occurrence.path;
|
|
5755
6219
|
const list = byPath.get(key);
|
|
5756
6220
|
if (list === void 0) {
|
|
5757
6221
|
byPath.set(key, [occurrence]);
|
|
@@ -5761,22 +6225,22 @@ function buildReferenceIndex(html, options = {}) {
|
|
|
5761
6225
|
}
|
|
5762
6226
|
const declarationByName = /* @__PURE__ */ new Map();
|
|
5763
6227
|
for (const declaration of declarations) {
|
|
5764
|
-
const key =
|
|
6228
|
+
const key = declaration.name;
|
|
5765
6229
|
if (!declarationByName.has(key)) declarationByName.set(key, declaration);
|
|
5766
6230
|
}
|
|
5767
6231
|
return {
|
|
5768
6232
|
occurrences,
|
|
5769
6233
|
declarations,
|
|
5770
6234
|
problems,
|
|
5771
|
-
referencesOf(
|
|
5772
|
-
return (byPath.get(
|
|
6235
|
+
referencesOf(path) {
|
|
6236
|
+
return (byPath.get(path) ?? []).slice();
|
|
5773
6237
|
},
|
|
5774
|
-
declarationOf(
|
|
5775
|
-
const exact = declarationByName.get(
|
|
6238
|
+
declarationOf(path) {
|
|
6239
|
+
const exact = declarationByName.get(path);
|
|
5776
6240
|
if (exact !== void 0) return exact;
|
|
5777
6241
|
const firstSegment = path.split(".")[0];
|
|
5778
6242
|
if (firstSegment === path) return null;
|
|
5779
|
-
return declarationByName.get(
|
|
6243
|
+
return declarationByName.get(firstSegment) ?? null;
|
|
5780
6244
|
},
|
|
5781
6245
|
occurrenceAt(offset) {
|
|
5782
6246
|
for (const occurrence of occurrences) {
|
|
@@ -6021,8 +6485,9 @@ function validateUpdatedCallbackDemand(html, stateTagName, bindAttrName, locale3
|
|
|
6021
6485
|
for (const block of blocks) {
|
|
6022
6486
|
const callback = analyzeCallableBodies(block.content).find((entry) => entry.name === STATE_UPDATED_CALLBACK && entry.kind === "method");
|
|
6023
6487
|
if (callback === void 0) continue;
|
|
6024
|
-
|
|
6025
|
-
const
|
|
6488
|
+
if (block.mountPath !== null) continue;
|
|
6489
|
+
const declared = new Set(analyzeStatePaths(block.content).map((p) => p.path));
|
|
6490
|
+
const bound = boundPaths;
|
|
6026
6491
|
const body = blankComments(callback.body);
|
|
6027
6492
|
PATH_TEST_LITERAL.lastIndex = 0;
|
|
6028
6493
|
let match;
|
|
@@ -6045,27 +6510,18 @@ function validateUpdatedCallbackDemand(html, stateTagName, bindAttrName, locale3
|
|
|
6045
6510
|
return out;
|
|
6046
6511
|
}
|
|
6047
6512
|
function collectBoundPaths(html, stateTagName, bindAttrName) {
|
|
6048
|
-
const
|
|
6049
|
-
const add = (stateName, path) => {
|
|
6050
|
-
let set = byState.get(stateName);
|
|
6051
|
-
if (set === void 0) {
|
|
6052
|
-
set = /* @__PURE__ */ new Set();
|
|
6053
|
-
byState.set(stateName, set);
|
|
6054
|
-
}
|
|
6055
|
-
set.add(path);
|
|
6056
|
-
};
|
|
6513
|
+
const bound = /* @__PURE__ */ new Set();
|
|
6057
6514
|
const index = buildReferenceIndex(html, { bindAttribute: bindAttrName, stateTagName });
|
|
6058
6515
|
for (const occurrence of index.occurrences) {
|
|
6059
|
-
add(occurrence.
|
|
6516
|
+
bound.add(occurrence.path);
|
|
6060
6517
|
if (!occurrence.path.startsWith(".")) continue;
|
|
6061
6518
|
const forPath = getInnermostForPath(html, occurrence.pathRange.start, bindAttrName);
|
|
6062
6519
|
if (forPath === null || forPath.startsWith(".")) continue;
|
|
6063
|
-
add(
|
|
6064
|
-
occurrence.stateName,
|
|
6520
|
+
bound.add(
|
|
6065
6521
|
occurrence.path === "." ? `${forPath}.*` : `${forPath}.*.${occurrence.path.slice(1)}`
|
|
6066
6522
|
);
|
|
6067
6523
|
}
|
|
6068
|
-
return
|
|
6524
|
+
return bound;
|
|
6069
6525
|
}
|
|
6070
6526
|
function validateSemantics(html, stateTagName = "wcs-state", locale3, bindAttrName = "data-wcs") {
|
|
6071
6527
|
const out = [];
|
|
@@ -6077,154 +6533,6 @@ function validateSemantics(html, stateTagName = "wcs-state", locale3, bindAttrNa
|
|
|
6077
6533
|
return out;
|
|
6078
6534
|
}
|
|
6079
6535
|
|
|
6080
|
-
// src/core/validateDocument.ts
|
|
6081
|
-
function validateDocument(text, options = {}) {
|
|
6082
|
-
const bindAttribute = options.bindAttribute ?? "data-wcs";
|
|
6083
|
-
const stateTagName = options.stateTagName ?? "wcs-state";
|
|
6084
|
-
const locale3 = options.locale;
|
|
6085
|
-
const fileReader = options.fileReader;
|
|
6086
|
-
const out = [];
|
|
6087
|
-
out.push(...validateBindings(text, bindAttribute, stateTagName, locale3, fileReader));
|
|
6088
|
-
out.push(...validateTemplateSyntax(text, stateTagName, bindAttribute, locale3, fileReader));
|
|
6089
|
-
out.push(...validateIoNodes(text, bindAttribute, stateTagName, locale3, fileReader));
|
|
6090
|
-
out.push(...validateAriaAttributes(text, bindAttribute, locale3));
|
|
6091
|
-
out.push(...validateDocumentEnv(text, locale3));
|
|
6092
|
-
out.push(...validateSemantics(text, stateTagName, locale3, bindAttribute));
|
|
6093
|
-
out.push(...validateArrayMutations(text, stateTagName, locale3));
|
|
6094
|
-
out.push(...validateWatchDeclarations(text, stateTagName, locale3));
|
|
6095
|
-
for (const d of validateStateTypes(text, stateTagName, locale3)) {
|
|
6096
|
-
out.push({ code: WcsDiagnosticCode.TypeAnnotation, start: d.start, end: d.end, message: d.message, severity: d.severity });
|
|
6097
|
-
}
|
|
6098
|
-
for (const d of validateNestedAssigns(text, stateTagName, locale3)) {
|
|
6099
|
-
out.push({ code: WcsDiagnosticCode.NestedAssign, start: d.start, end: d.end, message: d.message, severity: d.severity });
|
|
6100
|
-
}
|
|
6101
|
-
return sortDiagnostics(out);
|
|
6102
|
-
}
|
|
6103
|
-
|
|
6104
|
-
// src/core/sidecar/schemaSubset.ts
|
|
6105
|
-
var ALLOWED_SCHEMA_KEYWORDS = /* @__PURE__ */ new Set([
|
|
6106
|
-
"type",
|
|
6107
|
-
"properties",
|
|
6108
|
-
"required",
|
|
6109
|
-
"items",
|
|
6110
|
-
"enum",
|
|
6111
|
-
"const",
|
|
6112
|
-
"anyOf",
|
|
6113
|
-
"$defs",
|
|
6114
|
-
"$ref"
|
|
6115
|
-
]);
|
|
6116
|
-
var DiagnosticContext = class {
|
|
6117
|
-
constructor(spans) {
|
|
6118
|
-
this.spans = spans;
|
|
6119
|
-
}
|
|
6120
|
-
diagnostics = [];
|
|
6121
|
-
add(code, pointer2, message, severity, extra = {}, useKeySpan = false) {
|
|
6122
|
-
const span = this.spans.get(pointer2);
|
|
6123
|
-
const start = span === void 0 ? 0 : useKeySpan ? span.keyStart ?? span.start : span.start;
|
|
6124
|
-
const end = span === void 0 ? 0 : useKeySpan ? span.keyEnd ?? span.end : span.end;
|
|
6125
|
-
this.diagnostics.push({ code, start, end, message, severity, ...extra });
|
|
6126
|
-
}
|
|
6127
|
-
};
|
|
6128
|
-
function isSchemaObject(value) {
|
|
6129
|
-
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
6130
|
-
}
|
|
6131
|
-
function isSchemaMap(value) {
|
|
6132
|
-
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
6133
|
-
}
|
|
6134
|
-
function validateSchemaSubset(schema, pointerBase, ctx, rootDefs) {
|
|
6135
|
-
walkKeywords(schema, pointerBase, ctx, rootDefs);
|
|
6136
|
-
const safe = /* @__PURE__ */ new Set();
|
|
6137
|
-
detectCycles(schema, pointerBase, ctx, rootDefs, /* @__PURE__ */ new Set(), safe);
|
|
6138
|
-
for (const [name, def] of Object.entries(rootDefs)) {
|
|
6139
|
-
detectCycles(def, `${pointerBase}/$defs/${escape(name)}`, ctx, rootDefs, /* @__PURE__ */ new Set(), safe);
|
|
6140
|
-
}
|
|
6141
|
-
}
|
|
6142
|
-
function walkKeywords(node, ptr, ctx, rootDefs) {
|
|
6143
|
-
if (!isSchemaObject(node)) return;
|
|
6144
|
-
for (const keyword of Object.keys(node)) {
|
|
6145
|
-
if (!ALLOWED_SCHEMA_KEYWORDS.has(keyword)) {
|
|
6146
|
-
ctx.add(
|
|
6147
|
-
WcsDiagnosticCode.ManifestUnknownKeyword,
|
|
6148
|
-
`${ptr}/${escape(keyword)}`,
|
|
6149
|
-
`Unsupported schema keyword "${keyword}". Allowed: ${[...ALLOWED_SCHEMA_KEYWORDS].join(", ")}.`,
|
|
6150
|
-
"warning",
|
|
6151
|
-
{},
|
|
6152
|
-
true
|
|
6153
|
-
);
|
|
6154
|
-
}
|
|
6155
|
-
}
|
|
6156
|
-
if (typeof node.$ref === "string") {
|
|
6157
|
-
if (!node.$ref.startsWith("#/")) {
|
|
6158
|
-
ctx.add(
|
|
6159
|
-
WcsDiagnosticCode.ManifestExternalRef,
|
|
6160
|
-
`${ptr}/$ref`,
|
|
6161
|
-
`External $ref "${node.$ref}" is forbidden; only local "#/$defs/..." references are allowed.`,
|
|
6162
|
-
"error"
|
|
6163
|
-
);
|
|
6164
|
-
} else if (resolveLocalRef(node.$ref, rootDefs) === void 0) {
|
|
6165
|
-
ctx.add(
|
|
6166
|
-
WcsDiagnosticCode.ManifestRefUnresolved,
|
|
6167
|
-
`${ptr}/$ref`,
|
|
6168
|
-
`Unresolved local $ref "${node.$ref}".`,
|
|
6169
|
-
"error"
|
|
6170
|
-
);
|
|
6171
|
-
}
|
|
6172
|
-
}
|
|
6173
|
-
if (isSchemaMap(node.properties)) {
|
|
6174
|
-
for (const [name, child] of Object.entries(node.properties)) {
|
|
6175
|
-
walkKeywords(child, `${ptr}/properties/${escape(name)}`, ctx, rootDefs);
|
|
6176
|
-
}
|
|
6177
|
-
}
|
|
6178
|
-
if (node.items !== void 0 && isSchemaObject(node.items)) {
|
|
6179
|
-
walkKeywords(node.items, `${ptr}/items`, ctx, rootDefs);
|
|
6180
|
-
}
|
|
6181
|
-
if (Array.isArray(node.anyOf)) {
|
|
6182
|
-
node.anyOf.forEach((child, i) => walkKeywords(child, `${ptr}/anyOf/${i}`, ctx, rootDefs));
|
|
6183
|
-
}
|
|
6184
|
-
if (isSchemaMap(node.$defs)) {
|
|
6185
|
-
for (const [name, child] of Object.entries(node.$defs)) {
|
|
6186
|
-
walkKeywords(child, `${ptr}/$defs/${escape(name)}`, ctx, rootDefs);
|
|
6187
|
-
}
|
|
6188
|
-
}
|
|
6189
|
-
}
|
|
6190
|
-
function detectCycles(node, ptr, ctx, rootDefs, refStack, safe) {
|
|
6191
|
-
if (!isSchemaObject(node)) return;
|
|
6192
|
-
if (typeof node.$ref === "string") {
|
|
6193
|
-
const ref = node.$ref;
|
|
6194
|
-
if (!ref.startsWith("#/")) return;
|
|
6195
|
-
if (refStack.has(ref)) {
|
|
6196
|
-
ctx.add(WcsDiagnosticCode.ManifestRefCycle, `${ptr}/$ref`, `Cyclic $ref detected at "${ref}".`, "error");
|
|
6197
|
-
return;
|
|
6198
|
-
}
|
|
6199
|
-
if (safe.has(ref)) return;
|
|
6200
|
-
const target = resolveLocalRef(ref, rootDefs);
|
|
6201
|
-
if (target === void 0) return;
|
|
6202
|
-
refStack.add(ref);
|
|
6203
|
-
detectCycles(target, ptr, ctx, rootDefs, refStack, safe);
|
|
6204
|
-
refStack.delete(ref);
|
|
6205
|
-
safe.add(ref);
|
|
6206
|
-
return;
|
|
6207
|
-
}
|
|
6208
|
-
if (isSchemaMap(node.properties)) {
|
|
6209
|
-
for (const child of Object.values(node.properties)) detectCycles(child, ptr, ctx, rootDefs, refStack, safe);
|
|
6210
|
-
}
|
|
6211
|
-
if (node.items !== void 0 && isSchemaObject(node.items)) {
|
|
6212
|
-
detectCycles(node.items, ptr, ctx, rootDefs, refStack, safe);
|
|
6213
|
-
}
|
|
6214
|
-
if (Array.isArray(node.anyOf)) {
|
|
6215
|
-
for (const child of node.anyOf) detectCycles(child, ptr, ctx, rootDefs, refStack, safe);
|
|
6216
|
-
}
|
|
6217
|
-
}
|
|
6218
|
-
function resolveLocalRef(ref, rootDefs) {
|
|
6219
|
-
const match = /^#\/\$defs\/(.+)$/.exec(ref);
|
|
6220
|
-
if (match === null) return void 0;
|
|
6221
|
-
const name = match[1].replace(/~1/g, "/").replace(/~0/g, "~");
|
|
6222
|
-
return rootDefs[name];
|
|
6223
|
-
}
|
|
6224
|
-
function escape(key) {
|
|
6225
|
-
return key.replace(/~/g, "~0").replace(/\//g, "~1");
|
|
6226
|
-
}
|
|
6227
|
-
|
|
6228
6536
|
// src/core/sidecar/jsonSource.ts
|
|
6229
6537
|
var JsonReader = class {
|
|
6230
6538
|
constructor(text) {
|
|
@@ -6398,8 +6706,8 @@ function parseJsonWithSpans(text) {
|
|
|
6398
6706
|
}
|
|
6399
6707
|
|
|
6400
6708
|
// src/core/sidecar/types.ts
|
|
6401
|
-
var SUPPORTED_SCHEMA_VERSION =
|
|
6402
|
-
var SUPPORTED_NAMESPACE_VERSION =
|
|
6709
|
+
var SUPPORTED_SCHEMA_VERSION = 2;
|
|
6710
|
+
var SUPPORTED_NAMESPACE_VERSION = 2;
|
|
6403
6711
|
|
|
6404
6712
|
// src/core/sidecar/loader.ts
|
|
6405
6713
|
var NAMESPACE_KEYS = ["wcstack.types", "wcstack.async", "wcstack.platformCapabilities", "wcstack.application"];
|
|
@@ -6436,10 +6744,11 @@ function loadManifest(artifact) {
|
|
|
6436
6744
|
return { artifact, manifest: null, ctx, spans: parsed.spans };
|
|
6437
6745
|
}
|
|
6438
6746
|
if (obj.schemaVersion !== SUPPORTED_SCHEMA_VERSION) {
|
|
6747
|
+
const migration = obj.schemaVersion === 1 ? ` schemaVersion 1 (states[name]) predates v2's single state tree \u2014 regenerate with \`wcs-schema emit\`.` : "";
|
|
6439
6748
|
ctx.add(
|
|
6440
6749
|
WcsDiagnosticCode.ManifestSchemaVersion,
|
|
6441
6750
|
pointer("schemaVersion"),
|
|
6442
|
-
`Unsupported schemaVersion ${obj.schemaVersion}; this reader supports ${SUPPORTED_SCHEMA_VERSION}
|
|
6751
|
+
`Unsupported schemaVersion ${obj.schemaVersion}; this reader supports ${SUPPORTED_SCHEMA_VERSION}.${migration}`,
|
|
6443
6752
|
"error"
|
|
6444
6753
|
);
|
|
6445
6754
|
return { artifact, manifest: null, ctx, spans: parsed.spans };
|
|
@@ -6488,8 +6797,12 @@ function resolvePackageContracts(loaded) {
|
|
|
6488
6797
|
const collided = /* @__PURE__ */ new Set();
|
|
6489
6798
|
const firstSource = /* @__PURE__ */ new Map();
|
|
6490
6799
|
const filterOwner = /* @__PURE__ */ new Map();
|
|
6800
|
+
let schemaOwner;
|
|
6801
|
+
let schemaWinner;
|
|
6802
|
+
let hasApplicationArtifact = false;
|
|
6491
6803
|
for (const lm of loaded) {
|
|
6492
6804
|
if (lm.manifest === null) continue;
|
|
6805
|
+
if (lm.manifest.kind === "application") hasApplicationArtifact = true;
|
|
6493
6806
|
const types = lm.manifest.manifestExtensions?.["wcstack.types"];
|
|
6494
6807
|
if (lm.manifest.kind === "package" && types !== void 0) {
|
|
6495
6808
|
for (const [tag, component] of Object.entries(types.components ?? {})) {
|
|
@@ -6541,13 +6854,97 @@ function resolvePackageContracts(loaded) {
|
|
|
6541
6854
|
);
|
|
6542
6855
|
}
|
|
6543
6856
|
}
|
|
6857
|
+
if (lm.manifest.kind === "application" && application?.stateSchema !== void 0) {
|
|
6858
|
+
const schema = application.stateSchema;
|
|
6859
|
+
if (schema !== null && typeof schema === "object" && !Array.isArray(schema)) {
|
|
6860
|
+
if (schemaOwner === void 0) {
|
|
6861
|
+
schemaOwner = lm.artifact.source;
|
|
6862
|
+
schemaWinner = schema;
|
|
6863
|
+
} else {
|
|
6864
|
+
schemaWinner = void 0;
|
|
6865
|
+
ctxFor(lm).add(
|
|
6866
|
+
WcsDiagnosticCode.ManifestStateCollision,
|
|
6867
|
+
pointer("manifestExtensions", "wcstack.application", "stateSchema"),
|
|
6868
|
+
`Multiple application artifacts declare a stateSchema (also in "${schemaOwner}"); neither is used.`,
|
|
6869
|
+
"error",
|
|
6870
|
+
void 0,
|
|
6871
|
+
true
|
|
6872
|
+
);
|
|
6873
|
+
}
|
|
6874
|
+
}
|
|
6875
|
+
}
|
|
6544
6876
|
}
|
|
6545
6877
|
const diagnosticsBySource = /* @__PURE__ */ new Map();
|
|
6546
6878
|
for (const [source, diags] of perSource) {
|
|
6547
6879
|
const kept = diags.filter((d) => !(d.code === WcsDiagnosticCode.ManifestOverride && d.tag !== void 0 && collided.has(d.tag)));
|
|
6548
6880
|
if (kept.length > 0) diagnosticsBySource.set(source, kept);
|
|
6549
6881
|
}
|
|
6550
|
-
return { tags: winners, diagnosticsBySource };
|
|
6882
|
+
return { tags: winners, applicationSchema: schemaWinner, hasApplicationArtifact, diagnosticsBySource };
|
|
6883
|
+
}
|
|
6884
|
+
|
|
6885
|
+
// src/core/sidecar/discover.ts
|
|
6886
|
+
var APPLICATION_MANIFEST_FILENAME = "wcstack.manifest.json";
|
|
6887
|
+
var MAX_ASCEND = 16;
|
|
6888
|
+
function discoverApplicationManifest(fileReader) {
|
|
6889
|
+
for (let up = 0; up <= MAX_ASCEND; up++) {
|
|
6890
|
+
const relativePath = `${"../".repeat(up)}${APPLICATION_MANIFEST_FILENAME}`;
|
|
6891
|
+
const text = fileReader(relativePath);
|
|
6892
|
+
if (text === void 0) continue;
|
|
6893
|
+
const loaded = loadManifest({ text, source: relativePath });
|
|
6894
|
+
return { relativePath, text, loaded, schema: applicationSchemaOf(loaded) };
|
|
6895
|
+
}
|
|
6896
|
+
return void 0;
|
|
6897
|
+
}
|
|
6898
|
+
function applicationSchemaOf(loaded) {
|
|
6899
|
+
const manifest = loaded.manifest;
|
|
6900
|
+
if (manifest === null || manifest.kind !== "application") return void 0;
|
|
6901
|
+
const application = manifest.manifestExtensions?.["wcstack.application"];
|
|
6902
|
+
const schema = application?.stateSchema;
|
|
6903
|
+
if (schema !== null && typeof schema === "object" && !Array.isArray(schema)) {
|
|
6904
|
+
return schema;
|
|
6905
|
+
}
|
|
6906
|
+
return void 0;
|
|
6907
|
+
}
|
|
6908
|
+
function joinRelativeSource(htmlSource, relativePath) {
|
|
6909
|
+
const sepIndex = Math.max(htmlSource.lastIndexOf("/"), htmlSource.lastIndexOf("\\"));
|
|
6910
|
+
const dirSegments = sepIndex === -1 ? [] : htmlSource.slice(0, sepIndex).split(/[\\/]/);
|
|
6911
|
+
for (const segment of relativePath.split("/")) {
|
|
6912
|
+
if (segment === "" || segment === ".") continue;
|
|
6913
|
+
if (segment === "..") {
|
|
6914
|
+
if (dirSegments.length > 0 && dirSegments[dirSegments.length - 1] !== "..") dirSegments.pop();
|
|
6915
|
+
else dirSegments.push("..");
|
|
6916
|
+
continue;
|
|
6917
|
+
}
|
|
6918
|
+
dirSegments.push(segment);
|
|
6919
|
+
}
|
|
6920
|
+
return dirSegments.join("/");
|
|
6921
|
+
}
|
|
6922
|
+
|
|
6923
|
+
// src/core/validateDocument.ts
|
|
6924
|
+
function validateDocument(text, options = {}) {
|
|
6925
|
+
const bindAttribute = options.bindAttribute ?? "data-wcs";
|
|
6926
|
+
const stateTagName = options.stateTagName ?? "wcs-state";
|
|
6927
|
+
const locale3 = options.locale;
|
|
6928
|
+
const fileReader = options.fileReader;
|
|
6929
|
+
const applicationSchema = options.applicationSchema ?? (fileReader !== void 0 ? discoverApplicationManifest(fileReader)?.schema : void 0);
|
|
6930
|
+
const out = [];
|
|
6931
|
+
out.push(...validateBindings(text, bindAttribute, stateTagName, locale3, fileReader, applicationSchema));
|
|
6932
|
+
out.push(...validateTemplateSyntax(text, stateTagName, bindAttribute, locale3, fileReader, applicationSchema));
|
|
6933
|
+
out.push(...validateIoNodes(text, bindAttribute, stateTagName, locale3, fileReader));
|
|
6934
|
+
out.push(...validateAriaAttributes(text, bindAttribute, locale3));
|
|
6935
|
+
out.push(...validateDocumentEnv(text, locale3));
|
|
6936
|
+
out.push(...validateSemantics(text, stateTagName, locale3, bindAttribute));
|
|
6937
|
+
out.push(...validateArrayMutations(text, stateTagName, locale3));
|
|
6938
|
+
out.push(...validateWatchDeclarations(text, stateTagName, locale3));
|
|
6939
|
+
out.push(...validateNamedState(text, bindAttribute, stateTagName, locale3));
|
|
6940
|
+
out.push(...validateMountAttributes(text, stateTagName, locale3));
|
|
6941
|
+
for (const d of validateStateTypes(text, stateTagName, locale3)) {
|
|
6942
|
+
out.push({ code: WcsDiagnosticCode.TypeAnnotation, start: d.start, end: d.end, message: d.message, severity: d.severity });
|
|
6943
|
+
}
|
|
6944
|
+
for (const d of validateNestedAssigns(text, stateTagName, locale3)) {
|
|
6945
|
+
out.push({ code: WcsDiagnosticCode.NestedAssign, start: d.start, end: d.end, message: d.message, severity: d.severity });
|
|
6946
|
+
}
|
|
6947
|
+
return sortDiagnostics(out);
|
|
6551
6948
|
}
|
|
6552
6949
|
|
|
6553
6950
|
// src/core/sidecar/drift.ts
|
|
@@ -6606,13 +7003,23 @@ function checkDrift(tag, component, live, ctx) {
|
|
|
6606
7003
|
}
|
|
6607
7004
|
|
|
6608
7005
|
// src/core/sidecar/validate.ts
|
|
7006
|
+
function validateManifestArtifact(artifact) {
|
|
7007
|
+
const loaded = loadManifest(artifact);
|
|
7008
|
+
validateLoadedSchemas(loaded);
|
|
7009
|
+
return sortDiagnostics(loaded.ctx.diagnostics);
|
|
7010
|
+
}
|
|
6609
7011
|
function validateLoadedSchemas(loaded) {
|
|
6610
7012
|
if (loaded.manifest === null) return;
|
|
6611
7013
|
const types = loaded.manifest.manifestExtensions?.["wcstack.types"];
|
|
6612
|
-
|
|
6613
|
-
for (const [tag, component] of Object.entries(types.components ?? {})) {
|
|
7014
|
+
for (const [tag, component] of Object.entries(types?.components ?? {})) {
|
|
6614
7015
|
validateComponentSchemas(tag, component, loaded.ctx);
|
|
6615
7016
|
}
|
|
7017
|
+
const application = loaded.manifest.manifestExtensions?.["wcstack.application"];
|
|
7018
|
+
const stateSchema = application?.stateSchema;
|
|
7019
|
+
if (stateSchema !== void 0 && stateSchema !== null && typeof stateSchema === "object" && !Array.isArray(stateSchema)) {
|
|
7020
|
+
const ptr = pointer("manifestExtensions", "wcstack.application", "stateSchema");
|
|
7021
|
+
validateSchemaSubset(stateSchema, ptr, loaded.ctx, stateSchema.$defs ?? {});
|
|
7022
|
+
}
|
|
6616
7023
|
}
|
|
6617
7024
|
function validateComponentSchemas(tag, component, ctx) {
|
|
6618
7025
|
const base = pointer("manifestExtensions", "wcstack.types", "components", tag);
|
|
@@ -6662,7 +7069,9 @@ function validateManifestSet(input) {
|
|
|
6662
7069
|
return {
|
|
6663
7070
|
diagnostics: sortDiagnostics(all),
|
|
6664
7071
|
byArtifact: sortedByArtifact,
|
|
6665
|
-
resolvedTags
|
|
7072
|
+
resolvedTags,
|
|
7073
|
+
resolvedSchema: resolved.applicationSchema,
|
|
7074
|
+
hasApplicationArtifact: resolved.hasApplicationArtifact
|
|
6666
7075
|
};
|
|
6667
7076
|
}
|
|
6668
7077
|
function escapePtr(key) {
|
|
@@ -6673,13 +7082,10 @@ function escapePtr(key) {
|
|
|
6673
7082
|
var severityLabel = { error: "error", warning: "warning", info: "info" };
|
|
6674
7083
|
function runValidation(inputs, options = {}) {
|
|
6675
7084
|
const diagnosticsBySource = /* @__PURE__ */ new Map();
|
|
6676
|
-
|
|
6677
|
-
if (input.kind === "html") {
|
|
6678
|
-
const docOptions = input.fileReader !== void 0 ? { ...options, fileReader: input.fileReader } : options;
|
|
6679
|
-
diagnosticsBySource.set(input.source, validateDocument(input.text, docOptions));
|
|
6680
|
-
}
|
|
6681
|
-
}
|
|
7085
|
+
const textBySource = new Map(inputs.map((i) => [i.source, i.text]));
|
|
6682
7086
|
const manifestInputs = inputs.filter((i) => i.kind === "manifest");
|
|
7087
|
+
let explicitSchema;
|
|
7088
|
+
let haveExplicitApplication = false;
|
|
6683
7089
|
if (manifestInputs.length > 0) {
|
|
6684
7090
|
const result = validateManifestSet({
|
|
6685
7091
|
artifacts: manifestInputs.map((m) => ({ text: m.text, source: m.source })),
|
|
@@ -6688,8 +7094,32 @@ function runValidation(inputs, options = {}) {
|
|
|
6688
7094
|
for (const input of manifestInputs) {
|
|
6689
7095
|
diagnosticsBySource.set(input.source, result.byArtifact.get(input.source) ?? []);
|
|
6690
7096
|
}
|
|
7097
|
+
if (result.hasApplicationArtifact) {
|
|
7098
|
+
haveExplicitApplication = true;
|
|
7099
|
+
explicitSchema = result.resolvedSchema;
|
|
7100
|
+
}
|
|
7101
|
+
}
|
|
7102
|
+
for (const input of inputs) {
|
|
7103
|
+
if (input.kind !== "html") continue;
|
|
7104
|
+
let applicationSchema = explicitSchema;
|
|
7105
|
+
if (!haveExplicitApplication && input.fileReader !== void 0) {
|
|
7106
|
+
const discovered = discoverApplicationManifest(input.fileReader);
|
|
7107
|
+
applicationSchema = discovered?.schema;
|
|
7108
|
+
if (discovered !== void 0) {
|
|
7109
|
+
const source = joinRelativeSource(input.source, discovered.relativePath);
|
|
7110
|
+
if (!diagnosticsBySource.has(source)) {
|
|
7111
|
+
textBySource.set(source, discovered.text);
|
|
7112
|
+
diagnosticsBySource.set(source, validateManifestArtifact({ text: discovered.text, source }));
|
|
7113
|
+
}
|
|
7114
|
+
}
|
|
7115
|
+
}
|
|
7116
|
+
const docOptions = {
|
|
7117
|
+
...options,
|
|
7118
|
+
...input.fileReader !== void 0 ? { fileReader: input.fileReader } : {},
|
|
7119
|
+
...applicationSchema !== void 0 ? { applicationSchema } : {}
|
|
7120
|
+
};
|
|
7121
|
+
diagnosticsBySource.set(input.source, validateDocument(input.text, docOptions));
|
|
6691
7122
|
}
|
|
6692
|
-
const textBySource = new Map(inputs.map((i) => [i.source, i.text]));
|
|
6693
7123
|
const lines = [];
|
|
6694
7124
|
let errorCount = 0;
|
|
6695
7125
|
let warningCount = 0;
|
|
@@ -6711,7 +7141,7 @@ function runValidation(inputs, options = {}) {
|
|
|
6711
7141
|
errorCount,
|
|
6712
7142
|
warningCount,
|
|
6713
7143
|
infoCount,
|
|
6714
|
-
exitCode: errorCount > 0 ? 1 : 0,
|
|
7144
|
+
exitCode: errorCount > 0 || options.strict === true && warningCount > 0 ? 1 : 0,
|
|
6715
7145
|
diagnosticsBySource
|
|
6716
7146
|
};
|
|
6717
7147
|
}
|
|
@@ -6728,6 +7158,7 @@ function parseArgs(argv) {
|
|
|
6728
7158
|
else if (arg.startsWith("--state-tag=")) options.stateTagName = arg.slice("--state-tag=".length);
|
|
6729
7159
|
else if (arg.startsWith("--lang=")) options.locale = arg.slice("--lang=".length);
|
|
6730
7160
|
else if (arg === "--errors-only" || arg === "--quiet") options.errorsOnly = true;
|
|
7161
|
+
else if (arg === "--strict") options.strict = true;
|
|
6731
7162
|
else if (!arg.startsWith("-")) files.push(arg);
|
|
6732
7163
|
}
|
|
6733
7164
|
return { options, files };
|
|
@@ -6746,7 +7177,7 @@ function main(argv) {
|
|
|
6746
7177
|
const { options, files } = parseArgs(argv);
|
|
6747
7178
|
const locale3 = resolveCliLocale(options.locale);
|
|
6748
7179
|
if (files.length === 0) {
|
|
6749
|
-
process.stderr.write("usage: wcs-validate [--attr=data-wcs] [--state-tag=wcs-state] [--lang=ja|en] <file> [<file> ...]\n");
|
|
7180
|
+
process.stderr.write("usage: wcs-validate [--attr=data-wcs] [--state-tag=wcs-state] [--lang=ja|en] [--errors-only] [--strict] <file> [<file> ...]\n");
|
|
6750
7181
|
return 2;
|
|
6751
7182
|
}
|
|
6752
7183
|
const inputs = [];
|
|
@@ -6768,7 +7199,7 @@ function main(argv) {
|
|
|
6768
7199
|
}
|
|
6769
7200
|
process.stdout.write(
|
|
6770
7201
|
`
|
|
6771
|
-
${result.errorCount} error(s), ${result.warningCount} warning(s), ${result.infoCount} info
|
|
7202
|
+
${result.errorCount} error(s), ${result.warningCount} warning(s), ${result.infoCount} info${options.strict ? " (strict)" : ""}
|
|
6772
7203
|
`
|
|
6773
7204
|
);
|
|
6774
7205
|
return result.exitCode;
|