@wcstack/lint 1.33.0 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.ja.md +8 -12
- package/README.md +8 -12
- package/dist/cli.cjs +250 -217
- package/package.json +1 -1
package/README.ja.md
CHANGED
|
@@ -60,20 +60,16 @@ app.manifest.json:1:3 error wcs/manifest-broken Broken manifest JSON: ...
|
|
|
60
60
|
|
|
61
61
|
```json
|
|
62
62
|
{
|
|
63
|
-
"schemaVersion":
|
|
63
|
+
"schemaVersion": 2,
|
|
64
64
|
"kind": "application",
|
|
65
65
|
"manifestExtensions": {
|
|
66
66
|
"wcstack.application": {
|
|
67
|
-
"version":
|
|
68
|
-
"
|
|
69
|
-
"
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
"count": { "type": "number" },
|
|
74
|
-
"users": { "type": "array", "items": { "type": "object", "properties": { "name": { "type": "string" } } } }
|
|
75
|
-
}
|
|
76
|
-
}
|
|
67
|
+
"version": 2,
|
|
68
|
+
"stateSchema": {
|
|
69
|
+
"type": "object",
|
|
70
|
+
"properties": {
|
|
71
|
+
"count": { "type": "number" },
|
|
72
|
+
"users": { "type": "array", "items": { "type": "object", "properties": { "name": { "type": "string" } } } }
|
|
77
73
|
}
|
|
78
74
|
}
|
|
79
75
|
}
|
|
@@ -82,7 +78,7 @@ app.manifest.json:1:3 error wcs/manifest-broken Broken manifest JSON: ...
|
|
|
82
78
|
```
|
|
83
79
|
|
|
84
80
|
- **発見**: HTML ファイルから上へ辿って最も近い `wcstack.manifest.json` が自動で使われます — コマンドラインに渡す必要はありません。`application` artifact を含む `*.manifest.json` を引数に渡した場合は、その実行全体で発見を置き換えます。VS Code 拡張も同じファイルを発見するので IDE と CLI の結果は一致します。
|
|
85
|
-
- **効果**: `stateSchema`
|
|
81
|
+
- **効果**: `stateSchema` を宣言すると、schema 上に確定的に存在しないバインドパスは `wcs/path-nonexistent`(**error**・exit `1`)、`for:` に非配列は `wcs/path-type-mismatch`(**error**)になります。schema が開いたままの箇所(素の `{}`)は沈黙し、インラインスクリプトのメソッド / getter / `$listKeys` は引き続き存在扱いです。`stateSchema` が無ければ従来どおりです。
|
|
86
82
|
- **schema の出どころ**: 手で書く(受け付けるのは JSON-Schema サブセット `type / properties / required / items / enum / const / anyOf / $defs / $ref` のみ)か、TypeScript の state ファイルから `wcs-schema`(`@wcstack/typescript`)で生成します。manifest は派生物なので、CI では `wcs-schema check` で同期を保ってください。
|
|
87
83
|
|
|
88
84
|
## 生成 → 検証 → 修正ループでの利用
|
package/README.md
CHANGED
|
@@ -60,20 +60,16 @@ Without a contract, a path the validator cannot resolve is only a **warning** (`
|
|
|
60
60
|
|
|
61
61
|
```json
|
|
62
62
|
{
|
|
63
|
-
"schemaVersion":
|
|
63
|
+
"schemaVersion": 2,
|
|
64
64
|
"kind": "application",
|
|
65
65
|
"manifestExtensions": {
|
|
66
66
|
"wcstack.application": {
|
|
67
|
-
"version":
|
|
68
|
-
"
|
|
69
|
-
"
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
"count": { "type": "number" },
|
|
74
|
-
"users": { "type": "array", "items": { "type": "object", "properties": { "name": { "type": "string" } } } }
|
|
75
|
-
}
|
|
76
|
-
}
|
|
67
|
+
"version": 2,
|
|
68
|
+
"stateSchema": {
|
|
69
|
+
"type": "object",
|
|
70
|
+
"properties": {
|
|
71
|
+
"count": { "type": "number" },
|
|
72
|
+
"users": { "type": "array", "items": { "type": "object", "properties": { "name": { "type": "string" } } } }
|
|
77
73
|
}
|
|
78
74
|
}
|
|
79
75
|
}
|
|
@@ -82,7 +78,7 @@ Without a contract, a path the validator cannot resolve is only a **warning** (`
|
|
|
82
78
|
```
|
|
83
79
|
|
|
84
80
|
- **Discovery**: the nearest `wcstack.manifest.json` walking up from the HTML file is used automatically — nothing to pass on the command line. Passing `*.manifest.json` arguments that contain an `application` artifact replaces discovery for the whole run. The VS Code extension discovers the same file, so IDE and CLI agree.
|
|
85
|
-
- **Effect**:
|
|
81
|
+
- **Effect**: when a `stateSchema` is declared, a bound path that the schema definitely lacks is `wcs/path-nonexistent` (**error**, exit `1`); `for:` on a non-array is `wcs/path-type-mismatch` (**error**). Paths the schema leaves open (a bare `{}`) stay silent, and methods / getters / `$listKeys` from the inline script still count as existing. Without a `stateSchema`, behavior is unchanged.
|
|
86
82
|
- **Where the schema comes from**: write it by hand (only the JSON-Schema subset `type / properties / required / items / enum / const / anyOf / $defs / $ref` is accepted), or generate it from a TypeScript state file with `wcs-schema` (`@wcstack/typescript`). The manifest is a derived artifact — keep it in sync with `wcs-schema check` in CI.
|
|
87
83
|
|
|
88
84
|
## Use in generate–validate–fix loops
|
package/dist/cli.cjs
CHANGED
|
@@ -175,7 +175,11 @@ var WcsDiagnosticCode = {
|
|
|
175
175
|
// --- deprecations ---
|
|
176
176
|
// 名前付き State(`<wcs-state name>` / `path@name`)。v2 でマウント(`mount=` と接頭辞付きパス)に
|
|
177
177
|
// 置き換わる(docs/state-mount-design.md D16)。1.x では warning、v2 では parse error と同時に error。
|
|
178
|
-
NamedStateDeprecated: "wcs/named-state-deprecated"
|
|
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"
|
|
179
183
|
};
|
|
180
184
|
function sortDiagnostics(diagnostics) {
|
|
181
185
|
const severityRank = { error: 0, warning: 1, info: 2 };
|
|
@@ -791,7 +795,6 @@ var MAX_WILDCARD_DEPTH = 128;
|
|
|
791
795
|
var BINDING_SEPARATOR = ";";
|
|
792
796
|
var PROP_VALUE_SEPARATOR = ":";
|
|
793
797
|
var MODIFIER_SEPARATOR = "#";
|
|
794
|
-
var STATE_NAME_SEPARATOR = "@";
|
|
795
798
|
var FILTER_SEPARATOR = "|";
|
|
796
799
|
var MODIFIER_PREVENT = "prevent";
|
|
797
800
|
var MODIFIER_STOP = "stop";
|
|
@@ -849,7 +852,6 @@ function getWcsManifest() {
|
|
|
849
852
|
binding: BINDING_SEPARATOR,
|
|
850
853
|
propValue: PROP_VALUE_SEPARATOR,
|
|
851
854
|
modifier: MODIFIER_SEPARATOR,
|
|
852
|
-
stateName: STATE_NAME_SEPARATOR,
|
|
853
855
|
filter: FILTER_SEPARATOR
|
|
854
856
|
},
|
|
855
857
|
// 正本 STRUCTURAL_BINDING_TYPE_SET から導出(手書きの二重定義を排除)。
|
|
@@ -922,7 +924,7 @@ var RESERVED_COMMAND_TOKENS_KEY = "$commandTokens";
|
|
|
922
924
|
var RESERVED_EVENT_TOKENS_KEY = "$eventTokens";
|
|
923
925
|
var RESERVED_LIST_KEYS_KEY = "$listKeys";
|
|
924
926
|
var RESERVED_WATCH_KEY = "$watch";
|
|
925
|
-
function analyzeStatePaths(scriptContent
|
|
927
|
+
function analyzeStatePaths(scriptContent) {
|
|
926
928
|
const objectContent = extractDefaultExportObject(scriptContent);
|
|
927
929
|
if (!objectContent) return [];
|
|
928
930
|
const paths = [];
|
|
@@ -931,27 +933,27 @@ function analyzeStatePaths(scriptContent, stateName = "default") {
|
|
|
931
933
|
const pendingListKeys = [];
|
|
932
934
|
for (const prop of topLevelProps) {
|
|
933
935
|
if (prop.name.startsWith("$")) {
|
|
934
|
-
collectReservedKeyPaths(prop, paths, pendingStreamValues, pendingListKeys
|
|
936
|
+
collectReservedKeyPaths(prop, paths, pendingStreamValues, pendingListKeys);
|
|
935
937
|
continue;
|
|
936
938
|
}
|
|
937
939
|
if (prop.kind === "method") {
|
|
938
|
-
paths.push({ path: prop.name, kind: "method"
|
|
940
|
+
paths.push({ path: prop.name, kind: "method" });
|
|
939
941
|
continue;
|
|
940
942
|
}
|
|
941
943
|
if (prop.kind === "getter") {
|
|
942
|
-
if (!paths.some((p) => p.
|
|
943
|
-
paths.push({ path: prop.name, kind: "computed"
|
|
944
|
+
if (!paths.some((p) => p.path === prop.name)) {
|
|
945
|
+
paths.push({ path: prop.name, kind: "computed" });
|
|
944
946
|
}
|
|
945
947
|
continue;
|
|
946
948
|
}
|
|
947
|
-
pushDataPropertyPaths(prop, paths
|
|
949
|
+
pushDataPropertyPaths(prop, paths);
|
|
948
950
|
}
|
|
949
951
|
for (const streamValue of pendingStreamValues) {
|
|
950
|
-
if (paths.some((p) => p.
|
|
951
|
-
pushDataPropertyPaths(streamValue, paths
|
|
952
|
+
if (paths.some((p) => p.path === streamValue.name)) continue;
|
|
953
|
+
pushDataPropertyPaths(streamValue, paths);
|
|
952
954
|
}
|
|
953
955
|
for (const listKeyEntry of pendingListKeys) {
|
|
954
|
-
pushListKeyPaths(listKeyEntry, paths
|
|
956
|
+
pushListKeyPaths(listKeyEntry, paths);
|
|
955
957
|
}
|
|
956
958
|
return paths;
|
|
957
959
|
}
|
|
@@ -1038,7 +1040,7 @@ function findNonObjectWatch(scriptContent) {
|
|
|
1038
1040
|
if (!isArrowFunction && !isWholeLiteral) return null;
|
|
1039
1041
|
return span;
|
|
1040
1042
|
}
|
|
1041
|
-
function collectReservedKeyPaths(prop, paths, pendingStreamValues, pendingListKeys
|
|
1043
|
+
function collectReservedKeyPaths(prop, paths, pendingStreamValues, pendingListKeys) {
|
|
1042
1044
|
if (prop.name === RESERVED_STREAMS_KEY && prop.kind === "data" && prop.value && isObjectLiteral(prop.value)) {
|
|
1043
1045
|
const entries = parseTopLevelProperties(extractObjectContent(prop.value));
|
|
1044
1046
|
for (const entry of entries) {
|
|
@@ -1050,20 +1052,20 @@ function collectReservedKeyPaths(prop, paths, pendingStreamValues, pendingListKe
|
|
|
1050
1052
|
value: initial?.value,
|
|
1051
1053
|
typeHint: initial?.typeHint
|
|
1052
1054
|
});
|
|
1053
|
-
paths.push({ path: `$streamStatus.${entry.name}`, kind: "data", typeHint: "string"
|
|
1054
|
-
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" });
|
|
1055
1057
|
}
|
|
1056
1058
|
return;
|
|
1057
1059
|
}
|
|
1058
1060
|
if (prop.name === RESERVED_COMMAND_TOKENS_KEY && prop.value) {
|
|
1059
1061
|
for (const name of extractStringArrayItems(prop.value)) {
|
|
1060
|
-
paths.push({ path: `$command.${name}`, kind: "command"
|
|
1062
|
+
paths.push({ path: `$command.${name}`, kind: "command" });
|
|
1061
1063
|
}
|
|
1062
1064
|
return;
|
|
1063
1065
|
}
|
|
1064
1066
|
if (prop.name === RESERVED_EVENT_TOKENS_KEY && prop.value) {
|
|
1065
1067
|
for (const name of extractStringArrayItems(prop.value)) {
|
|
1066
|
-
paths.push({ path: name, kind: "eventToken"
|
|
1068
|
+
paths.push({ path: name, kind: "eventToken" });
|
|
1067
1069
|
}
|
|
1068
1070
|
return;
|
|
1069
1071
|
}
|
|
@@ -1075,22 +1077,22 @@ function collectReservedKeyPaths(prop, paths, pendingStreamValues, pendingListKe
|
|
|
1075
1077
|
return;
|
|
1076
1078
|
}
|
|
1077
1079
|
}
|
|
1078
|
-
function pushListKeyPaths(entry, paths
|
|
1080
|
+
function pushListKeyPaths(entry, paths) {
|
|
1079
1081
|
const listPath = entry.name;
|
|
1080
1082
|
const segments = listPath.split(".");
|
|
1081
1083
|
if (listPath.length === 0 || segments.some((s) => s.length === 0) || segments[segments.length - 1] === "*") {
|
|
1082
1084
|
return;
|
|
1083
1085
|
}
|
|
1084
|
-
const has = (path) => paths.some((p) => p.
|
|
1085
|
-
if (!has(listPath)) paths.push({ path: listPath, kind: "data", typeHint: "array"
|
|
1086
|
-
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" });
|
|
1087
1089
|
if (!has(`${listPath}.length`)) {
|
|
1088
|
-
paths.push({ path: `${listPath}.length`, kind: "data", typeHint: "number"
|
|
1090
|
+
paths.push({ path: `${listPath}.length`, kind: "data", typeHint: "number" });
|
|
1089
1091
|
}
|
|
1090
1092
|
const keyField = extractStringLiteralValue(entry.value);
|
|
1091
1093
|
if (keyField === null || keyField.includes(".") || keyField.includes("*")) return;
|
|
1092
1094
|
if (!has(`${listPath}.*.${keyField}`)) {
|
|
1093
|
-
paths.push({ path: `${listPath}.*.${keyField}`, kind: "data"
|
|
1095
|
+
paths.push({ path: `${listPath}.*.${keyField}`, kind: "data" });
|
|
1094
1096
|
}
|
|
1095
1097
|
}
|
|
1096
1098
|
function extractStringLiteralValue(value) {
|
|
@@ -1113,17 +1115,17 @@ function extractStringArrayItems(value) {
|
|
|
1113
1115
|
return items;
|
|
1114
1116
|
}
|
|
1115
1117
|
var MAX_OBJECT_NEST_DEPTH = 5;
|
|
1116
|
-
function pushDataPropertyPaths(prop, paths
|
|
1117
|
-
pushDataPropertyPathsAt(prop.name, prop, paths,
|
|
1118
|
+
function pushDataPropertyPaths(prop, paths) {
|
|
1119
|
+
pushDataPropertyPathsAt(prop.name, prop, paths, 0);
|
|
1118
1120
|
}
|
|
1119
|
-
function pushDataPropertyPathsAt(path, prop, paths,
|
|
1120
|
-
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() });
|
|
1121
1123
|
if (prop.value && isArrayLiteral(prop.value)) {
|
|
1122
|
-
paths.push({ path: `${path}.*`, kind: "list"
|
|
1123
|
-
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" });
|
|
1124
1126
|
if (depth >= MAX_OBJECT_NEST_DEPTH) return;
|
|
1125
1127
|
for (const childProp of extractArrayElementDataProperties(prop.value)) {
|
|
1126
|
-
pushDataPropertyPathsAt(`${path}.*.${childProp.name}`, childProp, paths,
|
|
1128
|
+
pushDataPropertyPathsAt(`${path}.*.${childProp.name}`, childProp, paths, depth + 1);
|
|
1127
1129
|
}
|
|
1128
1130
|
return;
|
|
1129
1131
|
}
|
|
@@ -1132,11 +1134,11 @@ function pushDataPropertyPathsAt(path, prop, paths, stateName, depth) {
|
|
|
1132
1134
|
const childProps = parseTopLevelProperties(extractObjectContent(prop.value));
|
|
1133
1135
|
for (const childProp of childProps) {
|
|
1134
1136
|
if (childProp.kind !== "data") continue;
|
|
1135
|
-
pushDataPropertyPathsAt(`${path}.${childProp.name}`, childProp, paths,
|
|
1137
|
+
pushDataPropertyPathsAt(`${path}.${childProp.name}`, childProp, paths, depth + 1);
|
|
1136
1138
|
}
|
|
1137
1139
|
}
|
|
1138
1140
|
}
|
|
1139
|
-
function analyzeJsonPaths(jsonString
|
|
1141
|
+
function analyzeJsonPaths(jsonString) {
|
|
1140
1142
|
let data;
|
|
1141
1143
|
try {
|
|
1142
1144
|
data = JSON.parse(jsonString);
|
|
@@ -1145,31 +1147,31 @@ function analyzeJsonPaths(jsonString, stateName = "default") {
|
|
|
1145
1147
|
}
|
|
1146
1148
|
if (typeof data !== "object" || data === null || Array.isArray(data)) return [];
|
|
1147
1149
|
const paths = [];
|
|
1148
|
-
collectJsonPaths(data, "", paths,
|
|
1150
|
+
collectJsonPaths(data, "", paths, 0);
|
|
1149
1151
|
return paths;
|
|
1150
1152
|
}
|
|
1151
|
-
function collectJsonPaths(obj, prefix, paths,
|
|
1153
|
+
function collectJsonPaths(obj, prefix, paths, depth) {
|
|
1152
1154
|
if (depth >= MAX_OBJECT_NEST_DEPTH) return;
|
|
1153
1155
|
for (const [key, value] of Object.entries(obj)) {
|
|
1154
1156
|
if (prefix === "" && key.startsWith("$")) continue;
|
|
1155
1157
|
const path = prefix ? `${prefix}.${key}` : key;
|
|
1156
|
-
pushJsonValuePaths(path, value, paths,
|
|
1158
|
+
pushJsonValuePaths(path, value, paths, depth);
|
|
1157
1159
|
}
|
|
1158
1160
|
}
|
|
1159
|
-
function pushJsonValuePaths(path, value, paths,
|
|
1160
|
-
paths.push({ path, kind: "data", typeHint: inferJsonTypeHint(value)
|
|
1161
|
+
function pushJsonValuePaths(path, value, paths, depth) {
|
|
1162
|
+
paths.push({ path, kind: "data", typeHint: inferJsonTypeHint(value) });
|
|
1161
1163
|
if (Array.isArray(value)) {
|
|
1162
|
-
paths.push({ path: `${path}.*`, kind: "list"
|
|
1163
|
-
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" });
|
|
1164
1166
|
if (depth >= MAX_OBJECT_NEST_DEPTH) return;
|
|
1165
1167
|
if (value.length > 0 && typeof value[0] === "object" && value[0] !== null && !Array.isArray(value[0])) {
|
|
1166
1168
|
const firstElement = value[0];
|
|
1167
1169
|
for (const [childKey, childValue] of Object.entries(firstElement)) {
|
|
1168
|
-
pushJsonValuePaths(`${path}.*.${childKey}`, childValue, paths,
|
|
1170
|
+
pushJsonValuePaths(`${path}.*.${childKey}`, childValue, paths, depth + 1);
|
|
1169
1171
|
}
|
|
1170
1172
|
}
|
|
1171
1173
|
} else if (typeof value === "object" && value !== null) {
|
|
1172
|
-
collectJsonPaths(value, path, paths,
|
|
1174
|
+
collectJsonPaths(value, path, paths, depth + 1);
|
|
1173
1175
|
}
|
|
1174
1176
|
}
|
|
1175
1177
|
function inferJsonTypeHint(value) {
|
|
@@ -1409,23 +1411,21 @@ function inferTypeHint(valueStart) {
|
|
|
1409
1411
|
if (v.startsWith("{")) return "object";
|
|
1410
1412
|
return void 0;
|
|
1411
1413
|
}
|
|
1412
|
-
function analyzeSchemaPaths(schema
|
|
1414
|
+
function analyzeSchemaPaths(schema) {
|
|
1413
1415
|
const paths = [];
|
|
1414
1416
|
const defs = schema.$defs ?? {};
|
|
1415
|
-
collectSchemaObjectPaths(schema, "", paths,
|
|
1417
|
+
collectSchemaObjectPaths(schema, "", paths, defs, 0);
|
|
1416
1418
|
return paths;
|
|
1417
1419
|
}
|
|
1418
|
-
function mergeSchemaCandidates(candidates,
|
|
1419
|
-
if (
|
|
1420
|
+
function mergeSchemaCandidates(candidates, applicationSchema) {
|
|
1421
|
+
if (applicationSchema === void 0) return candidates;
|
|
1420
1422
|
const schemaCandidates = [];
|
|
1421
1423
|
const schemaKeys = /* @__PURE__ */ new Set();
|
|
1422
|
-
for (const
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
schemaKeys.add(`${stateName} ${p.path}`);
|
|
1426
|
-
}
|
|
1424
|
+
for (const p of analyzeSchemaPaths(applicationSchema)) {
|
|
1425
|
+
schemaCandidates.push(p);
|
|
1426
|
+
schemaKeys.add(p.path);
|
|
1427
1427
|
}
|
|
1428
|
-
const kept = candidates.filter((p) => !schemaKeys.has(
|
|
1428
|
+
const kept = candidates.filter((p) => !schemaKeys.has(p.path));
|
|
1429
1429
|
return [...kept, ...schemaCandidates];
|
|
1430
1430
|
}
|
|
1431
1431
|
function derefSchemaNodes(node, defs) {
|
|
@@ -1477,7 +1477,7 @@ function schemaTypeHint(nodes) {
|
|
|
1477
1477
|
}
|
|
1478
1478
|
return hints.size === 0 ? void 0 : [...hints].join("|");
|
|
1479
1479
|
}
|
|
1480
|
-
function collectSchemaObjectPaths(node, prefix, paths,
|
|
1480
|
+
function collectSchemaObjectPaths(node, prefix, paths, defs, depth) {
|
|
1481
1481
|
if (depth >= MAX_OBJECT_NEST_DEPTH) return;
|
|
1482
1482
|
const seen = /* @__PURE__ */ new Set();
|
|
1483
1483
|
for (const n of derefSchemaNodes(node, defs)) {
|
|
@@ -1486,33 +1486,33 @@ function collectSchemaObjectPaths(node, prefix, paths, stateName, defs, depth) {
|
|
|
1486
1486
|
if (seen.has(key)) continue;
|
|
1487
1487
|
seen.add(key);
|
|
1488
1488
|
const path = prefix ? `${prefix}.${key}` : key;
|
|
1489
|
-
pushSchemaValuePaths(path, child, paths,
|
|
1489
|
+
pushSchemaValuePaths(path, child, paths, defs, depth);
|
|
1490
1490
|
}
|
|
1491
1491
|
}
|
|
1492
1492
|
}
|
|
1493
|
-
function pushSchemaValuePaths(path, node, paths,
|
|
1493
|
+
function pushSchemaValuePaths(path, node, paths, defs, depth) {
|
|
1494
1494
|
const nodes = derefSchemaNodes(node, defs);
|
|
1495
1495
|
const typeHint = schemaTypeHint(nodes);
|
|
1496
|
-
paths.push(withHint({ path, kind: "data",
|
|
1496
|
+
paths.push(withHint({ path, kind: "data", fromSchema: true }, typeHint));
|
|
1497
1497
|
const items = nodes.map((n) => n.items).find((i) => i !== void 0 && i !== null && typeof i === "object");
|
|
1498
1498
|
const isArray = items !== void 0 || (typeHint?.split("|").includes("array") ?? false);
|
|
1499
1499
|
if (isArray) {
|
|
1500
1500
|
const itemNodes = items !== void 0 ? derefSchemaNodes(items, defs) : [];
|
|
1501
|
-
paths.push(withHint({ path: `${path}.*`, kind: "list",
|
|
1502
|
-
paths.push({ path: `${path}.length`, kind: "data", typeHint: "number",
|
|
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
1503
|
if (depth >= MAX_OBJECT_NEST_DEPTH) return;
|
|
1504
1504
|
const seen = /* @__PURE__ */ new Set();
|
|
1505
1505
|
for (const n of itemNodes) {
|
|
1506
1506
|
for (const [childKey, childNode] of Object.entries(n.properties ?? {})) {
|
|
1507
1507
|
if (seen.has(childKey)) continue;
|
|
1508
1508
|
seen.add(childKey);
|
|
1509
|
-
pushSchemaValuePaths(`${path}.*.${childKey}`, childNode, paths,
|
|
1509
|
+
pushSchemaValuePaths(`${path}.*.${childKey}`, childNode, paths, defs, depth + 1);
|
|
1510
1510
|
}
|
|
1511
1511
|
}
|
|
1512
1512
|
return;
|
|
1513
1513
|
}
|
|
1514
1514
|
if (nodes.some((n) => n.properties !== void 0)) {
|
|
1515
|
-
collectSchemaObjectPaths(node, path, paths,
|
|
1515
|
+
collectSchemaObjectPaths(node, path, paths, defs, depth + 1);
|
|
1516
1516
|
}
|
|
1517
1517
|
}
|
|
1518
1518
|
function withHint(candidate, typeHint) {
|
|
@@ -1536,7 +1536,7 @@ function parseWcsScriptBlocks(html, stateTagName = "wcs-state") {
|
|
|
1536
1536
|
pos++;
|
|
1537
1537
|
continue;
|
|
1538
1538
|
}
|
|
1539
|
-
const
|
|
1539
|
+
const mountPath = extractAttribute(wcsMatch.tagContent, "mount");
|
|
1540
1540
|
pos = wcsMatch.end;
|
|
1541
1541
|
const wcsCloseIdx = findCloseTag(html, pos, stateTagName);
|
|
1542
1542
|
const wcsEnd = wcsCloseIdx === -1 ? len : wcsCloseIdx;
|
|
@@ -1568,7 +1568,7 @@ function parseWcsScriptBlocks(html, stateTagName = "wcs-state") {
|
|
|
1568
1568
|
contentStart,
|
|
1569
1569
|
contentEnd,
|
|
1570
1570
|
content: html.slice(contentStart, contentEnd),
|
|
1571
|
-
|
|
1571
|
+
mountPath
|
|
1572
1572
|
});
|
|
1573
1573
|
pos = html.indexOf(">", scriptCloseIdx) + 1;
|
|
1574
1574
|
if (pos === 0) break;
|
|
@@ -1597,7 +1597,7 @@ function parseWcsStateElements(html, stateTagName = "wcs-state") {
|
|
|
1597
1597
|
pos++;
|
|
1598
1598
|
continue;
|
|
1599
1599
|
}
|
|
1600
|
-
const
|
|
1600
|
+
const mountPath = extractAttribute(wcsMatch.tagContent, "mount");
|
|
1601
1601
|
const jsonAttr = extractAttribute(wcsMatch.tagContent, "json") ?? void 0;
|
|
1602
1602
|
const stateAttr = extractAttribute(wcsMatch.tagContent, "state") ?? void 0;
|
|
1603
1603
|
const srcAttr = extractAttribute(wcsMatch.tagContent, "src") ?? void 0;
|
|
@@ -1634,12 +1634,12 @@ function parseWcsStateElements(html, stateTagName = "wcs-state") {
|
|
|
1634
1634
|
contentStart,
|
|
1635
1635
|
contentEnd: scriptCloseIdx,
|
|
1636
1636
|
content: html.slice(contentStart, scriptCloseIdx),
|
|
1637
|
-
|
|
1637
|
+
mountPath
|
|
1638
1638
|
});
|
|
1639
1639
|
pos = html.indexOf(">", scriptCloseIdx) + 1;
|
|
1640
1640
|
if (pos === 0) break;
|
|
1641
1641
|
}
|
|
1642
|
-
elements.push({
|
|
1642
|
+
elements.push({ mountPath, jsonAttr, stateAttr, srcAttr, scriptBlocks, tagStart, tagEnd });
|
|
1643
1643
|
pos = wcsEnd;
|
|
1644
1644
|
if (wcsCloseIdx !== -1) {
|
|
1645
1645
|
const closeEnd = html.indexOf(">", wcsCloseIdx);
|
|
@@ -1750,33 +1750,45 @@ function getStatePathsFromHtml(html, stateTagName = "wcs-state", fileReader) {
|
|
|
1750
1750
|
return allPaths;
|
|
1751
1751
|
}
|
|
1752
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) {
|
|
1753
1765
|
if (element.stateAttr) {
|
|
1754
1766
|
const jsonContent = findScriptJsonById(html, element.stateAttr);
|
|
1755
1767
|
if (jsonContent) {
|
|
1756
|
-
const paths = analyzeJsonPaths(jsonContent
|
|
1768
|
+
const paths = analyzeJsonPaths(jsonContent);
|
|
1757
1769
|
if (paths.length > 0) return paths;
|
|
1758
1770
|
}
|
|
1759
1771
|
}
|
|
1760
1772
|
if (element.srcAttr && fileReader) {
|
|
1761
|
-
const paths = resolveSrcAttribute(element.srcAttr,
|
|
1773
|
+
const paths = resolveSrcAttribute(element.srcAttr, fileReader);
|
|
1762
1774
|
if (paths.length > 0) return paths;
|
|
1763
1775
|
}
|
|
1764
1776
|
if (element.jsonAttr) {
|
|
1765
|
-
const paths = analyzeJsonPaths(element.jsonAttr
|
|
1777
|
+
const paths = analyzeJsonPaths(element.jsonAttr);
|
|
1766
1778
|
if (paths.length > 0) return paths;
|
|
1767
1779
|
}
|
|
1768
1780
|
if (element.scriptBlocks.length > 0) {
|
|
1769
1781
|
return element.scriptBlocks.flatMap(
|
|
1770
|
-
(block) => analyzeStatePaths(block.content
|
|
1782
|
+
(block) => analyzeStatePaths(block.content)
|
|
1771
1783
|
);
|
|
1772
1784
|
}
|
|
1773
1785
|
return [];
|
|
1774
1786
|
}
|
|
1775
|
-
function resolveSrcAttribute(srcPath,
|
|
1787
|
+
function resolveSrcAttribute(srcPath, fileReader) {
|
|
1776
1788
|
if (srcPath.endsWith(".json")) {
|
|
1777
1789
|
const content = fileReader(srcPath);
|
|
1778
1790
|
if (content) {
|
|
1779
|
-
return analyzeJsonPaths(content
|
|
1791
|
+
return analyzeJsonPaths(content);
|
|
1780
1792
|
}
|
|
1781
1793
|
return [];
|
|
1782
1794
|
}
|
|
@@ -1784,18 +1796,18 @@ function resolveSrcAttribute(srcPath, stateName, fileReader) {
|
|
|
1784
1796
|
const tsPath = srcPath.replace(/\.js$/, ".ts");
|
|
1785
1797
|
const tsContent = fileReader(tsPath);
|
|
1786
1798
|
if (tsContent) {
|
|
1787
|
-
return analyzeStatePaths(tsContent
|
|
1799
|
+
return analyzeStatePaths(tsContent);
|
|
1788
1800
|
}
|
|
1789
1801
|
const jsContent = fileReader(srcPath);
|
|
1790
1802
|
if (jsContent) {
|
|
1791
|
-
return analyzeStatePaths(jsContent
|
|
1803
|
+
return analyzeStatePaths(jsContent);
|
|
1792
1804
|
}
|
|
1793
1805
|
return [];
|
|
1794
1806
|
}
|
|
1795
1807
|
if (srcPath.endsWith(".ts")) {
|
|
1796
1808
|
const content = fileReader(srcPath);
|
|
1797
1809
|
if (content) {
|
|
1798
|
-
return analyzeStatePaths(content
|
|
1810
|
+
return analyzeStatePaths(content);
|
|
1799
1811
|
}
|
|
1800
1812
|
return [];
|
|
1801
1813
|
}
|
|
@@ -1970,8 +1982,20 @@ var ja = {
|
|
|
1970
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`,
|
|
1971
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`,
|
|
1972
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`,
|
|
1973
|
-
namedStateAttrDeprecated: (name) =>
|
|
1974
|
-
namedStatePathDeprecated: (name) => name === "default" ? `"@default" \
|
|
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
|
+
}
|
|
1975
1999
|
};
|
|
1976
2000
|
var EN_EXPECTED_LABEL = {
|
|
1977
2001
|
array: "an array-typed path",
|
|
@@ -2029,8 +2053,20 @@ var en = {
|
|
|
2029
2053
|
devtoolsAfterState: () => `Load @wcstack/devtools/auto BEFORE @wcstack/state/auto (otherwise the wiring ledger is not captured live)`,
|
|
2030
2054
|
baseHrefMissing: () => `An SPA using @wcstack/router needs <base href="/"> in <head> (without it, deep links misderive the basename)`,
|
|
2031
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`,
|
|
2032
|
-
namedStateAttrDeprecated: (name) =>
|
|
2033
|
-
namedStatePathDeprecated: (name) => name === "default" ? `The "@default" selector
|
|
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
|
+
}
|
|
2034
2070
|
};
|
|
2035
2071
|
var CATALOGS = { ja, en };
|
|
2036
2072
|
function getMessages(locale3) {
|
|
@@ -2231,16 +2267,10 @@ function escape(key) {
|
|
|
2231
2267
|
|
|
2232
2268
|
// src/service/bindingValidator.ts
|
|
2233
2269
|
var filterMap = new Map(BUILTIN_FILTERS.map((f) => [f.name, f]));
|
|
2234
|
-
function validateBindings(html, attrName, stateTagName = "wcs-state", locale3, fileReader,
|
|
2270
|
+
function validateBindings(html, attrName, stateTagName = "wcs-state", locale3, fileReader, applicationSchema) {
|
|
2235
2271
|
const diagnostics = [];
|
|
2236
2272
|
const msgs = getMessages(locale3);
|
|
2237
|
-
const statePaths = mergeSchemaCandidates(getStatePathsFromHtml(html, stateTagName, fileReader),
|
|
2238
|
-
const pathsByState = /* @__PURE__ */ new Map();
|
|
2239
|
-
for (const p of statePaths) {
|
|
2240
|
-
const list = pathsByState.get(p.stateName) ?? [];
|
|
2241
|
-
list.push(p);
|
|
2242
|
-
pathsByState.set(p.stateName, list);
|
|
2243
|
-
}
|
|
2273
|
+
const statePaths = mergeSchemaCandidates(getStatePathsFromHtml(html, stateTagName, fileReader), applicationSchema);
|
|
2244
2274
|
const attrs = findAllBindAttributes(html, attrName);
|
|
2245
2275
|
let structuralTemplates = null;
|
|
2246
2276
|
const getStructuralTemplates = () => {
|
|
@@ -2273,7 +2303,7 @@ function validateBindings(html, attrName, stateTagName = "wcs-state", locale3, f
|
|
|
2273
2303
|
for (const binding of bindings) {
|
|
2274
2304
|
const bindingStart = attr.valueStart + pos;
|
|
2275
2305
|
const parsed = parseBindingExpression(binding);
|
|
2276
|
-
const scopedPaths =
|
|
2306
|
+
const scopedPaths = statePaths;
|
|
2277
2307
|
const scopedPathSet = new Set(scopedPaths.map((p) => p.path));
|
|
2278
2308
|
const propNoMod = parsed.property.replace(/#.*$/, "").trim();
|
|
2279
2309
|
if (propNoMod === "...") {
|
|
@@ -2357,7 +2387,7 @@ function validateBindings(html, attrName, stateTagName = "wcs-state", locale3, f
|
|
|
2357
2387
|
}
|
|
2358
2388
|
}
|
|
2359
2389
|
if (checkPath) {
|
|
2360
|
-
const schema =
|
|
2390
|
+
const schema = applicationSchema;
|
|
2361
2391
|
const verdict = schema !== void 0 ? validateSchemaPathExistence(checkPath, pathTrimmed, scopedPaths, scopedPathSet, commandNames, schema, msgs) : toMissingVerdict(validatePathExistence(checkPath, pathTrimmed, scopedPaths, scopedPathSet, commandNames, msgs));
|
|
2362
2392
|
if (verdict) {
|
|
2363
2393
|
const pathOffset = binding.indexOf(parsed.path);
|
|
@@ -2486,7 +2516,7 @@ function validateBindings(html, attrName, stateTagName = "wcs-state", locale3, f
|
|
|
2486
2516
|
if (typeReq && resultType !== typeReq.expected) {
|
|
2487
2517
|
const pathOffset = binding.indexOf(parsed.path);
|
|
2488
2518
|
const pathStart = bindingStart + pathOffset;
|
|
2489
|
-
const schemaDefinite = typeReq.expected === "array" && parsed.filters.length === 0 &&
|
|
2519
|
+
const schemaDefinite = typeReq.expected === "array" && parsed.filters.length === 0 && applicationSchema !== void 0 && scopedPaths.some((p) => p.path === pathTrimmed && p.fromSchema === true);
|
|
2490
2520
|
diagnostics.push({
|
|
2491
2521
|
code: schemaDefinite ? WcsDiagnosticCode.PathTypeMismatch : WcsDiagnosticCode.BindingTypeExpectation,
|
|
2492
2522
|
start: pathStart,
|
|
@@ -2540,7 +2570,7 @@ function splitBindingExpressions(value) {
|
|
|
2540
2570
|
function parseBindingExpression(expr) {
|
|
2541
2571
|
const colonIndex = expr.indexOf(":");
|
|
2542
2572
|
if (colonIndex === -1) {
|
|
2543
|
-
return { property: expr.trim(), path: null,
|
|
2573
|
+
return { property: expr.trim(), path: null, filters: [], inputFilters: [] };
|
|
2544
2574
|
}
|
|
2545
2575
|
const rawProp = expr.slice(0, colonIndex);
|
|
2546
2576
|
const propSegments = splitByPipe(rawProp);
|
|
@@ -2552,9 +2582,8 @@ function parseBindingExpression(expr) {
|
|
|
2552
2582
|
const filterSegments = segments.slice(1);
|
|
2553
2583
|
const atIndex = pathSegment.indexOf("@");
|
|
2554
2584
|
const path = atIndex !== -1 ? pathSegment.slice(0, atIndex) : pathSegment;
|
|
2555
|
-
const targetState = atIndex !== -1 ? pathSegment.slice(atIndex + 1).trim() || "default" : "default";
|
|
2556
2585
|
const filters = parseFilterSegments(expr, filterSegments, colonIndex + 1 + pathSegment.length + 1);
|
|
2557
|
-
return { property, path: path.trim() || null,
|
|
2586
|
+
return { property, path: path.trim() || null, filters, inputFilters };
|
|
2558
2587
|
}
|
|
2559
2588
|
function parseFilterSegments(expr, segments, searchStart) {
|
|
2560
2589
|
const filters = [];
|
|
@@ -3078,11 +3107,11 @@ function isInsideTag(html, offset, tagName) {
|
|
|
3078
3107
|
}
|
|
3079
3108
|
|
|
3080
3109
|
// src/service/templateSyntaxValidator.ts
|
|
3081
|
-
function validateTemplateSyntax(html, stateTagName, bindAttrName = "data-wcs", locale3, fileReader,
|
|
3110
|
+
function validateTemplateSyntax(html, stateTagName, bindAttrName = "data-wcs", locale3, fileReader, applicationSchema) {
|
|
3082
3111
|
const diagnostics = [];
|
|
3083
3112
|
const msgs = getMessages(locale3);
|
|
3084
|
-
const allPaths = mergeSchemaCandidates(getStatePathsFromHtml(html, stateTagName, fileReader),
|
|
3085
|
-
const defaultSchema =
|
|
3113
|
+
const allPaths = mergeSchemaCandidates(getStatePathsFromHtml(html, stateTagName, fileReader), applicationSchema);
|
|
3114
|
+
const defaultSchema = applicationSchema;
|
|
3086
3115
|
const missingVerdict = (path, displayPath, pathSet2, scoped) => {
|
|
3087
3116
|
if (isValidTemplatePath(path, pathSet2, scoped)) return null;
|
|
3088
3117
|
if (defaultSchema !== void 0 && !path.startsWith("$")) {
|
|
@@ -3092,7 +3121,7 @@ function validateTemplateSyntax(html, stateTagName, bindAttrName = "data-wcs", l
|
|
|
3092
3121
|
return { code: WcsDiagnosticCode.BindingPathMissing, severity: "warning", message: msgs.pathMissing(displayPath) };
|
|
3093
3122
|
};
|
|
3094
3123
|
if (allPaths.length === 0) return diagnostics;
|
|
3095
|
-
const defaultPaths = allPaths
|
|
3124
|
+
const defaultPaths = allPaths;
|
|
3096
3125
|
const pathSet = new Set(defaultPaths.map((p) => p.path));
|
|
3097
3126
|
const filterNameSet = new Set(BUILTIN_FILTERS.map((f) => f.name));
|
|
3098
3127
|
const mustaches = findAllMustacheSyntax(html);
|
|
@@ -3118,9 +3147,8 @@ function validateTemplateSyntax(html, stateTagName, bindAttrName = "data-wcs", l
|
|
|
3118
3147
|
}
|
|
3119
3148
|
if (!item.expression) continue;
|
|
3120
3149
|
const parts = item.expression.split("|");
|
|
3121
|
-
|
|
3122
|
-
|
|
3123
|
-
if (atIdx !== -1) pathPart = pathPart.slice(0, atIdx).trim();
|
|
3150
|
+
const pathPart = (parts[0] || "").trim();
|
|
3151
|
+
if (pathPart.includes("@")) continue;
|
|
3124
3152
|
const insideFor = item.insideTemplate && isInsideForTemplate(html, item.matchStart, bindAttrName);
|
|
3125
3153
|
if (pathPart && !/^-?\d|^["'`]|^true$|^false$|^null$/.test(pathPart)) {
|
|
3126
3154
|
if (!insideFor && pathPart.includes("*")) {
|
|
@@ -3141,7 +3169,7 @@ function validateTemplateSyntax(html, stateTagName, bindAttrName = "data-wcs", l
|
|
|
3141
3169
|
severity: "warning"
|
|
3142
3170
|
});
|
|
3143
3171
|
}
|
|
3144
|
-
if (insideFor && !pathPart.startsWith(".")
|
|
3172
|
+
if (insideFor && !pathPart.startsWith(".")) {
|
|
3145
3173
|
const indexMatch = /^\$(\d+)$/.exec(pathPart);
|
|
3146
3174
|
const needed = indexMatch !== null ? Number(indexMatch[1]) : pathPart.includes("*") ? countWildcardSegments(pathPart) : 0;
|
|
3147
3175
|
if (needed > 0) {
|
|
@@ -4603,7 +4631,7 @@ function validateBindingAgainstContract(tagName, contract, parsed, property, sta
|
|
|
4603
4631
|
return;
|
|
4604
4632
|
}
|
|
4605
4633
|
if (property === "trigger" && "trigger" in contract.inputs && parsed.path) {
|
|
4606
|
-
const cand = findDataSlot(getPaths(), parsed.path
|
|
4634
|
+
const cand = findDataSlot(getPaths(), parsed.path);
|
|
4607
4635
|
if (cand?.rawInitial === "true") {
|
|
4608
4636
|
diagnostics.push({
|
|
4609
4637
|
code: WcsDiagnosticCode.TriggerSeededTruthy,
|
|
@@ -4617,7 +4645,7 @@ function validateBindingAgainstContract(tagName, contract, parsed, property, sta
|
|
|
4617
4645
|
}
|
|
4618
4646
|
}
|
|
4619
4647
|
if (tagName === "wcs-storage" && property === "value" && !hasManual && parsed.path && !/(?:^|,)init=(?:element|auto)\b/.test(modifiers)) {
|
|
4620
|
-
const cand = findDataSlot(getPaths(), parsed.path
|
|
4648
|
+
const cand = findDataSlot(getPaths(), parsed.path);
|
|
4621
4649
|
if (cand?.rawInitial !== void 0 && EMPTYISH_SEEDS.has(normalizeSeed(cand.rawInitial))) {
|
|
4622
4650
|
diagnostics.push({
|
|
4623
4651
|
code: WcsDiagnosticCode.StorageSeedClobber,
|
|
@@ -4631,8 +4659,8 @@ function validateBindingAgainstContract(tagName, contract, parsed, property, sta
|
|
|
4631
4659
|
}
|
|
4632
4660
|
}
|
|
4633
4661
|
}
|
|
4634
|
-
function findDataSlot(paths, path
|
|
4635
|
-
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);
|
|
4636
4664
|
}
|
|
4637
4665
|
function normalizeSeed(raw) {
|
|
4638
4666
|
const compact = raw.replace(/\s+/g, "");
|
|
@@ -4907,7 +4935,7 @@ function blankJsComments(code) {
|
|
|
4907
4935
|
}
|
|
4908
4936
|
|
|
4909
4937
|
// src/service/watchDeclarationValidator.ts
|
|
4910
|
-
var
|
|
4938
|
+
var STATE_NAME_SEPARATOR = "@";
|
|
4911
4939
|
function validateWatchDeclarations(html, stateTagName = "wcs-state", locale3) {
|
|
4912
4940
|
const msgs = getMessages(locale3);
|
|
4913
4941
|
const out = [];
|
|
@@ -4924,7 +4952,7 @@ function validateWatchDeclarations(html, stateTagName = "wcs-state", locale3) {
|
|
|
4924
4952
|
}
|
|
4925
4953
|
const entries = analyzeWatchEntries(block.content);
|
|
4926
4954
|
if (entries.length === 0) continue;
|
|
4927
|
-
const paths = analyzeStatePaths(block.content
|
|
4955
|
+
const paths = analyzeStatePaths(block.content);
|
|
4928
4956
|
const pathSet = new Set(paths.map((p) => p.path));
|
|
4929
4957
|
for (const entry of entries) {
|
|
4930
4958
|
const diagnostic = validateEntry(entry, pathSet, msgs);
|
|
@@ -4943,7 +4971,7 @@ function validateWatchDeclarations(html, stateTagName = "wcs-state", locale3) {
|
|
|
4943
4971
|
function validateEntry(entry, pathSet, msgs) {
|
|
4944
4972
|
const { key } = entry;
|
|
4945
4973
|
const invalid = (message) => ({ code: WcsDiagnosticCode.WatchDeclarationInvalid, message, severity: "error" });
|
|
4946
|
-
if (key.includes(
|
|
4974
|
+
if (key.includes(STATE_NAME_SEPARATOR)) {
|
|
4947
4975
|
return invalid(msgs.watchKeyCrossState(key));
|
|
4948
4976
|
}
|
|
4949
4977
|
if (key.startsWith("$")) {
|
|
@@ -4992,7 +5020,6 @@ function validateNamedState(html, attrName, stateTagName = "wcs-state", locale3)
|
|
|
4992
5020
|
const diagnostics = [];
|
|
4993
5021
|
for (const element of parseWcsStateElements(html, stateTagName)) {
|
|
4994
5022
|
const tagText = html.slice(element.tagStart, element.tagEnd);
|
|
4995
|
-
if (/\sbind-component(?=[\s=>/])/i.test(tagText)) continue;
|
|
4996
5023
|
const match = /(?:^|\s)name\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s>]+))/i.exec(tagText);
|
|
4997
5024
|
if (match === null) continue;
|
|
4998
5025
|
const value = match[1] ?? match[2] ?? match[3] ?? "";
|
|
@@ -5003,7 +5030,7 @@ function validateNamedState(html, attrName, stateTagName = "wcs-state", locale3)
|
|
|
5003
5030
|
start: valueEnd - value.length,
|
|
5004
5031
|
end: valueEnd,
|
|
5005
5032
|
message: msgs.namedStateAttrDeprecated(value),
|
|
5006
|
-
severity: "
|
|
5033
|
+
severity: "error"
|
|
5007
5034
|
});
|
|
5008
5035
|
}
|
|
5009
5036
|
for (const attr of findAllBindAttributes(html, attrName)) {
|
|
@@ -5016,27 +5043,62 @@ function validateNamedState(html, attrName, stateTagName = "wcs-state", locale3)
|
|
|
5016
5043
|
start: attr.valueStart + pos + selector.start,
|
|
5017
5044
|
end: attr.valueStart + pos + selector.end,
|
|
5018
5045
|
message: msgs.namedStatePathDeprecated(selector.name),
|
|
5019
|
-
severity: "
|
|
5046
|
+
severity: "error"
|
|
5020
5047
|
});
|
|
5021
5048
|
}
|
|
5022
5049
|
pos += expr.length + 1;
|
|
5023
5050
|
}
|
|
5024
5051
|
}
|
|
5025
|
-
for (const
|
|
5026
|
-
const selector = findStateSelector(
|
|
5052
|
+
for (const item of [...findAllMustacheSyntax(html), ...findAllCommentBindings(html)]) {
|
|
5053
|
+
const selector = findStateSelector(item.expression, true);
|
|
5027
5054
|
if (selector !== null) {
|
|
5028
5055
|
diagnostics.push({
|
|
5029
5056
|
code: WcsDiagnosticCode.NamedStateDeprecated,
|
|
5030
|
-
start:
|
|
5031
|
-
end:
|
|
5057
|
+
start: item.exprStart + selector.start,
|
|
5058
|
+
end: item.exprStart + selector.end,
|
|
5032
5059
|
message: msgs.namedStatePathDeprecated(selector.name),
|
|
5033
|
-
severity: "
|
|
5060
|
+
severity: "error"
|
|
5034
5061
|
});
|
|
5035
5062
|
}
|
|
5036
5063
|
}
|
|
5037
5064
|
return diagnostics;
|
|
5038
5065
|
}
|
|
5039
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
|
+
|
|
5040
5102
|
// ../state/dist/parser.esm.js
|
|
5041
5103
|
var DELIMITER2 = ".";
|
|
5042
5104
|
var WILDCARD2 = "*";
|
|
@@ -5044,7 +5106,6 @@ var MAX_WILDCARD_DEPTH2 = 128;
|
|
|
5044
5106
|
var BINDING_SEPARATOR2 = ";";
|
|
5045
5107
|
var PROP_VALUE_SEPARATOR2 = ":";
|
|
5046
5108
|
var MODIFIER_SEPARATOR2 = "#";
|
|
5047
|
-
var STATE_NAME_SEPARATOR3 = "@";
|
|
5048
5109
|
var FILTER_SEPARATOR2 = "|";
|
|
5049
5110
|
var ELSE_KEYWORD2 = "else";
|
|
5050
5111
|
var SPREAD_PROP2 = "...";
|
|
@@ -5900,11 +5961,12 @@ function parseStatePart(statePart) {
|
|
|
5900
5961
|
} else {
|
|
5901
5962
|
stateAndPath = statePart.trim();
|
|
5902
5963
|
}
|
|
5903
|
-
if (stateAndPath.indexOf(
|
|
5904
|
-
|
|
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;
|
|
5905
5968
|
const pathInfo = getPathInfo(statePathName);
|
|
5906
5969
|
return {
|
|
5907
|
-
stateName,
|
|
5908
5970
|
statePathName,
|
|
5909
5971
|
statePathInfo: pathInfo,
|
|
5910
5972
|
outFilters: filters
|
|
@@ -5927,7 +5989,6 @@ function parseBindTextsForElement(bindText) {
|
|
|
5927
5989
|
propModifiers: [],
|
|
5928
5990
|
statePathName: "#else",
|
|
5929
5991
|
statePathInfo: pathInfo,
|
|
5930
|
-
stateName: "",
|
|
5931
5992
|
inFilters: [],
|
|
5932
5993
|
outFilters: [],
|
|
5933
5994
|
bindingType: "else"
|
|
@@ -6027,24 +6088,18 @@ function parseEmbeddedTextWithPositions(expression) {
|
|
|
6027
6088
|
error = e.message;
|
|
6028
6089
|
}
|
|
6029
6090
|
if (parsed === null) {
|
|
6030
|
-
return { exprRange, exprText: expression, parsed, error, propRange: null, pathRange: null
|
|
6091
|
+
return { exprRange, exprText: expression, parsed, error, propRange: null, pathRange: null };
|
|
6031
6092
|
}
|
|
6032
6093
|
const firstPipe = expression.indexOf(delimiters.filter);
|
|
6033
6094
|
const pathScopeEnd = firstPipe === -1 ? expression.length : firstPipe;
|
|
6034
6095
|
const pathLocal = locate(expression, parsed.statePathName, 0, pathScopeEnd);
|
|
6035
|
-
let stateNameLocal = null;
|
|
6036
|
-
const at = expression.indexOf(delimiters.stateName);
|
|
6037
|
-
if (at !== -1 && at < pathScopeEnd) {
|
|
6038
|
-
stateNameLocal = locate(expression, parsed.stateName, at + 1, pathScopeEnd);
|
|
6039
|
-
}
|
|
6040
6096
|
return {
|
|
6041
6097
|
exprRange,
|
|
6042
6098
|
exprText: expression,
|
|
6043
6099
|
parsed,
|
|
6044
6100
|
error,
|
|
6045
6101
|
propRange: null,
|
|
6046
|
-
pathRange: pathLocal
|
|
6047
|
-
stateNameRange: stateNameLocal
|
|
6102
|
+
pathRange: pathLocal
|
|
6048
6103
|
};
|
|
6049
6104
|
}
|
|
6050
6105
|
function parseBindTextWithPositions(bindText) {
|
|
@@ -6066,23 +6121,18 @@ function parseBindTextWithPositions(bindText) {
|
|
|
6066
6121
|
error = e.message;
|
|
6067
6122
|
}
|
|
6068
6123
|
if (parsed === null) {
|
|
6069
|
-
results.push({ exprRange, exprText: expr, parsed, error, propRange: null, pathRange: null
|
|
6124
|
+
results.push({ exprRange, exprText: expr, parsed, error, propRange: null, pathRange: null });
|
|
6070
6125
|
continue;
|
|
6071
6126
|
}
|
|
6072
6127
|
const colon = expr.indexOf(delimiters.propValue);
|
|
6073
6128
|
const propEndLimit = colon === -1 ? expr.length : colon;
|
|
6074
6129
|
const propLocal = locate(expr, parsed.propName, 0, propEndLimit);
|
|
6075
6130
|
let pathLocal = null;
|
|
6076
|
-
let stateNameLocal = null;
|
|
6077
6131
|
if (colon !== -1) {
|
|
6078
6132
|
const stateBase = colon + 1;
|
|
6079
6133
|
const firstPipe = expr.indexOf(delimiters.filter, stateBase);
|
|
6080
6134
|
const pathScopeEnd = firstPipe === -1 ? expr.length : firstPipe;
|
|
6081
6135
|
pathLocal = locate(expr, parsed.statePathName, stateBase, pathScopeEnd);
|
|
6082
|
-
const at = expr.indexOf(delimiters.stateName, stateBase);
|
|
6083
|
-
if (at !== -1 && at < pathScopeEnd) {
|
|
6084
|
-
stateNameLocal = locate(expr, parsed.stateName, at + 1, pathScopeEnd);
|
|
6085
|
-
}
|
|
6086
6136
|
}
|
|
6087
6137
|
const lift = (range) => range === null ? null : { start: exprStart + range.start, end: exprStart + range.end };
|
|
6088
6138
|
results.push({
|
|
@@ -6091,17 +6141,13 @@ function parseBindTextWithPositions(bindText) {
|
|
|
6091
6141
|
parsed,
|
|
6092
6142
|
error,
|
|
6093
6143
|
propRange: lift(propLocal),
|
|
6094
|
-
pathRange: lift(pathLocal)
|
|
6095
|
-
stateNameRange: lift(stateNameLocal)
|
|
6144
|
+
pathRange: lift(pathLocal)
|
|
6096
6145
|
});
|
|
6097
6146
|
}
|
|
6098
6147
|
return results;
|
|
6099
6148
|
}
|
|
6100
6149
|
|
|
6101
6150
|
// src/core/index/referenceIndex.ts
|
|
6102
|
-
function keyOf(stateName, path) {
|
|
6103
|
-
return `${stateName}\0${path}`;
|
|
6104
|
-
}
|
|
6105
6151
|
function buildReferenceIndex(html, options = {}) {
|
|
6106
6152
|
clearParserCaches();
|
|
6107
6153
|
const bindAttribute = options.bindAttribute ?? "data-wcs";
|
|
@@ -6119,13 +6165,11 @@ function buildReferenceIndex(html, options = {}) {
|
|
|
6119
6165
|
occurrences.push({
|
|
6120
6166
|
source: "attribute",
|
|
6121
6167
|
kind: binding.parsed.propSegments[0] === "eventToken" ? "eventToken" : "path",
|
|
6122
|
-
stateName: binding.parsed.stateName,
|
|
6123
6168
|
path: binding.parsed.statePathName,
|
|
6124
6169
|
pathRange: lift(binding.pathRange),
|
|
6125
6170
|
exprRange: lift(binding.exprRange),
|
|
6126
6171
|
propName: binding.parsed.propName,
|
|
6127
6172
|
propRange: binding.propRange === null ? null : lift(binding.propRange),
|
|
6128
|
-
stateNameRange: binding.stateNameRange === null ? null : lift(binding.stateNameRange),
|
|
6129
6173
|
bindingType: binding.parsed.bindingType
|
|
6130
6174
|
});
|
|
6131
6175
|
}
|
|
@@ -6148,22 +6192,21 @@ function buildReferenceIndex(html, options = {}) {
|
|
|
6148
6192
|
occurrences.push({
|
|
6149
6193
|
source: match.kind,
|
|
6150
6194
|
kind: "path",
|
|
6151
|
-
stateName: binding.parsed.stateName,
|
|
6152
6195
|
path: binding.parsed.statePathName,
|
|
6153
6196
|
pathRange: shift(binding.pathRange),
|
|
6154
6197
|
exprRange: { start: match.exprStart, end: match.exprEnd },
|
|
6155
6198
|
propName: null,
|
|
6156
6199
|
propRange: null,
|
|
6157
|
-
stateNameRange: binding.stateNameRange === null ? null : shift(binding.stateNameRange),
|
|
6158
6200
|
bindingType: "text"
|
|
6159
6201
|
});
|
|
6160
6202
|
}
|
|
6161
6203
|
const declarations = [];
|
|
6162
6204
|
for (const block of parseWcsScriptBlocks(html, stateTagName)) {
|
|
6205
|
+
const prefix = block.mountPath === null ? "" : block.mountPath + ".";
|
|
6163
6206
|
for (const span of analyzeDeclarationSpans(block.content)) {
|
|
6207
|
+
if (prefix !== "" && span.name.startsWith("$")) continue;
|
|
6164
6208
|
declarations.push({
|
|
6165
|
-
|
|
6166
|
-
name: span.name,
|
|
6209
|
+
name: prefix + span.name,
|
|
6167
6210
|
kind: span.kind,
|
|
6168
6211
|
range: { start: block.contentStart + span.start, end: block.contentStart + span.end }
|
|
6169
6212
|
});
|
|
@@ -6172,7 +6215,7 @@ function buildReferenceIndex(html, options = {}) {
|
|
|
6172
6215
|
const byPath = /* @__PURE__ */ new Map();
|
|
6173
6216
|
for (const occurrence of occurrences) {
|
|
6174
6217
|
if (occurrence.kind !== "path") continue;
|
|
6175
|
-
const key =
|
|
6218
|
+
const key = occurrence.path;
|
|
6176
6219
|
const list = byPath.get(key);
|
|
6177
6220
|
if (list === void 0) {
|
|
6178
6221
|
byPath.set(key, [occurrence]);
|
|
@@ -6182,22 +6225,22 @@ function buildReferenceIndex(html, options = {}) {
|
|
|
6182
6225
|
}
|
|
6183
6226
|
const declarationByName = /* @__PURE__ */ new Map();
|
|
6184
6227
|
for (const declaration of declarations) {
|
|
6185
|
-
const key =
|
|
6228
|
+
const key = declaration.name;
|
|
6186
6229
|
if (!declarationByName.has(key)) declarationByName.set(key, declaration);
|
|
6187
6230
|
}
|
|
6188
6231
|
return {
|
|
6189
6232
|
occurrences,
|
|
6190
6233
|
declarations,
|
|
6191
6234
|
problems,
|
|
6192
|
-
referencesOf(
|
|
6193
|
-
return (byPath.get(
|
|
6235
|
+
referencesOf(path) {
|
|
6236
|
+
return (byPath.get(path) ?? []).slice();
|
|
6194
6237
|
},
|
|
6195
|
-
declarationOf(
|
|
6196
|
-
const exact = declarationByName.get(
|
|
6238
|
+
declarationOf(path) {
|
|
6239
|
+
const exact = declarationByName.get(path);
|
|
6197
6240
|
if (exact !== void 0) return exact;
|
|
6198
6241
|
const firstSegment = path.split(".")[0];
|
|
6199
6242
|
if (firstSegment === path) return null;
|
|
6200
|
-
return declarationByName.get(
|
|
6243
|
+
return declarationByName.get(firstSegment) ?? null;
|
|
6201
6244
|
},
|
|
6202
6245
|
occurrenceAt(offset) {
|
|
6203
6246
|
for (const occurrence of occurrences) {
|
|
@@ -6442,8 +6485,9 @@ function validateUpdatedCallbackDemand(html, stateTagName, bindAttrName, locale3
|
|
|
6442
6485
|
for (const block of blocks) {
|
|
6443
6486
|
const callback = analyzeCallableBodies(block.content).find((entry) => entry.name === STATE_UPDATED_CALLBACK && entry.kind === "method");
|
|
6444
6487
|
if (callback === void 0) continue;
|
|
6445
|
-
|
|
6446
|
-
const
|
|
6488
|
+
if (block.mountPath !== null) continue;
|
|
6489
|
+
const declared = new Set(analyzeStatePaths(block.content).map((p) => p.path));
|
|
6490
|
+
const bound = boundPaths;
|
|
6447
6491
|
const body = blankComments(callback.body);
|
|
6448
6492
|
PATH_TEST_LITERAL.lastIndex = 0;
|
|
6449
6493
|
let match;
|
|
@@ -6466,27 +6510,18 @@ function validateUpdatedCallbackDemand(html, stateTagName, bindAttrName, locale3
|
|
|
6466
6510
|
return out;
|
|
6467
6511
|
}
|
|
6468
6512
|
function collectBoundPaths(html, stateTagName, bindAttrName) {
|
|
6469
|
-
const
|
|
6470
|
-
const add = (stateName, path) => {
|
|
6471
|
-
let set = byState.get(stateName);
|
|
6472
|
-
if (set === void 0) {
|
|
6473
|
-
set = /* @__PURE__ */ new Set();
|
|
6474
|
-
byState.set(stateName, set);
|
|
6475
|
-
}
|
|
6476
|
-
set.add(path);
|
|
6477
|
-
};
|
|
6513
|
+
const bound = /* @__PURE__ */ new Set();
|
|
6478
6514
|
const index = buildReferenceIndex(html, { bindAttribute: bindAttrName, stateTagName });
|
|
6479
6515
|
for (const occurrence of index.occurrences) {
|
|
6480
|
-
add(occurrence.
|
|
6516
|
+
bound.add(occurrence.path);
|
|
6481
6517
|
if (!occurrence.path.startsWith(".")) continue;
|
|
6482
6518
|
const forPath = getInnermostForPath(html, occurrence.pathRange.start, bindAttrName);
|
|
6483
6519
|
if (forPath === null || forPath.startsWith(".")) continue;
|
|
6484
|
-
add(
|
|
6485
|
-
occurrence.stateName,
|
|
6520
|
+
bound.add(
|
|
6486
6521
|
occurrence.path === "." ? `${forPath}.*` : `${forPath}.*.${occurrence.path.slice(1)}`
|
|
6487
6522
|
);
|
|
6488
6523
|
}
|
|
6489
|
-
return
|
|
6524
|
+
return bound;
|
|
6490
6525
|
}
|
|
6491
6526
|
function validateSemantics(html, stateTagName = "wcs-state", locale3, bindAttrName = "data-wcs") {
|
|
6492
6527
|
const out = [];
|
|
@@ -6671,8 +6706,8 @@ function parseJsonWithSpans(text) {
|
|
|
6671
6706
|
}
|
|
6672
6707
|
|
|
6673
6708
|
// src/core/sidecar/types.ts
|
|
6674
|
-
var SUPPORTED_SCHEMA_VERSION =
|
|
6675
|
-
var SUPPORTED_NAMESPACE_VERSION =
|
|
6709
|
+
var SUPPORTED_SCHEMA_VERSION = 2;
|
|
6710
|
+
var SUPPORTED_NAMESPACE_VERSION = 2;
|
|
6676
6711
|
|
|
6677
6712
|
// src/core/sidecar/loader.ts
|
|
6678
6713
|
var NAMESPACE_KEYS = ["wcstack.types", "wcstack.async", "wcstack.platformCapabilities", "wcstack.application"];
|
|
@@ -6709,10 +6744,11 @@ function loadManifest(artifact) {
|
|
|
6709
6744
|
return { artifact, manifest: null, ctx, spans: parsed.spans };
|
|
6710
6745
|
}
|
|
6711
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\`.` : "";
|
|
6712
6748
|
ctx.add(
|
|
6713
6749
|
WcsDiagnosticCode.ManifestSchemaVersion,
|
|
6714
6750
|
pointer("schemaVersion"),
|
|
6715
|
-
`Unsupported schemaVersion ${obj.schemaVersion}; this reader supports ${SUPPORTED_SCHEMA_VERSION}
|
|
6751
|
+
`Unsupported schemaVersion ${obj.schemaVersion}; this reader supports ${SUPPORTED_SCHEMA_VERSION}.${migration}`,
|
|
6716
6752
|
"error"
|
|
6717
6753
|
);
|
|
6718
6754
|
return { artifact, manifest: null, ctx, spans: parsed.spans };
|
|
@@ -6761,9 +6797,8 @@ function resolvePackageContracts(loaded) {
|
|
|
6761
6797
|
const collided = /* @__PURE__ */ new Set();
|
|
6762
6798
|
const firstSource = /* @__PURE__ */ new Map();
|
|
6763
6799
|
const filterOwner = /* @__PURE__ */ new Map();
|
|
6764
|
-
|
|
6765
|
-
|
|
6766
|
-
const collidedStates = /* @__PURE__ */ new Set();
|
|
6800
|
+
let schemaOwner;
|
|
6801
|
+
let schemaWinner;
|
|
6767
6802
|
let hasApplicationArtifact = false;
|
|
6768
6803
|
for (const lm of loaded) {
|
|
6769
6804
|
if (lm.manifest === null) continue;
|
|
@@ -6819,26 +6854,23 @@ function resolvePackageContracts(loaded) {
|
|
|
6819
6854
|
);
|
|
6820
6855
|
}
|
|
6821
6856
|
}
|
|
6822
|
-
if (lm.manifest.kind === "application" && application?.
|
|
6823
|
-
|
|
6824
|
-
|
|
6825
|
-
if (
|
|
6826
|
-
|
|
6827
|
-
|
|
6828
|
-
|
|
6829
|
-
|
|
6830
|
-
|
|
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
|
+
);
|
|
6831
6873
|
}
|
|
6832
|
-
collidedStates.add(name);
|
|
6833
|
-
stateWinners.delete(name);
|
|
6834
|
-
ctxFor(lm).add(
|
|
6835
|
-
WcsDiagnosticCode.ManifestStateCollision,
|
|
6836
|
-
pointer("manifestExtensions", "wcstack.application", "states", name),
|
|
6837
|
-
`State "${name}" declares a stateSchema in multiple application artifacts (also in "${priorSource}"); neither is used.`,
|
|
6838
|
-
"error",
|
|
6839
|
-
{ statePath: name },
|
|
6840
|
-
true
|
|
6841
|
-
);
|
|
6842
6874
|
}
|
|
6843
6875
|
}
|
|
6844
6876
|
}
|
|
@@ -6847,7 +6879,7 @@ function resolvePackageContracts(loaded) {
|
|
|
6847
6879
|
const kept = diags.filter((d) => !(d.code === WcsDiagnosticCode.ManifestOverride && d.tag !== void 0 && collided.has(d.tag)));
|
|
6848
6880
|
if (kept.length > 0) diagnosticsBySource.set(source, kept);
|
|
6849
6881
|
}
|
|
6850
|
-
return { tags: winners,
|
|
6882
|
+
return { tags: winners, applicationSchema: schemaWinner, hasApplicationArtifact, diagnosticsBySource };
|
|
6851
6883
|
}
|
|
6852
6884
|
|
|
6853
6885
|
// src/core/sidecar/discover.ts
|
|
@@ -6859,22 +6891,19 @@ function discoverApplicationManifest(fileReader) {
|
|
|
6859
6891
|
const text = fileReader(relativePath);
|
|
6860
6892
|
if (text === void 0) continue;
|
|
6861
6893
|
const loaded = loadManifest({ text, source: relativePath });
|
|
6862
|
-
return { relativePath, text, loaded,
|
|
6894
|
+
return { relativePath, text, loaded, schema: applicationSchemaOf(loaded) };
|
|
6863
6895
|
}
|
|
6864
6896
|
return void 0;
|
|
6865
6897
|
}
|
|
6866
|
-
function
|
|
6867
|
-
const states = /* @__PURE__ */ new Map();
|
|
6898
|
+
function applicationSchemaOf(loaded) {
|
|
6868
6899
|
const manifest = loaded.manifest;
|
|
6869
|
-
if (manifest === null || manifest.kind !== "application") return
|
|
6900
|
+
if (manifest === null || manifest.kind !== "application") return void 0;
|
|
6870
6901
|
const application = manifest.manifestExtensions?.["wcstack.application"];
|
|
6871
|
-
|
|
6872
|
-
|
|
6873
|
-
|
|
6874
|
-
states.set(name, schema);
|
|
6875
|
-
}
|
|
6902
|
+
const schema = application?.stateSchema;
|
|
6903
|
+
if (schema !== null && typeof schema === "object" && !Array.isArray(schema)) {
|
|
6904
|
+
return schema;
|
|
6876
6905
|
}
|
|
6877
|
-
return
|
|
6906
|
+
return void 0;
|
|
6878
6907
|
}
|
|
6879
6908
|
function joinRelativeSource(htmlSource, relativePath) {
|
|
6880
6909
|
const sepIndex = Math.max(htmlSource.lastIndexOf("/"), htmlSource.lastIndexOf("\\"));
|
|
@@ -6897,10 +6926,10 @@ function validateDocument(text, options = {}) {
|
|
|
6897
6926
|
const stateTagName = options.stateTagName ?? "wcs-state";
|
|
6898
6927
|
const locale3 = options.locale;
|
|
6899
6928
|
const fileReader = options.fileReader;
|
|
6900
|
-
const
|
|
6929
|
+
const applicationSchema = options.applicationSchema ?? (fileReader !== void 0 ? discoverApplicationManifest(fileReader)?.schema : void 0);
|
|
6901
6930
|
const out = [];
|
|
6902
|
-
out.push(...validateBindings(text, bindAttribute, stateTagName, locale3, fileReader,
|
|
6903
|
-
out.push(...validateTemplateSyntax(text, stateTagName, bindAttribute, locale3, fileReader,
|
|
6931
|
+
out.push(...validateBindings(text, bindAttribute, stateTagName, locale3, fileReader, applicationSchema));
|
|
6932
|
+
out.push(...validateTemplateSyntax(text, stateTagName, bindAttribute, locale3, fileReader, applicationSchema));
|
|
6904
6933
|
out.push(...validateIoNodes(text, bindAttribute, stateTagName, locale3, fileReader));
|
|
6905
6934
|
out.push(...validateAriaAttributes(text, bindAttribute, locale3));
|
|
6906
6935
|
out.push(...validateDocumentEnv(text, locale3));
|
|
@@ -6908,6 +6937,7 @@ function validateDocument(text, options = {}) {
|
|
|
6908
6937
|
out.push(...validateArrayMutations(text, stateTagName, locale3));
|
|
6909
6938
|
out.push(...validateWatchDeclarations(text, stateTagName, locale3));
|
|
6910
6939
|
out.push(...validateNamedState(text, bindAttribute, stateTagName, locale3));
|
|
6940
|
+
out.push(...validateMountAttributes(text, stateTagName, locale3));
|
|
6911
6941
|
for (const d of validateStateTypes(text, stateTagName, locale3)) {
|
|
6912
6942
|
out.push({ code: WcsDiagnosticCode.TypeAnnotation, start: d.start, end: d.end, message: d.message, severity: d.severity });
|
|
6913
6943
|
}
|
|
@@ -6985,11 +7015,10 @@ function validateLoadedSchemas(loaded) {
|
|
|
6985
7015
|
validateComponentSchemas(tag, component, loaded.ctx);
|
|
6986
7016
|
}
|
|
6987
7017
|
const application = loaded.manifest.manifestExtensions?.["wcstack.application"];
|
|
6988
|
-
|
|
6989
|
-
|
|
6990
|
-
|
|
6991
|
-
|
|
6992
|
-
validateSchemaSubset(schema, ptr, loaded.ctx, schema.$defs ?? {});
|
|
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 ?? {});
|
|
6993
7022
|
}
|
|
6994
7023
|
}
|
|
6995
7024
|
function validateComponentSchemas(tag, component, ctx) {
|
|
@@ -7041,7 +7070,7 @@ function validateManifestSet(input) {
|
|
|
7041
7070
|
diagnostics: sortDiagnostics(all),
|
|
7042
7071
|
byArtifact: sortedByArtifact,
|
|
7043
7072
|
resolvedTags,
|
|
7044
|
-
|
|
7073
|
+
resolvedSchema: resolved.applicationSchema,
|
|
7045
7074
|
hasApplicationArtifact: resolved.hasApplicationArtifact
|
|
7046
7075
|
};
|
|
7047
7076
|
}
|
|
@@ -7055,7 +7084,8 @@ function runValidation(inputs, options = {}) {
|
|
|
7055
7084
|
const diagnosticsBySource = /* @__PURE__ */ new Map();
|
|
7056
7085
|
const textBySource = new Map(inputs.map((i) => [i.source, i.text]));
|
|
7057
7086
|
const manifestInputs = inputs.filter((i) => i.kind === "manifest");
|
|
7058
|
-
let
|
|
7087
|
+
let explicitSchema;
|
|
7088
|
+
let haveExplicitApplication = false;
|
|
7059
7089
|
if (manifestInputs.length > 0) {
|
|
7060
7090
|
const result = validateManifestSet({
|
|
7061
7091
|
artifacts: manifestInputs.map((m) => ({ text: m.text, source: m.source })),
|
|
@@ -7064,14 +7094,17 @@ function runValidation(inputs, options = {}) {
|
|
|
7064
7094
|
for (const input of manifestInputs) {
|
|
7065
7095
|
diagnosticsBySource.set(input.source, result.byArtifact.get(input.source) ?? []);
|
|
7066
7096
|
}
|
|
7067
|
-
if (result.hasApplicationArtifact)
|
|
7097
|
+
if (result.hasApplicationArtifact) {
|
|
7098
|
+
haveExplicitApplication = true;
|
|
7099
|
+
explicitSchema = result.resolvedSchema;
|
|
7100
|
+
}
|
|
7068
7101
|
}
|
|
7069
7102
|
for (const input of inputs) {
|
|
7070
7103
|
if (input.kind !== "html") continue;
|
|
7071
|
-
let
|
|
7072
|
-
if (
|
|
7104
|
+
let applicationSchema = explicitSchema;
|
|
7105
|
+
if (!haveExplicitApplication && input.fileReader !== void 0) {
|
|
7073
7106
|
const discovered = discoverApplicationManifest(input.fileReader);
|
|
7074
|
-
|
|
7107
|
+
applicationSchema = discovered?.schema;
|
|
7075
7108
|
if (discovered !== void 0) {
|
|
7076
7109
|
const source = joinRelativeSource(input.source, discovered.relativePath);
|
|
7077
7110
|
if (!diagnosticsBySource.has(source)) {
|
|
@@ -7083,7 +7116,7 @@ function runValidation(inputs, options = {}) {
|
|
|
7083
7116
|
const docOptions = {
|
|
7084
7117
|
...options,
|
|
7085
7118
|
...input.fileReader !== void 0 ? { fileReader: input.fileReader } : {},
|
|
7086
|
-
...
|
|
7119
|
+
...applicationSchema !== void 0 ? { applicationSchema } : {}
|
|
7087
7120
|
};
|
|
7088
7121
|
diagnosticsBySource.set(input.source, validateDocument(input.text, docOptions));
|
|
7089
7122
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wcstack/lint",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"description": "Static-contract validator CLI (wcs-validate) for wcstack data-wcs bindings and wcstack.manifest.json sidecars. Thin npm wrapper around the wcstack-intellisense validator core.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|