@formspec/analysis 0.1.0-alpha.20 → 0.1.0-alpha.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/analysis.d.ts +69 -450
- package/dist/compiler-signatures.d.ts +48 -0
- package/dist/compiler-signatures.d.ts.map +1 -1
- package/dist/constants.d.ts +2 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/file-snapshots.d.ts +3 -0
- package/dist/file-snapshots.d.ts.map +1 -1
- package/dist/index.cjs +169 -2800
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +1 -19
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +168 -2761
- package/dist/index.js.map +1 -1
- package/dist/internal.cjs +3995 -0
- package/dist/internal.cjs.map +1 -0
- package/dist/internal.d.ts +21 -0
- package/dist/internal.d.ts.map +1 -0
- package/dist/internal.js +3902 -0
- package/dist/internal.js.map +1 -0
- package/dist/perf-tracing.d.ts +16 -0
- package/dist/perf-tracing.d.ts.map +1 -0
- package/dist/protocol.cjs +951 -0
- package/dist/protocol.cjs.map +1 -0
- package/dist/protocol.d.ts +4 -0
- package/dist/protocol.d.ts.map +1 -0
- package/dist/protocol.js +904 -0
- package/dist/protocol.js.map +1 -0
- package/dist/semantic-protocol.d.ts +49 -1
- package/dist/semantic-protocol.d.ts.map +1 -1
- package/dist/tag-registry.d.ts +2 -0
- package/dist/tag-registry.d.ts.map +1 -1
- package/dist/workspace-runtime.d.ts +6 -0
- package/dist/workspace-runtime.d.ts.map +1 -1
- package/package.json +12 -2
package/dist/internal.js
ADDED
|
@@ -0,0 +1,3902 @@
|
|
|
1
|
+
// src/path-target.ts
|
|
2
|
+
function extractPathTarget(text) {
|
|
3
|
+
const trimmed = text.trimStart();
|
|
4
|
+
const match = /^:([A-Za-z_]\w*(?:\.[A-Za-z_]\w*)*)(?:\s+([\s\S]*))?$/u.exec(trimmed);
|
|
5
|
+
if (!match?.[1]) {
|
|
6
|
+
return null;
|
|
7
|
+
}
|
|
8
|
+
return {
|
|
9
|
+
path: { segments: match[1].split(".") },
|
|
10
|
+
remainingText: match[2] ?? ""
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
function formatPathTarget(path2) {
|
|
14
|
+
if ("segments" in path2) {
|
|
15
|
+
return path2.segments.join(".");
|
|
16
|
+
}
|
|
17
|
+
return path2.join(".");
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// src/tag-registry.ts
|
|
21
|
+
import {
|
|
22
|
+
BUILTIN_CONSTRAINT_DEFINITIONS,
|
|
23
|
+
normalizeConstraintTagName
|
|
24
|
+
} from "@formspec/core";
|
|
25
|
+
var FORM_SPEC_PLACEMENTS = [
|
|
26
|
+
"class",
|
|
27
|
+
"class-field",
|
|
28
|
+
"class-method",
|
|
29
|
+
"interface",
|
|
30
|
+
"interface-field",
|
|
31
|
+
"type-alias",
|
|
32
|
+
"type-alias-field",
|
|
33
|
+
"variable",
|
|
34
|
+
"function",
|
|
35
|
+
"function-parameter",
|
|
36
|
+
"method-parameter"
|
|
37
|
+
];
|
|
38
|
+
var FORM_SPEC_TARGET_KINDS = [
|
|
39
|
+
"none",
|
|
40
|
+
"path",
|
|
41
|
+
"member",
|
|
42
|
+
"variant"
|
|
43
|
+
];
|
|
44
|
+
var FIELD_PLACEMENTS = [
|
|
45
|
+
"class-field",
|
|
46
|
+
"interface-field",
|
|
47
|
+
"type-alias-field",
|
|
48
|
+
"variable",
|
|
49
|
+
"function-parameter",
|
|
50
|
+
"method-parameter"
|
|
51
|
+
];
|
|
52
|
+
var TYPE_PLACEMENTS = [
|
|
53
|
+
"class",
|
|
54
|
+
"interface",
|
|
55
|
+
"type-alias"
|
|
56
|
+
];
|
|
57
|
+
var DECLARATION_PLACEMENTS = [
|
|
58
|
+
"class-method",
|
|
59
|
+
"function"
|
|
60
|
+
];
|
|
61
|
+
var ALL_PLACEMENTS = [
|
|
62
|
+
...TYPE_PLACEMENTS,
|
|
63
|
+
...FIELD_PLACEMENTS,
|
|
64
|
+
...DECLARATION_PLACEMENTS
|
|
65
|
+
];
|
|
66
|
+
var INTEGER_VALUE_TAGS = /* @__PURE__ */ new Set(["minLength", "maxLength", "minItems", "maxItems"]);
|
|
67
|
+
var SIGNED_INTEGER_VALUE_TAGS = /* @__PURE__ */ new Set(["order"]);
|
|
68
|
+
var JSON_VALUE_TAGS = /* @__PURE__ */ new Set(["const", "enumOptions"]);
|
|
69
|
+
var BOOLEAN_VALUE_TAGS = /* @__PURE__ */ new Set(["uniqueItems"]);
|
|
70
|
+
var STRING_VALUE_TAGS = /* @__PURE__ */ new Set([
|
|
71
|
+
"pattern",
|
|
72
|
+
"displayName",
|
|
73
|
+
"description",
|
|
74
|
+
"format",
|
|
75
|
+
"placeholder",
|
|
76
|
+
"group",
|
|
77
|
+
"example",
|
|
78
|
+
"remarks",
|
|
79
|
+
"see",
|
|
80
|
+
"apiName"
|
|
81
|
+
]);
|
|
82
|
+
var CONDITION_VALUE_TAGS = /* @__PURE__ */ new Set(["showWhen", "hideWhen", "enableWhen", "disableWhen"]);
|
|
83
|
+
var CONSTRAINT_COMPLETION_DETAIL = {
|
|
84
|
+
minimum: "Minimum numeric value (inclusive). Example: `@minimum 0`",
|
|
85
|
+
maximum: "Maximum numeric value (inclusive). Example: `@maximum 100`",
|
|
86
|
+
exclusiveMinimum: "Minimum numeric value (exclusive). Example: `@exclusiveMinimum 0`",
|
|
87
|
+
exclusiveMaximum: "Maximum numeric value (exclusive). Example: `@exclusiveMaximum 100`",
|
|
88
|
+
multipleOf: "Value must be a multiple of this number. Example: `@multipleOf 0.01`",
|
|
89
|
+
minLength: "Minimum string length. Example: `@minLength 1`",
|
|
90
|
+
maxLength: "Maximum string length. Example: `@maxLength 255`",
|
|
91
|
+
minItems: "Minimum number of array items. Example: `@minItems 1`",
|
|
92
|
+
maxItems: "Maximum number of array items. Example: `@maxItems 10`",
|
|
93
|
+
uniqueItems: "Require all array items to be distinct. Example: `@uniqueItems`",
|
|
94
|
+
pattern: "Regular expression pattern for string validation. Example: `@pattern ^[a-z]+$`",
|
|
95
|
+
enumOptions: 'Inline JSON array of allowed enum values. Example: `@enumOptions ["a","b","c"]`',
|
|
96
|
+
const: 'Require a constant JSON value. Example: `@const "USD"`'
|
|
97
|
+
};
|
|
98
|
+
var CONSTRAINT_HOVER_DOCS = {
|
|
99
|
+
minimum: [
|
|
100
|
+
"**@minimum** `<number>`",
|
|
101
|
+
"",
|
|
102
|
+
"Sets an inclusive lower bound on a numeric field.",
|
|
103
|
+
"",
|
|
104
|
+
"Maps to `minimum` in JSON Schema.",
|
|
105
|
+
"",
|
|
106
|
+
"**Signature:** `@minimum [:path] <number>`"
|
|
107
|
+
].join("\n"),
|
|
108
|
+
maximum: [
|
|
109
|
+
"**@maximum** `<number>`",
|
|
110
|
+
"",
|
|
111
|
+
"Sets an inclusive upper bound on a numeric field.",
|
|
112
|
+
"",
|
|
113
|
+
"Maps to `maximum` in JSON Schema.",
|
|
114
|
+
"",
|
|
115
|
+
"**Signature:** `@maximum [:path] <number>`"
|
|
116
|
+
].join("\n"),
|
|
117
|
+
exclusiveMinimum: [
|
|
118
|
+
"**@exclusiveMinimum** `<number>`",
|
|
119
|
+
"",
|
|
120
|
+
"Sets an exclusive lower bound on a numeric field.",
|
|
121
|
+
"",
|
|
122
|
+
"Maps to `exclusiveMinimum` in JSON Schema.",
|
|
123
|
+
"",
|
|
124
|
+
"**Signature:** `@exclusiveMinimum [:path] <number>`"
|
|
125
|
+
].join("\n"),
|
|
126
|
+
exclusiveMaximum: [
|
|
127
|
+
"**@exclusiveMaximum** `<number>`",
|
|
128
|
+
"",
|
|
129
|
+
"Sets an exclusive upper bound on a numeric field.",
|
|
130
|
+
"",
|
|
131
|
+
"Maps to `exclusiveMaximum` in JSON Schema.",
|
|
132
|
+
"",
|
|
133
|
+
"**Signature:** `@exclusiveMaximum [:path] <number>`"
|
|
134
|
+
].join("\n"),
|
|
135
|
+
multipleOf: [
|
|
136
|
+
"**@multipleOf** `<number>`",
|
|
137
|
+
"",
|
|
138
|
+
"Requires the numeric value to be a multiple of the given number.",
|
|
139
|
+
"",
|
|
140
|
+
"Maps to `multipleOf` in JSON Schema.",
|
|
141
|
+
"",
|
|
142
|
+
"**Signature:** `@multipleOf [:path] <number>`"
|
|
143
|
+
].join("\n"),
|
|
144
|
+
minLength: [
|
|
145
|
+
"**@minLength** `<integer>`",
|
|
146
|
+
"",
|
|
147
|
+
"Sets a minimum character length on a string field.",
|
|
148
|
+
"",
|
|
149
|
+
"Maps to `minLength` in JSON Schema.",
|
|
150
|
+
"",
|
|
151
|
+
"**Signature:** `@minLength [:path] <integer>`"
|
|
152
|
+
].join("\n"),
|
|
153
|
+
maxLength: [
|
|
154
|
+
"**@maxLength** `<integer>`",
|
|
155
|
+
"",
|
|
156
|
+
"Sets a maximum character length on a string field.",
|
|
157
|
+
"",
|
|
158
|
+
"Maps to `maxLength` in JSON Schema.",
|
|
159
|
+
"",
|
|
160
|
+
"**Signature:** `@maxLength [:path] <integer>`"
|
|
161
|
+
].join("\n"),
|
|
162
|
+
minItems: [
|
|
163
|
+
"**@minItems** `<integer>`",
|
|
164
|
+
"",
|
|
165
|
+
"Sets a minimum number of items in an array field.",
|
|
166
|
+
"",
|
|
167
|
+
"Maps to `minItems` in JSON Schema.",
|
|
168
|
+
"",
|
|
169
|
+
"**Signature:** `@minItems [:path] <integer>`"
|
|
170
|
+
].join("\n"),
|
|
171
|
+
maxItems: [
|
|
172
|
+
"**@maxItems** `<integer>`",
|
|
173
|
+
"",
|
|
174
|
+
"Sets a maximum number of items in an array field.",
|
|
175
|
+
"",
|
|
176
|
+
"Maps to `maxItems` in JSON Schema.",
|
|
177
|
+
"",
|
|
178
|
+
"**Signature:** `@maxItems [:path] <integer>`"
|
|
179
|
+
].join("\n"),
|
|
180
|
+
uniqueItems: [
|
|
181
|
+
"**@uniqueItems**",
|
|
182
|
+
"",
|
|
183
|
+
"Requires all items in an array field to be distinct.",
|
|
184
|
+
"",
|
|
185
|
+
"Maps to `uniqueItems` in JSON Schema.",
|
|
186
|
+
"",
|
|
187
|
+
"**Signature:** `@uniqueItems [:path]`"
|
|
188
|
+
].join("\n"),
|
|
189
|
+
pattern: [
|
|
190
|
+
"**@pattern** `<regex>`",
|
|
191
|
+
"",
|
|
192
|
+
"Sets a regular expression pattern that a string field must match.",
|
|
193
|
+
"",
|
|
194
|
+
"Maps to `pattern` in JSON Schema.",
|
|
195
|
+
"",
|
|
196
|
+
"**Signature:** `@pattern [:path] <regex>`"
|
|
197
|
+
].join("\n"),
|
|
198
|
+
enumOptions: [
|
|
199
|
+
"**@enumOptions** `<json-array>`",
|
|
200
|
+
"",
|
|
201
|
+
"Specifies the allowed values for an enum field as an inline JSON array.",
|
|
202
|
+
"",
|
|
203
|
+
"Maps to `enum` in JSON Schema.",
|
|
204
|
+
"",
|
|
205
|
+
"**Signature:** `@enumOptions <json-array>`"
|
|
206
|
+
].join("\n"),
|
|
207
|
+
const: [
|
|
208
|
+
"**@const** `<json-literal>`",
|
|
209
|
+
"",
|
|
210
|
+
"Requires the field value to equal a single constant JSON value.",
|
|
211
|
+
"",
|
|
212
|
+
"Maps to `const` in JSON Schema.",
|
|
213
|
+
"",
|
|
214
|
+
"**Signature:** `@const [:path] <json-literal>`"
|
|
215
|
+
].join("\n")
|
|
216
|
+
};
|
|
217
|
+
function inferValueKind(name) {
|
|
218
|
+
if (INTEGER_VALUE_TAGS.has(name)) return "integer";
|
|
219
|
+
if (SIGNED_INTEGER_VALUE_TAGS.has(name)) return "signedInteger";
|
|
220
|
+
if (JSON_VALUE_TAGS.has(name)) return "json";
|
|
221
|
+
if (BOOLEAN_VALUE_TAGS.has(name)) return "boolean";
|
|
222
|
+
if (STRING_VALUE_TAGS.has(name)) return "string";
|
|
223
|
+
if (CONDITION_VALUE_TAGS.has(name)) return "condition";
|
|
224
|
+
return null;
|
|
225
|
+
}
|
|
226
|
+
function getBuiltinValueKind(name) {
|
|
227
|
+
return inferValueKind(name) ?? "number";
|
|
228
|
+
}
|
|
229
|
+
function getBuiltinConstraintCapability(name) {
|
|
230
|
+
switch (name) {
|
|
231
|
+
case "minimum":
|
|
232
|
+
case "maximum":
|
|
233
|
+
case "exclusiveMinimum":
|
|
234
|
+
case "exclusiveMaximum":
|
|
235
|
+
case "multipleOf":
|
|
236
|
+
return "numeric-comparable";
|
|
237
|
+
case "minLength":
|
|
238
|
+
case "maxLength":
|
|
239
|
+
case "pattern":
|
|
240
|
+
return "string-like";
|
|
241
|
+
case "minItems":
|
|
242
|
+
case "maxItems":
|
|
243
|
+
case "uniqueItems":
|
|
244
|
+
return "array-like";
|
|
245
|
+
case "enumOptions":
|
|
246
|
+
return "enum-member-addressable";
|
|
247
|
+
case "const":
|
|
248
|
+
return "json-like";
|
|
249
|
+
default: {
|
|
250
|
+
const exhaustive = name;
|
|
251
|
+
return exhaustive;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
function capabilitiesForValueKind(valueKind) {
|
|
256
|
+
switch (valueKind) {
|
|
257
|
+
case "number":
|
|
258
|
+
case "integer":
|
|
259
|
+
case "signedInteger":
|
|
260
|
+
return ["numeric-comparable"];
|
|
261
|
+
case "string":
|
|
262
|
+
return ["string-like"];
|
|
263
|
+
case "json":
|
|
264
|
+
return ["json-like"];
|
|
265
|
+
case "condition":
|
|
266
|
+
return ["condition-like"];
|
|
267
|
+
case "boolean":
|
|
268
|
+
case null:
|
|
269
|
+
return [];
|
|
270
|
+
default: {
|
|
271
|
+
const exhaustive = valueKind;
|
|
272
|
+
return exhaustive;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
function valueLabelForKind(valueKind, fallback = "<value>") {
|
|
277
|
+
switch (valueKind) {
|
|
278
|
+
case "number":
|
|
279
|
+
return "<number>";
|
|
280
|
+
case "integer":
|
|
281
|
+
case "signedInteger":
|
|
282
|
+
return "<integer>";
|
|
283
|
+
case "string":
|
|
284
|
+
return "<text>";
|
|
285
|
+
case "json":
|
|
286
|
+
return "<json>";
|
|
287
|
+
case "condition":
|
|
288
|
+
return "<condition>";
|
|
289
|
+
case "boolean":
|
|
290
|
+
case null:
|
|
291
|
+
return "";
|
|
292
|
+
default: {
|
|
293
|
+
const exhaustive = valueKind;
|
|
294
|
+
void exhaustive;
|
|
295
|
+
return fallback;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
function targetLabelForKind(kind) {
|
|
300
|
+
switch (kind) {
|
|
301
|
+
case "path":
|
|
302
|
+
return "[:path]";
|
|
303
|
+
case "member":
|
|
304
|
+
return ":member";
|
|
305
|
+
case "variant":
|
|
306
|
+
return ":variant";
|
|
307
|
+
default: {
|
|
308
|
+
const exhaustive = kind;
|
|
309
|
+
return exhaustive;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
function parameterKindForTarget(targetKind) {
|
|
314
|
+
switch (targetKind) {
|
|
315
|
+
case "path":
|
|
316
|
+
return "target-path";
|
|
317
|
+
case "member":
|
|
318
|
+
return "target-member";
|
|
319
|
+
case "variant":
|
|
320
|
+
return "target-variant";
|
|
321
|
+
default: {
|
|
322
|
+
const exhaustive = targetKind;
|
|
323
|
+
return exhaustive;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
function createTargetParameter(targetKind, valueKind, pathCapability) {
|
|
328
|
+
const base = {
|
|
329
|
+
kind: parameterKindForTarget(targetKind),
|
|
330
|
+
label: targetLabelForKind(targetKind),
|
|
331
|
+
optional: targetKind === "path"
|
|
332
|
+
};
|
|
333
|
+
if (targetKind === "path") {
|
|
334
|
+
const capability = pathCapability ?? capabilitiesForValueKind(valueKind)[0];
|
|
335
|
+
return capability === void 0 ? base : { ...base, capability };
|
|
336
|
+
}
|
|
337
|
+
if (targetKind === "member") {
|
|
338
|
+
return { ...base, capability: "enum-member-addressable" };
|
|
339
|
+
}
|
|
340
|
+
return base;
|
|
341
|
+
}
|
|
342
|
+
function createSignature(name, placements, targetKind, valueKind, valueLabel, pathCapability) {
|
|
343
|
+
const parameters = [];
|
|
344
|
+
if (targetKind !== null) {
|
|
345
|
+
parameters.push(createTargetParameter(targetKind, valueKind, pathCapability));
|
|
346
|
+
}
|
|
347
|
+
if (valueLabel !== "") {
|
|
348
|
+
parameters.push(
|
|
349
|
+
valueKind === null ? {
|
|
350
|
+
kind: "value",
|
|
351
|
+
label: valueLabel
|
|
352
|
+
} : {
|
|
353
|
+
kind: "value",
|
|
354
|
+
label: valueLabel,
|
|
355
|
+
valueKind
|
|
356
|
+
}
|
|
357
|
+
);
|
|
358
|
+
}
|
|
359
|
+
const targetLabel = targetKind === null ? "" : ` ${targetLabelForKind(targetKind)}`;
|
|
360
|
+
const valueLabelSuffix = valueLabel === "" ? "" : ` ${valueLabel}`;
|
|
361
|
+
return {
|
|
362
|
+
label: `@${name}${targetLabel}${valueLabelSuffix}`,
|
|
363
|
+
placements,
|
|
364
|
+
parameters
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
function buildHoverMarkdown(name, hoverSummary, signatures, valueLabel) {
|
|
368
|
+
const header = valueLabel === "" ? `**@${name}**` : `**@${name}** \`${valueLabel}\``;
|
|
369
|
+
const signatureLines = signatures.length === 1 ? [`**Signature:** \`${signatures[0]?.label ?? `@${name}`}\``] : ["**Signatures:**", ...signatures.map((signature) => `- \`${signature.label}\``)];
|
|
370
|
+
return [header, "", hoverSummary, "", ...signatureLines].join("\n");
|
|
371
|
+
}
|
|
372
|
+
function makeConstraintSignatures(name) {
|
|
373
|
+
const valueKind = getBuiltinValueKind(name);
|
|
374
|
+
const subjectCapability = getBuiltinConstraintCapability(name);
|
|
375
|
+
const valueLabel = name === "pattern" ? "<regex>" : name === "enumOptions" ? "<json-array>" : name === "const" ? "<json-literal>" : valueLabelForKind(valueKind);
|
|
376
|
+
return [
|
|
377
|
+
createSignature(name, FIELD_PLACEMENTS, null, valueKind, valueLabel),
|
|
378
|
+
createSignature(name, FIELD_PLACEMENTS, "path", valueKind, valueLabel, subjectCapability)
|
|
379
|
+
];
|
|
380
|
+
}
|
|
381
|
+
var BUILTIN_TAG_DEFINITIONS = Object.fromEntries(
|
|
382
|
+
Object.keys(BUILTIN_CONSTRAINT_DEFINITIONS).map((name) => {
|
|
383
|
+
const valueKind = getBuiltinValueKind(name);
|
|
384
|
+
const subjectCapability = getBuiltinConstraintCapability(name);
|
|
385
|
+
return [
|
|
386
|
+
name,
|
|
387
|
+
{
|
|
388
|
+
canonicalName: name,
|
|
389
|
+
valueKind,
|
|
390
|
+
requiresArgument: valueKind !== "boolean",
|
|
391
|
+
supportedTargets: ["none", "path"],
|
|
392
|
+
allowDuplicates: false,
|
|
393
|
+
category: "constraint",
|
|
394
|
+
placements: FIELD_PLACEMENTS,
|
|
395
|
+
capabilities: [subjectCapability],
|
|
396
|
+
completionDetail: CONSTRAINT_COMPLETION_DETAIL[name] ?? `@${name}`,
|
|
397
|
+
hoverMarkdown: CONSTRAINT_HOVER_DOCS[name] ?? `**@${name}**`,
|
|
398
|
+
signatures: makeConstraintSignatures(name)
|
|
399
|
+
}
|
|
400
|
+
];
|
|
401
|
+
})
|
|
402
|
+
);
|
|
403
|
+
var EXTRA_TAG_SPECS = {
|
|
404
|
+
displayName: {
|
|
405
|
+
requiresArgument: true,
|
|
406
|
+
supportedTargets: ["none", "member", "variant"],
|
|
407
|
+
allowDuplicates: false,
|
|
408
|
+
category: "annotation",
|
|
409
|
+
placements: [...TYPE_PLACEMENTS, ...FIELD_PLACEMENTS],
|
|
410
|
+
completionDetail: "Display label for a type, field, or enum member.",
|
|
411
|
+
hoverSummary: "Provides a user-facing display label.",
|
|
412
|
+
valueLabel: "<label>",
|
|
413
|
+
targetPlacements: {
|
|
414
|
+
member: FIELD_PLACEMENTS,
|
|
415
|
+
variant: TYPE_PLACEMENTS
|
|
416
|
+
}
|
|
417
|
+
},
|
|
418
|
+
description: {
|
|
419
|
+
requiresArgument: true,
|
|
420
|
+
supportedTargets: ["none"],
|
|
421
|
+
allowDuplicates: false,
|
|
422
|
+
category: "annotation",
|
|
423
|
+
placements: [...TYPE_PLACEMENTS, ...FIELD_PLACEMENTS],
|
|
424
|
+
completionDetail: "Description text for a type or field.",
|
|
425
|
+
hoverSummary: "Provides descriptive documentation for a type or field."
|
|
426
|
+
},
|
|
427
|
+
format: {
|
|
428
|
+
requiresArgument: true,
|
|
429
|
+
supportedTargets: ["none"],
|
|
430
|
+
allowDuplicates: false,
|
|
431
|
+
category: "annotation",
|
|
432
|
+
placements: FIELD_PLACEMENTS,
|
|
433
|
+
completionDetail: "Format hint for a field.",
|
|
434
|
+
hoverSummary: "Provides a format hint for a field.",
|
|
435
|
+
valueLabel: "<format>"
|
|
436
|
+
},
|
|
437
|
+
placeholder: {
|
|
438
|
+
requiresArgument: true,
|
|
439
|
+
supportedTargets: ["none"],
|
|
440
|
+
allowDuplicates: false,
|
|
441
|
+
category: "annotation",
|
|
442
|
+
placements: FIELD_PLACEMENTS,
|
|
443
|
+
completionDetail: "Placeholder text for a field.",
|
|
444
|
+
hoverSummary: "Provides placeholder text for a field."
|
|
445
|
+
},
|
|
446
|
+
order: {
|
|
447
|
+
requiresArgument: true,
|
|
448
|
+
supportedTargets: ["none"],
|
|
449
|
+
allowDuplicates: false,
|
|
450
|
+
category: "annotation",
|
|
451
|
+
placements: FIELD_PLACEMENTS,
|
|
452
|
+
completionDetail: "Field display order hint.",
|
|
453
|
+
hoverSummary: "Provides an integer ordering hint for UI layout."
|
|
454
|
+
},
|
|
455
|
+
apiName: {
|
|
456
|
+
requiresArgument: true,
|
|
457
|
+
supportedTargets: ["none", "member", "variant"],
|
|
458
|
+
allowDuplicates: false,
|
|
459
|
+
category: "annotation",
|
|
460
|
+
placements: [...TYPE_PLACEMENTS, ...FIELD_PLACEMENTS],
|
|
461
|
+
completionDetail: "API-facing serialized name for a type, field, or variant.",
|
|
462
|
+
hoverSummary: "Overrides the serialized API name used in generated schema output.",
|
|
463
|
+
valueLabel: "<identifier>",
|
|
464
|
+
targetPlacements: {
|
|
465
|
+
member: FIELD_PLACEMENTS,
|
|
466
|
+
variant: TYPE_PLACEMENTS
|
|
467
|
+
}
|
|
468
|
+
},
|
|
469
|
+
group: {
|
|
470
|
+
requiresArgument: true,
|
|
471
|
+
supportedTargets: ["none"],
|
|
472
|
+
allowDuplicates: false,
|
|
473
|
+
category: "structure",
|
|
474
|
+
placements: FIELD_PLACEMENTS,
|
|
475
|
+
completionDetail: "Assigns a field to a UI group.",
|
|
476
|
+
hoverSummary: "Assigns the field to a named grouping container.",
|
|
477
|
+
valueLabel: "<group>"
|
|
478
|
+
},
|
|
479
|
+
showWhen: {
|
|
480
|
+
requiresArgument: true,
|
|
481
|
+
supportedTargets: ["none"],
|
|
482
|
+
allowDuplicates: true,
|
|
483
|
+
category: "structure",
|
|
484
|
+
placements: FIELD_PLACEMENTS,
|
|
485
|
+
completionDetail: "Conditional visibility rule.",
|
|
486
|
+
hoverSummary: "Shows the field only when the condition is satisfied."
|
|
487
|
+
},
|
|
488
|
+
hideWhen: {
|
|
489
|
+
requiresArgument: true,
|
|
490
|
+
supportedTargets: ["none"],
|
|
491
|
+
allowDuplicates: true,
|
|
492
|
+
category: "structure",
|
|
493
|
+
placements: FIELD_PLACEMENTS,
|
|
494
|
+
completionDetail: "Conditional visibility suppression rule.",
|
|
495
|
+
hoverSummary: "Hides the field when the condition is satisfied."
|
|
496
|
+
},
|
|
497
|
+
enableWhen: {
|
|
498
|
+
requiresArgument: true,
|
|
499
|
+
supportedTargets: ["none"],
|
|
500
|
+
allowDuplicates: true,
|
|
501
|
+
category: "structure",
|
|
502
|
+
placements: FIELD_PLACEMENTS,
|
|
503
|
+
completionDetail: "Conditional interactivity rule.",
|
|
504
|
+
hoverSummary: "Enables the field only when the condition is satisfied."
|
|
505
|
+
},
|
|
506
|
+
disableWhen: {
|
|
507
|
+
requiresArgument: true,
|
|
508
|
+
supportedTargets: ["none"],
|
|
509
|
+
allowDuplicates: true,
|
|
510
|
+
category: "structure",
|
|
511
|
+
placements: FIELD_PLACEMENTS,
|
|
512
|
+
completionDetail: "Conditional disablement rule.",
|
|
513
|
+
hoverSummary: "Disables the field when the condition is satisfied."
|
|
514
|
+
},
|
|
515
|
+
defaultValue: {
|
|
516
|
+
requiresArgument: true,
|
|
517
|
+
supportedTargets: ["none"],
|
|
518
|
+
allowDuplicates: false,
|
|
519
|
+
category: "ecosystem",
|
|
520
|
+
placements: FIELD_PLACEMENTS,
|
|
521
|
+
completionDetail: "Default JSON value for a field.",
|
|
522
|
+
hoverSummary: "Provides a default JSON value for ecosystem integrations.",
|
|
523
|
+
valueLabel: "<value>"
|
|
524
|
+
},
|
|
525
|
+
deprecated: {
|
|
526
|
+
requiresArgument: false,
|
|
527
|
+
supportedTargets: ["none"],
|
|
528
|
+
allowDuplicates: false,
|
|
529
|
+
category: "ecosystem",
|
|
530
|
+
placements: ALL_PLACEMENTS,
|
|
531
|
+
completionDetail: "Marks a declaration as deprecated.",
|
|
532
|
+
hoverSummary: "Marks the declaration as deprecated."
|
|
533
|
+
},
|
|
534
|
+
example: {
|
|
535
|
+
requiresArgument: true,
|
|
536
|
+
supportedTargets: ["none"],
|
|
537
|
+
allowDuplicates: true,
|
|
538
|
+
category: "ecosystem",
|
|
539
|
+
placements: [...TYPE_PLACEMENTS, ...FIELD_PLACEMENTS],
|
|
540
|
+
completionDetail: "Example serialized value.",
|
|
541
|
+
hoverSummary: "Provides an example value for documentation and tooling."
|
|
542
|
+
},
|
|
543
|
+
remarks: {
|
|
544
|
+
requiresArgument: true,
|
|
545
|
+
supportedTargets: ["none"],
|
|
546
|
+
allowDuplicates: false,
|
|
547
|
+
category: "ecosystem",
|
|
548
|
+
placements: ALL_PLACEMENTS,
|
|
549
|
+
completionDetail: "Additional remarks text.",
|
|
550
|
+
hoverSummary: "Provides additional remarks for the declaration."
|
|
551
|
+
},
|
|
552
|
+
see: {
|
|
553
|
+
requiresArgument: true,
|
|
554
|
+
supportedTargets: ["none"],
|
|
555
|
+
allowDuplicates: true,
|
|
556
|
+
category: "ecosystem",
|
|
557
|
+
placements: ALL_PLACEMENTS,
|
|
558
|
+
completionDetail: "Reference to related documentation.",
|
|
559
|
+
hoverSummary: "References related documentation or declarations.",
|
|
560
|
+
valueLabel: "<reference>"
|
|
561
|
+
}
|
|
562
|
+
};
|
|
563
|
+
function buildExtraTagDefinition(canonicalName, spec) {
|
|
564
|
+
const valueKind = spec.valueKind ?? inferValueKind(canonicalName);
|
|
565
|
+
const valueLabel = spec.requiresArgument ? spec.valueLabel ?? valueLabelForKind(valueKind) : "";
|
|
566
|
+
const signatures = [];
|
|
567
|
+
if (spec.supportedTargets.includes("none")) {
|
|
568
|
+
signatures.push(createSignature(canonicalName, spec.placements, null, valueKind, valueLabel));
|
|
569
|
+
}
|
|
570
|
+
if (spec.supportedTargets.includes("path")) {
|
|
571
|
+
signatures.push(
|
|
572
|
+
createSignature(
|
|
573
|
+
canonicalName,
|
|
574
|
+
spec.targetPlacements?.path ?? spec.placements,
|
|
575
|
+
"path",
|
|
576
|
+
valueKind,
|
|
577
|
+
valueLabel
|
|
578
|
+
)
|
|
579
|
+
);
|
|
580
|
+
}
|
|
581
|
+
if (spec.supportedTargets.includes("member")) {
|
|
582
|
+
signatures.push(
|
|
583
|
+
createSignature(
|
|
584
|
+
canonicalName,
|
|
585
|
+
spec.targetPlacements?.member ?? spec.placements,
|
|
586
|
+
"member",
|
|
587
|
+
valueKind,
|
|
588
|
+
valueLabel
|
|
589
|
+
)
|
|
590
|
+
);
|
|
591
|
+
}
|
|
592
|
+
if (spec.supportedTargets.includes("variant")) {
|
|
593
|
+
signatures.push(
|
|
594
|
+
createSignature(
|
|
595
|
+
canonicalName,
|
|
596
|
+
spec.targetPlacements?.variant ?? spec.placements,
|
|
597
|
+
"variant",
|
|
598
|
+
valueKind,
|
|
599
|
+
valueLabel
|
|
600
|
+
)
|
|
601
|
+
);
|
|
602
|
+
}
|
|
603
|
+
return {
|
|
604
|
+
canonicalName,
|
|
605
|
+
valueKind,
|
|
606
|
+
requiresArgument: spec.requiresArgument,
|
|
607
|
+
supportedTargets: spec.supportedTargets,
|
|
608
|
+
allowDuplicates: spec.allowDuplicates,
|
|
609
|
+
category: spec.category,
|
|
610
|
+
placements: spec.placements,
|
|
611
|
+
capabilities: capabilitiesForValueKind(valueKind),
|
|
612
|
+
completionDetail: spec.completionDetail,
|
|
613
|
+
hoverMarkdown: buildHoverMarkdown(canonicalName, spec.hoverSummary, signatures, valueLabel),
|
|
614
|
+
signatures
|
|
615
|
+
};
|
|
616
|
+
}
|
|
617
|
+
var EXTRA_TAG_DEFINITIONS = Object.fromEntries(
|
|
618
|
+
Object.entries(EXTRA_TAG_SPECS).map(([canonicalName, spec]) => [
|
|
619
|
+
canonicalName,
|
|
620
|
+
buildExtraTagDefinition(canonicalName, spec)
|
|
621
|
+
])
|
|
622
|
+
);
|
|
623
|
+
function normalizeFormSpecTagName(rawName) {
|
|
624
|
+
return normalizeConstraintTagName(rawName);
|
|
625
|
+
}
|
|
626
|
+
function getTagDefinition(rawName, extensions) {
|
|
627
|
+
const normalized = normalizeFormSpecTagName(rawName);
|
|
628
|
+
const builtin = BUILTIN_TAG_DEFINITIONS[normalized];
|
|
629
|
+
if (builtin !== void 0) {
|
|
630
|
+
return builtin;
|
|
631
|
+
}
|
|
632
|
+
const extra = EXTRA_TAG_DEFINITIONS[normalized];
|
|
633
|
+
if (extra !== void 0) {
|
|
634
|
+
return extra;
|
|
635
|
+
}
|
|
636
|
+
const extensionRegistration = getExtensionConstraintTags(extensions).find(
|
|
637
|
+
(tag) => tag.tagName === normalized
|
|
638
|
+
);
|
|
639
|
+
if (extensionRegistration === void 0) {
|
|
640
|
+
return null;
|
|
641
|
+
}
|
|
642
|
+
return {
|
|
643
|
+
canonicalName: extensionRegistration.tagName,
|
|
644
|
+
valueKind: null,
|
|
645
|
+
requiresArgument: true,
|
|
646
|
+
supportedTargets: ["none"],
|
|
647
|
+
allowDuplicates: true,
|
|
648
|
+
category: "constraint",
|
|
649
|
+
placements: FIELD_PLACEMENTS,
|
|
650
|
+
capabilities: [],
|
|
651
|
+
completionDetail: `Extension constraint tag from ${extensionRegistration.extensionId}`,
|
|
652
|
+
hoverMarkdown: [
|
|
653
|
+
`**@${extensionRegistration.tagName}** \`<value>\``,
|
|
654
|
+
"",
|
|
655
|
+
`Extension-defined constraint tag from \`${extensionRegistration.extensionId}\`.`,
|
|
656
|
+
"",
|
|
657
|
+
`**Signature:** \`@${extensionRegistration.tagName} <value>\``
|
|
658
|
+
].join("\n"),
|
|
659
|
+
signatures: [
|
|
660
|
+
{
|
|
661
|
+
label: `@${extensionRegistration.tagName} <value>`,
|
|
662
|
+
placements: FIELD_PLACEMENTS,
|
|
663
|
+
parameters: [{ kind: "value", label: "<value>" }]
|
|
664
|
+
}
|
|
665
|
+
]
|
|
666
|
+
};
|
|
667
|
+
}
|
|
668
|
+
function getConstraintTagDefinitions(extensions) {
|
|
669
|
+
const builtins = Object.values(BUILTIN_TAG_DEFINITIONS);
|
|
670
|
+
const custom = getExtensionConstraintTags(extensions).map((tag) => getTagDefinition(tag.tagName, extensions)).filter((tag) => tag !== null);
|
|
671
|
+
return [...builtins, ...custom];
|
|
672
|
+
}
|
|
673
|
+
function getAllTagDefinitions(extensions) {
|
|
674
|
+
const builtins = Object.values(BUILTIN_TAG_DEFINITIONS);
|
|
675
|
+
const extras = Object.values(EXTRA_TAG_DEFINITIONS);
|
|
676
|
+
const custom = getExtensionConstraintTags(extensions).map((tag) => getTagDefinition(tag.tagName, extensions)).filter((tag) => tag !== null);
|
|
677
|
+
return [...builtins, ...extras, ...custom];
|
|
678
|
+
}
|
|
679
|
+
function getTagHoverMarkdown(rawName, extensions) {
|
|
680
|
+
return getTagDefinition(rawName, extensions)?.hoverMarkdown ?? null;
|
|
681
|
+
}
|
|
682
|
+
function getExtensionConstraintTags(extensions) {
|
|
683
|
+
return extensions?.flatMap((extension) => {
|
|
684
|
+
const tagRecords = extension.constraintTags ?? [];
|
|
685
|
+
return tagRecords.map((tag) => ({
|
|
686
|
+
extensionId: extension.extensionId,
|
|
687
|
+
tagName: tag.tagName
|
|
688
|
+
}));
|
|
689
|
+
}) ?? [];
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
// src/comment-syntax.ts
|
|
693
|
+
function isWhitespace(char) {
|
|
694
|
+
return char === " " || char === " " || char === "\n" || char === "\r";
|
|
695
|
+
}
|
|
696
|
+
function isTagStart(lineText, index) {
|
|
697
|
+
if (lineText[index] !== "@") {
|
|
698
|
+
return false;
|
|
699
|
+
}
|
|
700
|
+
const nextChar = lineText[index + 1];
|
|
701
|
+
if (nextChar === void 0 || !/[A-Za-z]/u.test(nextChar)) {
|
|
702
|
+
return false;
|
|
703
|
+
}
|
|
704
|
+
const previousChar = lineText[index - 1];
|
|
705
|
+
return previousChar === void 0 || isWhitespace(previousChar);
|
|
706
|
+
}
|
|
707
|
+
function findTagEnd(lineText, index) {
|
|
708
|
+
let cursor = index + 1;
|
|
709
|
+
while (cursor < lineText.length && /[A-Za-z0-9]/u.test(lineText[cursor] ?? "")) {
|
|
710
|
+
cursor += 1;
|
|
711
|
+
}
|
|
712
|
+
return cursor;
|
|
713
|
+
}
|
|
714
|
+
function trimTrailingWhitespace(lineText, end) {
|
|
715
|
+
let cursor = end;
|
|
716
|
+
while (cursor > 0 && isWhitespace(lineText[cursor - 1])) {
|
|
717
|
+
cursor -= 1;
|
|
718
|
+
}
|
|
719
|
+
return cursor;
|
|
720
|
+
}
|
|
721
|
+
function spanFromLine(line, start, end, baseOffset) {
|
|
722
|
+
const rawStart = line.rawOffsets[start];
|
|
723
|
+
if (rawStart === void 0) {
|
|
724
|
+
throw new Error(`Invalid projected span start: ${String(start)}`);
|
|
725
|
+
}
|
|
726
|
+
const rawEnd = end >= line.text.length ? line.rawContentEnd : (line.rawOffsets[end - 1] ?? line.rawContentEnd - 1) + 1;
|
|
727
|
+
return {
|
|
728
|
+
start: baseOffset + rawStart,
|
|
729
|
+
end: baseOffset + rawEnd
|
|
730
|
+
};
|
|
731
|
+
}
|
|
732
|
+
function classifyTargetKind(canonicalName, targetText, extensions) {
|
|
733
|
+
if (targetText === "singular" || targetText === "plural") {
|
|
734
|
+
return "variant";
|
|
735
|
+
}
|
|
736
|
+
if (targetText.includes(".")) {
|
|
737
|
+
return "path";
|
|
738
|
+
}
|
|
739
|
+
const definition = getTagDefinition(canonicalName, extensions);
|
|
740
|
+
const supportedTargets = definition?.supportedTargets.filter((target) => target !== "none") ?? [];
|
|
741
|
+
if (supportedTargets.includes("path")) {
|
|
742
|
+
return "path";
|
|
743
|
+
}
|
|
744
|
+
if (supportedTargets.includes("member") && supportedTargets.includes("variant")) {
|
|
745
|
+
return "ambiguous";
|
|
746
|
+
}
|
|
747
|
+
if (supportedTargets.includes("member")) {
|
|
748
|
+
return "member";
|
|
749
|
+
}
|
|
750
|
+
if (supportedTargets.includes("variant")) {
|
|
751
|
+
return "variant";
|
|
752
|
+
}
|
|
753
|
+
return "path";
|
|
754
|
+
}
|
|
755
|
+
function parseTargetSpecifier(line, payloadStart, payloadEnd, canonicalName, baseOffset, extensions) {
|
|
756
|
+
if (payloadStart >= payloadEnd || line.text[payloadStart] !== ":") {
|
|
757
|
+
return null;
|
|
758
|
+
}
|
|
759
|
+
let targetEnd = payloadStart + 1;
|
|
760
|
+
while (targetEnd < payloadEnd && !isWhitespace(line.text[targetEnd])) {
|
|
761
|
+
targetEnd += 1;
|
|
762
|
+
}
|
|
763
|
+
const fullText = line.text.slice(payloadStart, targetEnd);
|
|
764
|
+
const targetText = fullText.slice(1);
|
|
765
|
+
const parsedPath = extractPathTarget(fullText);
|
|
766
|
+
const specifierSpan = spanFromLine(line, payloadStart + 1, targetEnd, baseOffset);
|
|
767
|
+
return {
|
|
768
|
+
rawText: targetText,
|
|
769
|
+
valid: parsedPath !== null && parsedPath.remainingText === "",
|
|
770
|
+
kind: classifyTargetKind(canonicalName, targetText, extensions),
|
|
771
|
+
fullSpan: spanFromLine(line, payloadStart, targetEnd, baseOffset),
|
|
772
|
+
colonSpan: spanFromLine(line, payloadStart, payloadStart + 1, baseOffset),
|
|
773
|
+
span: specifierSpan,
|
|
774
|
+
path: parsedPath?.path ?? null,
|
|
775
|
+
localEnd: targetEnd
|
|
776
|
+
};
|
|
777
|
+
}
|
|
778
|
+
function projectCommentLines(commentText) {
|
|
779
|
+
const projections = [];
|
|
780
|
+
const commentBodyStart = commentText.startsWith("/**") ? 3 : commentText.startsWith("/*") ? 2 : 0;
|
|
781
|
+
const commentBodyEnd = commentText.endsWith("*/") ? commentText.length - 2 : commentText.length;
|
|
782
|
+
let cursor = commentBodyStart;
|
|
783
|
+
while (cursor <= commentBodyEnd) {
|
|
784
|
+
const lineStart = cursor;
|
|
785
|
+
let lineEnd = cursor;
|
|
786
|
+
while (lineEnd < commentBodyEnd && commentText[lineEnd] !== "\n") {
|
|
787
|
+
lineEnd += 1;
|
|
788
|
+
}
|
|
789
|
+
let contentEnd = lineEnd;
|
|
790
|
+
if (contentEnd > lineStart && commentText[contentEnd - 1] === "\r") {
|
|
791
|
+
contentEnd -= 1;
|
|
792
|
+
}
|
|
793
|
+
let contentStart = lineStart;
|
|
794
|
+
while (contentStart < contentEnd && (commentText[contentStart] === " " || commentText[contentStart] === " ")) {
|
|
795
|
+
contentStart += 1;
|
|
796
|
+
}
|
|
797
|
+
if (contentStart < contentEnd && commentText[contentStart] === "*") {
|
|
798
|
+
contentStart += 1;
|
|
799
|
+
while (contentStart < contentEnd && (commentText[contentStart] === " " || commentText[contentStart] === " ")) {
|
|
800
|
+
contentStart += 1;
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
const rawOffsets = [];
|
|
804
|
+
let text = "";
|
|
805
|
+
for (let index = contentStart; index < contentEnd; index += 1) {
|
|
806
|
+
rawOffsets.push(index);
|
|
807
|
+
text += commentText[index] ?? "";
|
|
808
|
+
}
|
|
809
|
+
projections.push({
|
|
810
|
+
text,
|
|
811
|
+
rawOffsets,
|
|
812
|
+
rawContentEnd: contentEnd
|
|
813
|
+
});
|
|
814
|
+
if (lineEnd >= commentBodyEnd) {
|
|
815
|
+
break;
|
|
816
|
+
}
|
|
817
|
+
cursor = lineEnd + 1;
|
|
818
|
+
}
|
|
819
|
+
return projections;
|
|
820
|
+
}
|
|
821
|
+
function parseCommentBlock(commentText, options) {
|
|
822
|
+
const tags = [];
|
|
823
|
+
const baseOffset = options?.offset ?? 0;
|
|
824
|
+
for (const line of projectCommentLines(commentText)) {
|
|
825
|
+
const tagStarts = [];
|
|
826
|
+
for (let index = 0; index < line.text.length; index += 1) {
|
|
827
|
+
if (isTagStart(line.text, index)) {
|
|
828
|
+
tagStarts.push(index);
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
for (let tagIndex = 0; tagIndex < tagStarts.length; tagIndex += 1) {
|
|
832
|
+
const tagStart = tagStarts[tagIndex];
|
|
833
|
+
if (tagStart === void 0) {
|
|
834
|
+
continue;
|
|
835
|
+
}
|
|
836
|
+
const tagEnd = findTagEnd(line.text, tagStart);
|
|
837
|
+
const nextTagStart = tagStarts[tagIndex + 1] ?? line.text.length;
|
|
838
|
+
const trimmedTagSegmentEnd = trimTrailingWhitespace(line.text, nextTagStart);
|
|
839
|
+
const rawName = line.text.slice(tagStart + 1, tagEnd);
|
|
840
|
+
const canonicalName = normalizeFormSpecTagName(rawName);
|
|
841
|
+
let payloadStart = tagEnd;
|
|
842
|
+
while (payloadStart < trimmedTagSegmentEnd && isWhitespace(line.text[payloadStart])) {
|
|
843
|
+
payloadStart += 1;
|
|
844
|
+
}
|
|
845
|
+
const target = parseTargetSpecifier(
|
|
846
|
+
line,
|
|
847
|
+
payloadStart,
|
|
848
|
+
trimmedTagSegmentEnd,
|
|
849
|
+
canonicalName,
|
|
850
|
+
baseOffset,
|
|
851
|
+
options?.extensions
|
|
852
|
+
);
|
|
853
|
+
let valueStart = payloadStart;
|
|
854
|
+
if (target !== null) {
|
|
855
|
+
valueStart = target.localEnd;
|
|
856
|
+
while (valueStart < trimmedTagSegmentEnd && isWhitespace(line.text[valueStart])) {
|
|
857
|
+
valueStart += 1;
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
const payloadSpan = payloadStart < trimmedTagSegmentEnd ? spanFromLine(line, payloadStart, trimmedTagSegmentEnd, baseOffset) : null;
|
|
861
|
+
const valueSpan = valueStart < trimmedTagSegmentEnd ? spanFromLine(line, valueStart, trimmedTagSegmentEnd, baseOffset) : null;
|
|
862
|
+
const parsedTarget = target === null ? null : {
|
|
863
|
+
rawText: target.rawText,
|
|
864
|
+
valid: target.valid,
|
|
865
|
+
kind: target.kind,
|
|
866
|
+
fullSpan: target.fullSpan,
|
|
867
|
+
colonSpan: target.colonSpan,
|
|
868
|
+
span: target.span,
|
|
869
|
+
path: target.path
|
|
870
|
+
};
|
|
871
|
+
tags.push({
|
|
872
|
+
rawTagName: rawName,
|
|
873
|
+
normalizedTagName: canonicalName,
|
|
874
|
+
recognized: getTagDefinition(canonicalName, options?.extensions) !== null,
|
|
875
|
+
fullSpan: spanFromLine(line, tagStart, trimmedTagSegmentEnd, baseOffset),
|
|
876
|
+
tagNameSpan: spanFromLine(line, tagStart, tagEnd, baseOffset),
|
|
877
|
+
payloadSpan,
|
|
878
|
+
colonSpan: parsedTarget?.colonSpan ?? null,
|
|
879
|
+
target: parsedTarget,
|
|
880
|
+
argumentSpan: valueSpan,
|
|
881
|
+
argumentText: valueSpan === null ? "" : commentText.slice(valueSpan.start - baseOffset, valueSpan.end - baseOffset)
|
|
882
|
+
});
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
return {
|
|
886
|
+
commentText,
|
|
887
|
+
offset: baseOffset,
|
|
888
|
+
tags
|
|
889
|
+
};
|
|
890
|
+
}
|
|
891
|
+
function parseTagSyntax(rawTagName, payloadText, options) {
|
|
892
|
+
const separator = payloadText === "" || isWhitespace(payloadText[0]) ? "" : " ";
|
|
893
|
+
const parsed = parseCommentBlock(`/** @${rawTagName}${separator}${payloadText} */`, options);
|
|
894
|
+
const [tag] = parsed.tags;
|
|
895
|
+
if (tag === void 0) {
|
|
896
|
+
throw new Error(`Unable to parse synthetic tag syntax for @${rawTagName}`);
|
|
897
|
+
}
|
|
898
|
+
return tag;
|
|
899
|
+
}
|
|
900
|
+
function sliceCommentSpan(commentText, span, options) {
|
|
901
|
+
const baseOffset = options?.offset ?? 0;
|
|
902
|
+
return commentText.slice(span.start - baseOffset, span.end - baseOffset);
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
// src/ts-binding.ts
|
|
906
|
+
import * as ts from "typescript";
|
|
907
|
+
function stripNullishUnion(type) {
|
|
908
|
+
if (!type.isUnion()) {
|
|
909
|
+
return type;
|
|
910
|
+
}
|
|
911
|
+
const nonNullish = type.types.filter(
|
|
912
|
+
(member) => (member.flags & (ts.TypeFlags.Null | ts.TypeFlags.Undefined)) === 0
|
|
913
|
+
);
|
|
914
|
+
if (nonNullish.length === 1 && nonNullish[0] !== void 0) {
|
|
915
|
+
return nonNullish[0];
|
|
916
|
+
}
|
|
917
|
+
return type;
|
|
918
|
+
}
|
|
919
|
+
function isIntersectionWithBase(type, predicate) {
|
|
920
|
+
return type.isIntersection() && type.types.some((member) => predicate(member));
|
|
921
|
+
}
|
|
922
|
+
function isNumberLike(type) {
|
|
923
|
+
const stripped = stripNullishUnion(type);
|
|
924
|
+
if (stripped.flags & (ts.TypeFlags.Number | ts.TypeFlags.NumberLiteral | ts.TypeFlags.BigInt | ts.TypeFlags.BigIntLiteral)) {
|
|
925
|
+
return true;
|
|
926
|
+
}
|
|
927
|
+
if (stripped.isUnion()) {
|
|
928
|
+
return stripped.types.every((member) => isNumberLike(member));
|
|
929
|
+
}
|
|
930
|
+
return isIntersectionWithBase(stripped, isNumberLike);
|
|
931
|
+
}
|
|
932
|
+
function isStringLike(type) {
|
|
933
|
+
const stripped = stripNullishUnion(type);
|
|
934
|
+
if (stripped.flags & (ts.TypeFlags.String | ts.TypeFlags.StringLiteral)) {
|
|
935
|
+
return true;
|
|
936
|
+
}
|
|
937
|
+
if (stripped.isUnion()) {
|
|
938
|
+
return stripped.types.every((member) => isStringLike(member));
|
|
939
|
+
}
|
|
940
|
+
return isIntersectionWithBase(stripped, isStringLike);
|
|
941
|
+
}
|
|
942
|
+
function isBooleanLike(type) {
|
|
943
|
+
const stripped = stripNullishUnion(type);
|
|
944
|
+
if (stripped.flags & (ts.TypeFlags.Boolean | ts.TypeFlags.BooleanLiteral)) {
|
|
945
|
+
return true;
|
|
946
|
+
}
|
|
947
|
+
if (stripped.isUnion()) {
|
|
948
|
+
return stripped.types.every((member) => isBooleanLike(member));
|
|
949
|
+
}
|
|
950
|
+
return isIntersectionWithBase(stripped, isBooleanLike);
|
|
951
|
+
}
|
|
952
|
+
function isNullLike(type) {
|
|
953
|
+
const stripped = stripNullishUnion(type);
|
|
954
|
+
if (stripped.flags & ts.TypeFlags.Null) {
|
|
955
|
+
return true;
|
|
956
|
+
}
|
|
957
|
+
if (stripped.isUnion()) {
|
|
958
|
+
return stripped.types.every((member) => isNullLike(member));
|
|
959
|
+
}
|
|
960
|
+
return isIntersectionWithBase(stripped, isNullLike);
|
|
961
|
+
}
|
|
962
|
+
function isArrayLike(type, checker) {
|
|
963
|
+
const stripped = stripNullishUnion(type);
|
|
964
|
+
if (checker.isArrayType(stripped)) {
|
|
965
|
+
return true;
|
|
966
|
+
}
|
|
967
|
+
const symbol = stripped.getSymbol();
|
|
968
|
+
return symbol?.name === "Array" || symbol?.name === "ReadonlyArray";
|
|
969
|
+
}
|
|
970
|
+
function isStringLiteralUnion(type) {
|
|
971
|
+
const stripped = stripNullishUnion(type);
|
|
972
|
+
return stripped.isUnion() && stripped.types.length > 0 && stripped.types.every(isStringLike);
|
|
973
|
+
}
|
|
974
|
+
function isObjectLike(type, checker) {
|
|
975
|
+
const stripped = stripNullishUnion(type);
|
|
976
|
+
return !isArrayLike(stripped, checker) && (stripped.flags & ts.TypeFlags.Object) !== 0;
|
|
977
|
+
}
|
|
978
|
+
function isJsonLike(type, checker) {
|
|
979
|
+
const stripped = stripNullishUnion(type);
|
|
980
|
+
if (isNullLike(stripped) || isNumberLike(stripped) || isStringLike(stripped) || isBooleanLike(stripped)) {
|
|
981
|
+
return true;
|
|
982
|
+
}
|
|
983
|
+
if (stripped.isUnion()) {
|
|
984
|
+
return stripped.types.every((member) => isJsonLike(member, checker));
|
|
985
|
+
}
|
|
986
|
+
if (isArrayLike(stripped, checker) || isObjectLike(stripped, checker)) {
|
|
987
|
+
return true;
|
|
988
|
+
}
|
|
989
|
+
return isIntersectionWithBase(stripped, (member) => isJsonLike(member, checker));
|
|
990
|
+
}
|
|
991
|
+
function getArrayElementType(type, checker) {
|
|
992
|
+
const stripped = stripNullishUnion(type);
|
|
993
|
+
if (!checker.isArrayType(stripped)) {
|
|
994
|
+
return null;
|
|
995
|
+
}
|
|
996
|
+
return checker.getTypeArguments(stripped)[0] ?? null;
|
|
997
|
+
}
|
|
998
|
+
function resolveDeclarationPlacement(node) {
|
|
999
|
+
if (ts.isClassDeclaration(node)) return "class";
|
|
1000
|
+
if (ts.isPropertyDeclaration(node)) return "class-field";
|
|
1001
|
+
if (ts.isMethodDeclaration(node)) return "class-method";
|
|
1002
|
+
if (ts.isInterfaceDeclaration(node)) return "interface";
|
|
1003
|
+
if (ts.isPropertySignature(node)) return "interface-field";
|
|
1004
|
+
if (ts.isTypeAliasDeclaration(node)) return "type-alias";
|
|
1005
|
+
if (ts.isVariableDeclaration(node)) return "variable";
|
|
1006
|
+
if (ts.isFunctionDeclaration(node)) return "function";
|
|
1007
|
+
if (ts.isParameter(node)) {
|
|
1008
|
+
if (ts.isMethodDeclaration(node.parent) || ts.isConstructorDeclaration(node.parent)) {
|
|
1009
|
+
return "method-parameter";
|
|
1010
|
+
}
|
|
1011
|
+
return "function-parameter";
|
|
1012
|
+
}
|
|
1013
|
+
return null;
|
|
1014
|
+
}
|
|
1015
|
+
function getTypeSemanticCapabilities(type, checker) {
|
|
1016
|
+
const capabilities = /* @__PURE__ */ new Set();
|
|
1017
|
+
if (isNumberLike(type)) {
|
|
1018
|
+
capabilities.add("numeric-comparable");
|
|
1019
|
+
}
|
|
1020
|
+
if (isStringLike(type)) {
|
|
1021
|
+
capabilities.add("string-like");
|
|
1022
|
+
}
|
|
1023
|
+
if (isJsonLike(type, checker)) {
|
|
1024
|
+
capabilities.add("json-like");
|
|
1025
|
+
}
|
|
1026
|
+
if (isArrayLike(type, checker)) {
|
|
1027
|
+
capabilities.add("array-like");
|
|
1028
|
+
}
|
|
1029
|
+
if (isStringLiteralUnion(type)) {
|
|
1030
|
+
capabilities.add("enum-member-addressable");
|
|
1031
|
+
}
|
|
1032
|
+
if (isObjectLike(type, checker)) {
|
|
1033
|
+
capabilities.add("object-like");
|
|
1034
|
+
}
|
|
1035
|
+
return [...capabilities];
|
|
1036
|
+
}
|
|
1037
|
+
function hasTypeSemanticCapability(type, checker, capability) {
|
|
1038
|
+
return getTypeSemanticCapabilities(type, checker).includes(capability);
|
|
1039
|
+
}
|
|
1040
|
+
function resolvePathTargetType(type, checker, segments) {
|
|
1041
|
+
const stripped = stripNullishUnion(type);
|
|
1042
|
+
if (segments.length === 0) {
|
|
1043
|
+
return {
|
|
1044
|
+
kind: "resolved",
|
|
1045
|
+
type: stripped
|
|
1046
|
+
};
|
|
1047
|
+
}
|
|
1048
|
+
const arrayElementType = getArrayElementType(stripped, checker);
|
|
1049
|
+
if (arrayElementType !== null) {
|
|
1050
|
+
return resolvePathTargetType(arrayElementType, checker, segments);
|
|
1051
|
+
}
|
|
1052
|
+
if ((stripped.flags & ts.TypeFlags.Object) === 0) {
|
|
1053
|
+
return {
|
|
1054
|
+
kind: "unresolvable",
|
|
1055
|
+
type: stripped
|
|
1056
|
+
};
|
|
1057
|
+
}
|
|
1058
|
+
const [segment, ...rest] = segments;
|
|
1059
|
+
if (segment === void 0) {
|
|
1060
|
+
throw new Error("Invariant violation: path traversal requires a segment");
|
|
1061
|
+
}
|
|
1062
|
+
const property = stripped.getProperty(segment);
|
|
1063
|
+
if (property === void 0) {
|
|
1064
|
+
return {
|
|
1065
|
+
kind: "missing-property",
|
|
1066
|
+
segment
|
|
1067
|
+
};
|
|
1068
|
+
}
|
|
1069
|
+
const declaration = property.valueDeclaration ?? property.declarations?.[0];
|
|
1070
|
+
if (declaration === void 0) {
|
|
1071
|
+
return {
|
|
1072
|
+
kind: "unresolvable",
|
|
1073
|
+
type: stripped
|
|
1074
|
+
};
|
|
1075
|
+
}
|
|
1076
|
+
return resolvePathTargetType(
|
|
1077
|
+
checker.getTypeOfSymbolAtLocation(property, declaration),
|
|
1078
|
+
checker,
|
|
1079
|
+
rest
|
|
1080
|
+
);
|
|
1081
|
+
}
|
|
1082
|
+
function collectPropertyPaths(type, checker, capability, prefix, visited) {
|
|
1083
|
+
const stripped = stripNullishUnion(type);
|
|
1084
|
+
if (visited.has(stripped)) {
|
|
1085
|
+
return [];
|
|
1086
|
+
}
|
|
1087
|
+
visited.add(stripped);
|
|
1088
|
+
const suggestions = [];
|
|
1089
|
+
if (hasTypeSemanticCapability(stripped, checker, capability) && prefix.length > 0) {
|
|
1090
|
+
suggestions.push(prefix.join("."));
|
|
1091
|
+
}
|
|
1092
|
+
for (const property of stripped.getProperties()) {
|
|
1093
|
+
const declaration = property.valueDeclaration ?? property.declarations?.[0];
|
|
1094
|
+
if (declaration === void 0) {
|
|
1095
|
+
continue;
|
|
1096
|
+
}
|
|
1097
|
+
const propertyType = checker.getTypeOfSymbolAtLocation(property, declaration);
|
|
1098
|
+
const nextPrefix = [...prefix, property.name];
|
|
1099
|
+
suggestions.push(
|
|
1100
|
+
...collectPropertyPaths(propertyType, checker, capability, nextPrefix, visited)
|
|
1101
|
+
);
|
|
1102
|
+
}
|
|
1103
|
+
return suggestions;
|
|
1104
|
+
}
|
|
1105
|
+
function collectCompatiblePathTargets(type, checker, capability) {
|
|
1106
|
+
return collectPropertyPaths(type, checker, capability, [], /* @__PURE__ */ new Set());
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
// src/cursor-context.ts
|
|
1110
|
+
function isWordChar(char) {
|
|
1111
|
+
return char !== void 0 && /[A-Za-z0-9]/u.test(char);
|
|
1112
|
+
}
|
|
1113
|
+
function isWhitespaceLike(char) {
|
|
1114
|
+
return char === void 0 || /\s/u.test(char) || char === "*";
|
|
1115
|
+
}
|
|
1116
|
+
function containsOffset(tag, offset) {
|
|
1117
|
+
return offset >= tag.tagNameSpan.start && offset <= tag.tagNameSpan.end;
|
|
1118
|
+
}
|
|
1119
|
+
function filterSignaturesByPlacement(signatures, placement) {
|
|
1120
|
+
if (placement === void 0 || placement === null) {
|
|
1121
|
+
return signatures;
|
|
1122
|
+
}
|
|
1123
|
+
const filtered = signatures.filter((signature) => signature.placements.includes(placement));
|
|
1124
|
+
return filtered.length > 0 ? filtered : signatures;
|
|
1125
|
+
}
|
|
1126
|
+
function getCompatiblePathTargetsForSignatures(signatures, checker, subjectType) {
|
|
1127
|
+
if (checker === void 0 || subjectType === void 0) {
|
|
1128
|
+
return [];
|
|
1129
|
+
}
|
|
1130
|
+
const suggestions = /* @__PURE__ */ new Set();
|
|
1131
|
+
for (const signature of signatures) {
|
|
1132
|
+
for (const parameter of signature.parameters) {
|
|
1133
|
+
if (parameter.kind !== "target-path" || parameter.capability === void 0) {
|
|
1134
|
+
continue;
|
|
1135
|
+
}
|
|
1136
|
+
for (const target of collectCompatiblePathTargets(
|
|
1137
|
+
subjectType,
|
|
1138
|
+
checker,
|
|
1139
|
+
parameter.capability
|
|
1140
|
+
)) {
|
|
1141
|
+
suggestions.add(target);
|
|
1142
|
+
}
|
|
1143
|
+
}
|
|
1144
|
+
}
|
|
1145
|
+
return [...suggestions].sort();
|
|
1146
|
+
}
|
|
1147
|
+
function getSupportedTargets(signatures) {
|
|
1148
|
+
const supportedTargets = /* @__PURE__ */ new Set(["none"]);
|
|
1149
|
+
for (const signature of signatures) {
|
|
1150
|
+
for (const parameter of signature.parameters) {
|
|
1151
|
+
switch (parameter.kind) {
|
|
1152
|
+
case "target-path":
|
|
1153
|
+
supportedTargets.add("path");
|
|
1154
|
+
break;
|
|
1155
|
+
case "target-member":
|
|
1156
|
+
supportedTargets.add("member");
|
|
1157
|
+
break;
|
|
1158
|
+
case "target-variant":
|
|
1159
|
+
supportedTargets.add("variant");
|
|
1160
|
+
break;
|
|
1161
|
+
default:
|
|
1162
|
+
break;
|
|
1163
|
+
}
|
|
1164
|
+
}
|
|
1165
|
+
}
|
|
1166
|
+
return [...supportedTargets];
|
|
1167
|
+
}
|
|
1168
|
+
function getTargetCompletions(signatures, compatiblePathTargets) {
|
|
1169
|
+
const completions = /* @__PURE__ */ new Set();
|
|
1170
|
+
for (const signature of signatures) {
|
|
1171
|
+
for (const parameter of signature.parameters) {
|
|
1172
|
+
switch (parameter.kind) {
|
|
1173
|
+
case "target-path":
|
|
1174
|
+
for (const target of compatiblePathTargets) {
|
|
1175
|
+
completions.add(target);
|
|
1176
|
+
}
|
|
1177
|
+
break;
|
|
1178
|
+
case "target-variant":
|
|
1179
|
+
completions.add("singular");
|
|
1180
|
+
completions.add("plural");
|
|
1181
|
+
break;
|
|
1182
|
+
default:
|
|
1183
|
+
break;
|
|
1184
|
+
}
|
|
1185
|
+
}
|
|
1186
|
+
}
|
|
1187
|
+
return [...completions];
|
|
1188
|
+
}
|
|
1189
|
+
function getCommentTagSemanticContext(tag, options) {
|
|
1190
|
+
const tagDefinition = getTagDefinition(tag.normalizedTagName, options?.extensions);
|
|
1191
|
+
const signatures = filterSignaturesByPlacement(
|
|
1192
|
+
tagDefinition?.signatures ?? [],
|
|
1193
|
+
options?.placement
|
|
1194
|
+
);
|
|
1195
|
+
const compatiblePathTargets = getCompatiblePathTargetsForSignatures(
|
|
1196
|
+
signatures,
|
|
1197
|
+
options?.checker,
|
|
1198
|
+
options?.subjectType
|
|
1199
|
+
);
|
|
1200
|
+
const semantic = {
|
|
1201
|
+
tag,
|
|
1202
|
+
tagDefinition,
|
|
1203
|
+
placement: options?.placement ?? null,
|
|
1204
|
+
signatures,
|
|
1205
|
+
supportedTargets: getSupportedTargets(signatures),
|
|
1206
|
+
targetCompletions: getTargetCompletions(signatures, compatiblePathTargets),
|
|
1207
|
+
compatiblePathTargets,
|
|
1208
|
+
valueLabels: getValueLabels(signatures),
|
|
1209
|
+
tagHoverMarkdown: tagDefinition?.hoverMarkdown ?? null,
|
|
1210
|
+
targetHoverMarkdown: null,
|
|
1211
|
+
argumentHoverMarkdown: null
|
|
1212
|
+
};
|
|
1213
|
+
return {
|
|
1214
|
+
...semantic,
|
|
1215
|
+
targetHoverMarkdown: buildTargetHoverMarkdown(semantic),
|
|
1216
|
+
argumentHoverMarkdown: buildArgumentHoverMarkdown(semantic)
|
|
1217
|
+
};
|
|
1218
|
+
}
|
|
1219
|
+
function getValueLabels(signatures) {
|
|
1220
|
+
const labels = /* @__PURE__ */ new Set();
|
|
1221
|
+
for (const signature of signatures) {
|
|
1222
|
+
for (const parameter of signature.parameters) {
|
|
1223
|
+
if (parameter.kind === "value") {
|
|
1224
|
+
labels.add(parameter.label);
|
|
1225
|
+
}
|
|
1226
|
+
}
|
|
1227
|
+
}
|
|
1228
|
+
return [...labels];
|
|
1229
|
+
}
|
|
1230
|
+
function getTargetKindLabels(supportedTargets) {
|
|
1231
|
+
const labels = supportedTargets.filter((kind) => kind !== "none").map((kind) => `\`${kind}\``);
|
|
1232
|
+
return labels.length === 0 ? "none" : labels.join(", ");
|
|
1233
|
+
}
|
|
1234
|
+
function buildTargetHoverMarkdown(semantic) {
|
|
1235
|
+
if (semantic.tagDefinition === null) {
|
|
1236
|
+
return null;
|
|
1237
|
+
}
|
|
1238
|
+
const currentTarget = semantic.tag.target?.rawText ?? "";
|
|
1239
|
+
const lines = [
|
|
1240
|
+
`**Target for @${semantic.tagDefinition.canonicalName}**`,
|
|
1241
|
+
"",
|
|
1242
|
+
`Supported target forms: ${getTargetKindLabels(semantic.supportedTargets)}`
|
|
1243
|
+
];
|
|
1244
|
+
if (currentTarget !== "") {
|
|
1245
|
+
lines.push("", `Current target: \`:${currentTarget}\``);
|
|
1246
|
+
}
|
|
1247
|
+
const MAX_HOVER_PATH_TARGETS = 8;
|
|
1248
|
+
if (semantic.compatiblePathTargets.length > 0) {
|
|
1249
|
+
lines.push("", "**Compatible path targets:**");
|
|
1250
|
+
for (const target of semantic.compatiblePathTargets.slice(0, MAX_HOVER_PATH_TARGETS)) {
|
|
1251
|
+
lines.push(`- \`:${target}\``);
|
|
1252
|
+
}
|
|
1253
|
+
} else if (semantic.supportedTargets.includes("variant")) {
|
|
1254
|
+
lines.push("", "Use `:singular` or `:plural` for variant-specific names.");
|
|
1255
|
+
} else if (semantic.supportedTargets.includes("path")) {
|
|
1256
|
+
lines.push(
|
|
1257
|
+
"",
|
|
1258
|
+
"Type-aware path completions become available when TypeScript binding is provided."
|
|
1259
|
+
);
|
|
1260
|
+
}
|
|
1261
|
+
return lines.join("\n");
|
|
1262
|
+
}
|
|
1263
|
+
function buildArgumentHoverMarkdown(semantic) {
|
|
1264
|
+
if (semantic.tagDefinition === null) {
|
|
1265
|
+
return null;
|
|
1266
|
+
}
|
|
1267
|
+
const valueLabels = getValueLabels(semantic.signatures);
|
|
1268
|
+
const formattedValueLabels = valueLabels.map((label) => `\`${label}\``);
|
|
1269
|
+
const soleSignature = semantic.signatures.length === 1 ? semantic.signatures[0] : void 0;
|
|
1270
|
+
const signatureLines = semantic.signatures.length === 0 ? [] : soleSignature !== void 0 ? [`**Signature:** \`${soleSignature.label}\``] : [
|
|
1271
|
+
"**Signatures:**",
|
|
1272
|
+
...semantic.signatures.map((signature) => `- \`${signature.label}\``)
|
|
1273
|
+
];
|
|
1274
|
+
return [
|
|
1275
|
+
`**Argument for @${semantic.tagDefinition.canonicalName}**`,
|
|
1276
|
+
"",
|
|
1277
|
+
`Expected value: ${formattedValueLabels.join(" or ") || "`<value>`"}`,
|
|
1278
|
+
"",
|
|
1279
|
+
...signatureLines
|
|
1280
|
+
].join("\n");
|
|
1281
|
+
}
|
|
1282
|
+
function findEnclosingDocComment(documentText, offset, options) {
|
|
1283
|
+
const commentPattern = /\/\*\*[\s\S]*?\*\//gu;
|
|
1284
|
+
for (const match of documentText.matchAll(commentPattern)) {
|
|
1285
|
+
const fullMatch = match[0];
|
|
1286
|
+
const index = match.index;
|
|
1287
|
+
const start = index;
|
|
1288
|
+
const end = start + fullMatch.length;
|
|
1289
|
+
if (offset >= start && offset <= end) {
|
|
1290
|
+
return {
|
|
1291
|
+
text: fullMatch,
|
|
1292
|
+
start,
|
|
1293
|
+
end,
|
|
1294
|
+
parsed: parseCommentBlock(fullMatch, {
|
|
1295
|
+
offset: start,
|
|
1296
|
+
...options?.extensions !== void 0 ? { extensions: options.extensions } : {}
|
|
1297
|
+
})
|
|
1298
|
+
};
|
|
1299
|
+
}
|
|
1300
|
+
}
|
|
1301
|
+
return null;
|
|
1302
|
+
}
|
|
1303
|
+
function findCommentTagAtOffset(documentText, offset, options) {
|
|
1304
|
+
const comment = findEnclosingDocComment(documentText, offset, options);
|
|
1305
|
+
if (comment === null) {
|
|
1306
|
+
return null;
|
|
1307
|
+
}
|
|
1308
|
+
return comment.parsed.tags.find((tag) => containsOffset(tag, offset)) ?? null;
|
|
1309
|
+
}
|
|
1310
|
+
function getCommentCursorTargetAtOffset(documentText, offset, options) {
|
|
1311
|
+
const comment = findEnclosingDocComment(documentText, offset, options);
|
|
1312
|
+
if (comment === null) {
|
|
1313
|
+
return null;
|
|
1314
|
+
}
|
|
1315
|
+
for (const tag of comment.parsed.tags) {
|
|
1316
|
+
if (containsOffset(tag, offset)) {
|
|
1317
|
+
return {
|
|
1318
|
+
kind: "tag-name",
|
|
1319
|
+
tag
|
|
1320
|
+
};
|
|
1321
|
+
}
|
|
1322
|
+
if (tag.colonSpan !== null && offset >= tag.colonSpan.start && offset <= tag.colonSpan.end) {
|
|
1323
|
+
return {
|
|
1324
|
+
kind: "colon",
|
|
1325
|
+
tag
|
|
1326
|
+
};
|
|
1327
|
+
}
|
|
1328
|
+
if (tag.target !== null && offset >= tag.target.span.start && offset <= tag.target.span.end) {
|
|
1329
|
+
return {
|
|
1330
|
+
kind: "target",
|
|
1331
|
+
tag
|
|
1332
|
+
};
|
|
1333
|
+
}
|
|
1334
|
+
if (tag.argumentSpan !== null && offset >= tag.argumentSpan.start && offset <= tag.argumentSpan.end) {
|
|
1335
|
+
return {
|
|
1336
|
+
kind: "argument",
|
|
1337
|
+
tag
|
|
1338
|
+
};
|
|
1339
|
+
}
|
|
1340
|
+
}
|
|
1341
|
+
return null;
|
|
1342
|
+
}
|
|
1343
|
+
function getTagCompletionPrefixAtOffset(documentText, offset) {
|
|
1344
|
+
const comment = findEnclosingDocComment(documentText, offset);
|
|
1345
|
+
if (comment === null) {
|
|
1346
|
+
return null;
|
|
1347
|
+
}
|
|
1348
|
+
const relativeOffset = offset - comment.start;
|
|
1349
|
+
if (relativeOffset < 0 || relativeOffset > comment.text.length) {
|
|
1350
|
+
return null;
|
|
1351
|
+
}
|
|
1352
|
+
let cursor = relativeOffset;
|
|
1353
|
+
while (cursor > 0 && isWordChar(comment.text[cursor - 1])) {
|
|
1354
|
+
cursor -= 1;
|
|
1355
|
+
}
|
|
1356
|
+
const atIndex = cursor - 1;
|
|
1357
|
+
if (atIndex < 0 || comment.text[atIndex] !== "@") {
|
|
1358
|
+
return null;
|
|
1359
|
+
}
|
|
1360
|
+
const previousChar = atIndex > 0 ? comment.text[atIndex - 1] : void 0;
|
|
1361
|
+
if (!isWhitespaceLike(previousChar)) {
|
|
1362
|
+
return null;
|
|
1363
|
+
}
|
|
1364
|
+
return comment.text.slice(cursor, relativeOffset);
|
|
1365
|
+
}
|
|
1366
|
+
function getCommentCompletionContextAtOffset(documentText, offset, options) {
|
|
1367
|
+
const prefix = getTagCompletionPrefixAtOffset(documentText, offset);
|
|
1368
|
+
if (prefix !== null) {
|
|
1369
|
+
return {
|
|
1370
|
+
kind: "tag-name",
|
|
1371
|
+
prefix
|
|
1372
|
+
};
|
|
1373
|
+
}
|
|
1374
|
+
const target = getCommentCursorTargetAtOffset(documentText, offset, options);
|
|
1375
|
+
if (target?.kind === "target" || target?.kind === "colon") {
|
|
1376
|
+
return {
|
|
1377
|
+
kind: "target",
|
|
1378
|
+
tag: target.tag
|
|
1379
|
+
};
|
|
1380
|
+
}
|
|
1381
|
+
if (target?.kind === "argument") {
|
|
1382
|
+
return {
|
|
1383
|
+
kind: "argument",
|
|
1384
|
+
tag: target.tag
|
|
1385
|
+
};
|
|
1386
|
+
}
|
|
1387
|
+
return {
|
|
1388
|
+
kind: "none"
|
|
1389
|
+
};
|
|
1390
|
+
}
|
|
1391
|
+
function getSemanticCommentCompletionContextAtOffset(documentText, offset, options) {
|
|
1392
|
+
const prefix = getTagCompletionPrefixAtOffset(documentText, offset);
|
|
1393
|
+
if (prefix !== null) {
|
|
1394
|
+
return {
|
|
1395
|
+
kind: "tag-name",
|
|
1396
|
+
prefix,
|
|
1397
|
+
availableTags: getAllTagDefinitions(options?.extensions)
|
|
1398
|
+
};
|
|
1399
|
+
}
|
|
1400
|
+
const target = getCommentCursorTargetAtOffset(
|
|
1401
|
+
documentText,
|
|
1402
|
+
offset,
|
|
1403
|
+
options?.extensions ? { extensions: options.extensions } : void 0
|
|
1404
|
+
);
|
|
1405
|
+
if (target?.kind === "target" || target?.kind === "colon") {
|
|
1406
|
+
return {
|
|
1407
|
+
kind: "target",
|
|
1408
|
+
semantic: getCommentTagSemanticContext(target.tag, options)
|
|
1409
|
+
};
|
|
1410
|
+
}
|
|
1411
|
+
if (target?.kind === "argument") {
|
|
1412
|
+
const semantic = getCommentTagSemanticContext(target.tag, options);
|
|
1413
|
+
return {
|
|
1414
|
+
kind: "argument",
|
|
1415
|
+
semantic,
|
|
1416
|
+
valueLabels: semantic.valueLabels
|
|
1417
|
+
};
|
|
1418
|
+
}
|
|
1419
|
+
return { kind: "none" };
|
|
1420
|
+
}
|
|
1421
|
+
function getCommentHoverInfoAtOffset(documentText, offset, options) {
|
|
1422
|
+
const target = getCommentCursorTargetAtOffset(
|
|
1423
|
+
documentText,
|
|
1424
|
+
offset,
|
|
1425
|
+
options?.extensions ? { extensions: options.extensions } : void 0
|
|
1426
|
+
);
|
|
1427
|
+
if (target === null) {
|
|
1428
|
+
return null;
|
|
1429
|
+
}
|
|
1430
|
+
const semantic = getCommentTagSemanticContext(target.tag, options);
|
|
1431
|
+
let markdown = null;
|
|
1432
|
+
switch (target.kind) {
|
|
1433
|
+
case "tag-name":
|
|
1434
|
+
markdown = semantic.tagHoverMarkdown;
|
|
1435
|
+
break;
|
|
1436
|
+
case "colon":
|
|
1437
|
+
case "target":
|
|
1438
|
+
markdown = semantic.targetHoverMarkdown;
|
|
1439
|
+
break;
|
|
1440
|
+
case "argument":
|
|
1441
|
+
markdown = semantic.argumentHoverMarkdown;
|
|
1442
|
+
break;
|
|
1443
|
+
default: {
|
|
1444
|
+
const exhaustive = target.kind;
|
|
1445
|
+
void exhaustive;
|
|
1446
|
+
break;
|
|
1447
|
+
}
|
|
1448
|
+
}
|
|
1449
|
+
return markdown === null ? null : {
|
|
1450
|
+
kind: target.kind === "colon" ? "target" : target.kind,
|
|
1451
|
+
markdown
|
|
1452
|
+
};
|
|
1453
|
+
}
|
|
1454
|
+
|
|
1455
|
+
// src/tag-value-parser.ts
|
|
1456
|
+
import {
|
|
1457
|
+
BUILTIN_CONSTRAINT_DEFINITIONS as BUILTIN_CONSTRAINT_DEFINITIONS2,
|
|
1458
|
+
isBuiltinConstraintName
|
|
1459
|
+
} from "@formspec/core";
|
|
1460
|
+
var NUMERIC_CONSTRAINT_MAP = {
|
|
1461
|
+
minimum: "minimum",
|
|
1462
|
+
maximum: "maximum",
|
|
1463
|
+
exclusiveMinimum: "exclusiveMinimum",
|
|
1464
|
+
exclusiveMaximum: "exclusiveMaximum",
|
|
1465
|
+
multipleOf: "multipleOf"
|
|
1466
|
+
};
|
|
1467
|
+
var LENGTH_CONSTRAINT_MAP = {
|
|
1468
|
+
minLength: "minLength",
|
|
1469
|
+
maxLength: "maxLength",
|
|
1470
|
+
minItems: "minItems",
|
|
1471
|
+
maxItems: "maxItems"
|
|
1472
|
+
};
|
|
1473
|
+
function tryParseJson(text) {
|
|
1474
|
+
try {
|
|
1475
|
+
return JSON.parse(text);
|
|
1476
|
+
} catch {
|
|
1477
|
+
return null;
|
|
1478
|
+
}
|
|
1479
|
+
}
|
|
1480
|
+
function syntaxOptions(registry) {
|
|
1481
|
+
return registry?.extensions !== void 0 ? { extensions: registry.extensions } : void 0;
|
|
1482
|
+
}
|
|
1483
|
+
function parseConstraintTagValue(tagName, text, provenance, options) {
|
|
1484
|
+
const customConstraint = parseExtensionConstraintTagValue(tagName, text, provenance, options);
|
|
1485
|
+
if (customConstraint !== null) {
|
|
1486
|
+
return customConstraint;
|
|
1487
|
+
}
|
|
1488
|
+
if (!isBuiltinConstraintName(tagName)) {
|
|
1489
|
+
return null;
|
|
1490
|
+
}
|
|
1491
|
+
const parsedTag = parseTagSyntax(tagName, text, syntaxOptions(options?.registry));
|
|
1492
|
+
if (parsedTag.target !== null && !parsedTag.target.valid) {
|
|
1493
|
+
return null;
|
|
1494
|
+
}
|
|
1495
|
+
const effectiveText = parsedTag.argumentText;
|
|
1496
|
+
const path2 = parsedTag.target?.path ?? void 0;
|
|
1497
|
+
const expectedType = BUILTIN_CONSTRAINT_DEFINITIONS2[tagName];
|
|
1498
|
+
if (expectedType !== "boolean" && effectiveText.trim() === "") {
|
|
1499
|
+
return null;
|
|
1500
|
+
}
|
|
1501
|
+
if (expectedType === "number") {
|
|
1502
|
+
const value = Number(effectiveText);
|
|
1503
|
+
if (Number.isNaN(value)) {
|
|
1504
|
+
return null;
|
|
1505
|
+
}
|
|
1506
|
+
const numericKind = NUMERIC_CONSTRAINT_MAP[tagName];
|
|
1507
|
+
if (numericKind !== void 0) {
|
|
1508
|
+
return {
|
|
1509
|
+
kind: "constraint",
|
|
1510
|
+
constraintKind: numericKind,
|
|
1511
|
+
value,
|
|
1512
|
+
...path2 !== void 0 && { path: path2 },
|
|
1513
|
+
provenance
|
|
1514
|
+
};
|
|
1515
|
+
}
|
|
1516
|
+
const lengthKind = LENGTH_CONSTRAINT_MAP[tagName];
|
|
1517
|
+
if (lengthKind !== void 0) {
|
|
1518
|
+
return {
|
|
1519
|
+
kind: "constraint",
|
|
1520
|
+
constraintKind: lengthKind,
|
|
1521
|
+
value,
|
|
1522
|
+
...path2 !== void 0 && { path: path2 },
|
|
1523
|
+
provenance
|
|
1524
|
+
};
|
|
1525
|
+
}
|
|
1526
|
+
return null;
|
|
1527
|
+
}
|
|
1528
|
+
if (expectedType === "boolean") {
|
|
1529
|
+
const trimmed = effectiveText.trim();
|
|
1530
|
+
if (trimmed !== "" && trimmed !== "true") {
|
|
1531
|
+
return null;
|
|
1532
|
+
}
|
|
1533
|
+
if (tagName === "uniqueItems") {
|
|
1534
|
+
return {
|
|
1535
|
+
kind: "constraint",
|
|
1536
|
+
constraintKind: "uniqueItems",
|
|
1537
|
+
value: true,
|
|
1538
|
+
...path2 !== void 0 && { path: path2 },
|
|
1539
|
+
provenance
|
|
1540
|
+
};
|
|
1541
|
+
}
|
|
1542
|
+
return null;
|
|
1543
|
+
}
|
|
1544
|
+
if (expectedType === "json") {
|
|
1545
|
+
if (tagName === "const") {
|
|
1546
|
+
const trimmedText = effectiveText.trim();
|
|
1547
|
+
if (trimmedText === "") {
|
|
1548
|
+
return null;
|
|
1549
|
+
}
|
|
1550
|
+
try {
|
|
1551
|
+
const parsed2 = JSON.parse(trimmedText);
|
|
1552
|
+
return {
|
|
1553
|
+
kind: "constraint",
|
|
1554
|
+
constraintKind: "const",
|
|
1555
|
+
value: parsed2,
|
|
1556
|
+
...path2 !== void 0 && { path: path2 },
|
|
1557
|
+
provenance
|
|
1558
|
+
};
|
|
1559
|
+
} catch {
|
|
1560
|
+
return {
|
|
1561
|
+
kind: "constraint",
|
|
1562
|
+
constraintKind: "const",
|
|
1563
|
+
value: trimmedText,
|
|
1564
|
+
...path2 !== void 0 && { path: path2 },
|
|
1565
|
+
provenance
|
|
1566
|
+
};
|
|
1567
|
+
}
|
|
1568
|
+
}
|
|
1569
|
+
const parsed = tryParseJson(effectiveText);
|
|
1570
|
+
if (!Array.isArray(parsed)) {
|
|
1571
|
+
return null;
|
|
1572
|
+
}
|
|
1573
|
+
const members = [];
|
|
1574
|
+
for (const item of parsed) {
|
|
1575
|
+
if (typeof item === "string" || typeof item === "number") {
|
|
1576
|
+
members.push(item);
|
|
1577
|
+
continue;
|
|
1578
|
+
}
|
|
1579
|
+
if (typeof item === "object" && item !== null && "id" in item) {
|
|
1580
|
+
const id = item["id"];
|
|
1581
|
+
if (typeof id === "string" || typeof id === "number") {
|
|
1582
|
+
members.push(id);
|
|
1583
|
+
}
|
|
1584
|
+
}
|
|
1585
|
+
}
|
|
1586
|
+
return {
|
|
1587
|
+
kind: "constraint",
|
|
1588
|
+
constraintKind: "allowedMembers",
|
|
1589
|
+
members,
|
|
1590
|
+
...path2 !== void 0 && { path: path2 },
|
|
1591
|
+
provenance
|
|
1592
|
+
};
|
|
1593
|
+
}
|
|
1594
|
+
return {
|
|
1595
|
+
kind: "constraint",
|
|
1596
|
+
constraintKind: "pattern",
|
|
1597
|
+
pattern: effectiveText,
|
|
1598
|
+
...path2 !== void 0 && { path: path2 },
|
|
1599
|
+
provenance
|
|
1600
|
+
};
|
|
1601
|
+
}
|
|
1602
|
+
function parseDefaultValueTagValue(text, provenance) {
|
|
1603
|
+
const trimmed = text.trim();
|
|
1604
|
+
let value;
|
|
1605
|
+
if (trimmed === "null") {
|
|
1606
|
+
value = null;
|
|
1607
|
+
} else if (trimmed === "true") {
|
|
1608
|
+
value = true;
|
|
1609
|
+
} else if (trimmed === "false") {
|
|
1610
|
+
value = false;
|
|
1611
|
+
} else {
|
|
1612
|
+
const parsed = tryParseJson(trimmed);
|
|
1613
|
+
value = parsed !== null ? parsed : trimmed;
|
|
1614
|
+
}
|
|
1615
|
+
return {
|
|
1616
|
+
kind: "annotation",
|
|
1617
|
+
annotationKind: "defaultValue",
|
|
1618
|
+
value,
|
|
1619
|
+
provenance
|
|
1620
|
+
};
|
|
1621
|
+
}
|
|
1622
|
+
function parseExtensionConstraintTagValue(tagName, text, provenance, options) {
|
|
1623
|
+
const parsedTag = parseTagSyntax(tagName, text, syntaxOptions(options?.registry));
|
|
1624
|
+
if (parsedTag.target !== null && !parsedTag.target.valid) {
|
|
1625
|
+
return null;
|
|
1626
|
+
}
|
|
1627
|
+
const effectiveText = parsedTag.argumentText;
|
|
1628
|
+
const path2 = parsedTag.target?.path ?? void 0;
|
|
1629
|
+
const registry = options?.registry;
|
|
1630
|
+
if (registry === void 0) {
|
|
1631
|
+
return null;
|
|
1632
|
+
}
|
|
1633
|
+
if (effectiveText.trim() === "") {
|
|
1634
|
+
return null;
|
|
1635
|
+
}
|
|
1636
|
+
const directTag = registry.findConstraintTag(tagName);
|
|
1637
|
+
if (directTag !== void 0) {
|
|
1638
|
+
return makeCustomConstraintNode(
|
|
1639
|
+
directTag.extensionId,
|
|
1640
|
+
directTag.registration.constraintName,
|
|
1641
|
+
directTag.registration.parseValue(effectiveText),
|
|
1642
|
+
provenance,
|
|
1643
|
+
path2,
|
|
1644
|
+
registry
|
|
1645
|
+
);
|
|
1646
|
+
}
|
|
1647
|
+
if (!isBuiltinConstraintName(tagName)) {
|
|
1648
|
+
return null;
|
|
1649
|
+
}
|
|
1650
|
+
const broadenedTypeId = getBroadenedCustomTypeId(options?.fieldType);
|
|
1651
|
+
if (broadenedTypeId === void 0) {
|
|
1652
|
+
return null;
|
|
1653
|
+
}
|
|
1654
|
+
const broadened = registry.findBuiltinConstraintBroadening(broadenedTypeId, tagName);
|
|
1655
|
+
if (broadened === void 0) {
|
|
1656
|
+
return null;
|
|
1657
|
+
}
|
|
1658
|
+
return makeCustomConstraintNode(
|
|
1659
|
+
broadened.extensionId,
|
|
1660
|
+
broadened.registration.constraintName,
|
|
1661
|
+
broadened.registration.parseValue(effectiveText),
|
|
1662
|
+
provenance,
|
|
1663
|
+
path2,
|
|
1664
|
+
registry
|
|
1665
|
+
);
|
|
1666
|
+
}
|
|
1667
|
+
function getBroadenedCustomTypeId(fieldType) {
|
|
1668
|
+
if (fieldType?.kind === "custom") {
|
|
1669
|
+
return fieldType.typeId;
|
|
1670
|
+
}
|
|
1671
|
+
if (fieldType?.kind !== "union") {
|
|
1672
|
+
return void 0;
|
|
1673
|
+
}
|
|
1674
|
+
const customMembers = fieldType.members.filter(
|
|
1675
|
+
(member) => member.kind === "custom"
|
|
1676
|
+
);
|
|
1677
|
+
if (customMembers.length !== 1) {
|
|
1678
|
+
return void 0;
|
|
1679
|
+
}
|
|
1680
|
+
const nonCustomMembers = fieldType.members.filter((member) => member.kind !== "custom");
|
|
1681
|
+
const allOtherMembersAreNull = nonCustomMembers.every(
|
|
1682
|
+
(member) => member.kind === "primitive" && member.primitiveKind === "null"
|
|
1683
|
+
);
|
|
1684
|
+
const customMember = customMembers[0];
|
|
1685
|
+
return allOtherMembersAreNull && customMember !== void 0 ? customMember.typeId : void 0;
|
|
1686
|
+
}
|
|
1687
|
+
function makeCustomConstraintNode(extensionId, constraintName, payload, provenance, path2, registry) {
|
|
1688
|
+
const constraintId = `${extensionId}/${constraintName}`;
|
|
1689
|
+
const registration = registry.findConstraint(constraintId);
|
|
1690
|
+
if (registration === void 0) {
|
|
1691
|
+
throw new Error(
|
|
1692
|
+
`Custom TSDoc tag resolved to unregistered constraint "${constraintId}". Register the constraint before using its tag.`
|
|
1693
|
+
);
|
|
1694
|
+
}
|
|
1695
|
+
return {
|
|
1696
|
+
kind: "constraint",
|
|
1697
|
+
constraintKind: "custom",
|
|
1698
|
+
constraintId,
|
|
1699
|
+
payload,
|
|
1700
|
+
compositionRule: registration.compositionRule,
|
|
1701
|
+
...path2 !== void 0 && { path: path2 },
|
|
1702
|
+
provenance
|
|
1703
|
+
};
|
|
1704
|
+
}
|
|
1705
|
+
|
|
1706
|
+
// src/semantic-targets.ts
|
|
1707
|
+
import { normalizeConstraintTagName as normalizeConstraintTagName2 } from "@formspec/core";
|
|
1708
|
+
function pathKey(path2) {
|
|
1709
|
+
return path2?.segments.join(".") ?? "";
|
|
1710
|
+
}
|
|
1711
|
+
function formatConstraintTargetName(fieldName, path2) {
|
|
1712
|
+
if (path2 === null || path2.segments.length === 0) {
|
|
1713
|
+
return fieldName;
|
|
1714
|
+
}
|
|
1715
|
+
return `${fieldName}.${path2.segments.join(".")}`;
|
|
1716
|
+
}
|
|
1717
|
+
function dereferenceAnalysisType(type, typeRegistry) {
|
|
1718
|
+
let current = type;
|
|
1719
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1720
|
+
while (current.kind === "reference") {
|
|
1721
|
+
if (seen.has(current.name)) {
|
|
1722
|
+
return current;
|
|
1723
|
+
}
|
|
1724
|
+
seen.add(current.name);
|
|
1725
|
+
const definition = typeRegistry[current.name];
|
|
1726
|
+
if (definition === void 0) {
|
|
1727
|
+
return current;
|
|
1728
|
+
}
|
|
1729
|
+
current = definition.type;
|
|
1730
|
+
}
|
|
1731
|
+
return current;
|
|
1732
|
+
}
|
|
1733
|
+
function collectReferencedTypeConstraints(type, typeRegistry) {
|
|
1734
|
+
const collected = [];
|
|
1735
|
+
let current = type;
|
|
1736
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1737
|
+
while (current.kind === "reference") {
|
|
1738
|
+
if (seen.has(current.name)) {
|
|
1739
|
+
break;
|
|
1740
|
+
}
|
|
1741
|
+
seen.add(current.name);
|
|
1742
|
+
const definition = typeRegistry[current.name];
|
|
1743
|
+
if (definition === void 0) {
|
|
1744
|
+
break;
|
|
1745
|
+
}
|
|
1746
|
+
if (definition.constraints !== void 0) {
|
|
1747
|
+
collected.push(...definition.constraints);
|
|
1748
|
+
}
|
|
1749
|
+
current = definition.type;
|
|
1750
|
+
}
|
|
1751
|
+
return collected;
|
|
1752
|
+
}
|
|
1753
|
+
function collectReferencedTypeAnnotations(type, typeRegistry) {
|
|
1754
|
+
const collected = [];
|
|
1755
|
+
let current = type;
|
|
1756
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1757
|
+
while (current.kind === "reference") {
|
|
1758
|
+
if (seen.has(current.name)) {
|
|
1759
|
+
break;
|
|
1760
|
+
}
|
|
1761
|
+
seen.add(current.name);
|
|
1762
|
+
const definition = typeRegistry[current.name];
|
|
1763
|
+
if (definition === void 0) {
|
|
1764
|
+
break;
|
|
1765
|
+
}
|
|
1766
|
+
if (definition.annotations !== void 0) {
|
|
1767
|
+
collected.push(...definition.annotations);
|
|
1768
|
+
}
|
|
1769
|
+
current = definition.type;
|
|
1770
|
+
}
|
|
1771
|
+
return collected;
|
|
1772
|
+
}
|
|
1773
|
+
function resolveProperty(type, typeRegistry, segments) {
|
|
1774
|
+
const effectiveType = dereferenceAnalysisType(type, typeRegistry);
|
|
1775
|
+
if (segments.length === 0) {
|
|
1776
|
+
return { kind: "resolved", property: null, rawType: type, type: effectiveType };
|
|
1777
|
+
}
|
|
1778
|
+
if (effectiveType.kind === "array") {
|
|
1779
|
+
return resolveProperty(effectiveType.items, typeRegistry, segments);
|
|
1780
|
+
}
|
|
1781
|
+
if (effectiveType.kind !== "object") {
|
|
1782
|
+
return { kind: "unresolvable", type: effectiveType };
|
|
1783
|
+
}
|
|
1784
|
+
const [segment, ...rest] = segments;
|
|
1785
|
+
if (segment === void 0) {
|
|
1786
|
+
throw new Error("Invariant violation: object traversal requires a segment");
|
|
1787
|
+
}
|
|
1788
|
+
const property = effectiveType.properties.find((candidate) => candidate.name === segment);
|
|
1789
|
+
if (property === void 0) {
|
|
1790
|
+
return { kind: "missing-property", segment };
|
|
1791
|
+
}
|
|
1792
|
+
if (rest.length === 0) {
|
|
1793
|
+
return {
|
|
1794
|
+
kind: "resolved",
|
|
1795
|
+
property,
|
|
1796
|
+
rawType: property.type,
|
|
1797
|
+
type: dereferenceAnalysisType(property.type, typeRegistry)
|
|
1798
|
+
};
|
|
1799
|
+
}
|
|
1800
|
+
return resolveProperty(property.type, typeRegistry, rest);
|
|
1801
|
+
}
|
|
1802
|
+
function resolveConstraintTargetState(fieldName, fieldType, path2, localConstraints, typeRegistry) {
|
|
1803
|
+
if (path2 === null) {
|
|
1804
|
+
const inheritedConstraints2 = collectReferencedTypeConstraints(fieldType, typeRegistry);
|
|
1805
|
+
const inheritedAnnotations2 = collectReferencedTypeAnnotations(fieldType, typeRegistry);
|
|
1806
|
+
const type = dereferenceAnalysisType(fieldType, typeRegistry);
|
|
1807
|
+
return {
|
|
1808
|
+
kind: "resolved",
|
|
1809
|
+
fieldName,
|
|
1810
|
+
path: path2,
|
|
1811
|
+
targetName: fieldName,
|
|
1812
|
+
type,
|
|
1813
|
+
inheritedConstraints: inheritedConstraints2,
|
|
1814
|
+
inheritedAnnotations: inheritedAnnotations2,
|
|
1815
|
+
localConstraints,
|
|
1816
|
+
effectiveConstraints: [...inheritedConstraints2, ...localConstraints]
|
|
1817
|
+
};
|
|
1818
|
+
}
|
|
1819
|
+
const resolution = resolveProperty(fieldType, typeRegistry, path2.segments);
|
|
1820
|
+
const targetName = formatConstraintTargetName(fieldName, path2);
|
|
1821
|
+
if (resolution.kind === "missing-property") {
|
|
1822
|
+
return {
|
|
1823
|
+
kind: "missing-property",
|
|
1824
|
+
fieldName,
|
|
1825
|
+
path: path2,
|
|
1826
|
+
targetName,
|
|
1827
|
+
segment: resolution.segment,
|
|
1828
|
+
localConstraints
|
|
1829
|
+
};
|
|
1830
|
+
}
|
|
1831
|
+
if (resolution.kind === "unresolvable") {
|
|
1832
|
+
return {
|
|
1833
|
+
kind: "unresolvable",
|
|
1834
|
+
fieldName,
|
|
1835
|
+
path: path2,
|
|
1836
|
+
targetName,
|
|
1837
|
+
type: resolution.type,
|
|
1838
|
+
localConstraints
|
|
1839
|
+
};
|
|
1840
|
+
}
|
|
1841
|
+
const propertyConstraints = resolution.property?.constraints ?? [];
|
|
1842
|
+
const propertyAnnotations = resolution.property?.annotations ?? [];
|
|
1843
|
+
const referencedConstraints = collectReferencedTypeConstraints(resolution.rawType, typeRegistry);
|
|
1844
|
+
const referencedAnnotations = collectReferencedTypeAnnotations(resolution.rawType, typeRegistry);
|
|
1845
|
+
const inheritedConstraints = [...propertyConstraints, ...referencedConstraints];
|
|
1846
|
+
const inheritedAnnotations = [...propertyAnnotations, ...referencedAnnotations];
|
|
1847
|
+
return {
|
|
1848
|
+
kind: "resolved",
|
|
1849
|
+
fieldName,
|
|
1850
|
+
path: path2,
|
|
1851
|
+
targetName,
|
|
1852
|
+
type: resolution.type,
|
|
1853
|
+
inheritedConstraints,
|
|
1854
|
+
inheritedAnnotations,
|
|
1855
|
+
localConstraints,
|
|
1856
|
+
effectiveConstraints: [...inheritedConstraints, ...localConstraints]
|
|
1857
|
+
};
|
|
1858
|
+
}
|
|
1859
|
+
function cloneTargetPath(path2) {
|
|
1860
|
+
if (path2 === void 0) {
|
|
1861
|
+
return null;
|
|
1862
|
+
}
|
|
1863
|
+
return { segments: [...path2.segments] };
|
|
1864
|
+
}
|
|
1865
|
+
function buildConstraintTargetStates(fieldName, fieldType, constraints, typeRegistry) {
|
|
1866
|
+
const grouped = /* @__PURE__ */ new Map([
|
|
1867
|
+
["", { path: null, constraints: [] }]
|
|
1868
|
+
]);
|
|
1869
|
+
for (const constraint of constraints) {
|
|
1870
|
+
const path2 = cloneTargetPath(constraint.path);
|
|
1871
|
+
const key = pathKey(path2);
|
|
1872
|
+
let bucket = grouped.get(key);
|
|
1873
|
+
if (bucket === void 0) {
|
|
1874
|
+
bucket = { path: path2, constraints: [] };
|
|
1875
|
+
grouped.set(key, bucket);
|
|
1876
|
+
}
|
|
1877
|
+
bucket.constraints.push(constraint);
|
|
1878
|
+
}
|
|
1879
|
+
return [...grouped.values()].map(
|
|
1880
|
+
(group) => resolveConstraintTargetState(fieldName, fieldType, group.path, group.constraints, typeRegistry)
|
|
1881
|
+
);
|
|
1882
|
+
}
|
|
1883
|
+
function addContradiction(diagnostics, message, primary, related) {
|
|
1884
|
+
diagnostics.push({
|
|
1885
|
+
code: "CONTRADICTING_CONSTRAINTS",
|
|
1886
|
+
message,
|
|
1887
|
+
severity: "error",
|
|
1888
|
+
primaryLocation: primary,
|
|
1889
|
+
relatedLocations: [related]
|
|
1890
|
+
});
|
|
1891
|
+
}
|
|
1892
|
+
function addTypeMismatch(diagnostics, message, primary) {
|
|
1893
|
+
diagnostics.push({
|
|
1894
|
+
code: "TYPE_MISMATCH",
|
|
1895
|
+
message,
|
|
1896
|
+
severity: "error",
|
|
1897
|
+
primaryLocation: primary,
|
|
1898
|
+
relatedLocations: []
|
|
1899
|
+
});
|
|
1900
|
+
}
|
|
1901
|
+
function addUnknownExtension(diagnostics, message, primary) {
|
|
1902
|
+
diagnostics.push({
|
|
1903
|
+
code: "UNKNOWN_EXTENSION",
|
|
1904
|
+
message,
|
|
1905
|
+
severity: "warning",
|
|
1906
|
+
primaryLocation: primary,
|
|
1907
|
+
relatedLocations: []
|
|
1908
|
+
});
|
|
1909
|
+
}
|
|
1910
|
+
function addUnknownPathTarget(diagnostics, message, primary) {
|
|
1911
|
+
diagnostics.push({
|
|
1912
|
+
code: "UNKNOWN_PATH_TARGET",
|
|
1913
|
+
message,
|
|
1914
|
+
severity: "error",
|
|
1915
|
+
primaryLocation: primary,
|
|
1916
|
+
relatedLocations: []
|
|
1917
|
+
});
|
|
1918
|
+
}
|
|
1919
|
+
function addConstraintBroadening(diagnostics, message, primary, related) {
|
|
1920
|
+
diagnostics.push({
|
|
1921
|
+
code: "CONSTRAINT_BROADENING",
|
|
1922
|
+
message,
|
|
1923
|
+
severity: "error",
|
|
1924
|
+
primaryLocation: primary,
|
|
1925
|
+
relatedLocations: [related]
|
|
1926
|
+
});
|
|
1927
|
+
}
|
|
1928
|
+
function getExtensionIdFromConstraintId(constraintId) {
|
|
1929
|
+
const separator = constraintId.lastIndexOf("/");
|
|
1930
|
+
if (separator <= 0) {
|
|
1931
|
+
return null;
|
|
1932
|
+
}
|
|
1933
|
+
return constraintId.slice(0, separator);
|
|
1934
|
+
}
|
|
1935
|
+
function typeLabel(type) {
|
|
1936
|
+
switch (type.kind) {
|
|
1937
|
+
case "primitive":
|
|
1938
|
+
return type.primitiveKind;
|
|
1939
|
+
case "enum":
|
|
1940
|
+
return "enum";
|
|
1941
|
+
case "array":
|
|
1942
|
+
return "array";
|
|
1943
|
+
case "object":
|
|
1944
|
+
return "object";
|
|
1945
|
+
case "record":
|
|
1946
|
+
return "record";
|
|
1947
|
+
case "union":
|
|
1948
|
+
return "union";
|
|
1949
|
+
case "reference":
|
|
1950
|
+
return `reference(${type.name})`;
|
|
1951
|
+
case "dynamic":
|
|
1952
|
+
return `dynamic(${type.dynamicKind})`;
|
|
1953
|
+
case "custom":
|
|
1954
|
+
return `custom(${type.typeId})`;
|
|
1955
|
+
default: {
|
|
1956
|
+
const exhaustive = type;
|
|
1957
|
+
return String(exhaustive);
|
|
1958
|
+
}
|
|
1959
|
+
}
|
|
1960
|
+
}
|
|
1961
|
+
function isJsonObject(value) {
|
|
1962
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1963
|
+
}
|
|
1964
|
+
function isJsonArray(value) {
|
|
1965
|
+
return Array.isArray(value);
|
|
1966
|
+
}
|
|
1967
|
+
function jsonValueEquals(left, right) {
|
|
1968
|
+
if (left === right) {
|
|
1969
|
+
return true;
|
|
1970
|
+
}
|
|
1971
|
+
if (isJsonArray(left) || isJsonArray(right)) {
|
|
1972
|
+
if (!isJsonArray(left) || !isJsonArray(right) || left.length !== right.length) {
|
|
1973
|
+
return false;
|
|
1974
|
+
}
|
|
1975
|
+
for (const [index, item] of left.entries()) {
|
|
1976
|
+
const rightItem = right[index];
|
|
1977
|
+
if (rightItem === void 0 || !jsonValueEquals(item, rightItem)) {
|
|
1978
|
+
return false;
|
|
1979
|
+
}
|
|
1980
|
+
}
|
|
1981
|
+
return true;
|
|
1982
|
+
}
|
|
1983
|
+
if (isJsonObject(left) || isJsonObject(right)) {
|
|
1984
|
+
if (!isJsonObject(left) || !isJsonObject(right)) {
|
|
1985
|
+
return false;
|
|
1986
|
+
}
|
|
1987
|
+
const leftKeys = Object.keys(left).sort();
|
|
1988
|
+
const rightKeys = Object.keys(right).sort();
|
|
1989
|
+
if (leftKeys.length !== rightKeys.length) {
|
|
1990
|
+
return false;
|
|
1991
|
+
}
|
|
1992
|
+
return leftKeys.every((key, index) => {
|
|
1993
|
+
const rightKey = rightKeys[index];
|
|
1994
|
+
if (rightKey !== key) {
|
|
1995
|
+
return false;
|
|
1996
|
+
}
|
|
1997
|
+
const leftValue = left[key];
|
|
1998
|
+
const rightValue = right[rightKey];
|
|
1999
|
+
return leftValue !== void 0 && rightValue !== void 0 && jsonValueEquals(leftValue, rightValue);
|
|
2000
|
+
});
|
|
2001
|
+
}
|
|
2002
|
+
return false;
|
|
2003
|
+
}
|
|
2004
|
+
function findNumeric(constraints, constraintKind) {
|
|
2005
|
+
return constraints.find(
|
|
2006
|
+
(constraint) => constraint.constraintKind === constraintKind
|
|
2007
|
+
);
|
|
2008
|
+
}
|
|
2009
|
+
function findLength(constraints, constraintKind) {
|
|
2010
|
+
return constraints.find(
|
|
2011
|
+
(constraint) => constraint.constraintKind === constraintKind
|
|
2012
|
+
);
|
|
2013
|
+
}
|
|
2014
|
+
function findAllowedMembers(constraints) {
|
|
2015
|
+
return constraints.filter(
|
|
2016
|
+
(constraint) => constraint.constraintKind === "allowedMembers"
|
|
2017
|
+
);
|
|
2018
|
+
}
|
|
2019
|
+
function findConstConstraints(constraints) {
|
|
2020
|
+
return constraints.filter(
|
|
2021
|
+
(constraint) => constraint.constraintKind === "const"
|
|
2022
|
+
);
|
|
2023
|
+
}
|
|
2024
|
+
function isOrderedBoundConstraint(constraint) {
|
|
2025
|
+
return constraint.constraintKind === "minimum" || constraint.constraintKind === "exclusiveMinimum" || constraint.constraintKind === "minLength" || constraint.constraintKind === "minItems" || constraint.constraintKind === "maximum" || constraint.constraintKind === "exclusiveMaximum" || constraint.constraintKind === "maxLength" || constraint.constraintKind === "maxItems";
|
|
2026
|
+
}
|
|
2027
|
+
function constraintPathKey(constraint) {
|
|
2028
|
+
return constraint.path?.segments.join(".") ?? "";
|
|
2029
|
+
}
|
|
2030
|
+
function orderedBoundFamily(kind) {
|
|
2031
|
+
switch (kind) {
|
|
2032
|
+
case "minimum":
|
|
2033
|
+
case "exclusiveMinimum":
|
|
2034
|
+
return "numeric-lower";
|
|
2035
|
+
case "maximum":
|
|
2036
|
+
case "exclusiveMaximum":
|
|
2037
|
+
return "numeric-upper";
|
|
2038
|
+
case "minLength":
|
|
2039
|
+
return "minLength";
|
|
2040
|
+
case "minItems":
|
|
2041
|
+
return "minItems";
|
|
2042
|
+
case "maxLength":
|
|
2043
|
+
return "maxLength";
|
|
2044
|
+
case "maxItems":
|
|
2045
|
+
return "maxItems";
|
|
2046
|
+
default: {
|
|
2047
|
+
const exhaustive = kind;
|
|
2048
|
+
return exhaustive;
|
|
2049
|
+
}
|
|
2050
|
+
}
|
|
2051
|
+
}
|
|
2052
|
+
function isNumericLowerKind(kind) {
|
|
2053
|
+
return kind === "minimum" || kind === "exclusiveMinimum";
|
|
2054
|
+
}
|
|
2055
|
+
function isNumericUpperKind(kind) {
|
|
2056
|
+
return kind === "maximum" || kind === "exclusiveMaximum";
|
|
2057
|
+
}
|
|
2058
|
+
function describeConstraintTag(constraint) {
|
|
2059
|
+
return `@${constraint.constraintKind}`;
|
|
2060
|
+
}
|
|
2061
|
+
function compareConstraintStrength(current, previous) {
|
|
2062
|
+
const family = orderedBoundFamily(current.constraintKind);
|
|
2063
|
+
if (family === "numeric-lower") {
|
|
2064
|
+
if (!isNumericLowerKind(current.constraintKind) || !isNumericLowerKind(previous.constraintKind)) {
|
|
2065
|
+
throw new Error("numeric-lower family received non-numeric lower-bound constraint");
|
|
2066
|
+
}
|
|
2067
|
+
if (current.value !== previous.value) {
|
|
2068
|
+
return current.value > previous.value ? 1 : -1;
|
|
2069
|
+
}
|
|
2070
|
+
if (current.constraintKind === "exclusiveMinimum" && previous.constraintKind === "minimum") {
|
|
2071
|
+
return 1;
|
|
2072
|
+
}
|
|
2073
|
+
if (current.constraintKind === "minimum" && previous.constraintKind === "exclusiveMinimum") {
|
|
2074
|
+
return -1;
|
|
2075
|
+
}
|
|
2076
|
+
return 0;
|
|
2077
|
+
}
|
|
2078
|
+
if (family === "numeric-upper") {
|
|
2079
|
+
if (!isNumericUpperKind(current.constraintKind) || !isNumericUpperKind(previous.constraintKind)) {
|
|
2080
|
+
throw new Error("numeric-upper family received non-numeric upper-bound constraint");
|
|
2081
|
+
}
|
|
2082
|
+
if (current.value !== previous.value) {
|
|
2083
|
+
return current.value < previous.value ? 1 : -1;
|
|
2084
|
+
}
|
|
2085
|
+
if (current.constraintKind === "exclusiveMaximum" && previous.constraintKind === "maximum") {
|
|
2086
|
+
return 1;
|
|
2087
|
+
}
|
|
2088
|
+
if (current.constraintKind === "maximum" && previous.constraintKind === "exclusiveMaximum") {
|
|
2089
|
+
return -1;
|
|
2090
|
+
}
|
|
2091
|
+
return 0;
|
|
2092
|
+
}
|
|
2093
|
+
switch (family) {
|
|
2094
|
+
case "minLength":
|
|
2095
|
+
case "minItems":
|
|
2096
|
+
if (current.value === previous.value) {
|
|
2097
|
+
return 0;
|
|
2098
|
+
}
|
|
2099
|
+
return current.value > previous.value ? 1 : -1;
|
|
2100
|
+
case "maxLength":
|
|
2101
|
+
case "maxItems":
|
|
2102
|
+
if (current.value === previous.value) {
|
|
2103
|
+
return 0;
|
|
2104
|
+
}
|
|
2105
|
+
return current.value < previous.value ? 1 : -1;
|
|
2106
|
+
default: {
|
|
2107
|
+
const exhaustive = family;
|
|
2108
|
+
return exhaustive;
|
|
2109
|
+
}
|
|
2110
|
+
}
|
|
2111
|
+
}
|
|
2112
|
+
function compareSemanticInclusivity(currentInclusive, previousInclusive) {
|
|
2113
|
+
if (currentInclusive === previousInclusive) {
|
|
2114
|
+
return 0;
|
|
2115
|
+
}
|
|
2116
|
+
return currentInclusive ? -1 : 1;
|
|
2117
|
+
}
|
|
2118
|
+
function compareCustomConstraintStrength(current, previous) {
|
|
2119
|
+
const order = current.comparePayloads(current.constraint.payload, previous.constraint.payload);
|
|
2120
|
+
const equalPayloadTiebreaker = order === 0 ? compareSemanticInclusivity(current.role.inclusive, previous.role.inclusive) : order;
|
|
2121
|
+
switch (current.role.bound) {
|
|
2122
|
+
case "lower":
|
|
2123
|
+
return equalPayloadTiebreaker;
|
|
2124
|
+
case "upper":
|
|
2125
|
+
return equalPayloadTiebreaker === 0 ? 0 : -equalPayloadTiebreaker;
|
|
2126
|
+
case "exact":
|
|
2127
|
+
return order === 0 ? 0 : Number.NaN;
|
|
2128
|
+
default: {
|
|
2129
|
+
const exhaustive = current.role.bound;
|
|
2130
|
+
return exhaustive;
|
|
2131
|
+
}
|
|
2132
|
+
}
|
|
2133
|
+
}
|
|
2134
|
+
function customConstraintsContradict(lower, upper) {
|
|
2135
|
+
const order = lower.comparePayloads(lower.constraint.payload, upper.constraint.payload);
|
|
2136
|
+
if (order > 0) {
|
|
2137
|
+
return true;
|
|
2138
|
+
}
|
|
2139
|
+
if (order < 0) {
|
|
2140
|
+
return false;
|
|
2141
|
+
}
|
|
2142
|
+
return !lower.role.inclusive || !upper.role.inclusive;
|
|
2143
|
+
}
|
|
2144
|
+
function describeCustomConstraintTag(constraint) {
|
|
2145
|
+
return constraint.provenance.tagName ?? constraint.constraintId;
|
|
2146
|
+
}
|
|
2147
|
+
function isNullType(type) {
|
|
2148
|
+
return type.kind === "primitive" && type.primitiveKind === "null";
|
|
2149
|
+
}
|
|
2150
|
+
function collectCustomConstraintCandidateTypes(type, typeRegistry) {
|
|
2151
|
+
const effectiveType = dereferenceAnalysisType(type, typeRegistry);
|
|
2152
|
+
const candidates = [effectiveType];
|
|
2153
|
+
if (effectiveType.kind === "array") {
|
|
2154
|
+
candidates.push(...collectCustomConstraintCandidateTypes(effectiveType.items, typeRegistry));
|
|
2155
|
+
}
|
|
2156
|
+
if (effectiveType.kind === "union") {
|
|
2157
|
+
const memberTypes = effectiveType.members.map(
|
|
2158
|
+
(member) => dereferenceAnalysisType(member, typeRegistry)
|
|
2159
|
+
);
|
|
2160
|
+
const nonNullMembers = memberTypes.filter((member) => !isNullType(member));
|
|
2161
|
+
if (nonNullMembers.length === 1 && nonNullMembers.length < memberTypes.length) {
|
|
2162
|
+
const [nullableMember] = nonNullMembers;
|
|
2163
|
+
if (nullableMember !== void 0) {
|
|
2164
|
+
candidates.push(...collectCustomConstraintCandidateTypes(nullableMember, typeRegistry));
|
|
2165
|
+
}
|
|
2166
|
+
}
|
|
2167
|
+
}
|
|
2168
|
+
return candidates;
|
|
2169
|
+
}
|
|
2170
|
+
function checkNumericContradictions(diagnostics, fieldName, constraints) {
|
|
2171
|
+
const min = findNumeric(constraints, "minimum");
|
|
2172
|
+
const max = findNumeric(constraints, "maximum");
|
|
2173
|
+
const exMin = findNumeric(constraints, "exclusiveMinimum");
|
|
2174
|
+
const exMax = findNumeric(constraints, "exclusiveMaximum");
|
|
2175
|
+
if (min !== void 0 && max !== void 0 && min.value > max.value) {
|
|
2176
|
+
addContradiction(
|
|
2177
|
+
diagnostics,
|
|
2178
|
+
`Field "${fieldName}": minimum (${String(min.value)}) is greater than maximum (${String(max.value)})`,
|
|
2179
|
+
min.provenance,
|
|
2180
|
+
max.provenance
|
|
2181
|
+
);
|
|
2182
|
+
}
|
|
2183
|
+
if (exMin !== void 0 && max !== void 0 && exMin.value >= max.value) {
|
|
2184
|
+
addContradiction(
|
|
2185
|
+
diagnostics,
|
|
2186
|
+
`Field "${fieldName}": exclusiveMinimum (${String(exMin.value)}) is greater than or equal to maximum (${String(max.value)})`,
|
|
2187
|
+
exMin.provenance,
|
|
2188
|
+
max.provenance
|
|
2189
|
+
);
|
|
2190
|
+
}
|
|
2191
|
+
if (min !== void 0 && exMax !== void 0 && min.value >= exMax.value) {
|
|
2192
|
+
addContradiction(
|
|
2193
|
+
diagnostics,
|
|
2194
|
+
`Field "${fieldName}": minimum (${String(min.value)}) is greater than or equal to exclusiveMaximum (${String(exMax.value)})`,
|
|
2195
|
+
min.provenance,
|
|
2196
|
+
exMax.provenance
|
|
2197
|
+
);
|
|
2198
|
+
}
|
|
2199
|
+
if (exMin !== void 0 && exMax !== void 0 && exMin.value >= exMax.value) {
|
|
2200
|
+
addContradiction(
|
|
2201
|
+
diagnostics,
|
|
2202
|
+
`Field "${fieldName}": exclusiveMinimum (${String(exMin.value)}) is greater than or equal to exclusiveMaximum (${String(exMax.value)})`,
|
|
2203
|
+
exMin.provenance,
|
|
2204
|
+
exMax.provenance
|
|
2205
|
+
);
|
|
2206
|
+
}
|
|
2207
|
+
}
|
|
2208
|
+
function checkLengthContradictions(diagnostics, fieldName, constraints) {
|
|
2209
|
+
const minLen = findLength(constraints, "minLength");
|
|
2210
|
+
const maxLen = findLength(constraints, "maxLength");
|
|
2211
|
+
if (minLen !== void 0 && maxLen !== void 0 && minLen.value > maxLen.value) {
|
|
2212
|
+
addContradiction(
|
|
2213
|
+
diagnostics,
|
|
2214
|
+
`Field "${fieldName}": minLength (${String(minLen.value)}) is greater than maxLength (${String(maxLen.value)})`,
|
|
2215
|
+
minLen.provenance,
|
|
2216
|
+
maxLen.provenance
|
|
2217
|
+
);
|
|
2218
|
+
}
|
|
2219
|
+
const minItems = findLength(constraints, "minItems");
|
|
2220
|
+
const maxItems = findLength(constraints, "maxItems");
|
|
2221
|
+
if (minItems !== void 0 && maxItems !== void 0 && minItems.value > maxItems.value) {
|
|
2222
|
+
addContradiction(
|
|
2223
|
+
diagnostics,
|
|
2224
|
+
`Field "${fieldName}": minItems (${String(minItems.value)}) is greater than maxItems (${String(maxItems.value)})`,
|
|
2225
|
+
minItems.provenance,
|
|
2226
|
+
maxItems.provenance
|
|
2227
|
+
);
|
|
2228
|
+
}
|
|
2229
|
+
}
|
|
2230
|
+
function checkAllowedMembersContradiction(diagnostics, fieldName, constraints) {
|
|
2231
|
+
const members = findAllowedMembers(constraints);
|
|
2232
|
+
if (members.length < 2) {
|
|
2233
|
+
return;
|
|
2234
|
+
}
|
|
2235
|
+
const firstSet = new Set(members[0]?.members ?? []);
|
|
2236
|
+
for (let index = 1; index < members.length; index += 1) {
|
|
2237
|
+
const current = members[index];
|
|
2238
|
+
if (current === void 0) {
|
|
2239
|
+
continue;
|
|
2240
|
+
}
|
|
2241
|
+
for (const member of firstSet) {
|
|
2242
|
+
if (!current.members.includes(member)) {
|
|
2243
|
+
firstSet.delete(member);
|
|
2244
|
+
}
|
|
2245
|
+
}
|
|
2246
|
+
}
|
|
2247
|
+
if (firstSet.size === 0) {
|
|
2248
|
+
const first = members[0];
|
|
2249
|
+
const second = members[1];
|
|
2250
|
+
if (first !== void 0 && second !== void 0) {
|
|
2251
|
+
addContradiction(
|
|
2252
|
+
diagnostics,
|
|
2253
|
+
`Field "${fieldName}": allowedMembers constraints have an empty intersection (no valid values remain)`,
|
|
2254
|
+
first.provenance,
|
|
2255
|
+
second.provenance
|
|
2256
|
+
);
|
|
2257
|
+
}
|
|
2258
|
+
}
|
|
2259
|
+
}
|
|
2260
|
+
function checkConstContradictions(diagnostics, fieldName, constraints) {
|
|
2261
|
+
const constConstraints = findConstConstraints(constraints);
|
|
2262
|
+
if (constConstraints.length < 2) {
|
|
2263
|
+
return;
|
|
2264
|
+
}
|
|
2265
|
+
const first = constConstraints[0];
|
|
2266
|
+
if (first === void 0) {
|
|
2267
|
+
return;
|
|
2268
|
+
}
|
|
2269
|
+
for (let index = 1; index < constConstraints.length; index += 1) {
|
|
2270
|
+
const current = constConstraints[index];
|
|
2271
|
+
if (current === void 0 || jsonValueEquals(first.value, current.value)) {
|
|
2272
|
+
continue;
|
|
2273
|
+
}
|
|
2274
|
+
addContradiction(
|
|
2275
|
+
diagnostics,
|
|
2276
|
+
`Field "${fieldName}": conflicting @const constraints require both ${JSON.stringify(first.value)} and ${JSON.stringify(current.value)}`,
|
|
2277
|
+
first.provenance,
|
|
2278
|
+
current.provenance
|
|
2279
|
+
);
|
|
2280
|
+
}
|
|
2281
|
+
}
|
|
2282
|
+
function checkConstraintBroadening(diagnostics, fieldName, constraints) {
|
|
2283
|
+
const strongestByKey = /* @__PURE__ */ new Map();
|
|
2284
|
+
for (const constraint of constraints) {
|
|
2285
|
+
if (!isOrderedBoundConstraint(constraint)) {
|
|
2286
|
+
continue;
|
|
2287
|
+
}
|
|
2288
|
+
const key = `${orderedBoundFamily(constraint.constraintKind)}:${constraintPathKey(constraint)}`;
|
|
2289
|
+
const previous = strongestByKey.get(key);
|
|
2290
|
+
if (previous === void 0) {
|
|
2291
|
+
strongestByKey.set(key, constraint);
|
|
2292
|
+
continue;
|
|
2293
|
+
}
|
|
2294
|
+
const strength = compareConstraintStrength(constraint, previous);
|
|
2295
|
+
if (strength < 0) {
|
|
2296
|
+
addConstraintBroadening(
|
|
2297
|
+
diagnostics,
|
|
2298
|
+
`Field "${fieldName}": ${describeConstraintTag(constraint)} (${String(constraint.value)}) is broader than earlier ${describeConstraintTag(previous)} (${String(previous.value)}). Constraints can only narrow.`,
|
|
2299
|
+
constraint.provenance,
|
|
2300
|
+
previous.provenance
|
|
2301
|
+
);
|
|
2302
|
+
continue;
|
|
2303
|
+
}
|
|
2304
|
+
if (strength > 0) {
|
|
2305
|
+
strongestByKey.set(key, constraint);
|
|
2306
|
+
}
|
|
2307
|
+
}
|
|
2308
|
+
}
|
|
2309
|
+
function checkCustomConstraintSemantics(diagnostics, fieldName, constraints, extensionRegistry) {
|
|
2310
|
+
if (extensionRegistry === void 0) {
|
|
2311
|
+
return;
|
|
2312
|
+
}
|
|
2313
|
+
const strongestByKey = /* @__PURE__ */ new Map();
|
|
2314
|
+
const lowerByFamily = /* @__PURE__ */ new Map();
|
|
2315
|
+
const upperByFamily = /* @__PURE__ */ new Map();
|
|
2316
|
+
for (const constraint of constraints) {
|
|
2317
|
+
if (constraint.constraintKind !== "custom") {
|
|
2318
|
+
continue;
|
|
2319
|
+
}
|
|
2320
|
+
const registration = extensionRegistry.findConstraint(constraint.constraintId);
|
|
2321
|
+
if (registration?.comparePayloads === void 0 || registration.semanticRole === void 0) {
|
|
2322
|
+
continue;
|
|
2323
|
+
}
|
|
2324
|
+
const entry = {
|
|
2325
|
+
constraint,
|
|
2326
|
+
comparePayloads: registration.comparePayloads,
|
|
2327
|
+
role: registration.semanticRole
|
|
2328
|
+
};
|
|
2329
|
+
const familyKey = `${registration.semanticRole.family}:${constraintPathKey(constraint)}`;
|
|
2330
|
+
const boundKey = `${familyKey}:${registration.semanticRole.bound}`;
|
|
2331
|
+
const previous = strongestByKey.get(boundKey);
|
|
2332
|
+
if (previous !== void 0) {
|
|
2333
|
+
const strength = compareCustomConstraintStrength(entry, previous);
|
|
2334
|
+
if (Number.isNaN(strength)) {
|
|
2335
|
+
addContradiction(
|
|
2336
|
+
diagnostics,
|
|
2337
|
+
`Field "${fieldName}": ${describeCustomConstraintTag(constraint)} conflicts with ${describeCustomConstraintTag(previous.constraint)}`,
|
|
2338
|
+
constraint.provenance,
|
|
2339
|
+
previous.constraint.provenance
|
|
2340
|
+
);
|
|
2341
|
+
continue;
|
|
2342
|
+
}
|
|
2343
|
+
if (strength < 0) {
|
|
2344
|
+
addConstraintBroadening(
|
|
2345
|
+
diagnostics,
|
|
2346
|
+
`Field "${fieldName}": ${describeCustomConstraintTag(constraint)} is broader than earlier ${describeCustomConstraintTag(previous.constraint)}. Constraints can only narrow.`,
|
|
2347
|
+
constraint.provenance,
|
|
2348
|
+
previous.constraint.provenance
|
|
2349
|
+
);
|
|
2350
|
+
continue;
|
|
2351
|
+
}
|
|
2352
|
+
if (strength > 0) {
|
|
2353
|
+
strongestByKey.set(boundKey, entry);
|
|
2354
|
+
}
|
|
2355
|
+
} else {
|
|
2356
|
+
strongestByKey.set(boundKey, entry);
|
|
2357
|
+
}
|
|
2358
|
+
if (registration.semanticRole.bound === "lower") {
|
|
2359
|
+
lowerByFamily.set(familyKey, strongestByKey.get(boundKey) ?? entry);
|
|
2360
|
+
} else if (registration.semanticRole.bound === "upper") {
|
|
2361
|
+
upperByFamily.set(familyKey, strongestByKey.get(boundKey) ?? entry);
|
|
2362
|
+
}
|
|
2363
|
+
}
|
|
2364
|
+
for (const [familyKey, lower] of lowerByFamily) {
|
|
2365
|
+
const upper = upperByFamily.get(familyKey);
|
|
2366
|
+
if (upper === void 0 || !customConstraintsContradict(lower, upper)) {
|
|
2367
|
+
continue;
|
|
2368
|
+
}
|
|
2369
|
+
addContradiction(
|
|
2370
|
+
diagnostics,
|
|
2371
|
+
`Field "${fieldName}": ${describeCustomConstraintTag(lower.constraint)} contradicts ${describeCustomConstraintTag(upper.constraint)}`,
|
|
2372
|
+
lower.constraint.provenance,
|
|
2373
|
+
upper.constraint.provenance
|
|
2374
|
+
);
|
|
2375
|
+
}
|
|
2376
|
+
}
|
|
2377
|
+
function checkCustomConstraint(diagnostics, fieldName, type, constraint, typeRegistry, extensionRegistry) {
|
|
2378
|
+
if (extensionRegistry === void 0) {
|
|
2379
|
+
return;
|
|
2380
|
+
}
|
|
2381
|
+
const registration = extensionRegistry.findConstraint(constraint.constraintId);
|
|
2382
|
+
if (registration === void 0) {
|
|
2383
|
+
addUnknownExtension(
|
|
2384
|
+
diagnostics,
|
|
2385
|
+
`Field "${fieldName}": custom constraint "${constraint.constraintId}" is not registered in the extension registry`,
|
|
2386
|
+
constraint.provenance
|
|
2387
|
+
);
|
|
2388
|
+
return;
|
|
2389
|
+
}
|
|
2390
|
+
const candidateTypes = collectCustomConstraintCandidateTypes(type, typeRegistry);
|
|
2391
|
+
const normalizedTagName = constraint.provenance.tagName === void 0 ? void 0 : normalizeConstraintTagName2(constraint.provenance.tagName.replace(/^@/, ""));
|
|
2392
|
+
if (normalizedTagName !== void 0) {
|
|
2393
|
+
const tagRegistration = extensionRegistry.findConstraintTag(normalizedTagName);
|
|
2394
|
+
const extensionId = getExtensionIdFromConstraintId(constraint.constraintId);
|
|
2395
|
+
if (extensionId !== null && tagRegistration?.extensionId === extensionId && tagRegistration.registration.constraintName === registration.constraintName && !candidateTypes.some(
|
|
2396
|
+
(candidateType) => tagRegistration.registration.isApplicableToType?.(candidateType) !== false
|
|
2397
|
+
)) {
|
|
2398
|
+
addTypeMismatch(
|
|
2399
|
+
diagnostics,
|
|
2400
|
+
`Field "${fieldName}": custom constraint "${constraint.constraintId}" is not applicable to type "${typeLabel(type)}"`,
|
|
2401
|
+
constraint.provenance
|
|
2402
|
+
);
|
|
2403
|
+
return;
|
|
2404
|
+
}
|
|
2405
|
+
}
|
|
2406
|
+
if (registration.applicableTypes === null) {
|
|
2407
|
+
if (!candidateTypes.some(
|
|
2408
|
+
(candidateType) => registration.isApplicableToType?.(candidateType) !== false
|
|
2409
|
+
)) {
|
|
2410
|
+
addTypeMismatch(
|
|
2411
|
+
diagnostics,
|
|
2412
|
+
`Field "${fieldName}": custom constraint "${constraint.constraintId}" is not applicable to type "${typeLabel(type)}"`,
|
|
2413
|
+
constraint.provenance
|
|
2414
|
+
);
|
|
2415
|
+
}
|
|
2416
|
+
return;
|
|
2417
|
+
}
|
|
2418
|
+
const applicableTypes = registration.applicableTypes;
|
|
2419
|
+
const matchesApplicableType = candidateTypes.some(
|
|
2420
|
+
(candidateType) => applicableTypes.includes(candidateType.kind) && registration.isApplicableToType?.(candidateType) !== false
|
|
2421
|
+
);
|
|
2422
|
+
if (!matchesApplicableType) {
|
|
2423
|
+
addTypeMismatch(
|
|
2424
|
+
diagnostics,
|
|
2425
|
+
`Field "${fieldName}": custom constraint "${constraint.constraintId}" is not applicable to type "${typeLabel(type)}"`,
|
|
2426
|
+
constraint.provenance
|
|
2427
|
+
);
|
|
2428
|
+
}
|
|
2429
|
+
}
|
|
2430
|
+
function checkConstraintOnType(diagnostics, fieldName, type, constraint, typeRegistry, extensionRegistry) {
|
|
2431
|
+
const effectiveType = dereferenceAnalysisType(type, typeRegistry);
|
|
2432
|
+
const isNumber = effectiveType.kind === "primitive" && ["number", "integer", "bigint"].includes(effectiveType.primitiveKind);
|
|
2433
|
+
const isString = effectiveType.kind === "primitive" && effectiveType.primitiveKind === "string";
|
|
2434
|
+
const isArray = effectiveType.kind === "array";
|
|
2435
|
+
const isEnum = effectiveType.kind === "enum";
|
|
2436
|
+
const arrayItemType = effectiveType.kind === "array" ? dereferenceAnalysisType(effectiveType.items, typeRegistry) : void 0;
|
|
2437
|
+
const isStringArray2 = arrayItemType?.kind === "primitive" && arrayItemType.primitiveKind === "string";
|
|
2438
|
+
const label = typeLabel(effectiveType);
|
|
2439
|
+
switch (constraint.constraintKind) {
|
|
2440
|
+
case "minimum":
|
|
2441
|
+
case "maximum":
|
|
2442
|
+
case "exclusiveMinimum":
|
|
2443
|
+
case "exclusiveMaximum":
|
|
2444
|
+
case "multipleOf":
|
|
2445
|
+
if (!isNumber) {
|
|
2446
|
+
addTypeMismatch(
|
|
2447
|
+
diagnostics,
|
|
2448
|
+
`Field "${fieldName}": constraint "${constraint.constraintKind}" is only valid on number fields, but field type is "${label}"`,
|
|
2449
|
+
constraint.provenance
|
|
2450
|
+
);
|
|
2451
|
+
}
|
|
2452
|
+
break;
|
|
2453
|
+
case "minLength":
|
|
2454
|
+
case "maxLength":
|
|
2455
|
+
case "pattern":
|
|
2456
|
+
if (!isString && !isStringArray2) {
|
|
2457
|
+
addTypeMismatch(
|
|
2458
|
+
diagnostics,
|
|
2459
|
+
`Field "${fieldName}": constraint "${constraint.constraintKind}" is only valid on string fields or string array items, but field type is "${label}"`,
|
|
2460
|
+
constraint.provenance
|
|
2461
|
+
);
|
|
2462
|
+
}
|
|
2463
|
+
break;
|
|
2464
|
+
case "minItems":
|
|
2465
|
+
case "maxItems":
|
|
2466
|
+
case "uniqueItems":
|
|
2467
|
+
if (!isArray) {
|
|
2468
|
+
addTypeMismatch(
|
|
2469
|
+
diagnostics,
|
|
2470
|
+
`Field "${fieldName}": constraint "${constraint.constraintKind}" is only valid on array fields, but field type is "${label}"`,
|
|
2471
|
+
constraint.provenance
|
|
2472
|
+
);
|
|
2473
|
+
}
|
|
2474
|
+
break;
|
|
2475
|
+
case "allowedMembers":
|
|
2476
|
+
if (!isEnum) {
|
|
2477
|
+
addTypeMismatch(
|
|
2478
|
+
diagnostics,
|
|
2479
|
+
`Field "${fieldName}": constraint "allowedMembers" is only valid on enum fields, but field type is "${label}"`,
|
|
2480
|
+
constraint.provenance
|
|
2481
|
+
);
|
|
2482
|
+
}
|
|
2483
|
+
break;
|
|
2484
|
+
case "const": {
|
|
2485
|
+
const isPrimitiveConstType = effectiveType.kind === "primitive" && ["string", "number", "integer", "bigint", "boolean", "null"].includes(
|
|
2486
|
+
effectiveType.primitiveKind
|
|
2487
|
+
) || effectiveType.kind === "enum";
|
|
2488
|
+
if (!isPrimitiveConstType) {
|
|
2489
|
+
addTypeMismatch(
|
|
2490
|
+
diagnostics,
|
|
2491
|
+
`Field "${fieldName}": constraint "const" is only valid on primitive or enum fields, but field type is "${label}"`,
|
|
2492
|
+
constraint.provenance
|
|
2493
|
+
);
|
|
2494
|
+
break;
|
|
2495
|
+
}
|
|
2496
|
+
if (effectiveType.kind === "primitive") {
|
|
2497
|
+
const valueType = constraint.value === null ? "null" : Array.isArray(constraint.value) ? "array" : typeof constraint.value;
|
|
2498
|
+
const expectedValueType = effectiveType.primitiveKind === "integer" || effectiveType.primitiveKind === "bigint" ? "number" : effectiveType.primitiveKind;
|
|
2499
|
+
if (valueType !== expectedValueType) {
|
|
2500
|
+
addTypeMismatch(
|
|
2501
|
+
diagnostics,
|
|
2502
|
+
`Field "${fieldName}": @const value type "${valueType}" is incompatible with field type "${effectiveType.primitiveKind}"`,
|
|
2503
|
+
constraint.provenance
|
|
2504
|
+
);
|
|
2505
|
+
}
|
|
2506
|
+
break;
|
|
2507
|
+
}
|
|
2508
|
+
const memberValues = effectiveType.members.map((member) => member.value);
|
|
2509
|
+
if (!memberValues.some((member) => jsonValueEquals(member, constraint.value))) {
|
|
2510
|
+
addTypeMismatch(
|
|
2511
|
+
diagnostics,
|
|
2512
|
+
`Field "${fieldName}": @const value ${JSON.stringify(constraint.value)} is not one of the enum members`,
|
|
2513
|
+
constraint.provenance
|
|
2514
|
+
);
|
|
2515
|
+
}
|
|
2516
|
+
break;
|
|
2517
|
+
}
|
|
2518
|
+
case "custom":
|
|
2519
|
+
checkCustomConstraint(
|
|
2520
|
+
diagnostics,
|
|
2521
|
+
fieldName,
|
|
2522
|
+
effectiveType,
|
|
2523
|
+
constraint,
|
|
2524
|
+
typeRegistry,
|
|
2525
|
+
extensionRegistry
|
|
2526
|
+
);
|
|
2527
|
+
break;
|
|
2528
|
+
default: {
|
|
2529
|
+
const exhaustive = constraint;
|
|
2530
|
+
throw new Error(`Unhandled constraint: ${JSON.stringify(exhaustive)}`);
|
|
2531
|
+
}
|
|
2532
|
+
}
|
|
2533
|
+
}
|
|
2534
|
+
function analyzeResolvedTargetState(diagnostics, state, typeRegistry, extensionRegistry) {
|
|
2535
|
+
checkNumericContradictions(diagnostics, state.targetName, state.effectiveConstraints);
|
|
2536
|
+
checkLengthContradictions(diagnostics, state.targetName, state.effectiveConstraints);
|
|
2537
|
+
checkAllowedMembersContradiction(diagnostics, state.targetName, state.effectiveConstraints);
|
|
2538
|
+
checkConstContradictions(diagnostics, state.targetName, state.effectiveConstraints);
|
|
2539
|
+
checkConstraintBroadening(diagnostics, state.targetName, state.effectiveConstraints);
|
|
2540
|
+
checkCustomConstraintSemantics(
|
|
2541
|
+
diagnostics,
|
|
2542
|
+
state.targetName,
|
|
2543
|
+
state.effectiveConstraints,
|
|
2544
|
+
extensionRegistry
|
|
2545
|
+
);
|
|
2546
|
+
for (const constraint of state.effectiveConstraints) {
|
|
2547
|
+
checkConstraintOnType(
|
|
2548
|
+
diagnostics,
|
|
2549
|
+
state.targetName,
|
|
2550
|
+
state.type,
|
|
2551
|
+
constraint,
|
|
2552
|
+
typeRegistry,
|
|
2553
|
+
extensionRegistry
|
|
2554
|
+
);
|
|
2555
|
+
}
|
|
2556
|
+
}
|
|
2557
|
+
function analyzeConstraintTargets(fieldName, fieldType, constraints, typeRegistry, options) {
|
|
2558
|
+
const diagnostics = [];
|
|
2559
|
+
const targetStates = buildConstraintTargetStates(fieldName, fieldType, constraints, typeRegistry);
|
|
2560
|
+
for (const targetState of targetStates) {
|
|
2561
|
+
switch (targetState.kind) {
|
|
2562
|
+
case "resolved":
|
|
2563
|
+
analyzeResolvedTargetState(
|
|
2564
|
+
diagnostics,
|
|
2565
|
+
targetState,
|
|
2566
|
+
typeRegistry,
|
|
2567
|
+
options?.extensionRegistry
|
|
2568
|
+
);
|
|
2569
|
+
break;
|
|
2570
|
+
case "missing-property":
|
|
2571
|
+
for (const constraint of targetState.localConstraints) {
|
|
2572
|
+
addUnknownPathTarget(
|
|
2573
|
+
diagnostics,
|
|
2574
|
+
`Field "${targetState.targetName}": path-targeted constraint "${constraint.constraintKind}" references unknown path segment "${targetState.segment}"`,
|
|
2575
|
+
constraint.provenance
|
|
2576
|
+
);
|
|
2577
|
+
}
|
|
2578
|
+
break;
|
|
2579
|
+
case "unresolvable":
|
|
2580
|
+
for (const constraint of targetState.localConstraints) {
|
|
2581
|
+
addTypeMismatch(
|
|
2582
|
+
diagnostics,
|
|
2583
|
+
`Field "${targetState.targetName}": path-targeted constraint "${constraint.constraintKind}" is invalid because type "${typeLabel(targetState.type)}" cannot be traversed`,
|
|
2584
|
+
constraint.provenance
|
|
2585
|
+
);
|
|
2586
|
+
}
|
|
2587
|
+
break;
|
|
2588
|
+
default: {
|
|
2589
|
+
const exhaustive = targetState;
|
|
2590
|
+
throw new Error(`Unhandled target state: ${String(exhaustive)}`);
|
|
2591
|
+
}
|
|
2592
|
+
}
|
|
2593
|
+
}
|
|
2594
|
+
return {
|
|
2595
|
+
diagnostics,
|
|
2596
|
+
targetStates
|
|
2597
|
+
};
|
|
2598
|
+
}
|
|
2599
|
+
|
|
2600
|
+
// src/constants.ts
|
|
2601
|
+
var FORM_SPEC_SYNTHETIC_BATCH_CACHE_ENTRIES = 64;
|
|
2602
|
+
|
|
2603
|
+
// src/file-snapshots.ts
|
|
2604
|
+
import * as ts4 from "typescript";
|
|
2605
|
+
|
|
2606
|
+
// src/compiler-signatures.ts
|
|
2607
|
+
import * as ts2 from "typescript";
|
|
2608
|
+
|
|
2609
|
+
// src/perf-tracing.ts
|
|
2610
|
+
var EMPTY_PERFORMANCE_EVENTS = Object.freeze([]);
|
|
2611
|
+
function getHighResolutionTime() {
|
|
2612
|
+
return performance.now();
|
|
2613
|
+
}
|
|
2614
|
+
var FormSpecPerformanceRecorderImpl = class {
|
|
2615
|
+
mutableEvents = [];
|
|
2616
|
+
get events() {
|
|
2617
|
+
return this.mutableEvents;
|
|
2618
|
+
}
|
|
2619
|
+
measure(name, detail, callback) {
|
|
2620
|
+
const startedAt = getHighResolutionTime();
|
|
2621
|
+
try {
|
|
2622
|
+
return callback();
|
|
2623
|
+
} finally {
|
|
2624
|
+
this.record({
|
|
2625
|
+
name,
|
|
2626
|
+
durationMs: getHighResolutionTime() - startedAt,
|
|
2627
|
+
...detail === void 0 ? {} : { detail }
|
|
2628
|
+
});
|
|
2629
|
+
}
|
|
2630
|
+
}
|
|
2631
|
+
record(event) {
|
|
2632
|
+
this.mutableEvents.push(event);
|
|
2633
|
+
}
|
|
2634
|
+
};
|
|
2635
|
+
var NoopFormSpecPerformanceRecorderImpl = class {
|
|
2636
|
+
get events() {
|
|
2637
|
+
return EMPTY_PERFORMANCE_EVENTS;
|
|
2638
|
+
}
|
|
2639
|
+
measure(_name, _detail, callback) {
|
|
2640
|
+
return callback();
|
|
2641
|
+
}
|
|
2642
|
+
record(_event) {
|
|
2643
|
+
}
|
|
2644
|
+
};
|
|
2645
|
+
var NOOP_FORMSPEC_PERFORMANCE_RECORDER = new NoopFormSpecPerformanceRecorderImpl();
|
|
2646
|
+
function createFormSpecPerformanceRecorder() {
|
|
2647
|
+
return new FormSpecPerformanceRecorderImpl();
|
|
2648
|
+
}
|
|
2649
|
+
function getFormSpecPerformanceNow() {
|
|
2650
|
+
return getHighResolutionTime();
|
|
2651
|
+
}
|
|
2652
|
+
function optionalMeasure(recorder, name, detail, callback) {
|
|
2653
|
+
return (recorder ?? NOOP_FORMSPEC_PERFORMANCE_RECORDER).measure(name, detail, callback);
|
|
2654
|
+
}
|
|
2655
|
+
|
|
2656
|
+
// src/compiler-signatures.ts
|
|
2657
|
+
var SYNTHETIC_CHECK_EVENT = {
|
|
2658
|
+
batch: "analysis.syntheticCheckBatch",
|
|
2659
|
+
narrowBatch: "analysis.narrowSyntheticCheckBatch"
|
|
2660
|
+
};
|
|
2661
|
+
var PRELUDE_LINES = [
|
|
2662
|
+
"type FormSpecPlacement =",
|
|
2663
|
+
' | "class"',
|
|
2664
|
+
' | "class-field"',
|
|
2665
|
+
' | "class-method"',
|
|
2666
|
+
' | "interface"',
|
|
2667
|
+
' | "interface-field"',
|
|
2668
|
+
' | "type-alias"',
|
|
2669
|
+
' | "type-alias-field"',
|
|
2670
|
+
' | "variable"',
|
|
2671
|
+
' | "function"',
|
|
2672
|
+
' | "function-parameter"',
|
|
2673
|
+
' | "method-parameter";',
|
|
2674
|
+
"",
|
|
2675
|
+
"type FormSpecCapability =",
|
|
2676
|
+
' | "numeric-comparable"',
|
|
2677
|
+
' | "string-like"',
|
|
2678
|
+
' | "array-like"',
|
|
2679
|
+
' | "enum-member-addressable"',
|
|
2680
|
+
' | "json-like"',
|
|
2681
|
+
' | "condition-like"',
|
|
2682
|
+
' | "object-like";',
|
|
2683
|
+
"",
|
|
2684
|
+
"interface TagContext<P extends FormSpecPlacement, Host, Subject> {",
|
|
2685
|
+
" readonly placement: P;",
|
|
2686
|
+
" readonly hostType: Host;",
|
|
2687
|
+
" readonly subjectType: Subject;",
|
|
2688
|
+
"}",
|
|
2689
|
+
"",
|
|
2690
|
+
"type NonNullish<T> = Exclude<T, null | undefined>;",
|
|
2691
|
+
"",
|
|
2692
|
+
"type ProvidesCapability<T, Capability extends FormSpecCapability> =",
|
|
2693
|
+
' Capability extends "numeric-comparable"',
|
|
2694
|
+
" ? NonNullish<T> extends number | bigint",
|
|
2695
|
+
" ? true",
|
|
2696
|
+
" : false",
|
|
2697
|
+
' : Capability extends "string-like"',
|
|
2698
|
+
" ? NonNullish<T> extends string",
|
|
2699
|
+
" ? true",
|
|
2700
|
+
" : false",
|
|
2701
|
+
' : Capability extends "array-like"',
|
|
2702
|
+
" ? NonNullish<T> extends readonly unknown[]",
|
|
2703
|
+
" ? true",
|
|
2704
|
+
" : false",
|
|
2705
|
+
' : Capability extends "enum-member-addressable"',
|
|
2706
|
+
" ? NonNullish<T> extends string",
|
|
2707
|
+
" ? true",
|
|
2708
|
+
" : false",
|
|
2709
|
+
' : Capability extends "json-like"',
|
|
2710
|
+
" ? true",
|
|
2711
|
+
' : Capability extends "condition-like"',
|
|
2712
|
+
" ? true",
|
|
2713
|
+
' : Capability extends "object-like"',
|
|
2714
|
+
" ? NonNullish<T> extends readonly unknown[]",
|
|
2715
|
+
" ? false",
|
|
2716
|
+
" : NonNullish<T> extends object",
|
|
2717
|
+
" ? true",
|
|
2718
|
+
" : false",
|
|
2719
|
+
" : false;",
|
|
2720
|
+
"",
|
|
2721
|
+
"type NestedPathOfCapability<Subject, Capability extends FormSpecCapability> =",
|
|
2722
|
+
" NonNullish<Subject> extends readonly (infer Item)[]",
|
|
2723
|
+
" ? NestedPathOfCapability<Item, Capability>",
|
|
2724
|
+
" : NonNullish<Subject> extends object",
|
|
2725
|
+
" ? {",
|
|
2726
|
+
" [Key in Extract<keyof NonNullish<Subject>, string>]:",
|
|
2727
|
+
" | (ProvidesCapability<NonNullish<Subject>[Key], Capability> extends true ? Key : never)",
|
|
2728
|
+
" | (NestedPathOfCapability<NonNullish<Subject>[Key], Capability> extends never",
|
|
2729
|
+
" ? never",
|
|
2730
|
+
" : `${Key}.${NestedPathOfCapability<NonNullish<Subject>[Key], Capability>}`);",
|
|
2731
|
+
" }[Extract<keyof NonNullish<Subject>, string>]",
|
|
2732
|
+
" : never;",
|
|
2733
|
+
"",
|
|
2734
|
+
"type PathOfCapability<Subject, Capability extends FormSpecCapability> =",
|
|
2735
|
+
" NestedPathOfCapability<Subject, Capability>;",
|
|
2736
|
+
"",
|
|
2737
|
+
"type MemberTarget<Subject> = Extract<keyof NonNullish<Subject>, string>;",
|
|
2738
|
+
"",
|
|
2739
|
+
'type VariantTarget<Subject> = "singular" | "plural";',
|
|
2740
|
+
"",
|
|
2741
|
+
"type FormSpecCondition = unknown;",
|
|
2742
|
+
"type JsonValue = unknown;",
|
|
2743
|
+
"",
|
|
2744
|
+
"declare function __ctx<P extends FormSpecPlacement, Host, Subject>(): TagContext<P, Host, Subject>;",
|
|
2745
|
+
"declare function __path<Subject, Capability extends FormSpecCapability>(",
|
|
2746
|
+
" path: PathOfCapability<Subject, Capability>",
|
|
2747
|
+
"): PathOfCapability<Subject, Capability>;",
|
|
2748
|
+
"declare function __member<Subject>(member: MemberTarget<Subject>): MemberTarget<Subject>;",
|
|
2749
|
+
"declare function __variant<Subject>(variant: VariantTarget<Subject>): VariantTarget<Subject>;"
|
|
2750
|
+
];
|
|
2751
|
+
function placementUnion(placements) {
|
|
2752
|
+
return placements.map((placement) => JSON.stringify(placement)).join(" | ");
|
|
2753
|
+
}
|
|
2754
|
+
function renderValueType(valueKind) {
|
|
2755
|
+
switch (valueKind) {
|
|
2756
|
+
case "number":
|
|
2757
|
+
case "integer":
|
|
2758
|
+
case "signedInteger":
|
|
2759
|
+
return "number";
|
|
2760
|
+
case "string":
|
|
2761
|
+
return "string";
|
|
2762
|
+
case "json":
|
|
2763
|
+
return "JsonValue";
|
|
2764
|
+
case "boolean":
|
|
2765
|
+
return "boolean";
|
|
2766
|
+
case "condition":
|
|
2767
|
+
return "FormSpecCondition";
|
|
2768
|
+
case void 0:
|
|
2769
|
+
return "unknown";
|
|
2770
|
+
default: {
|
|
2771
|
+
const exhaustive = valueKind;
|
|
2772
|
+
return exhaustive;
|
|
2773
|
+
}
|
|
2774
|
+
}
|
|
2775
|
+
}
|
|
2776
|
+
function renderTargetParameterType(parameter) {
|
|
2777
|
+
switch (parameter.kind) {
|
|
2778
|
+
case "target-path":
|
|
2779
|
+
return parameter.capability === void 0 ? "PathOfCapability<Subject, FormSpecCapability>" : `PathOfCapability<Subject, ${JSON.stringify(parameter.capability)}>`;
|
|
2780
|
+
case "target-member":
|
|
2781
|
+
return "MemberTarget<Subject>";
|
|
2782
|
+
case "target-variant":
|
|
2783
|
+
return "VariantTarget<Subject>";
|
|
2784
|
+
case "value":
|
|
2785
|
+
return renderValueType(parameter.valueKind);
|
|
2786
|
+
default: {
|
|
2787
|
+
const exhaustive = parameter.kind;
|
|
2788
|
+
return exhaustive;
|
|
2789
|
+
}
|
|
2790
|
+
}
|
|
2791
|
+
}
|
|
2792
|
+
function renderSignature(tagName, signature) {
|
|
2793
|
+
const parameters = signature.parameters.map((parameter, index) => {
|
|
2794
|
+
const name = parameter.kind === "value" ? "value" : `target${String(index)}`;
|
|
2795
|
+
return `${name}: ${renderTargetParameterType(parameter)}`;
|
|
2796
|
+
});
|
|
2797
|
+
return [
|
|
2798
|
+
` function ${getSyntheticTagHelperName(tagName)}<Host, Subject>(`,
|
|
2799
|
+
` ctx: TagContext<${placementUnion(signature.placements)}, Host, Subject>${parameters.length > 0 ? "," : ""}`,
|
|
2800
|
+
...parameters.map(
|
|
2801
|
+
(parameter, index) => ` ${parameter}${index === parameters.length - 1 ? "" : ","}`
|
|
2802
|
+
),
|
|
2803
|
+
" ): void;"
|
|
2804
|
+
].join("\n");
|
|
2805
|
+
}
|
|
2806
|
+
function getSyntheticTagHelperName(tagName) {
|
|
2807
|
+
return `tag_${tagName}`;
|
|
2808
|
+
}
|
|
2809
|
+
function targetKindForParameter(parameter) {
|
|
2810
|
+
switch (parameter.kind) {
|
|
2811
|
+
case "target-path":
|
|
2812
|
+
return "path";
|
|
2813
|
+
case "target-member":
|
|
2814
|
+
return "member";
|
|
2815
|
+
case "target-variant":
|
|
2816
|
+
return "variant";
|
|
2817
|
+
case "value":
|
|
2818
|
+
return null;
|
|
2819
|
+
default: {
|
|
2820
|
+
const exhaustive = parameter.kind;
|
|
2821
|
+
return exhaustive;
|
|
2822
|
+
}
|
|
2823
|
+
}
|
|
2824
|
+
}
|
|
2825
|
+
function getSignatureTargetKind(signature) {
|
|
2826
|
+
for (const parameter of signature.parameters) {
|
|
2827
|
+
const targetKind = targetKindForParameter(parameter);
|
|
2828
|
+
if (targetKind !== null) {
|
|
2829
|
+
return targetKind;
|
|
2830
|
+
}
|
|
2831
|
+
}
|
|
2832
|
+
return null;
|
|
2833
|
+
}
|
|
2834
|
+
function getTargetParameter(signature) {
|
|
2835
|
+
return signature.parameters.find(
|
|
2836
|
+
(parameter) => parameter.kind !== "value"
|
|
2837
|
+
) ?? null;
|
|
2838
|
+
}
|
|
2839
|
+
function getPathTargetCapability(signature) {
|
|
2840
|
+
const parameter = getTargetParameter(signature);
|
|
2841
|
+
if (parameter?.kind !== "target-path") {
|
|
2842
|
+
throw new Error(`Invariant violation: expected a path-target synthetic signature`);
|
|
2843
|
+
}
|
|
2844
|
+
if (parameter.capability === void 0) {
|
|
2845
|
+
throw new Error(
|
|
2846
|
+
`Invariant violation: path-target synthetic signatures must declare a capability`
|
|
2847
|
+
);
|
|
2848
|
+
}
|
|
2849
|
+
return JSON.stringify(parameter.capability);
|
|
2850
|
+
}
|
|
2851
|
+
function renderTargetArgument(target, signature, subjectType) {
|
|
2852
|
+
switch (target.kind) {
|
|
2853
|
+
case "path":
|
|
2854
|
+
return `__path<${subjectType}, ${getPathTargetCapability(signature)}>(${JSON.stringify(
|
|
2855
|
+
target.text
|
|
2856
|
+
)})`;
|
|
2857
|
+
case "member":
|
|
2858
|
+
return `__member<${subjectType}>(${JSON.stringify(target.text)})`;
|
|
2859
|
+
case "variant":
|
|
2860
|
+
return `__variant<${subjectType}>(${JSON.stringify(target.text)})`;
|
|
2861
|
+
}
|
|
2862
|
+
}
|
|
2863
|
+
function getMatchingTagSignatures(definition, placement, targetKind) {
|
|
2864
|
+
return definition.signatures.filter(
|
|
2865
|
+
(signature) => signature.placements.includes(placement) && getSignatureTargetKind(signature) === targetKind
|
|
2866
|
+
);
|
|
2867
|
+
}
|
|
2868
|
+
function buildSyntheticHelperPrelude(extensions) {
|
|
2869
|
+
const lines = [...PRELUDE_LINES, "", "declare namespace __formspec {"];
|
|
2870
|
+
for (const definition of getAllTagDefinitions(extensions)) {
|
|
2871
|
+
for (const signature of definition.signatures) {
|
|
2872
|
+
lines.push(renderSignature(definition.canonicalName, signature));
|
|
2873
|
+
}
|
|
2874
|
+
}
|
|
2875
|
+
lines.push("}");
|
|
2876
|
+
return lines.join("\n");
|
|
2877
|
+
}
|
|
2878
|
+
function lowerTagApplicationToSyntheticCall(options) {
|
|
2879
|
+
const definition = getTagDefinition(options.tagName, options.extensions);
|
|
2880
|
+
if (definition === null) {
|
|
2881
|
+
throw new Error(`Unknown FormSpec tag: ${options.tagName}`);
|
|
2882
|
+
}
|
|
2883
|
+
const targetKind = options.target?.kind ?? null;
|
|
2884
|
+
const matchingSignatures = getMatchingTagSignatures(definition, options.placement, targetKind);
|
|
2885
|
+
if (matchingSignatures.length === 0) {
|
|
2886
|
+
throw new Error(
|
|
2887
|
+
`No synthetic signature for @${definition.canonicalName} on placement "${options.placement}"` + (targetKind === null ? "" : ` with target kind "${targetKind}"`)
|
|
2888
|
+
);
|
|
2889
|
+
}
|
|
2890
|
+
const args = [
|
|
2891
|
+
`__ctx<${JSON.stringify(options.placement)}, ${options.hostType}, ${options.subjectType}>()`
|
|
2892
|
+
];
|
|
2893
|
+
const signature = matchingSignatures[0];
|
|
2894
|
+
if (signature === void 0) {
|
|
2895
|
+
throw new Error(
|
|
2896
|
+
`Invariant violation: missing synthetic signature for @${definition.canonicalName}`
|
|
2897
|
+
);
|
|
2898
|
+
}
|
|
2899
|
+
if (options.target !== void 0 && options.target !== null) {
|
|
2900
|
+
args.push(renderTargetArgument(options.target, signature, options.subjectType));
|
|
2901
|
+
}
|
|
2902
|
+
if (options.argumentExpression !== void 0 && options.argumentExpression !== null) {
|
|
2903
|
+
args.push(options.argumentExpression);
|
|
2904
|
+
}
|
|
2905
|
+
return {
|
|
2906
|
+
definition,
|
|
2907
|
+
matchingSignatures,
|
|
2908
|
+
callExpression: `__formspec.${getSyntheticTagHelperName(definition.canonicalName)}(${args.join(", ")});`
|
|
2909
|
+
};
|
|
2910
|
+
}
|
|
2911
|
+
function createSyntheticCompilerHost(fileName, sourceText, compilerOptions) {
|
|
2912
|
+
const host = ts2.createCompilerHost(compilerOptions, true);
|
|
2913
|
+
const originalGetSourceFile = host.getSourceFile.bind(host);
|
|
2914
|
+
host.getSourceFile = (requestedFileName, languageVersion, onError, shouldCreateNewSourceFile) => {
|
|
2915
|
+
if (requestedFileName === fileName) {
|
|
2916
|
+
return ts2.createSourceFile(requestedFileName, sourceText, languageVersion, true);
|
|
2917
|
+
}
|
|
2918
|
+
return originalGetSourceFile(
|
|
2919
|
+
requestedFileName,
|
|
2920
|
+
languageVersion,
|
|
2921
|
+
onError,
|
|
2922
|
+
shouldCreateNewSourceFile
|
|
2923
|
+
);
|
|
2924
|
+
};
|
|
2925
|
+
host.readFile = (requestedFileName) => {
|
|
2926
|
+
if (requestedFileName === fileName) {
|
|
2927
|
+
return sourceText;
|
|
2928
|
+
}
|
|
2929
|
+
return ts2.sys.readFile(requestedFileName);
|
|
2930
|
+
};
|
|
2931
|
+
host.fileExists = (requestedFileName) => requestedFileName === fileName || ts2.sys.fileExists(requestedFileName);
|
|
2932
|
+
host.writeFile = () => void 0;
|
|
2933
|
+
return host;
|
|
2934
|
+
}
|
|
2935
|
+
function flattenDiagnosticMessage(message) {
|
|
2936
|
+
return ts2.flattenDiagnosticMessageText(message, "\n");
|
|
2937
|
+
}
|
|
2938
|
+
var SYNTHETIC_COMPILER_OPTIONS = {
|
|
2939
|
+
strict: true,
|
|
2940
|
+
noEmit: true,
|
|
2941
|
+
target: ts2.ScriptTarget.ES2022,
|
|
2942
|
+
module: ts2.ModuleKind.ESNext,
|
|
2943
|
+
lib: ["lib.es2022.d.ts"],
|
|
2944
|
+
types: []
|
|
2945
|
+
};
|
|
2946
|
+
var LruCache = class {
|
|
2947
|
+
constructor(maxEntries) {
|
|
2948
|
+
this.maxEntries = maxEntries;
|
|
2949
|
+
}
|
|
2950
|
+
map = /* @__PURE__ */ new Map();
|
|
2951
|
+
get(key) {
|
|
2952
|
+
const cached = this.map.get(key);
|
|
2953
|
+
if (cached === void 0) {
|
|
2954
|
+
return void 0;
|
|
2955
|
+
}
|
|
2956
|
+
this.map.delete(key);
|
|
2957
|
+
this.map.set(key, cached);
|
|
2958
|
+
return cached;
|
|
2959
|
+
}
|
|
2960
|
+
set(key, value) {
|
|
2961
|
+
if (this.map.has(key)) {
|
|
2962
|
+
this.map.delete(key);
|
|
2963
|
+
}
|
|
2964
|
+
this.map.set(key, value);
|
|
2965
|
+
if (this.map.size <= this.maxEntries) {
|
|
2966
|
+
return;
|
|
2967
|
+
}
|
|
2968
|
+
const oldestKey = this.map.keys().next().value;
|
|
2969
|
+
if (oldestKey !== void 0) {
|
|
2970
|
+
this.map.delete(oldestKey);
|
|
2971
|
+
}
|
|
2972
|
+
}
|
|
2973
|
+
};
|
|
2974
|
+
var syntheticBatchResultCache = new LruCache(
|
|
2975
|
+
FORM_SPEC_SYNTHETIC_BATCH_CACHE_ENTRIES
|
|
2976
|
+
);
|
|
2977
|
+
function pushChunkLines(target, chunk) {
|
|
2978
|
+
target.push(...chunk.split("\n"));
|
|
2979
|
+
}
|
|
2980
|
+
function buildSyntheticBatchSource(applications) {
|
|
2981
|
+
const lines = [];
|
|
2982
|
+
pushChunkLines(lines, buildSyntheticHelperPrelude(collectBatchExtensions(applications)));
|
|
2983
|
+
lines.push("");
|
|
2984
|
+
const applicationLineRanges = [];
|
|
2985
|
+
for (const [index, application] of applications.entries()) {
|
|
2986
|
+
const namespaceName = `__formspec_app_${String(index)}`;
|
|
2987
|
+
const startLine = lines.length;
|
|
2988
|
+
lines.push(`namespace ${namespaceName} {`);
|
|
2989
|
+
for (const declaration of application.options.supportingDeclarations ?? []) {
|
|
2990
|
+
pushChunkLines(lines, declaration);
|
|
2991
|
+
}
|
|
2992
|
+
lines.push(`type __Host = ${application.options.hostType};`);
|
|
2993
|
+
lines.push(`type __Subject = ${application.options.subjectType};`);
|
|
2994
|
+
lines.push(application.lowered.callExpression);
|
|
2995
|
+
lines.push("}");
|
|
2996
|
+
applicationLineRanges.push({
|
|
2997
|
+
startLine,
|
|
2998
|
+
endLine: lines.length - 1
|
|
2999
|
+
});
|
|
3000
|
+
}
|
|
3001
|
+
return {
|
|
3002
|
+
sourceText: lines.join("\n"),
|
|
3003
|
+
applicationLineRanges
|
|
3004
|
+
};
|
|
3005
|
+
}
|
|
3006
|
+
function collectBatchExtensions(applications) {
|
|
3007
|
+
const extensions = applications.flatMap((application) => application.options.extensions ?? []);
|
|
3008
|
+
return extensions.length === 0 ? void 0 : extensions;
|
|
3009
|
+
}
|
|
3010
|
+
function mapBatchDiagnostics(diagnostics, sourceFile, applicationLineRanges) {
|
|
3011
|
+
const diagnosticsByApplication = applicationLineRanges.map(
|
|
3012
|
+
() => []
|
|
3013
|
+
);
|
|
3014
|
+
const defaultResult = diagnosticsByApplication[0];
|
|
3015
|
+
for (const diagnostic of diagnostics) {
|
|
3016
|
+
const serialized = {
|
|
3017
|
+
code: diagnostic.code,
|
|
3018
|
+
message: flattenDiagnosticMessage(diagnostic.messageText)
|
|
3019
|
+
};
|
|
3020
|
+
if (diagnostic.file === void 0 || diagnostic.start === void 0) {
|
|
3021
|
+
if (defaultResult !== void 0) {
|
|
3022
|
+
defaultResult.push(serialized);
|
|
3023
|
+
}
|
|
3024
|
+
continue;
|
|
3025
|
+
}
|
|
3026
|
+
const line = diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start).line;
|
|
3027
|
+
const applicationIndex = applicationLineRanges.findIndex(
|
|
3028
|
+
(range) => line >= range.startLine && line <= range.endLine
|
|
3029
|
+
);
|
|
3030
|
+
if (applicationIndex < 0) {
|
|
3031
|
+
if (defaultResult !== void 0) {
|
|
3032
|
+
defaultResult.push(serialized);
|
|
3033
|
+
}
|
|
3034
|
+
continue;
|
|
3035
|
+
}
|
|
3036
|
+
const targetResult = diagnosticsByApplication[applicationIndex];
|
|
3037
|
+
if (targetResult !== void 0) {
|
|
3038
|
+
targetResult.push(serialized);
|
|
3039
|
+
}
|
|
3040
|
+
}
|
|
3041
|
+
return diagnosticsByApplication.map((diagnosticsForApplication) => ({
|
|
3042
|
+
sourceText: sourceFile.text,
|
|
3043
|
+
diagnostics: diagnosticsForApplication
|
|
3044
|
+
}));
|
|
3045
|
+
}
|
|
3046
|
+
function runSyntheticProgram(fileName, sourceText, performance2, eventPrefix, missingSourceFileMessage) {
|
|
3047
|
+
const host = optionalMeasure(
|
|
3048
|
+
performance2,
|
|
3049
|
+
`${eventPrefix}.createCompilerHost`,
|
|
3050
|
+
void 0,
|
|
3051
|
+
() => createSyntheticCompilerHost(fileName, sourceText, SYNTHETIC_COMPILER_OPTIONS)
|
|
3052
|
+
);
|
|
3053
|
+
const program = optionalMeasure(
|
|
3054
|
+
performance2,
|
|
3055
|
+
`${eventPrefix}.createProgram`,
|
|
3056
|
+
void 0,
|
|
3057
|
+
() => ts2.createProgram([fileName], SYNTHETIC_COMPILER_OPTIONS, host)
|
|
3058
|
+
);
|
|
3059
|
+
const diagnostics = optionalMeasure(
|
|
3060
|
+
performance2,
|
|
3061
|
+
`${eventPrefix}.getPreEmitDiagnostics`,
|
|
3062
|
+
void 0,
|
|
3063
|
+
() => ts2.getPreEmitDiagnostics(program).filter(
|
|
3064
|
+
(diagnostic) => diagnostic.file === void 0 || diagnostic.file.fileName === fileName
|
|
3065
|
+
)
|
|
3066
|
+
);
|
|
3067
|
+
const sourceFile = program.getSourceFile(fileName);
|
|
3068
|
+
if (sourceFile === void 0) {
|
|
3069
|
+
throw new Error(missingSourceFileMessage);
|
|
3070
|
+
}
|
|
3071
|
+
return {
|
|
3072
|
+
sourceFile,
|
|
3073
|
+
diagnostics
|
|
3074
|
+
};
|
|
3075
|
+
}
|
|
3076
|
+
function runBatchSyntheticCheck(options) {
|
|
3077
|
+
if (options.applications.length === 0) {
|
|
3078
|
+
return [];
|
|
3079
|
+
}
|
|
3080
|
+
const resolvedApplications = options.lowerApplications(options.applications, options.performance);
|
|
3081
|
+
const batchSource = options.buildBatchSource(resolvedApplications, options.performance);
|
|
3082
|
+
const cached = options.cache.get(batchSource.sourceText);
|
|
3083
|
+
if (cached !== void 0) {
|
|
3084
|
+
options.performance?.record({
|
|
3085
|
+
name: `${options.eventPrefix}.cacheHit`,
|
|
3086
|
+
durationMs: 0,
|
|
3087
|
+
detail: {
|
|
3088
|
+
applicationCount: options.applications.length
|
|
3089
|
+
}
|
|
3090
|
+
});
|
|
3091
|
+
return cached;
|
|
3092
|
+
}
|
|
3093
|
+
const { sourceFile, diagnostics } = runSyntheticProgram(
|
|
3094
|
+
options.fileName,
|
|
3095
|
+
batchSource.sourceText,
|
|
3096
|
+
options.performance,
|
|
3097
|
+
options.eventPrefix,
|
|
3098
|
+
options.missingSourceFileMessage
|
|
3099
|
+
);
|
|
3100
|
+
const results = optionalMeasure(
|
|
3101
|
+
options.performance,
|
|
3102
|
+
`${options.eventPrefix}.mapDiagnostics`,
|
|
3103
|
+
void 0,
|
|
3104
|
+
() => mapBatchDiagnostics(diagnostics, sourceFile, batchSource.applicationLineRanges)
|
|
3105
|
+
);
|
|
3106
|
+
options.cache.set(batchSource.sourceText, results);
|
|
3107
|
+
return results;
|
|
3108
|
+
}
|
|
3109
|
+
function checkSyntheticTagApplications(options) {
|
|
3110
|
+
return runBatchSyntheticCheck({
|
|
3111
|
+
applications: options.applications,
|
|
3112
|
+
performance: options.performance,
|
|
3113
|
+
cache: syntheticBatchResultCache,
|
|
3114
|
+
eventPrefix: SYNTHETIC_CHECK_EVENT.batch,
|
|
3115
|
+
missingSourceFileMessage: "Invariant violation: missing synthetic batch source file",
|
|
3116
|
+
fileName: "/virtual/formspec-synthetic-batch.ts",
|
|
3117
|
+
lowerApplications: (applications, performance2) => optionalMeasure(
|
|
3118
|
+
performance2,
|
|
3119
|
+
`${SYNTHETIC_CHECK_EVENT.batch}.lower`,
|
|
3120
|
+
void 0,
|
|
3121
|
+
() => applications.map((application) => ({
|
|
3122
|
+
options: application,
|
|
3123
|
+
lowered: lowerTagApplicationToSyntheticCall({
|
|
3124
|
+
...application,
|
|
3125
|
+
hostType: "__Host",
|
|
3126
|
+
subjectType: "__Subject"
|
|
3127
|
+
})
|
|
3128
|
+
}))
|
|
3129
|
+
),
|
|
3130
|
+
buildBatchSource: (applications, performance2) => optionalMeasure(
|
|
3131
|
+
performance2,
|
|
3132
|
+
`${SYNTHETIC_CHECK_EVENT.batch}.buildSource`,
|
|
3133
|
+
void 0,
|
|
3134
|
+
() => buildSyntheticBatchSource(applications)
|
|
3135
|
+
)
|
|
3136
|
+
});
|
|
3137
|
+
}
|
|
3138
|
+
function checkSyntheticTagApplication(options) {
|
|
3139
|
+
const result = checkSyntheticTagApplications({
|
|
3140
|
+
applications: [options],
|
|
3141
|
+
...options.performance === void 0 ? {} : { performance: options.performance }
|
|
3142
|
+
})[0];
|
|
3143
|
+
if (result === void 0) {
|
|
3144
|
+
throw new Error("Invariant violation: missing synthetic batch result for singular check");
|
|
3145
|
+
}
|
|
3146
|
+
return result;
|
|
3147
|
+
}
|
|
3148
|
+
|
|
3149
|
+
// src/source-bindings.ts
|
|
3150
|
+
import * as ts3 from "typescript";
|
|
3151
|
+
function getLastLeadingDocCommentRange(node, sourceFile) {
|
|
3152
|
+
const ranges = ts3.getLeadingCommentRanges(sourceFile.text, node.getFullStart()) ?? [];
|
|
3153
|
+
const docRanges = ranges.filter(
|
|
3154
|
+
(range) => sourceFile.text.slice(range.pos, range.end).startsWith("/**")
|
|
3155
|
+
);
|
|
3156
|
+
return docRanges.length === 0 ? null : docRanges[docRanges.length - 1] ?? null;
|
|
3157
|
+
}
|
|
3158
|
+
function getSubjectType(node, checker) {
|
|
3159
|
+
if (ts3.isClassDeclaration(node) || ts3.isInterfaceDeclaration(node) || ts3.isTypeAliasDeclaration(node) || ts3.isPropertyDeclaration(node) || ts3.isPropertySignature(node) || ts3.isMethodDeclaration(node) || ts3.isFunctionDeclaration(node) || ts3.isVariableDeclaration(node) || ts3.isParameter(node)) {
|
|
3160
|
+
return checker.getTypeAtLocation(node);
|
|
3161
|
+
}
|
|
3162
|
+
return void 0;
|
|
3163
|
+
}
|
|
3164
|
+
function getHostType(node, checker) {
|
|
3165
|
+
const parent = node.parent;
|
|
3166
|
+
if (ts3.isClassDeclaration(parent) || ts3.isInterfaceDeclaration(parent) || ts3.isTypeLiteralNode(parent) || ts3.isTypeAliasDeclaration(parent)) {
|
|
3167
|
+
return checker.getTypeAtLocation(parent);
|
|
3168
|
+
}
|
|
3169
|
+
return getSubjectType(node, checker);
|
|
3170
|
+
}
|
|
3171
|
+
function findDeclarationForCommentOffset(sourceFile, offset) {
|
|
3172
|
+
let bestMatch = null;
|
|
3173
|
+
const visit = (node) => {
|
|
3174
|
+
if (resolveDeclarationPlacement(node) !== null) {
|
|
3175
|
+
const range = getLastLeadingDocCommentRange(node, sourceFile);
|
|
3176
|
+
if (range !== null && offset >= range.pos && offset <= range.end) {
|
|
3177
|
+
if (bestMatch === null || node.getWidth(sourceFile) < bestMatch.getWidth(sourceFile)) {
|
|
3178
|
+
bestMatch = node;
|
|
3179
|
+
}
|
|
3180
|
+
}
|
|
3181
|
+
}
|
|
3182
|
+
ts3.forEachChild(node, visit);
|
|
3183
|
+
};
|
|
3184
|
+
visit(sourceFile);
|
|
3185
|
+
return bestMatch;
|
|
3186
|
+
}
|
|
3187
|
+
|
|
3188
|
+
// src/semantic-protocol.ts
|
|
3189
|
+
var FORMSPEC_ANALYSIS_PROTOCOL_VERSION = 1;
|
|
3190
|
+
var FORMSPEC_ANALYSIS_SCHEMA_VERSION = 1;
|
|
3191
|
+
function isObjectRecord(value) {
|
|
3192
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3193
|
+
}
|
|
3194
|
+
function isCommentSpan(value) {
|
|
3195
|
+
if (!isObjectRecord(value)) {
|
|
3196
|
+
return false;
|
|
3197
|
+
}
|
|
3198
|
+
const candidate = value;
|
|
3199
|
+
return typeof candidate.start === "number" && typeof candidate.end === "number";
|
|
3200
|
+
}
|
|
3201
|
+
function isStringArray(value) {
|
|
3202
|
+
return Array.isArray(value) && value.every((entry) => typeof entry === "string");
|
|
3203
|
+
}
|
|
3204
|
+
var FORM_SPEC_PLACEMENT_VALUES = new Set(FORM_SPEC_PLACEMENTS);
|
|
3205
|
+
var FORM_SPEC_TARGET_KIND_VALUES = new Set(FORM_SPEC_TARGET_KINDS);
|
|
3206
|
+
function isPlacementValue(value) {
|
|
3207
|
+
return typeof value === "string" && FORM_SPEC_PLACEMENT_VALUES.has(value);
|
|
3208
|
+
}
|
|
3209
|
+
function isTargetKindValue(value) {
|
|
3210
|
+
return typeof value === "string" && FORM_SPEC_TARGET_KIND_VALUES.has(value);
|
|
3211
|
+
}
|
|
3212
|
+
function isPlacementArray(value) {
|
|
3213
|
+
return Array.isArray(value) && value.every(isPlacementValue);
|
|
3214
|
+
}
|
|
3215
|
+
function isTargetKindArray(value) {
|
|
3216
|
+
return Array.isArray(value) && value.every(isTargetKindValue);
|
|
3217
|
+
}
|
|
3218
|
+
function isIpcEndpoint(value) {
|
|
3219
|
+
if (!isObjectRecord(value)) {
|
|
3220
|
+
return false;
|
|
3221
|
+
}
|
|
3222
|
+
const candidate = value;
|
|
3223
|
+
return (candidate.kind === "unix-socket" || candidate.kind === "windows-pipe") && typeof candidate.address === "string";
|
|
3224
|
+
}
|
|
3225
|
+
function isSerializedTagDefinition(value) {
|
|
3226
|
+
if (!isObjectRecord(value)) {
|
|
3227
|
+
return false;
|
|
3228
|
+
}
|
|
3229
|
+
const candidate = value;
|
|
3230
|
+
return typeof candidate.canonicalName === "string" && typeof candidate.completionDetail === "string" && typeof candidate.hoverMarkdown === "string";
|
|
3231
|
+
}
|
|
3232
|
+
function isSerializedTagSignature(value) {
|
|
3233
|
+
if (!isObjectRecord(value)) {
|
|
3234
|
+
return false;
|
|
3235
|
+
}
|
|
3236
|
+
const candidate = value;
|
|
3237
|
+
return typeof candidate.label === "string" && isPlacementArray(candidate.placements);
|
|
3238
|
+
}
|
|
3239
|
+
function isSerializedCommentTargetSpecifier(value) {
|
|
3240
|
+
if (!isObjectRecord(value)) {
|
|
3241
|
+
return false;
|
|
3242
|
+
}
|
|
3243
|
+
const candidate = value;
|
|
3244
|
+
return typeof candidate.rawText === "string" && typeof candidate.valid === "boolean" && typeof candidate.kind === "string" && isCommentSpan(candidate.fullSpan) && isCommentSpan(candidate.colonSpan) && isCommentSpan(candidate.span);
|
|
3245
|
+
}
|
|
3246
|
+
function isSerializedTagSemanticContext(value) {
|
|
3247
|
+
if (!isObjectRecord(value)) {
|
|
3248
|
+
return false;
|
|
3249
|
+
}
|
|
3250
|
+
const candidate = value;
|
|
3251
|
+
return typeof candidate.tagName === "string" && (candidate.tagDefinition === null || isSerializedTagDefinition(candidate.tagDefinition)) && (candidate.placement === null || isPlacementValue(candidate.placement)) && isTargetKindArray(candidate.supportedTargets) && isStringArray(candidate.targetCompletions) && isStringArray(candidate.compatiblePathTargets) && isStringArray(candidate.valueLabels) && Array.isArray(candidate.signatures) && candidate.signatures.every(isSerializedTagSignature) && (candidate.tagHoverMarkdown === null || typeof candidate.tagHoverMarkdown === "string") && (candidate.targetHoverMarkdown === null || typeof candidate.targetHoverMarkdown === "string") && (candidate.argumentHoverMarkdown === null || typeof candidate.argumentHoverMarkdown === "string");
|
|
3252
|
+
}
|
|
3253
|
+
function isSerializedCompletionContext(value) {
|
|
3254
|
+
if (!isObjectRecord(value)) {
|
|
3255
|
+
return false;
|
|
3256
|
+
}
|
|
3257
|
+
const candidate = value;
|
|
3258
|
+
if (typeof candidate.kind !== "string") {
|
|
3259
|
+
return false;
|
|
3260
|
+
}
|
|
3261
|
+
switch (candidate.kind) {
|
|
3262
|
+
case "tag-name":
|
|
3263
|
+
return typeof candidate.prefix === "string" && Array.isArray(candidate.availableTags) ? candidate.availableTags.every(isSerializedTagDefinition) : false;
|
|
3264
|
+
case "target":
|
|
3265
|
+
return isSerializedTagSemanticContext(candidate.semantic);
|
|
3266
|
+
case "argument":
|
|
3267
|
+
return isSerializedTagSemanticContext(candidate.semantic) && isStringArray(candidate.valueLabels);
|
|
3268
|
+
case "none":
|
|
3269
|
+
return true;
|
|
3270
|
+
default:
|
|
3271
|
+
return false;
|
|
3272
|
+
}
|
|
3273
|
+
}
|
|
3274
|
+
function isSerializedHoverInfo(value) {
|
|
3275
|
+
if (!isObjectRecord(value)) {
|
|
3276
|
+
return false;
|
|
3277
|
+
}
|
|
3278
|
+
const candidate = value;
|
|
3279
|
+
return (candidate.kind === "tag-name" || candidate.kind === "target" || candidate.kind === "argument") && typeof candidate.markdown === "string";
|
|
3280
|
+
}
|
|
3281
|
+
function hasCurrentProtocolVersion(value) {
|
|
3282
|
+
return isObjectRecord(value) && value["protocolVersion"] === FORMSPEC_ANALYSIS_PROTOCOL_VERSION;
|
|
3283
|
+
}
|
|
3284
|
+
function isAnalysisDiagnostic(value) {
|
|
3285
|
+
if (!isObjectRecord(value)) {
|
|
3286
|
+
return false;
|
|
3287
|
+
}
|
|
3288
|
+
const candidate = value;
|
|
3289
|
+
return typeof candidate.code === "string" && typeof candidate.message === "string" && isCommentSpan(candidate.range) && (candidate.severity === "error" || candidate.severity === "warning" || candidate.severity === "info");
|
|
3290
|
+
}
|
|
3291
|
+
function isAnalysisTagSnapshot(value) {
|
|
3292
|
+
if (!isObjectRecord(value)) {
|
|
3293
|
+
return false;
|
|
3294
|
+
}
|
|
3295
|
+
const candidate = value;
|
|
3296
|
+
return typeof candidate.rawTagName === "string" && typeof candidate.normalizedTagName === "string" && typeof candidate.recognized === "boolean" && isCommentSpan(candidate.fullSpan) && isCommentSpan(candidate.tagNameSpan) && (candidate.payloadSpan === null || isCommentSpan(candidate.payloadSpan)) && (candidate.target === null || isSerializedCommentTargetSpecifier(candidate.target)) && (candidate.argumentSpan === null || isCommentSpan(candidate.argumentSpan)) && typeof candidate.argumentText === "string" && isSerializedTagSemanticContext(candidate.semantic);
|
|
3297
|
+
}
|
|
3298
|
+
function isAnalysisCommentSnapshot(value) {
|
|
3299
|
+
if (!isObjectRecord(value)) {
|
|
3300
|
+
return false;
|
|
3301
|
+
}
|
|
3302
|
+
const candidate = value;
|
|
3303
|
+
return isCommentSpan(candidate.commentSpan) && isCommentSpan(candidate.declarationSpan) && (candidate.placement === null || isPlacementValue(candidate.placement)) && (candidate.subjectType === null || typeof candidate.subjectType === "string") && (candidate.hostType === null || typeof candidate.hostType === "string") && Array.isArray(candidate.tags) && candidate.tags.every(isAnalysisTagSnapshot);
|
|
3304
|
+
}
|
|
3305
|
+
function isAnalysisFileSnapshot(value) {
|
|
3306
|
+
if (!isObjectRecord(value)) {
|
|
3307
|
+
return false;
|
|
3308
|
+
}
|
|
3309
|
+
const candidate = value;
|
|
3310
|
+
return typeof candidate.filePath === "string" && typeof candidate.sourceHash === "string" && typeof candidate.generatedAt === "string" && Array.isArray(candidate.comments) && candidate.comments.every(isAnalysisCommentSnapshot) && Array.isArray(candidate.diagnostics) && candidate.diagnostics.every(isAnalysisDiagnostic);
|
|
3311
|
+
}
|
|
3312
|
+
function isFormSpecAnalysisManifest(value) {
|
|
3313
|
+
if (!isObjectRecord(value)) {
|
|
3314
|
+
return false;
|
|
3315
|
+
}
|
|
3316
|
+
const candidate = value;
|
|
3317
|
+
return candidate.protocolVersion === FORMSPEC_ANALYSIS_PROTOCOL_VERSION && candidate.analysisSchemaVersion === FORMSPEC_ANALYSIS_SCHEMA_VERSION && typeof candidate.workspaceRoot === "string" && typeof candidate.workspaceId === "string" && isIpcEndpoint(candidate.endpoint) && typeof candidate.typescriptVersion === "string" && typeof candidate.extensionFingerprint === "string" && typeof candidate.generation === "number" && typeof candidate.updatedAt === "string";
|
|
3318
|
+
}
|
|
3319
|
+
function isFormSpecSemanticQuery(value) {
|
|
3320
|
+
if (!hasCurrentProtocolVersion(value)) {
|
|
3321
|
+
return false;
|
|
3322
|
+
}
|
|
3323
|
+
const candidate = value;
|
|
3324
|
+
if (typeof candidate.kind !== "string") {
|
|
3325
|
+
return false;
|
|
3326
|
+
}
|
|
3327
|
+
switch (candidate.kind) {
|
|
3328
|
+
case "health":
|
|
3329
|
+
return true;
|
|
3330
|
+
case "completion":
|
|
3331
|
+
case "hover":
|
|
3332
|
+
return typeof candidate.filePath === "string" && typeof candidate.offset === "number";
|
|
3333
|
+
case "diagnostics":
|
|
3334
|
+
case "file-snapshot":
|
|
3335
|
+
return typeof candidate.filePath === "string";
|
|
3336
|
+
default:
|
|
3337
|
+
return false;
|
|
3338
|
+
}
|
|
3339
|
+
}
|
|
3340
|
+
function isFormSpecSemanticResponse(value) {
|
|
3341
|
+
if (!hasCurrentProtocolVersion(value)) {
|
|
3342
|
+
return false;
|
|
3343
|
+
}
|
|
3344
|
+
const candidate = value;
|
|
3345
|
+
if (typeof candidate.kind !== "string") {
|
|
3346
|
+
return false;
|
|
3347
|
+
}
|
|
3348
|
+
switch (candidate.kind) {
|
|
3349
|
+
case "health":
|
|
3350
|
+
return isFormSpecAnalysisManifest(candidate.manifest);
|
|
3351
|
+
case "completion":
|
|
3352
|
+
return typeof candidate.sourceHash === "string" && isSerializedCompletionContext(candidate.context);
|
|
3353
|
+
case "hover":
|
|
3354
|
+
return typeof candidate.sourceHash === "string" && (candidate.hover === null || isSerializedHoverInfo(candidate.hover));
|
|
3355
|
+
case "diagnostics":
|
|
3356
|
+
return typeof candidate.sourceHash === "string" && Array.isArray(candidate.diagnostics) && candidate.diagnostics.every(isAnalysisDiagnostic);
|
|
3357
|
+
case "file-snapshot":
|
|
3358
|
+
return candidate.snapshot === null || isAnalysisFileSnapshot(candidate.snapshot);
|
|
3359
|
+
case "error":
|
|
3360
|
+
return typeof candidate.error === "string";
|
|
3361
|
+
default:
|
|
3362
|
+
return false;
|
|
3363
|
+
}
|
|
3364
|
+
}
|
|
3365
|
+
function computeFormSpecTextHash(text) {
|
|
3366
|
+
let hash = 2166136261;
|
|
3367
|
+
for (let index = 0; index < text.length; index += 1) {
|
|
3368
|
+
hash ^= text.charCodeAt(index);
|
|
3369
|
+
hash = Math.imul(hash, 16777619);
|
|
3370
|
+
}
|
|
3371
|
+
return (hash >>> 0).toString(16).padStart(8, "0");
|
|
3372
|
+
}
|
|
3373
|
+
function serializeCommentTargetSpecifier(target) {
|
|
3374
|
+
if (target === null) {
|
|
3375
|
+
return null;
|
|
3376
|
+
}
|
|
3377
|
+
return {
|
|
3378
|
+
rawText: target.rawText,
|
|
3379
|
+
valid: target.valid,
|
|
3380
|
+
kind: target.kind,
|
|
3381
|
+
fullSpan: target.fullSpan,
|
|
3382
|
+
colonSpan: target.colonSpan,
|
|
3383
|
+
span: target.span
|
|
3384
|
+
};
|
|
3385
|
+
}
|
|
3386
|
+
function serializeCommentTagSemanticContext(semantic) {
|
|
3387
|
+
return {
|
|
3388
|
+
tagName: semantic.tag.normalizedTagName,
|
|
3389
|
+
tagDefinition: semantic.tagDefinition === null ? null : {
|
|
3390
|
+
canonicalName: semantic.tagDefinition.canonicalName,
|
|
3391
|
+
completionDetail: semantic.tagDefinition.completionDetail,
|
|
3392
|
+
hoverMarkdown: semantic.tagDefinition.hoverMarkdown
|
|
3393
|
+
},
|
|
3394
|
+
placement: semantic.placement,
|
|
3395
|
+
supportedTargets: semantic.supportedTargets,
|
|
3396
|
+
targetCompletions: semantic.targetCompletions,
|
|
3397
|
+
compatiblePathTargets: semantic.compatiblePathTargets,
|
|
3398
|
+
valueLabels: semantic.valueLabels,
|
|
3399
|
+
signatures: semantic.signatures.map((signature) => ({
|
|
3400
|
+
label: signature.label,
|
|
3401
|
+
placements: signature.placements
|
|
3402
|
+
})),
|
|
3403
|
+
tagHoverMarkdown: semantic.tagHoverMarkdown,
|
|
3404
|
+
targetHoverMarkdown: semantic.targetHoverMarkdown,
|
|
3405
|
+
argumentHoverMarkdown: semantic.argumentHoverMarkdown
|
|
3406
|
+
};
|
|
3407
|
+
}
|
|
3408
|
+
function serializeCompletionContext(context) {
|
|
3409
|
+
switch (context.kind) {
|
|
3410
|
+
case "tag-name":
|
|
3411
|
+
return {
|
|
3412
|
+
kind: "tag-name",
|
|
3413
|
+
prefix: context.prefix,
|
|
3414
|
+
availableTags: context.availableTags.map((tag) => ({
|
|
3415
|
+
canonicalName: tag.canonicalName,
|
|
3416
|
+
completionDetail: tag.completionDetail,
|
|
3417
|
+
hoverMarkdown: tag.hoverMarkdown
|
|
3418
|
+
}))
|
|
3419
|
+
};
|
|
3420
|
+
case "target":
|
|
3421
|
+
return {
|
|
3422
|
+
kind: "target",
|
|
3423
|
+
semantic: serializeCommentTagSemanticContext(context.semantic)
|
|
3424
|
+
};
|
|
3425
|
+
case "argument":
|
|
3426
|
+
return {
|
|
3427
|
+
kind: "argument",
|
|
3428
|
+
semantic: serializeCommentTagSemanticContext(context.semantic),
|
|
3429
|
+
valueLabels: context.valueLabels
|
|
3430
|
+
};
|
|
3431
|
+
case "none":
|
|
3432
|
+
return { kind: "none" };
|
|
3433
|
+
default: {
|
|
3434
|
+
const exhaustive = context;
|
|
3435
|
+
return exhaustive;
|
|
3436
|
+
}
|
|
3437
|
+
}
|
|
3438
|
+
}
|
|
3439
|
+
function serializeHoverInfo(hover) {
|
|
3440
|
+
return hover === null ? null : {
|
|
3441
|
+
kind: hover.kind,
|
|
3442
|
+
markdown: hover.markdown
|
|
3443
|
+
};
|
|
3444
|
+
}
|
|
3445
|
+
function serializeParsedCommentTag(tag, semantic) {
|
|
3446
|
+
return {
|
|
3447
|
+
rawTagName: tag.rawTagName,
|
|
3448
|
+
normalizedTagName: tag.normalizedTagName,
|
|
3449
|
+
recognized: tag.recognized,
|
|
3450
|
+
fullSpan: tag.fullSpan,
|
|
3451
|
+
tagNameSpan: tag.tagNameSpan,
|
|
3452
|
+
payloadSpan: tag.payloadSpan,
|
|
3453
|
+
target: serializeCommentTargetSpecifier(tag.target),
|
|
3454
|
+
argumentSpan: tag.argumentSpan,
|
|
3455
|
+
argumentText: tag.argumentText,
|
|
3456
|
+
semantic: serializeCommentTagSemanticContext(semantic)
|
|
3457
|
+
};
|
|
3458
|
+
}
|
|
3459
|
+
|
|
3460
|
+
// src/file-snapshots.ts
|
|
3461
|
+
var SYNTHETIC_TYPE_NODE_BUILDER_FLAGS = ts4.NodeBuilderFlags.NoTruncation | ts4.NodeBuilderFlags.UseStructuralFallback | ts4.NodeBuilderFlags.IgnoreErrors | ts4.NodeBuilderFlags.InTypeAlias;
|
|
3462
|
+
var SYNTHETIC_TYPE_PRINT_SOURCE_FILE = ts4.createSourceFile(
|
|
3463
|
+
"/virtual/formspec-standalone-type.ts",
|
|
3464
|
+
"",
|
|
3465
|
+
ts4.ScriptTarget.ES2022,
|
|
3466
|
+
false,
|
|
3467
|
+
ts4.ScriptKind.TS
|
|
3468
|
+
);
|
|
3469
|
+
var SYNTHETIC_TYPE_PRINTER = ts4.createPrinter({ removeComments: true });
|
|
3470
|
+
function spanFromPos(start, end) {
|
|
3471
|
+
return { start, end };
|
|
3472
|
+
}
|
|
3473
|
+
function typeToString(type, checker) {
|
|
3474
|
+
if (type === void 0) {
|
|
3475
|
+
return null;
|
|
3476
|
+
}
|
|
3477
|
+
return checker.typeToString(type, void 0, ts4.TypeFormatFlags.NoTruncation);
|
|
3478
|
+
}
|
|
3479
|
+
function supportingDeclarationsForType(type) {
|
|
3480
|
+
if (type === void 0) {
|
|
3481
|
+
return [];
|
|
3482
|
+
}
|
|
3483
|
+
const symbol = type.aliasSymbol ?? type.getSymbol();
|
|
3484
|
+
const declarations = symbol?.declarations ?? [];
|
|
3485
|
+
return declarations.map(
|
|
3486
|
+
(declaration) => declaration.getSourceFile().text.slice(declaration.getFullStart(), declaration.getEnd())
|
|
3487
|
+
).filter((declarationText) => declarationText.trim().length > 0);
|
|
3488
|
+
}
|
|
3489
|
+
function renderStandaloneTypeSyntax(type, checker) {
|
|
3490
|
+
if (type === void 0) {
|
|
3491
|
+
return null;
|
|
3492
|
+
}
|
|
3493
|
+
const typeNode = checker.typeToTypeNode(type, void 0, SYNTHETIC_TYPE_NODE_BUILDER_FLAGS);
|
|
3494
|
+
if (typeNode === void 0) {
|
|
3495
|
+
return null;
|
|
3496
|
+
}
|
|
3497
|
+
const rendered = SYNTHETIC_TYPE_PRINTER.printNode(
|
|
3498
|
+
ts4.EmitHint.Unspecified,
|
|
3499
|
+
typeNode,
|
|
3500
|
+
SYNTHETIC_TYPE_PRINT_SOURCE_FILE
|
|
3501
|
+
).trim();
|
|
3502
|
+
return rendered === "" ? null : rendered;
|
|
3503
|
+
}
|
|
3504
|
+
function requiresSupportingDeclarationsForStandaloneTypeSyntax(typeText) {
|
|
3505
|
+
if (typeText === null) {
|
|
3506
|
+
return true;
|
|
3507
|
+
}
|
|
3508
|
+
const sourceFile = ts4.createSourceFile(
|
|
3509
|
+
"/virtual/formspec-standalone-type-analysis.ts",
|
|
3510
|
+
`type __FormSpecStandalone = ${typeText};`,
|
|
3511
|
+
ts4.ScriptTarget.ES2022,
|
|
3512
|
+
false,
|
|
3513
|
+
ts4.ScriptKind.TS
|
|
3514
|
+
);
|
|
3515
|
+
const statement = sourceFile.statements[0];
|
|
3516
|
+
if (statement === void 0 || !ts4.isTypeAliasDeclaration(statement)) {
|
|
3517
|
+
return true;
|
|
3518
|
+
}
|
|
3519
|
+
let requiresDeclarations = false;
|
|
3520
|
+
const visit = (node) => {
|
|
3521
|
+
if (ts4.isTypeReferenceNode(node) || ts4.isExpressionWithTypeArguments(node) || ts4.isImportTypeNode(node) || ts4.isTypeQueryNode(node)) {
|
|
3522
|
+
requiresDeclarations = true;
|
|
3523
|
+
return;
|
|
3524
|
+
}
|
|
3525
|
+
ts4.forEachChild(node, visit);
|
|
3526
|
+
};
|
|
3527
|
+
visit(statement.type);
|
|
3528
|
+
return requiresDeclarations;
|
|
3529
|
+
}
|
|
3530
|
+
function dedupeSupportingDeclarations(declarations) {
|
|
3531
|
+
return [...new Set(declarations)];
|
|
3532
|
+
}
|
|
3533
|
+
function getSyntheticTargetForTag(tag) {
|
|
3534
|
+
if (tag.target === null) {
|
|
3535
|
+
return null;
|
|
3536
|
+
}
|
|
3537
|
+
switch (tag.target.kind) {
|
|
3538
|
+
case "path":
|
|
3539
|
+
case "member":
|
|
3540
|
+
case "variant":
|
|
3541
|
+
return {
|
|
3542
|
+
kind: tag.target.kind,
|
|
3543
|
+
text: tag.target.rawText
|
|
3544
|
+
};
|
|
3545
|
+
case "ambiguous":
|
|
3546
|
+
return {
|
|
3547
|
+
kind: "path",
|
|
3548
|
+
text: tag.target.rawText
|
|
3549
|
+
};
|
|
3550
|
+
default: {
|
|
3551
|
+
const exhaustive = tag.target.kind;
|
|
3552
|
+
return exhaustive;
|
|
3553
|
+
}
|
|
3554
|
+
}
|
|
3555
|
+
}
|
|
3556
|
+
function getDeclaredSubjectType(node, checker, subjectType) {
|
|
3557
|
+
if ((ts4.isPropertyDeclaration(node) || ts4.isPropertySignature(node) || ts4.isParameter(node) || ts4.isVariableDeclaration(node) || ts4.isTypeAliasDeclaration(node)) && node.type !== void 0) {
|
|
3558
|
+
return checker.getTypeFromTypeNode(node.type);
|
|
3559
|
+
}
|
|
3560
|
+
return subjectType;
|
|
3561
|
+
}
|
|
3562
|
+
function getArgumentExpression(argumentText, valueLabels, capabilityTargets) {
|
|
3563
|
+
const trimmed = argumentText.trim();
|
|
3564
|
+
if (trimmed === "") {
|
|
3565
|
+
return null;
|
|
3566
|
+
}
|
|
3567
|
+
if (valueLabels.some((label) => label.includes("number") || label.includes("integer"))) {
|
|
3568
|
+
return trimmed;
|
|
3569
|
+
}
|
|
3570
|
+
if (valueLabels.some((label) => label.includes("boolean"))) {
|
|
3571
|
+
return trimmed === "true" || trimmed === "false" ? trimmed : null;
|
|
3572
|
+
}
|
|
3573
|
+
if (valueLabels.some((label) => label.includes("json"))) {
|
|
3574
|
+
try {
|
|
3575
|
+
return JSON.stringify(JSON.parse(trimmed));
|
|
3576
|
+
} catch {
|
|
3577
|
+
return null;
|
|
3578
|
+
}
|
|
3579
|
+
}
|
|
3580
|
+
if (valueLabels.some((label) => label.includes("condition"))) {
|
|
3581
|
+
return "undefined as unknown as FormSpecCondition";
|
|
3582
|
+
}
|
|
3583
|
+
if (capabilityTargets.length > 0 || valueLabels.some((label) => label.includes("string"))) {
|
|
3584
|
+
return JSON.stringify(trimmed);
|
|
3585
|
+
}
|
|
3586
|
+
return JSON.stringify(trimmed);
|
|
3587
|
+
}
|
|
3588
|
+
function diagnosticSeverity(code) {
|
|
3589
|
+
switch (code) {
|
|
3590
|
+
case "INVALID_TAG_ARGUMENT":
|
|
3591
|
+
case "INVALID_TAG_PLACEMENT":
|
|
3592
|
+
case "TYPE_MISMATCH":
|
|
3593
|
+
case "UNKNOWN_PATH_TARGET":
|
|
3594
|
+
return "error";
|
|
3595
|
+
default:
|
|
3596
|
+
return "warning";
|
|
3597
|
+
}
|
|
3598
|
+
}
|
|
3599
|
+
function buildTagDiagnostics(node, sourceFile, checker, placement, hostType, subjectType, commentTags, semanticOptions, performance2) {
|
|
3600
|
+
if (placement === null || subjectType === void 0) {
|
|
3601
|
+
return [];
|
|
3602
|
+
}
|
|
3603
|
+
const declaredSubjectType = getDeclaredSubjectType(node, checker, subjectType);
|
|
3604
|
+
const diagnostics = [];
|
|
3605
|
+
const standaloneHostTypeText = optionalMeasure(
|
|
3606
|
+
performance2,
|
|
3607
|
+
"analysis.renderStandaloneHostType",
|
|
3608
|
+
void 0,
|
|
3609
|
+
() => renderStandaloneTypeSyntax(hostType, checker)
|
|
3610
|
+
);
|
|
3611
|
+
const standaloneSubjectTypeText = optionalMeasure(
|
|
3612
|
+
performance2,
|
|
3613
|
+
"analysis.renderStandaloneSubjectType",
|
|
3614
|
+
void 0,
|
|
3615
|
+
() => renderStandaloneTypeSyntax(subjectType, checker)
|
|
3616
|
+
);
|
|
3617
|
+
const hostTypeNeedsDeclarations = requiresSupportingDeclarationsForStandaloneTypeSyntax(standaloneHostTypeText);
|
|
3618
|
+
const subjectTypeNeedsDeclarations = requiresSupportingDeclarationsForStandaloneTypeSyntax(standaloneSubjectTypeText);
|
|
3619
|
+
const hostTypeText = standaloneHostTypeText ?? typeToString(hostType, checker) ?? "unknown";
|
|
3620
|
+
const subjectTypeText = standaloneSubjectTypeText ?? typeToString(subjectType, checker) ?? "unknown";
|
|
3621
|
+
const supportingDeclarations = dedupeSupportingDeclarations([
|
|
3622
|
+
...hostTypeNeedsDeclarations ? supportingDeclarationsForType(hostType) : [],
|
|
3623
|
+
...subjectTypeNeedsDeclarations ? supportingDeclarationsForType(subjectType) : []
|
|
3624
|
+
]);
|
|
3625
|
+
const syntheticApplications = [];
|
|
3626
|
+
for (const tag of commentTags) {
|
|
3627
|
+
const semantic = getCommentTagSemanticContext(tag, semanticOptions);
|
|
3628
|
+
if (semantic.tagDefinition === null) {
|
|
3629
|
+
continue;
|
|
3630
|
+
}
|
|
3631
|
+
const target = getSyntheticTargetForTag(tag);
|
|
3632
|
+
const pathTargetResolution = tag.target?.kind === "path" || tag.target?.kind === "ambiguous" ? tag.target.path === null ? null : resolvePathTargetType(declaredSubjectType, checker, tag.target.path.segments) : null;
|
|
3633
|
+
const argumentExpression = getArgumentExpression(
|
|
3634
|
+
tag.argumentText,
|
|
3635
|
+
semantic.valueLabels,
|
|
3636
|
+
semantic.compatiblePathTargets
|
|
3637
|
+
);
|
|
3638
|
+
try {
|
|
3639
|
+
const syntheticOptions = {
|
|
3640
|
+
tagName: tag.normalizedTagName,
|
|
3641
|
+
placement,
|
|
3642
|
+
hostType: hostTypeText,
|
|
3643
|
+
subjectType: subjectTypeText,
|
|
3644
|
+
supportingDeclarations,
|
|
3645
|
+
...target === null ? {} : { target },
|
|
3646
|
+
...argumentExpression === null ? {} : { argumentExpression },
|
|
3647
|
+
...semanticOptions.extensions === void 0 ? {} : { extensions: semanticOptions.extensions }
|
|
3648
|
+
};
|
|
3649
|
+
lowerTagApplicationToSyntheticCall(syntheticOptions);
|
|
3650
|
+
syntheticApplications.push({
|
|
3651
|
+
tag,
|
|
3652
|
+
target,
|
|
3653
|
+
pathTargetResolution,
|
|
3654
|
+
options: syntheticOptions
|
|
3655
|
+
});
|
|
3656
|
+
} catch (error) {
|
|
3657
|
+
diagnostics.push({
|
|
3658
|
+
code: "INVALID_TAG_PLACEMENT",
|
|
3659
|
+
message: error instanceof Error ? error.message : String(error),
|
|
3660
|
+
range: tag.fullSpan,
|
|
3661
|
+
severity: "error"
|
|
3662
|
+
});
|
|
3663
|
+
}
|
|
3664
|
+
}
|
|
3665
|
+
const batchResults = optionalMeasure(
|
|
3666
|
+
performance2,
|
|
3667
|
+
"analysis.syntheticCheckBatch",
|
|
3668
|
+
{
|
|
3669
|
+
tagCount: syntheticApplications.length
|
|
3670
|
+
},
|
|
3671
|
+
() => checkSyntheticTagApplications({
|
|
3672
|
+
applications: syntheticApplications.map((application) => application.options),
|
|
3673
|
+
...performance2 === void 0 ? {} : { performance: performance2 }
|
|
3674
|
+
})
|
|
3675
|
+
);
|
|
3676
|
+
for (const [index, result] of batchResults.entries()) {
|
|
3677
|
+
const application = syntheticApplications[index];
|
|
3678
|
+
if (application === void 0) {
|
|
3679
|
+
continue;
|
|
3680
|
+
}
|
|
3681
|
+
for (const diagnostic of result.diagnostics) {
|
|
3682
|
+
const code = application.target !== null && diagnostic.message.includes("not assignable") ? application.target.kind === "path" && application.pathTargetResolution?.kind === "missing-property" ? "UNKNOWN_PATH_TARGET" : "TYPE_MISMATCH" : diagnostic.message.includes("Expected") ? "INVALID_TAG_ARGUMENT" : diagnostic.message.includes("No overload") ? "INVALID_TAG_PLACEMENT" : "TYPE_MISMATCH";
|
|
3683
|
+
diagnostics.push({
|
|
3684
|
+
code,
|
|
3685
|
+
message: diagnostic.message,
|
|
3686
|
+
range: application.tag.fullSpan,
|
|
3687
|
+
severity: diagnosticSeverity(code)
|
|
3688
|
+
});
|
|
3689
|
+
}
|
|
3690
|
+
}
|
|
3691
|
+
return diagnostics;
|
|
3692
|
+
}
|
|
3693
|
+
function deserializeSnapshotTagsForDiagnostics(snapshot) {
|
|
3694
|
+
return snapshot.tags.map((tag) => ({
|
|
3695
|
+
rawTagName: tag.rawTagName,
|
|
3696
|
+
normalizedTagName: tag.normalizedTagName,
|
|
3697
|
+
recognized: tag.recognized,
|
|
3698
|
+
fullSpan: tag.fullSpan,
|
|
3699
|
+
tagNameSpan: tag.tagNameSpan,
|
|
3700
|
+
payloadSpan: tag.payloadSpan,
|
|
3701
|
+
colonSpan: tag.target?.colonSpan ?? null,
|
|
3702
|
+
target: tag.target === null ? null : {
|
|
3703
|
+
rawText: tag.target.rawText,
|
|
3704
|
+
valid: tag.target.valid,
|
|
3705
|
+
kind: tag.target.kind,
|
|
3706
|
+
fullSpan: tag.target.fullSpan,
|
|
3707
|
+
colonSpan: tag.target.colonSpan,
|
|
3708
|
+
span: tag.target.span,
|
|
3709
|
+
path: extractPathTarget(`:${tag.target.rawText}`)?.path ?? null
|
|
3710
|
+
},
|
|
3711
|
+
argumentSpan: tag.argumentSpan,
|
|
3712
|
+
argumentText: tag.argumentText
|
|
3713
|
+
}));
|
|
3714
|
+
}
|
|
3715
|
+
function buildCommentSnapshot(node, sourceFile, checker, extensions, performance2) {
|
|
3716
|
+
return optionalMeasure(
|
|
3717
|
+
performance2,
|
|
3718
|
+
"analysis.buildCommentSnapshot",
|
|
3719
|
+
{
|
|
3720
|
+
nodeKind: ts4.SyntaxKind[node.kind]
|
|
3721
|
+
},
|
|
3722
|
+
() => {
|
|
3723
|
+
const docComment = getLastLeadingDocCommentRange(node, sourceFile);
|
|
3724
|
+
if (docComment === null) {
|
|
3725
|
+
return null;
|
|
3726
|
+
}
|
|
3727
|
+
const commentText = sourceFile.text.slice(docComment.pos, docComment.end);
|
|
3728
|
+
const parsed = parseCommentBlock(commentText, {
|
|
3729
|
+
offset: docComment.pos,
|
|
3730
|
+
...extensions === void 0 ? {} : { extensions }
|
|
3731
|
+
});
|
|
3732
|
+
if (parsed.tags.length === 0) {
|
|
3733
|
+
return null;
|
|
3734
|
+
}
|
|
3735
|
+
const placement = resolveDeclarationPlacement(node);
|
|
3736
|
+
const subjectType = getSubjectType(node, checker);
|
|
3737
|
+
const hostType = getHostType(node, checker);
|
|
3738
|
+
const semanticOptions = {
|
|
3739
|
+
checker,
|
|
3740
|
+
...subjectType === void 0 ? {} : { subjectType },
|
|
3741
|
+
...placement === null ? {} : { placement },
|
|
3742
|
+
...extensions === void 0 ? {} : { extensions }
|
|
3743
|
+
};
|
|
3744
|
+
const tags = parsed.tags.map(
|
|
3745
|
+
(tag) => serializeParsedCommentTag(tag, getCommentTagSemanticContext(tag, semanticOptions))
|
|
3746
|
+
);
|
|
3747
|
+
return {
|
|
3748
|
+
commentSpan: spanFromPos(docComment.pos, docComment.end),
|
|
3749
|
+
declarationSpan: spanFromPos(node.getStart(sourceFile), node.getEnd()),
|
|
3750
|
+
placement,
|
|
3751
|
+
subjectType: typeToString(subjectType, checker),
|
|
3752
|
+
hostType: typeToString(hostType, checker),
|
|
3753
|
+
tags
|
|
3754
|
+
};
|
|
3755
|
+
}
|
|
3756
|
+
);
|
|
3757
|
+
}
|
|
3758
|
+
function buildFormSpecAnalysisFileSnapshot(sourceFile, options) {
|
|
3759
|
+
const startedAt = getFormSpecPerformanceNow();
|
|
3760
|
+
const comments = [];
|
|
3761
|
+
const diagnostics = [];
|
|
3762
|
+
const visit = (node) => {
|
|
3763
|
+
const placement = resolveDeclarationPlacement(node);
|
|
3764
|
+
if (placement !== null) {
|
|
3765
|
+
const snapshot2 = buildCommentSnapshot(
|
|
3766
|
+
node,
|
|
3767
|
+
sourceFile,
|
|
3768
|
+
options.checker,
|
|
3769
|
+
options.extensions,
|
|
3770
|
+
options.performance
|
|
3771
|
+
);
|
|
3772
|
+
if (snapshot2 !== null) {
|
|
3773
|
+
comments.push(snapshot2);
|
|
3774
|
+
const subjectType = getSubjectType(node, options.checker);
|
|
3775
|
+
const hostType = getHostType(node, options.checker);
|
|
3776
|
+
diagnostics.push(
|
|
3777
|
+
...optionalMeasure(
|
|
3778
|
+
options.performance,
|
|
3779
|
+
"analysis.buildTagDiagnostics",
|
|
3780
|
+
{
|
|
3781
|
+
placement,
|
|
3782
|
+
tagCount: snapshot2.tags.length
|
|
3783
|
+
},
|
|
3784
|
+
() => buildTagDiagnostics(
|
|
3785
|
+
node,
|
|
3786
|
+
sourceFile,
|
|
3787
|
+
options.checker,
|
|
3788
|
+
placement,
|
|
3789
|
+
hostType,
|
|
3790
|
+
subjectType,
|
|
3791
|
+
deserializeSnapshotTagsForDiagnostics(snapshot2),
|
|
3792
|
+
{
|
|
3793
|
+
checker: options.checker,
|
|
3794
|
+
...subjectType === void 0 ? {} : { subjectType },
|
|
3795
|
+
placement,
|
|
3796
|
+
...options.extensions === void 0 ? {} : { extensions: options.extensions }
|
|
3797
|
+
},
|
|
3798
|
+
options.performance
|
|
3799
|
+
)
|
|
3800
|
+
)
|
|
3801
|
+
);
|
|
3802
|
+
}
|
|
3803
|
+
}
|
|
3804
|
+
ts4.forEachChild(node, visit);
|
|
3805
|
+
};
|
|
3806
|
+
visit(sourceFile);
|
|
3807
|
+
const snapshot = {
|
|
3808
|
+
filePath: sourceFile.fileName,
|
|
3809
|
+
sourceHash: computeFormSpecTextHash(sourceFile.text),
|
|
3810
|
+
generatedAt: (options.now?.() ?? /* @__PURE__ */ new Date()).toISOString(),
|
|
3811
|
+
comments,
|
|
3812
|
+
diagnostics
|
|
3813
|
+
};
|
|
3814
|
+
options.performance?.record({
|
|
3815
|
+
name: "analysis.buildFileSnapshot",
|
|
3816
|
+
durationMs: getFormSpecPerformanceNow() - startedAt,
|
|
3817
|
+
detail: {
|
|
3818
|
+
filePath: sourceFile.fileName,
|
|
3819
|
+
commentCount: comments.length,
|
|
3820
|
+
diagnosticCount: diagnostics.length
|
|
3821
|
+
}
|
|
3822
|
+
});
|
|
3823
|
+
return snapshot;
|
|
3824
|
+
}
|
|
3825
|
+
|
|
3826
|
+
// src/workspace-runtime.ts
|
|
3827
|
+
import path from "path";
|
|
3828
|
+
function getFormSpecWorkspaceId(workspaceRoot) {
|
|
3829
|
+
return computeFormSpecTextHash(workspaceRoot);
|
|
3830
|
+
}
|
|
3831
|
+
function getFormSpecWorkspaceRuntimeDirectory(workspaceRoot) {
|
|
3832
|
+
return path.join(workspaceRoot, ".cache", "formspec", "tooling");
|
|
3833
|
+
}
|
|
3834
|
+
function getFormSpecManifestPath(workspaceRoot) {
|
|
3835
|
+
return path.join(getFormSpecWorkspaceRuntimeDirectory(workspaceRoot), "manifest.json");
|
|
3836
|
+
}
|
|
3837
|
+
export {
|
|
3838
|
+
FORMSPEC_ANALYSIS_PROTOCOL_VERSION,
|
|
3839
|
+
FORMSPEC_ANALYSIS_SCHEMA_VERSION,
|
|
3840
|
+
FORM_SPEC_PLACEMENTS,
|
|
3841
|
+
FORM_SPEC_SYNTHETIC_BATCH_CACHE_ENTRIES,
|
|
3842
|
+
FORM_SPEC_TARGET_KINDS,
|
|
3843
|
+
NOOP_FORMSPEC_PERFORMANCE_RECORDER,
|
|
3844
|
+
analyzeConstraintTargets,
|
|
3845
|
+
buildConstraintTargetStates,
|
|
3846
|
+
buildFormSpecAnalysisFileSnapshot,
|
|
3847
|
+
buildSyntheticHelperPrelude,
|
|
3848
|
+
checkSyntheticTagApplication,
|
|
3849
|
+
checkSyntheticTagApplications,
|
|
3850
|
+
collectCompatiblePathTargets,
|
|
3851
|
+
collectReferencedTypeAnnotations,
|
|
3852
|
+
collectReferencedTypeConstraints,
|
|
3853
|
+
computeFormSpecTextHash,
|
|
3854
|
+
createFormSpecPerformanceRecorder,
|
|
3855
|
+
dereferenceAnalysisType,
|
|
3856
|
+
extractPathTarget,
|
|
3857
|
+
findCommentTagAtOffset,
|
|
3858
|
+
findDeclarationForCommentOffset,
|
|
3859
|
+
findEnclosingDocComment,
|
|
3860
|
+
formatConstraintTargetName,
|
|
3861
|
+
formatPathTarget,
|
|
3862
|
+
getAllTagDefinitions,
|
|
3863
|
+
getCommentCompletionContextAtOffset,
|
|
3864
|
+
getCommentCursorTargetAtOffset,
|
|
3865
|
+
getCommentHoverInfoAtOffset,
|
|
3866
|
+
getCommentTagSemanticContext,
|
|
3867
|
+
getConstraintTagDefinitions,
|
|
3868
|
+
getFormSpecManifestPath,
|
|
3869
|
+
getFormSpecPerformanceNow,
|
|
3870
|
+
getFormSpecWorkspaceId,
|
|
3871
|
+
getFormSpecWorkspaceRuntimeDirectory,
|
|
3872
|
+
getHostType,
|
|
3873
|
+
getLastLeadingDocCommentRange,
|
|
3874
|
+
getMatchingTagSignatures,
|
|
3875
|
+
getSemanticCommentCompletionContextAtOffset,
|
|
3876
|
+
getSubjectType,
|
|
3877
|
+
getTagCompletionPrefixAtOffset,
|
|
3878
|
+
getTagDefinition,
|
|
3879
|
+
getTagHoverMarkdown,
|
|
3880
|
+
getTypeSemanticCapabilities,
|
|
3881
|
+
hasTypeSemanticCapability,
|
|
3882
|
+
isFormSpecAnalysisManifest,
|
|
3883
|
+
isFormSpecSemanticQuery,
|
|
3884
|
+
isFormSpecSemanticResponse,
|
|
3885
|
+
lowerTagApplicationToSyntheticCall,
|
|
3886
|
+
normalizeFormSpecTagName,
|
|
3887
|
+
optionalMeasure,
|
|
3888
|
+
parseCommentBlock,
|
|
3889
|
+
parseConstraintTagValue,
|
|
3890
|
+
parseDefaultValueTagValue,
|
|
3891
|
+
parseTagSyntax,
|
|
3892
|
+
resolveConstraintTargetState,
|
|
3893
|
+
resolveDeclarationPlacement,
|
|
3894
|
+
resolvePathTargetType,
|
|
3895
|
+
serializeCommentTagSemanticContext,
|
|
3896
|
+
serializeCommentTargetSpecifier,
|
|
3897
|
+
serializeCompletionContext,
|
|
3898
|
+
serializeHoverInfo,
|
|
3899
|
+
serializeParsedCommentTag,
|
|
3900
|
+
sliceCommentSpan
|
|
3901
|
+
};
|
|
3902
|
+
//# sourceMappingURL=internal.js.map
|