@formspec/build 0.1.0-alpha.27 → 0.1.0-alpha.28
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/LICENSE +21 -0
- package/README.md +3 -2
- package/dist/analyzer/class-analyzer.d.ts +1 -1
- package/dist/analyzer/class-analyzer.d.ts.map +1 -1
- package/dist/cli.cjs +491 -35
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +488 -30
- package/dist/cli.js.map +1 -1
- package/dist/generators/method-schema.d.ts.map +1 -1
- package/dist/index.cjs +489 -33
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +488 -30
- package/dist/index.js.map +1 -1
- package/dist/internals.cjs +506 -34
- package/dist/internals.cjs.map +1 -1
- package/dist/internals.js +505 -31
- package/dist/internals.js.map +1 -1
- package/package.json +7 -6
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"method-schema.d.ts","sourceRoot":"","sources":["../../src/generators/method-schema.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,KAAK,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"method-schema.d.ts","sourceRoot":"","sources":["../../src/generators/method-schema.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,KAAK,EAAE,MAAM,YAAY,CAAC;AAEtC,OAAO,KAAK,EAAE,UAAU,EAAiB,MAAM,+BAA+B,CAAC;AAG/E,OAAO,EAA4B,KAAK,cAAc,EAAE,MAAM,gCAAgC,CAAC;AAG/F;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC,+BAA+B;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,4BAA4B;IAC5B,UAAU,EAAE,OAAO,CAAC;IACpB,gDAAgD;IAChD,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,kBAAkB;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,2DAA2D;IAC3D,MAAM,EAAE,mBAAmB,GAAG,IAAI,CAAC;IACnC,yBAAyB;IACzB,UAAU,EAAE,cAAc,CAAC;CAC5B;AAED;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,iCAAiC;IACjC,UAAU,EAAE,cAAc,CAAC;IAC3B,yDAAyD;IACzD,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,gDAAgD;IAChD,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;CAC/B;AA+DD;;;;;;;;;;;GAWG;AACH,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,UAAU,EAClB,OAAO,EAAE,EAAE,CAAC,WAAW,EACvB,eAAe,EAAE,GAAG,CAAC,MAAM,EAAE,qBAAqB,CAAC,GAClD,aAAa,CAYf;AAmED;;;;;;;GAOG;AACH,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,UAAU,EAAE,GAAG,GAAG,CAAC,MAAM,CAAC,CAY5E"}
|
package/dist/index.cjs
CHANGED
|
@@ -1258,6 +1258,7 @@ var path = __toESM(require("path"), 1);
|
|
|
1258
1258
|
|
|
1259
1259
|
// src/analyzer/class-analyzer.ts
|
|
1260
1260
|
var ts3 = __toESM(require("typescript"), 1);
|
|
1261
|
+
var import_internal2 = require("@formspec/analysis/internal");
|
|
1261
1262
|
|
|
1262
1263
|
// src/analyzer/jsdoc-constraints.ts
|
|
1263
1264
|
var ts2 = __toESM(require("typescript"), 1);
|
|
@@ -2180,9 +2181,17 @@ function analyzeClassToIR(classDecl, checker, file = "", extensionRegistry) {
|
|
|
2180
2181
|
}
|
|
2181
2182
|
}
|
|
2182
2183
|
}
|
|
2184
|
+
const specializedFields = applyDeclarationDiscriminatorToFields(
|
|
2185
|
+
fields,
|
|
2186
|
+
classDecl,
|
|
2187
|
+
classType,
|
|
2188
|
+
checker,
|
|
2189
|
+
file,
|
|
2190
|
+
diagnostics
|
|
2191
|
+
);
|
|
2183
2192
|
return {
|
|
2184
2193
|
name,
|
|
2185
|
-
fields,
|
|
2194
|
+
fields: specializedFields,
|
|
2186
2195
|
fieldLayouts,
|
|
2187
2196
|
typeRegistry,
|
|
2188
2197
|
...annotations.length > 0 && { annotations },
|
|
@@ -2222,10 +2231,18 @@ function analyzeInterfaceToIR(interfaceDecl, checker, file = "", extensionRegist
|
|
|
2222
2231
|
}
|
|
2223
2232
|
}
|
|
2224
2233
|
}
|
|
2225
|
-
const
|
|
2234
|
+
const specializedFields = applyDeclarationDiscriminatorToFields(
|
|
2235
|
+
fields,
|
|
2236
|
+
interfaceDecl,
|
|
2237
|
+
interfaceType,
|
|
2238
|
+
checker,
|
|
2239
|
+
file,
|
|
2240
|
+
diagnostics
|
|
2241
|
+
);
|
|
2242
|
+
const fieldLayouts = specializedFields.map(() => ({}));
|
|
2226
2243
|
return {
|
|
2227
2244
|
name,
|
|
2228
|
-
fields,
|
|
2245
|
+
fields: specializedFields,
|
|
2229
2246
|
fieldLayouts,
|
|
2230
2247
|
typeRegistry,
|
|
2231
2248
|
...annotations.length > 0 && { annotations },
|
|
@@ -2274,12 +2291,20 @@ function analyzeTypeAliasToIR(typeAlias, checker, file = "", extensionRegistry)
|
|
|
2274
2291
|
}
|
|
2275
2292
|
}
|
|
2276
2293
|
}
|
|
2294
|
+
const specializedFields = applyDeclarationDiscriminatorToFields(
|
|
2295
|
+
fields,
|
|
2296
|
+
typeAlias,
|
|
2297
|
+
aliasType,
|
|
2298
|
+
checker,
|
|
2299
|
+
file,
|
|
2300
|
+
diagnostics
|
|
2301
|
+
);
|
|
2277
2302
|
return {
|
|
2278
2303
|
ok: true,
|
|
2279
2304
|
analysis: {
|
|
2280
2305
|
name,
|
|
2281
|
-
fields,
|
|
2282
|
-
fieldLayouts:
|
|
2306
|
+
fields: specializedFields,
|
|
2307
|
+
fieldLayouts: specializedFields.map(() => ({})),
|
|
2283
2308
|
typeRegistry,
|
|
2284
2309
|
...annotations.length > 0 && { annotations },
|
|
2285
2310
|
...diagnostics.length > 0 && { diagnostics },
|
|
@@ -2288,6 +2313,396 @@ function analyzeTypeAliasToIR(typeAlias, checker, file = "", extensionRegistry)
|
|
|
2288
2313
|
}
|
|
2289
2314
|
};
|
|
2290
2315
|
}
|
|
2316
|
+
function makeAnalysisDiagnostic(code, message, primaryLocation, relatedLocations = []) {
|
|
2317
|
+
return {
|
|
2318
|
+
code,
|
|
2319
|
+
message,
|
|
2320
|
+
severity: "error",
|
|
2321
|
+
primaryLocation,
|
|
2322
|
+
relatedLocations
|
|
2323
|
+
};
|
|
2324
|
+
}
|
|
2325
|
+
function getLeadingParsedTags(node) {
|
|
2326
|
+
const sourceFile = node.getSourceFile();
|
|
2327
|
+
const sourceText = sourceFile.getFullText();
|
|
2328
|
+
const commentRanges = ts3.getLeadingCommentRanges(sourceText, node.getFullStart());
|
|
2329
|
+
if (commentRanges === void 0) {
|
|
2330
|
+
return [];
|
|
2331
|
+
}
|
|
2332
|
+
const parsedTags = [];
|
|
2333
|
+
for (const range of commentRanges) {
|
|
2334
|
+
if (range.kind !== ts3.SyntaxKind.MultiLineCommentTrivia) {
|
|
2335
|
+
continue;
|
|
2336
|
+
}
|
|
2337
|
+
const commentText = sourceText.slice(range.pos, range.end);
|
|
2338
|
+
if (!commentText.startsWith("/**")) {
|
|
2339
|
+
continue;
|
|
2340
|
+
}
|
|
2341
|
+
parsedTags.push(...(0, import_internal2.parseCommentBlock)(commentText, { offset: range.pos }).tags);
|
|
2342
|
+
}
|
|
2343
|
+
return parsedTags;
|
|
2344
|
+
}
|
|
2345
|
+
function findDiscriminatorProperty(node, fieldName) {
|
|
2346
|
+
if (ts3.isClassDeclaration(node)) {
|
|
2347
|
+
for (const member of node.members) {
|
|
2348
|
+
if (ts3.isPropertyDeclaration(member) && ts3.isIdentifier(member.name) && member.name.text === fieldName) {
|
|
2349
|
+
return member;
|
|
2350
|
+
}
|
|
2351
|
+
}
|
|
2352
|
+
return null;
|
|
2353
|
+
}
|
|
2354
|
+
if (ts3.isInterfaceDeclaration(node)) {
|
|
2355
|
+
for (const member of node.members) {
|
|
2356
|
+
if (ts3.isPropertySignature(member) && ts3.isIdentifier(member.name) && member.name.text === fieldName) {
|
|
2357
|
+
return member;
|
|
2358
|
+
}
|
|
2359
|
+
}
|
|
2360
|
+
return null;
|
|
2361
|
+
}
|
|
2362
|
+
if (ts3.isTypeLiteralNode(node.type)) {
|
|
2363
|
+
for (const member of node.type.members) {
|
|
2364
|
+
if (ts3.isPropertySignature(member) && ts3.isIdentifier(member.name) && member.name.text === fieldName) {
|
|
2365
|
+
return member;
|
|
2366
|
+
}
|
|
2367
|
+
}
|
|
2368
|
+
}
|
|
2369
|
+
return null;
|
|
2370
|
+
}
|
|
2371
|
+
function isLocalTypeParameterName(node, typeParameterName) {
|
|
2372
|
+
return node.typeParameters?.some((typeParameter) => typeParameter.name.text === typeParameterName) ?? false;
|
|
2373
|
+
}
|
|
2374
|
+
function isNullishSemanticType(type) {
|
|
2375
|
+
if (type.flags & (ts3.TypeFlags.Null | ts3.TypeFlags.Undefined | ts3.TypeFlags.Void | ts3.TypeFlags.Unknown | ts3.TypeFlags.Any)) {
|
|
2376
|
+
return true;
|
|
2377
|
+
}
|
|
2378
|
+
return type.isUnion() && type.types.some((member) => isNullishSemanticType(member));
|
|
2379
|
+
}
|
|
2380
|
+
function isStringLikeSemanticType(type) {
|
|
2381
|
+
if (type.flags & ts3.TypeFlags.StringLike) {
|
|
2382
|
+
return true;
|
|
2383
|
+
}
|
|
2384
|
+
if (type.isUnion()) {
|
|
2385
|
+
return type.types.length > 0 && type.types.every((member) => isStringLikeSemanticType(member));
|
|
2386
|
+
}
|
|
2387
|
+
return false;
|
|
2388
|
+
}
|
|
2389
|
+
function extractDiscriminatorDirective(node, file, diagnostics) {
|
|
2390
|
+
const discriminatorTags = getLeadingParsedTags(node).filter(
|
|
2391
|
+
(tag) => tag.normalizedTagName === "discriminator"
|
|
2392
|
+
);
|
|
2393
|
+
if (discriminatorTags.length === 0) {
|
|
2394
|
+
return null;
|
|
2395
|
+
}
|
|
2396
|
+
const [firstTag, ...duplicateTags] = discriminatorTags;
|
|
2397
|
+
for (const _duplicateTag of duplicateTags) {
|
|
2398
|
+
diagnostics.push(
|
|
2399
|
+
makeAnalysisDiagnostic(
|
|
2400
|
+
"DUPLICATE_TAG",
|
|
2401
|
+
'Duplicate "@discriminator" tag. Only one discriminator declaration is allowed per declaration.',
|
|
2402
|
+
provenanceForNode(node, file)
|
|
2403
|
+
)
|
|
2404
|
+
);
|
|
2405
|
+
}
|
|
2406
|
+
if (firstTag === void 0) {
|
|
2407
|
+
return null;
|
|
2408
|
+
}
|
|
2409
|
+
const firstTarget = firstTag.target;
|
|
2410
|
+
if (firstTarget?.path === null || firstTarget?.valid !== true) {
|
|
2411
|
+
diagnostics.push(
|
|
2412
|
+
makeAnalysisDiagnostic(
|
|
2413
|
+
"INVALID_TAG_ARGUMENT",
|
|
2414
|
+
'Tag "@discriminator" requires a direct path target like ":kind".',
|
|
2415
|
+
provenanceForNode(node, file)
|
|
2416
|
+
)
|
|
2417
|
+
);
|
|
2418
|
+
return null;
|
|
2419
|
+
}
|
|
2420
|
+
if (firstTarget.path.segments.length !== 1) {
|
|
2421
|
+
diagnostics.push(
|
|
2422
|
+
makeAnalysisDiagnostic(
|
|
2423
|
+
"INVALID_TAG_ARGUMENT",
|
|
2424
|
+
'Tag "@discriminator" only supports direct property targets in v1; nested paths are out of scope.',
|
|
2425
|
+
provenanceForNode(node, file)
|
|
2426
|
+
)
|
|
2427
|
+
);
|
|
2428
|
+
return null;
|
|
2429
|
+
}
|
|
2430
|
+
const typeParameterName = firstTag.argumentText.trim();
|
|
2431
|
+
if (!/^[A-Za-z_$][\w$]*$/u.test(typeParameterName)) {
|
|
2432
|
+
diagnostics.push(
|
|
2433
|
+
makeAnalysisDiagnostic(
|
|
2434
|
+
"INVALID_TAG_ARGUMENT",
|
|
2435
|
+
'Tag "@discriminator" requires a local type parameter name as its source operand.',
|
|
2436
|
+
provenanceForNode(node, file)
|
|
2437
|
+
)
|
|
2438
|
+
);
|
|
2439
|
+
return null;
|
|
2440
|
+
}
|
|
2441
|
+
return {
|
|
2442
|
+
fieldName: firstTarget.path.segments[0] ?? firstTarget.rawText,
|
|
2443
|
+
typeParameterName,
|
|
2444
|
+
provenance: provenanceForNode(node, file)
|
|
2445
|
+
};
|
|
2446
|
+
}
|
|
2447
|
+
function validateDiscriminatorDirective(node, checker, file, diagnostics) {
|
|
2448
|
+
const directive = extractDiscriminatorDirective(node, file, diagnostics);
|
|
2449
|
+
if (directive === null) {
|
|
2450
|
+
return null;
|
|
2451
|
+
}
|
|
2452
|
+
if (!isLocalTypeParameterName(node, directive.typeParameterName)) {
|
|
2453
|
+
diagnostics.push(
|
|
2454
|
+
makeAnalysisDiagnostic(
|
|
2455
|
+
"INVALID_TAG_ARGUMENT",
|
|
2456
|
+
`Tag "@discriminator" references "${directive.typeParameterName}", but the source operand must be a type parameter declared on the same declaration.`,
|
|
2457
|
+
directive.provenance
|
|
2458
|
+
)
|
|
2459
|
+
);
|
|
2460
|
+
return null;
|
|
2461
|
+
}
|
|
2462
|
+
const propertyDecl = findDiscriminatorProperty(node, directive.fieldName);
|
|
2463
|
+
if (propertyDecl === null) {
|
|
2464
|
+
diagnostics.push(
|
|
2465
|
+
makeAnalysisDiagnostic(
|
|
2466
|
+
"UNKNOWN_PATH_TARGET",
|
|
2467
|
+
`Tag "@discriminator" targets "${directive.fieldName}", but no direct property with that name exists on this declaration.`,
|
|
2468
|
+
directive.provenance
|
|
2469
|
+
)
|
|
2470
|
+
);
|
|
2471
|
+
return null;
|
|
2472
|
+
}
|
|
2473
|
+
if (propertyDecl.questionToken !== void 0) {
|
|
2474
|
+
diagnostics.push(
|
|
2475
|
+
makeAnalysisDiagnostic(
|
|
2476
|
+
"TYPE_MISMATCH",
|
|
2477
|
+
`Discriminator field "${directive.fieldName}" must be required; optional discriminator fields are not supported.`,
|
|
2478
|
+
directive.provenance,
|
|
2479
|
+
[provenanceForNode(propertyDecl, file)]
|
|
2480
|
+
)
|
|
2481
|
+
);
|
|
2482
|
+
return null;
|
|
2483
|
+
}
|
|
2484
|
+
const propertyType = checker.getTypeAtLocation(propertyDecl);
|
|
2485
|
+
if (isNullishSemanticType(propertyType)) {
|
|
2486
|
+
diagnostics.push(
|
|
2487
|
+
makeAnalysisDiagnostic(
|
|
2488
|
+
"TYPE_MISMATCH",
|
|
2489
|
+
`Discriminator field "${directive.fieldName}" must not be nullable.`,
|
|
2490
|
+
directive.provenance,
|
|
2491
|
+
[provenanceForNode(propertyDecl, file)]
|
|
2492
|
+
)
|
|
2493
|
+
);
|
|
2494
|
+
return null;
|
|
2495
|
+
}
|
|
2496
|
+
if (!isStringLikeSemanticType(propertyType)) {
|
|
2497
|
+
diagnostics.push(
|
|
2498
|
+
makeAnalysisDiagnostic(
|
|
2499
|
+
"TYPE_MISMATCH",
|
|
2500
|
+
`Discriminator field "${directive.fieldName}" must be string-like.`,
|
|
2501
|
+
directive.provenance,
|
|
2502
|
+
[provenanceForNode(propertyDecl, file)]
|
|
2503
|
+
)
|
|
2504
|
+
);
|
|
2505
|
+
return null;
|
|
2506
|
+
}
|
|
2507
|
+
return directive;
|
|
2508
|
+
}
|
|
2509
|
+
function getConcreteTypeArgumentForDiscriminator(node, subjectType, checker, typeParameterName) {
|
|
2510
|
+
const typeParameterIndex = node.typeParameters?.findIndex(
|
|
2511
|
+
(typeParameter) => typeParameter.name.text === typeParameterName
|
|
2512
|
+
) ?? -1;
|
|
2513
|
+
if (typeParameterIndex < 0) {
|
|
2514
|
+
return null;
|
|
2515
|
+
}
|
|
2516
|
+
const referenceTypeArguments = (isTypeReference(subjectType) ? subjectType.typeArguments : void 0) ?? subjectType.aliasTypeArguments;
|
|
2517
|
+
if (referenceTypeArguments?.[typeParameterIndex] !== void 0) {
|
|
2518
|
+
return referenceTypeArguments[typeParameterIndex] ?? null;
|
|
2519
|
+
}
|
|
2520
|
+
const localTypeParameter = node.typeParameters?.[typeParameterIndex];
|
|
2521
|
+
return localTypeParameter === void 0 ? null : checker.getTypeAtLocation(localTypeParameter);
|
|
2522
|
+
}
|
|
2523
|
+
function extractDeclarationApiName(node) {
|
|
2524
|
+
for (const tag of getLeadingParsedTags(node)) {
|
|
2525
|
+
if (tag.normalizedTagName !== "apiName") {
|
|
2526
|
+
continue;
|
|
2527
|
+
}
|
|
2528
|
+
if (tag.target === null && tag.argumentText.trim() !== "") {
|
|
2529
|
+
return tag.argumentText.trim();
|
|
2530
|
+
}
|
|
2531
|
+
if (tag.target?.kind === "variant" && tag.target.rawText === "singular") {
|
|
2532
|
+
const value = tag.argumentText.trim();
|
|
2533
|
+
if (value !== "") {
|
|
2534
|
+
return value;
|
|
2535
|
+
}
|
|
2536
|
+
}
|
|
2537
|
+
}
|
|
2538
|
+
return null;
|
|
2539
|
+
}
|
|
2540
|
+
function inferJsonFacingName(name) {
|
|
2541
|
+
return name.replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/([A-Z]+)([A-Z][a-z])/g, "$1_$2").replace(/[-\s]+/g, "_").toLowerCase();
|
|
2542
|
+
}
|
|
2543
|
+
function resolveNamedDiscriminatorDeclaration(type, checker, seen = /* @__PURE__ */ new Set()) {
|
|
2544
|
+
if (seen.has(type)) {
|
|
2545
|
+
return null;
|
|
2546
|
+
}
|
|
2547
|
+
seen.add(type);
|
|
2548
|
+
const symbol = type.aliasSymbol ?? type.getSymbol();
|
|
2549
|
+
if (symbol !== void 0) {
|
|
2550
|
+
const aliased = symbol.flags & ts3.SymbolFlags.Alias ? checker.getAliasedSymbol(symbol) : void 0;
|
|
2551
|
+
const targetSymbol = aliased ?? symbol;
|
|
2552
|
+
const declaration = targetSymbol.declarations?.find(
|
|
2553
|
+
(candidate) => ts3.isClassDeclaration(candidate) || ts3.isInterfaceDeclaration(candidate) || ts3.isTypeAliasDeclaration(candidate) || ts3.isEnumDeclaration(candidate)
|
|
2554
|
+
);
|
|
2555
|
+
if (declaration !== void 0) {
|
|
2556
|
+
if (ts3.isTypeAliasDeclaration(declaration) && ts3.isTypeReferenceNode(declaration.type) && checker.getTypeFromTypeNode(declaration.type) !== type) {
|
|
2557
|
+
return resolveNamedDiscriminatorDeclaration(
|
|
2558
|
+
checker.getTypeFromTypeNode(declaration.type),
|
|
2559
|
+
checker,
|
|
2560
|
+
seen
|
|
2561
|
+
);
|
|
2562
|
+
}
|
|
2563
|
+
return declaration;
|
|
2564
|
+
}
|
|
2565
|
+
}
|
|
2566
|
+
return null;
|
|
2567
|
+
}
|
|
2568
|
+
function resolveDiscriminatorValue(boundType, checker, provenance, diagnostics) {
|
|
2569
|
+
if (boundType === null) {
|
|
2570
|
+
diagnostics.push(
|
|
2571
|
+
makeAnalysisDiagnostic(
|
|
2572
|
+
"INVALID_TAG_ARGUMENT",
|
|
2573
|
+
"Discriminator resolution failed because no concrete type argument is available for the referenced type parameter.",
|
|
2574
|
+
provenance
|
|
2575
|
+
)
|
|
2576
|
+
);
|
|
2577
|
+
return null;
|
|
2578
|
+
}
|
|
2579
|
+
if (boundType.isStringLiteral()) {
|
|
2580
|
+
return boundType.value;
|
|
2581
|
+
}
|
|
2582
|
+
if (boundType.isUnion()) {
|
|
2583
|
+
const nonNullMembers = boundType.types.filter(
|
|
2584
|
+
(member) => !(member.flags & (ts3.TypeFlags.Null | ts3.TypeFlags.Undefined))
|
|
2585
|
+
);
|
|
2586
|
+
if (nonNullMembers.every((member) => member.isStringLiteral())) {
|
|
2587
|
+
diagnostics.push(
|
|
2588
|
+
makeAnalysisDiagnostic(
|
|
2589
|
+
"INVALID_TAG_ARGUMENT",
|
|
2590
|
+
"Discriminator resolution for unions of string literals is out of scope for v1.",
|
|
2591
|
+
provenance
|
|
2592
|
+
)
|
|
2593
|
+
);
|
|
2594
|
+
return null;
|
|
2595
|
+
}
|
|
2596
|
+
}
|
|
2597
|
+
const declaration = resolveNamedDiscriminatorDeclaration(boundType, checker);
|
|
2598
|
+
if (declaration !== null) {
|
|
2599
|
+
return extractDeclarationApiName(declaration) ?? inferJsonFacingName(getDeclarationName(declaration));
|
|
2600
|
+
}
|
|
2601
|
+
diagnostics.push(
|
|
2602
|
+
makeAnalysisDiagnostic(
|
|
2603
|
+
"INVALID_TAG_ARGUMENT",
|
|
2604
|
+
"Discriminator resolution could not derive a JSON-facing discriminator value from the referenced type argument.",
|
|
2605
|
+
provenance
|
|
2606
|
+
)
|
|
2607
|
+
);
|
|
2608
|
+
return null;
|
|
2609
|
+
}
|
|
2610
|
+
function getDeclarationName(node) {
|
|
2611
|
+
if (ts3.isClassDeclaration(node) || ts3.isInterfaceDeclaration(node) || ts3.isTypeAliasDeclaration(node) || ts3.isEnumDeclaration(node)) {
|
|
2612
|
+
return node.name?.text ?? "anonymous";
|
|
2613
|
+
}
|
|
2614
|
+
return "anonymous";
|
|
2615
|
+
}
|
|
2616
|
+
function applyDeclarationDiscriminatorToFields(fields, node, subjectType, checker, file, diagnostics) {
|
|
2617
|
+
const directive = validateDiscriminatorDirective(node, checker, file, diagnostics);
|
|
2618
|
+
if (directive === null) {
|
|
2619
|
+
return [...fields];
|
|
2620
|
+
}
|
|
2621
|
+
const discriminatorValue = resolveDiscriminatorValue(
|
|
2622
|
+
getConcreteTypeArgumentForDiscriminator(
|
|
2623
|
+
node,
|
|
2624
|
+
subjectType,
|
|
2625
|
+
checker,
|
|
2626
|
+
directive.typeParameterName
|
|
2627
|
+
),
|
|
2628
|
+
checker,
|
|
2629
|
+
directive.provenance,
|
|
2630
|
+
diagnostics
|
|
2631
|
+
);
|
|
2632
|
+
if (discriminatorValue === null) {
|
|
2633
|
+
return [...fields];
|
|
2634
|
+
}
|
|
2635
|
+
return fields.map(
|
|
2636
|
+
(field) => field.name === directive.fieldName ? {
|
|
2637
|
+
...field,
|
|
2638
|
+
type: {
|
|
2639
|
+
kind: "enum",
|
|
2640
|
+
members: [{ value: discriminatorValue }]
|
|
2641
|
+
}
|
|
2642
|
+
} : field
|
|
2643
|
+
);
|
|
2644
|
+
}
|
|
2645
|
+
function buildInstantiatedReferenceName(baseName, typeArguments, checker) {
|
|
2646
|
+
const renderedArguments = typeArguments.map(
|
|
2647
|
+
(typeArgument) => checker.typeToString(typeArgument).replace(/[^A-Za-z0-9]+/g, "_").replace(/^_+|_+$/g, "")
|
|
2648
|
+
).filter((value) => value !== "");
|
|
2649
|
+
return renderedArguments.length === 0 ? baseName : `${baseName}__${renderedArguments.join("__")}`;
|
|
2650
|
+
}
|
|
2651
|
+
function extractReferenceTypeArguments(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry, diagnostics) {
|
|
2652
|
+
const typeNode = sourceNode === void 0 ? void 0 : extractTypeNodeFromSource(sourceNode);
|
|
2653
|
+
if (typeNode === void 0) {
|
|
2654
|
+
return [];
|
|
2655
|
+
}
|
|
2656
|
+
const resolvedTypeNode = resolveAliasedTypeNode(typeNode, checker);
|
|
2657
|
+
if (!ts3.isTypeReferenceNode(resolvedTypeNode) || resolvedTypeNode.typeArguments === void 0) {
|
|
2658
|
+
return [];
|
|
2659
|
+
}
|
|
2660
|
+
return resolvedTypeNode.typeArguments.map((argumentNode) => {
|
|
2661
|
+
const argumentType = checker.getTypeFromTypeNode(argumentNode);
|
|
2662
|
+
return {
|
|
2663
|
+
tsType: argumentType,
|
|
2664
|
+
typeNode: resolveTypeNode(
|
|
2665
|
+
argumentType,
|
|
2666
|
+
checker,
|
|
2667
|
+
file,
|
|
2668
|
+
typeRegistry,
|
|
2669
|
+
visiting,
|
|
2670
|
+
argumentNode,
|
|
2671
|
+
extensionRegistry,
|
|
2672
|
+
diagnostics
|
|
2673
|
+
)
|
|
2674
|
+
};
|
|
2675
|
+
});
|
|
2676
|
+
}
|
|
2677
|
+
function applyDiscriminatorToObjectProperties(properties, node, subjectType, checker, file, diagnostics) {
|
|
2678
|
+
const directive = validateDiscriminatorDirective(node, checker, file, diagnostics);
|
|
2679
|
+
if (directive === null) {
|
|
2680
|
+
return properties;
|
|
2681
|
+
}
|
|
2682
|
+
const discriminatorValue = resolveDiscriminatorValue(
|
|
2683
|
+
getConcreteTypeArgumentForDiscriminator(
|
|
2684
|
+
node,
|
|
2685
|
+
subjectType,
|
|
2686
|
+
checker,
|
|
2687
|
+
directive.typeParameterName
|
|
2688
|
+
),
|
|
2689
|
+
checker,
|
|
2690
|
+
directive.provenance,
|
|
2691
|
+
diagnostics
|
|
2692
|
+
);
|
|
2693
|
+
if (discriminatorValue === null) {
|
|
2694
|
+
return properties;
|
|
2695
|
+
}
|
|
2696
|
+
return properties.map(
|
|
2697
|
+
(property) => property.name === directive.fieldName ? {
|
|
2698
|
+
...property,
|
|
2699
|
+
type: {
|
|
2700
|
+
kind: "enum",
|
|
2701
|
+
members: [{ value: discriminatorValue }]
|
|
2702
|
+
}
|
|
2703
|
+
} : property
|
|
2704
|
+
);
|
|
2705
|
+
}
|
|
2291
2706
|
function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting, diagnostics, hostType, extensionRegistry) {
|
|
2292
2707
|
if (!ts3.isIdentifier(prop.name)) {
|
|
2293
2708
|
return null;
|
|
@@ -2576,6 +2991,7 @@ function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode
|
|
|
2576
2991
|
file,
|
|
2577
2992
|
typeRegistry,
|
|
2578
2993
|
visiting,
|
|
2994
|
+
sourceNode,
|
|
2579
2995
|
extensionRegistry,
|
|
2580
2996
|
diagnostics
|
|
2581
2997
|
);
|
|
@@ -2839,35 +3255,60 @@ function typeNodeContainsReference(type, targetName) {
|
|
|
2839
3255
|
}
|
|
2840
3256
|
}
|
|
2841
3257
|
}
|
|
2842
|
-
function resolveObjectType(type, checker, file, typeRegistry, visiting, extensionRegistry, diagnostics) {
|
|
3258
|
+
function resolveObjectType(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry, diagnostics) {
|
|
3259
|
+
const collectedDiagnostics = diagnostics ?? [];
|
|
2843
3260
|
const typeName = getNamedTypeName(type);
|
|
2844
3261
|
const namedTypeName = typeName ?? void 0;
|
|
2845
3262
|
const namedDecl = getNamedTypeDeclaration(type);
|
|
2846
|
-
const
|
|
3263
|
+
const referenceTypeArguments = extractReferenceTypeArguments(
|
|
3264
|
+
type,
|
|
3265
|
+
checker,
|
|
3266
|
+
file,
|
|
3267
|
+
typeRegistry,
|
|
3268
|
+
visiting,
|
|
3269
|
+
sourceNode,
|
|
3270
|
+
extensionRegistry,
|
|
3271
|
+
collectedDiagnostics
|
|
3272
|
+
);
|
|
3273
|
+
const instantiatedTypeName = namedTypeName !== void 0 && referenceTypeArguments.length > 0 ? buildInstantiatedReferenceName(
|
|
3274
|
+
namedTypeName,
|
|
3275
|
+
referenceTypeArguments.map((argument) => argument.tsType),
|
|
3276
|
+
checker
|
|
3277
|
+
) : void 0;
|
|
3278
|
+
const registryTypeName = instantiatedTypeName ?? namedTypeName;
|
|
3279
|
+
const shouldRegisterNamedType = registryTypeName !== void 0 && !(registryTypeName === "Record" && namedDecl?.getSourceFile().fileName !== file);
|
|
2847
3280
|
const clearNamedTypeRegistration = () => {
|
|
2848
|
-
if (
|
|
3281
|
+
if (registryTypeName === void 0 || !shouldRegisterNamedType) {
|
|
2849
3282
|
return;
|
|
2850
3283
|
}
|
|
2851
|
-
Reflect.deleteProperty(typeRegistry,
|
|
3284
|
+
Reflect.deleteProperty(typeRegistry, registryTypeName);
|
|
2852
3285
|
};
|
|
2853
3286
|
if (visiting.has(type)) {
|
|
2854
|
-
if (
|
|
2855
|
-
return {
|
|
3287
|
+
if (registryTypeName !== void 0 && shouldRegisterNamedType) {
|
|
3288
|
+
return {
|
|
3289
|
+
kind: "reference",
|
|
3290
|
+
name: registryTypeName,
|
|
3291
|
+
typeArguments: referenceTypeArguments.map((argument) => argument.typeNode)
|
|
3292
|
+
};
|
|
2856
3293
|
}
|
|
2857
3294
|
return { kind: "object", properties: [], additionalProperties: false };
|
|
2858
3295
|
}
|
|
2859
|
-
if (
|
|
2860
|
-
typeRegistry[
|
|
2861
|
-
name:
|
|
3296
|
+
if (registryTypeName !== void 0 && shouldRegisterNamedType && !typeRegistry[registryTypeName]) {
|
|
3297
|
+
typeRegistry[registryTypeName] = {
|
|
3298
|
+
name: registryTypeName,
|
|
2862
3299
|
type: RESOLVING_TYPE_PLACEHOLDER,
|
|
2863
3300
|
provenance: provenanceForDeclaration(namedDecl, file)
|
|
2864
3301
|
};
|
|
2865
3302
|
}
|
|
2866
3303
|
visiting.add(type);
|
|
2867
|
-
if (
|
|
2868
|
-
if (typeRegistry[
|
|
3304
|
+
if (registryTypeName !== void 0 && shouldRegisterNamedType && typeRegistry[registryTypeName]?.type !== void 0) {
|
|
3305
|
+
if (typeRegistry[registryTypeName].type !== RESOLVING_TYPE_PLACEHOLDER) {
|
|
2869
3306
|
visiting.delete(type);
|
|
2870
|
-
return {
|
|
3307
|
+
return {
|
|
3308
|
+
kind: "reference",
|
|
3309
|
+
name: registryTypeName,
|
|
3310
|
+
typeArguments: referenceTypeArguments.map((argument) => argument.typeNode)
|
|
3311
|
+
};
|
|
2871
3312
|
}
|
|
2872
3313
|
}
|
|
2873
3314
|
const recordNode = tryResolveRecordType(
|
|
@@ -2877,24 +3318,28 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting, extensio
|
|
|
2877
3318
|
typeRegistry,
|
|
2878
3319
|
visiting,
|
|
2879
3320
|
extensionRegistry,
|
|
2880
|
-
|
|
3321
|
+
collectedDiagnostics
|
|
2881
3322
|
);
|
|
2882
3323
|
if (recordNode) {
|
|
2883
3324
|
visiting.delete(type);
|
|
2884
|
-
if (
|
|
2885
|
-
const isRecursiveRecord = typeNodeContainsReference(recordNode.valueType,
|
|
3325
|
+
if (registryTypeName !== void 0 && shouldRegisterNamedType) {
|
|
3326
|
+
const isRecursiveRecord = typeNodeContainsReference(recordNode.valueType, registryTypeName);
|
|
2886
3327
|
if (!isRecursiveRecord) {
|
|
2887
3328
|
clearNamedTypeRegistration();
|
|
2888
3329
|
return recordNode;
|
|
2889
3330
|
}
|
|
2890
3331
|
const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file, makeParseOptions(extensionRegistry)) : void 0;
|
|
2891
|
-
typeRegistry[
|
|
2892
|
-
name:
|
|
3332
|
+
typeRegistry[registryTypeName] = {
|
|
3333
|
+
name: registryTypeName,
|
|
2893
3334
|
type: recordNode,
|
|
2894
3335
|
...annotations !== void 0 && annotations.length > 0 && { annotations },
|
|
2895
3336
|
provenance: provenanceForDeclaration(namedDecl, file)
|
|
2896
3337
|
};
|
|
2897
|
-
return {
|
|
3338
|
+
return {
|
|
3339
|
+
kind: "reference",
|
|
3340
|
+
name: registryTypeName,
|
|
3341
|
+
typeArguments: referenceTypeArguments.map((argument) => argument.typeNode)
|
|
3342
|
+
};
|
|
2898
3343
|
}
|
|
2899
3344
|
return recordNode;
|
|
2900
3345
|
}
|
|
@@ -2905,7 +3350,7 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting, extensio
|
|
|
2905
3350
|
file,
|
|
2906
3351
|
typeRegistry,
|
|
2907
3352
|
visiting,
|
|
2908
|
-
|
|
3353
|
+
collectedDiagnostics,
|
|
2909
3354
|
extensionRegistry
|
|
2910
3355
|
);
|
|
2911
3356
|
for (const prop of type.getProperties()) {
|
|
@@ -2921,7 +3366,7 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting, extensio
|
|
|
2921
3366
|
visiting,
|
|
2922
3367
|
declaration,
|
|
2923
3368
|
extensionRegistry,
|
|
2924
|
-
|
|
3369
|
+
collectedDiagnostics
|
|
2925
3370
|
);
|
|
2926
3371
|
const fieldNodeInfo = fieldInfoMap?.get(prop.name);
|
|
2927
3372
|
properties.push({
|
|
@@ -2936,18 +3381,29 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting, extensio
|
|
|
2936
3381
|
visiting.delete(type);
|
|
2937
3382
|
const objectNode = {
|
|
2938
3383
|
kind: "object",
|
|
2939
|
-
properties
|
|
3384
|
+
properties: namedDecl !== void 0 && (ts3.isClassDeclaration(namedDecl) || ts3.isInterfaceDeclaration(namedDecl) || ts3.isTypeAliasDeclaration(namedDecl)) ? applyDiscriminatorToObjectProperties(
|
|
3385
|
+
properties,
|
|
3386
|
+
namedDecl,
|
|
3387
|
+
type,
|
|
3388
|
+
checker,
|
|
3389
|
+
file,
|
|
3390
|
+
collectedDiagnostics
|
|
3391
|
+
) : properties,
|
|
2940
3392
|
additionalProperties: true
|
|
2941
3393
|
};
|
|
2942
|
-
if (
|
|
3394
|
+
if (registryTypeName !== void 0 && shouldRegisterNamedType) {
|
|
2943
3395
|
const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file, makeParseOptions(extensionRegistry)) : void 0;
|
|
2944
|
-
typeRegistry[
|
|
2945
|
-
name:
|
|
3396
|
+
typeRegistry[registryTypeName] = {
|
|
3397
|
+
name: registryTypeName,
|
|
2946
3398
|
type: objectNode,
|
|
2947
3399
|
...annotations !== void 0 && annotations.length > 0 && { annotations },
|
|
2948
3400
|
provenance: provenanceForDeclaration(namedDecl, file)
|
|
2949
3401
|
};
|
|
2950
|
-
return {
|
|
3402
|
+
return {
|
|
3403
|
+
kind: "reference",
|
|
3404
|
+
name: registryTypeName,
|
|
3405
|
+
typeArguments: referenceTypeArguments.map((argument) => argument.typeNode)
|
|
3406
|
+
};
|
|
2951
3407
|
}
|
|
2952
3408
|
return objectNode;
|
|
2953
3409
|
}
|
|
@@ -3321,9 +3777,9 @@ function analyzeNamedTypeToIRFromProgramContext(ctx, filePath, typeName, extensi
|
|
|
3321
3777
|
}
|
|
3322
3778
|
|
|
3323
3779
|
// src/validate/constraint-validator.ts
|
|
3324
|
-
var
|
|
3780
|
+
var import_internal3 = require("@formspec/analysis/internal");
|
|
3325
3781
|
function validateFieldNode(ctx, field) {
|
|
3326
|
-
const analysis = (0,
|
|
3782
|
+
const analysis = (0, import_internal3.analyzeConstraintTargets)(
|
|
3327
3783
|
field.name,
|
|
3328
3784
|
field.type,
|
|
3329
3785
|
field.constraints,
|
|
@@ -3341,7 +3797,7 @@ function validateFieldNode(ctx, field) {
|
|
|
3341
3797
|
}
|
|
3342
3798
|
function validateObjectProperty(ctx, parentName, property) {
|
|
3343
3799
|
const qualifiedName = `${parentName}.${property.name}`;
|
|
3344
|
-
const analysis = (0,
|
|
3800
|
+
const analysis = (0, import_internal3.analyzeConstraintTargets)(
|
|
3345
3801
|
qualifiedName,
|
|
3346
3802
|
property.type,
|
|
3347
3803
|
property.constraints,
|