@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
package/dist/cli.js
CHANGED
|
@@ -2169,6 +2169,9 @@ var init_jsdoc_constraints = __esm({
|
|
|
2169
2169
|
|
|
2170
2170
|
// src/analyzer/class-analyzer.ts
|
|
2171
2171
|
import * as ts3 from "typescript";
|
|
2172
|
+
import {
|
|
2173
|
+
parseCommentBlock as parseCommentBlock2
|
|
2174
|
+
} from "@formspec/analysis/internal";
|
|
2172
2175
|
function isObjectType(type) {
|
|
2173
2176
|
return !!(type.flags & ts3.TypeFlags.Object);
|
|
2174
2177
|
}
|
|
@@ -2232,9 +2235,17 @@ function analyzeClassToIR(classDecl, checker, file = "", extensionRegistry) {
|
|
|
2232
2235
|
}
|
|
2233
2236
|
}
|
|
2234
2237
|
}
|
|
2238
|
+
const specializedFields = applyDeclarationDiscriminatorToFields(
|
|
2239
|
+
fields,
|
|
2240
|
+
classDecl,
|
|
2241
|
+
classType,
|
|
2242
|
+
checker,
|
|
2243
|
+
file,
|
|
2244
|
+
diagnostics
|
|
2245
|
+
);
|
|
2235
2246
|
return {
|
|
2236
2247
|
name,
|
|
2237
|
-
fields,
|
|
2248
|
+
fields: specializedFields,
|
|
2238
2249
|
fieldLayouts,
|
|
2239
2250
|
typeRegistry,
|
|
2240
2251
|
...annotations.length > 0 && { annotations },
|
|
@@ -2274,10 +2285,18 @@ function analyzeInterfaceToIR(interfaceDecl, checker, file = "", extensionRegist
|
|
|
2274
2285
|
}
|
|
2275
2286
|
}
|
|
2276
2287
|
}
|
|
2277
|
-
const
|
|
2288
|
+
const specializedFields = applyDeclarationDiscriminatorToFields(
|
|
2289
|
+
fields,
|
|
2290
|
+
interfaceDecl,
|
|
2291
|
+
interfaceType,
|
|
2292
|
+
checker,
|
|
2293
|
+
file,
|
|
2294
|
+
diagnostics
|
|
2295
|
+
);
|
|
2296
|
+
const fieldLayouts = specializedFields.map(() => ({}));
|
|
2278
2297
|
return {
|
|
2279
2298
|
name,
|
|
2280
|
-
fields,
|
|
2299
|
+
fields: specializedFields,
|
|
2281
2300
|
fieldLayouts,
|
|
2282
2301
|
typeRegistry,
|
|
2283
2302
|
...annotations.length > 0 && { annotations },
|
|
@@ -2326,12 +2345,20 @@ function analyzeTypeAliasToIR(typeAlias, checker, file = "", extensionRegistry)
|
|
|
2326
2345
|
}
|
|
2327
2346
|
}
|
|
2328
2347
|
}
|
|
2348
|
+
const specializedFields = applyDeclarationDiscriminatorToFields(
|
|
2349
|
+
fields,
|
|
2350
|
+
typeAlias,
|
|
2351
|
+
aliasType,
|
|
2352
|
+
checker,
|
|
2353
|
+
file,
|
|
2354
|
+
diagnostics
|
|
2355
|
+
);
|
|
2329
2356
|
return {
|
|
2330
2357
|
ok: true,
|
|
2331
2358
|
analysis: {
|
|
2332
2359
|
name,
|
|
2333
|
-
fields,
|
|
2334
|
-
fieldLayouts:
|
|
2360
|
+
fields: specializedFields,
|
|
2361
|
+
fieldLayouts: specializedFields.map(() => ({})),
|
|
2335
2362
|
typeRegistry,
|
|
2336
2363
|
...annotations.length > 0 && { annotations },
|
|
2337
2364
|
...diagnostics.length > 0 && { diagnostics },
|
|
@@ -2340,6 +2367,396 @@ function analyzeTypeAliasToIR(typeAlias, checker, file = "", extensionRegistry)
|
|
|
2340
2367
|
}
|
|
2341
2368
|
};
|
|
2342
2369
|
}
|
|
2370
|
+
function makeAnalysisDiagnostic(code, message, primaryLocation, relatedLocations = []) {
|
|
2371
|
+
return {
|
|
2372
|
+
code,
|
|
2373
|
+
message,
|
|
2374
|
+
severity: "error",
|
|
2375
|
+
primaryLocation,
|
|
2376
|
+
relatedLocations
|
|
2377
|
+
};
|
|
2378
|
+
}
|
|
2379
|
+
function getLeadingParsedTags(node) {
|
|
2380
|
+
const sourceFile = node.getSourceFile();
|
|
2381
|
+
const sourceText = sourceFile.getFullText();
|
|
2382
|
+
const commentRanges = ts3.getLeadingCommentRanges(sourceText, node.getFullStart());
|
|
2383
|
+
if (commentRanges === void 0) {
|
|
2384
|
+
return [];
|
|
2385
|
+
}
|
|
2386
|
+
const parsedTags = [];
|
|
2387
|
+
for (const range of commentRanges) {
|
|
2388
|
+
if (range.kind !== ts3.SyntaxKind.MultiLineCommentTrivia) {
|
|
2389
|
+
continue;
|
|
2390
|
+
}
|
|
2391
|
+
const commentText = sourceText.slice(range.pos, range.end);
|
|
2392
|
+
if (!commentText.startsWith("/**")) {
|
|
2393
|
+
continue;
|
|
2394
|
+
}
|
|
2395
|
+
parsedTags.push(...parseCommentBlock2(commentText, { offset: range.pos }).tags);
|
|
2396
|
+
}
|
|
2397
|
+
return parsedTags;
|
|
2398
|
+
}
|
|
2399
|
+
function findDiscriminatorProperty(node, fieldName) {
|
|
2400
|
+
if (ts3.isClassDeclaration(node)) {
|
|
2401
|
+
for (const member of node.members) {
|
|
2402
|
+
if (ts3.isPropertyDeclaration(member) && ts3.isIdentifier(member.name) && member.name.text === fieldName) {
|
|
2403
|
+
return member;
|
|
2404
|
+
}
|
|
2405
|
+
}
|
|
2406
|
+
return null;
|
|
2407
|
+
}
|
|
2408
|
+
if (ts3.isInterfaceDeclaration(node)) {
|
|
2409
|
+
for (const member of node.members) {
|
|
2410
|
+
if (ts3.isPropertySignature(member) && ts3.isIdentifier(member.name) && member.name.text === fieldName) {
|
|
2411
|
+
return member;
|
|
2412
|
+
}
|
|
2413
|
+
}
|
|
2414
|
+
return null;
|
|
2415
|
+
}
|
|
2416
|
+
if (ts3.isTypeLiteralNode(node.type)) {
|
|
2417
|
+
for (const member of node.type.members) {
|
|
2418
|
+
if (ts3.isPropertySignature(member) && ts3.isIdentifier(member.name) && member.name.text === fieldName) {
|
|
2419
|
+
return member;
|
|
2420
|
+
}
|
|
2421
|
+
}
|
|
2422
|
+
}
|
|
2423
|
+
return null;
|
|
2424
|
+
}
|
|
2425
|
+
function isLocalTypeParameterName(node, typeParameterName) {
|
|
2426
|
+
return node.typeParameters?.some((typeParameter) => typeParameter.name.text === typeParameterName) ?? false;
|
|
2427
|
+
}
|
|
2428
|
+
function isNullishSemanticType(type) {
|
|
2429
|
+
if (type.flags & (ts3.TypeFlags.Null | ts3.TypeFlags.Undefined | ts3.TypeFlags.Void | ts3.TypeFlags.Unknown | ts3.TypeFlags.Any)) {
|
|
2430
|
+
return true;
|
|
2431
|
+
}
|
|
2432
|
+
return type.isUnion() && type.types.some((member) => isNullishSemanticType(member));
|
|
2433
|
+
}
|
|
2434
|
+
function isStringLikeSemanticType(type) {
|
|
2435
|
+
if (type.flags & ts3.TypeFlags.StringLike) {
|
|
2436
|
+
return true;
|
|
2437
|
+
}
|
|
2438
|
+
if (type.isUnion()) {
|
|
2439
|
+
return type.types.length > 0 && type.types.every((member) => isStringLikeSemanticType(member));
|
|
2440
|
+
}
|
|
2441
|
+
return false;
|
|
2442
|
+
}
|
|
2443
|
+
function extractDiscriminatorDirective(node, file, diagnostics) {
|
|
2444
|
+
const discriminatorTags = getLeadingParsedTags(node).filter(
|
|
2445
|
+
(tag) => tag.normalizedTagName === "discriminator"
|
|
2446
|
+
);
|
|
2447
|
+
if (discriminatorTags.length === 0) {
|
|
2448
|
+
return null;
|
|
2449
|
+
}
|
|
2450
|
+
const [firstTag, ...duplicateTags] = discriminatorTags;
|
|
2451
|
+
for (const _duplicateTag of duplicateTags) {
|
|
2452
|
+
diagnostics.push(
|
|
2453
|
+
makeAnalysisDiagnostic(
|
|
2454
|
+
"DUPLICATE_TAG",
|
|
2455
|
+
'Duplicate "@discriminator" tag. Only one discriminator declaration is allowed per declaration.',
|
|
2456
|
+
provenanceForNode(node, file)
|
|
2457
|
+
)
|
|
2458
|
+
);
|
|
2459
|
+
}
|
|
2460
|
+
if (firstTag === void 0) {
|
|
2461
|
+
return null;
|
|
2462
|
+
}
|
|
2463
|
+
const firstTarget = firstTag.target;
|
|
2464
|
+
if (firstTarget?.path === null || firstTarget?.valid !== true) {
|
|
2465
|
+
diagnostics.push(
|
|
2466
|
+
makeAnalysisDiagnostic(
|
|
2467
|
+
"INVALID_TAG_ARGUMENT",
|
|
2468
|
+
'Tag "@discriminator" requires a direct path target like ":kind".',
|
|
2469
|
+
provenanceForNode(node, file)
|
|
2470
|
+
)
|
|
2471
|
+
);
|
|
2472
|
+
return null;
|
|
2473
|
+
}
|
|
2474
|
+
if (firstTarget.path.segments.length !== 1) {
|
|
2475
|
+
diagnostics.push(
|
|
2476
|
+
makeAnalysisDiagnostic(
|
|
2477
|
+
"INVALID_TAG_ARGUMENT",
|
|
2478
|
+
'Tag "@discriminator" only supports direct property targets in v1; nested paths are out of scope.',
|
|
2479
|
+
provenanceForNode(node, file)
|
|
2480
|
+
)
|
|
2481
|
+
);
|
|
2482
|
+
return null;
|
|
2483
|
+
}
|
|
2484
|
+
const typeParameterName = firstTag.argumentText.trim();
|
|
2485
|
+
if (!/^[A-Za-z_$][\w$]*$/u.test(typeParameterName)) {
|
|
2486
|
+
diagnostics.push(
|
|
2487
|
+
makeAnalysisDiagnostic(
|
|
2488
|
+
"INVALID_TAG_ARGUMENT",
|
|
2489
|
+
'Tag "@discriminator" requires a local type parameter name as its source operand.',
|
|
2490
|
+
provenanceForNode(node, file)
|
|
2491
|
+
)
|
|
2492
|
+
);
|
|
2493
|
+
return null;
|
|
2494
|
+
}
|
|
2495
|
+
return {
|
|
2496
|
+
fieldName: firstTarget.path.segments[0] ?? firstTarget.rawText,
|
|
2497
|
+
typeParameterName,
|
|
2498
|
+
provenance: provenanceForNode(node, file)
|
|
2499
|
+
};
|
|
2500
|
+
}
|
|
2501
|
+
function validateDiscriminatorDirective(node, checker, file, diagnostics) {
|
|
2502
|
+
const directive = extractDiscriminatorDirective(node, file, diagnostics);
|
|
2503
|
+
if (directive === null) {
|
|
2504
|
+
return null;
|
|
2505
|
+
}
|
|
2506
|
+
if (!isLocalTypeParameterName(node, directive.typeParameterName)) {
|
|
2507
|
+
diagnostics.push(
|
|
2508
|
+
makeAnalysisDiagnostic(
|
|
2509
|
+
"INVALID_TAG_ARGUMENT",
|
|
2510
|
+
`Tag "@discriminator" references "${directive.typeParameterName}", but the source operand must be a type parameter declared on the same declaration.`,
|
|
2511
|
+
directive.provenance
|
|
2512
|
+
)
|
|
2513
|
+
);
|
|
2514
|
+
return null;
|
|
2515
|
+
}
|
|
2516
|
+
const propertyDecl = findDiscriminatorProperty(node, directive.fieldName);
|
|
2517
|
+
if (propertyDecl === null) {
|
|
2518
|
+
diagnostics.push(
|
|
2519
|
+
makeAnalysisDiagnostic(
|
|
2520
|
+
"UNKNOWN_PATH_TARGET",
|
|
2521
|
+
`Tag "@discriminator" targets "${directive.fieldName}", but no direct property with that name exists on this declaration.`,
|
|
2522
|
+
directive.provenance
|
|
2523
|
+
)
|
|
2524
|
+
);
|
|
2525
|
+
return null;
|
|
2526
|
+
}
|
|
2527
|
+
if (propertyDecl.questionToken !== void 0) {
|
|
2528
|
+
diagnostics.push(
|
|
2529
|
+
makeAnalysisDiagnostic(
|
|
2530
|
+
"TYPE_MISMATCH",
|
|
2531
|
+
`Discriminator field "${directive.fieldName}" must be required; optional discriminator fields are not supported.`,
|
|
2532
|
+
directive.provenance,
|
|
2533
|
+
[provenanceForNode(propertyDecl, file)]
|
|
2534
|
+
)
|
|
2535
|
+
);
|
|
2536
|
+
return null;
|
|
2537
|
+
}
|
|
2538
|
+
const propertyType = checker.getTypeAtLocation(propertyDecl);
|
|
2539
|
+
if (isNullishSemanticType(propertyType)) {
|
|
2540
|
+
diagnostics.push(
|
|
2541
|
+
makeAnalysisDiagnostic(
|
|
2542
|
+
"TYPE_MISMATCH",
|
|
2543
|
+
`Discriminator field "${directive.fieldName}" must not be nullable.`,
|
|
2544
|
+
directive.provenance,
|
|
2545
|
+
[provenanceForNode(propertyDecl, file)]
|
|
2546
|
+
)
|
|
2547
|
+
);
|
|
2548
|
+
return null;
|
|
2549
|
+
}
|
|
2550
|
+
if (!isStringLikeSemanticType(propertyType)) {
|
|
2551
|
+
diagnostics.push(
|
|
2552
|
+
makeAnalysisDiagnostic(
|
|
2553
|
+
"TYPE_MISMATCH",
|
|
2554
|
+
`Discriminator field "${directive.fieldName}" must be string-like.`,
|
|
2555
|
+
directive.provenance,
|
|
2556
|
+
[provenanceForNode(propertyDecl, file)]
|
|
2557
|
+
)
|
|
2558
|
+
);
|
|
2559
|
+
return null;
|
|
2560
|
+
}
|
|
2561
|
+
return directive;
|
|
2562
|
+
}
|
|
2563
|
+
function getConcreteTypeArgumentForDiscriminator(node, subjectType, checker, typeParameterName) {
|
|
2564
|
+
const typeParameterIndex = node.typeParameters?.findIndex(
|
|
2565
|
+
(typeParameter) => typeParameter.name.text === typeParameterName
|
|
2566
|
+
) ?? -1;
|
|
2567
|
+
if (typeParameterIndex < 0) {
|
|
2568
|
+
return null;
|
|
2569
|
+
}
|
|
2570
|
+
const referenceTypeArguments = (isTypeReference(subjectType) ? subjectType.typeArguments : void 0) ?? subjectType.aliasTypeArguments;
|
|
2571
|
+
if (referenceTypeArguments?.[typeParameterIndex] !== void 0) {
|
|
2572
|
+
return referenceTypeArguments[typeParameterIndex] ?? null;
|
|
2573
|
+
}
|
|
2574
|
+
const localTypeParameter = node.typeParameters?.[typeParameterIndex];
|
|
2575
|
+
return localTypeParameter === void 0 ? null : checker.getTypeAtLocation(localTypeParameter);
|
|
2576
|
+
}
|
|
2577
|
+
function extractDeclarationApiName(node) {
|
|
2578
|
+
for (const tag of getLeadingParsedTags(node)) {
|
|
2579
|
+
if (tag.normalizedTagName !== "apiName") {
|
|
2580
|
+
continue;
|
|
2581
|
+
}
|
|
2582
|
+
if (tag.target === null && tag.argumentText.trim() !== "") {
|
|
2583
|
+
return tag.argumentText.trim();
|
|
2584
|
+
}
|
|
2585
|
+
if (tag.target?.kind === "variant" && tag.target.rawText === "singular") {
|
|
2586
|
+
const value = tag.argumentText.trim();
|
|
2587
|
+
if (value !== "") {
|
|
2588
|
+
return value;
|
|
2589
|
+
}
|
|
2590
|
+
}
|
|
2591
|
+
}
|
|
2592
|
+
return null;
|
|
2593
|
+
}
|
|
2594
|
+
function inferJsonFacingName(name) {
|
|
2595
|
+
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();
|
|
2596
|
+
}
|
|
2597
|
+
function resolveNamedDiscriminatorDeclaration(type, checker, seen = /* @__PURE__ */ new Set()) {
|
|
2598
|
+
if (seen.has(type)) {
|
|
2599
|
+
return null;
|
|
2600
|
+
}
|
|
2601
|
+
seen.add(type);
|
|
2602
|
+
const symbol = type.aliasSymbol ?? type.getSymbol();
|
|
2603
|
+
if (symbol !== void 0) {
|
|
2604
|
+
const aliased = symbol.flags & ts3.SymbolFlags.Alias ? checker.getAliasedSymbol(symbol) : void 0;
|
|
2605
|
+
const targetSymbol = aliased ?? symbol;
|
|
2606
|
+
const declaration = targetSymbol.declarations?.find(
|
|
2607
|
+
(candidate) => ts3.isClassDeclaration(candidate) || ts3.isInterfaceDeclaration(candidate) || ts3.isTypeAliasDeclaration(candidate) || ts3.isEnumDeclaration(candidate)
|
|
2608
|
+
);
|
|
2609
|
+
if (declaration !== void 0) {
|
|
2610
|
+
if (ts3.isTypeAliasDeclaration(declaration) && ts3.isTypeReferenceNode(declaration.type) && checker.getTypeFromTypeNode(declaration.type) !== type) {
|
|
2611
|
+
return resolveNamedDiscriminatorDeclaration(
|
|
2612
|
+
checker.getTypeFromTypeNode(declaration.type),
|
|
2613
|
+
checker,
|
|
2614
|
+
seen
|
|
2615
|
+
);
|
|
2616
|
+
}
|
|
2617
|
+
return declaration;
|
|
2618
|
+
}
|
|
2619
|
+
}
|
|
2620
|
+
return null;
|
|
2621
|
+
}
|
|
2622
|
+
function resolveDiscriminatorValue(boundType, checker, provenance, diagnostics) {
|
|
2623
|
+
if (boundType === null) {
|
|
2624
|
+
diagnostics.push(
|
|
2625
|
+
makeAnalysisDiagnostic(
|
|
2626
|
+
"INVALID_TAG_ARGUMENT",
|
|
2627
|
+
"Discriminator resolution failed because no concrete type argument is available for the referenced type parameter.",
|
|
2628
|
+
provenance
|
|
2629
|
+
)
|
|
2630
|
+
);
|
|
2631
|
+
return null;
|
|
2632
|
+
}
|
|
2633
|
+
if (boundType.isStringLiteral()) {
|
|
2634
|
+
return boundType.value;
|
|
2635
|
+
}
|
|
2636
|
+
if (boundType.isUnion()) {
|
|
2637
|
+
const nonNullMembers = boundType.types.filter(
|
|
2638
|
+
(member) => !(member.flags & (ts3.TypeFlags.Null | ts3.TypeFlags.Undefined))
|
|
2639
|
+
);
|
|
2640
|
+
if (nonNullMembers.every((member) => member.isStringLiteral())) {
|
|
2641
|
+
diagnostics.push(
|
|
2642
|
+
makeAnalysisDiagnostic(
|
|
2643
|
+
"INVALID_TAG_ARGUMENT",
|
|
2644
|
+
"Discriminator resolution for unions of string literals is out of scope for v1.",
|
|
2645
|
+
provenance
|
|
2646
|
+
)
|
|
2647
|
+
);
|
|
2648
|
+
return null;
|
|
2649
|
+
}
|
|
2650
|
+
}
|
|
2651
|
+
const declaration = resolveNamedDiscriminatorDeclaration(boundType, checker);
|
|
2652
|
+
if (declaration !== null) {
|
|
2653
|
+
return extractDeclarationApiName(declaration) ?? inferJsonFacingName(getDeclarationName(declaration));
|
|
2654
|
+
}
|
|
2655
|
+
diagnostics.push(
|
|
2656
|
+
makeAnalysisDiagnostic(
|
|
2657
|
+
"INVALID_TAG_ARGUMENT",
|
|
2658
|
+
"Discriminator resolution could not derive a JSON-facing discriminator value from the referenced type argument.",
|
|
2659
|
+
provenance
|
|
2660
|
+
)
|
|
2661
|
+
);
|
|
2662
|
+
return null;
|
|
2663
|
+
}
|
|
2664
|
+
function getDeclarationName(node) {
|
|
2665
|
+
if (ts3.isClassDeclaration(node) || ts3.isInterfaceDeclaration(node) || ts3.isTypeAliasDeclaration(node) || ts3.isEnumDeclaration(node)) {
|
|
2666
|
+
return node.name?.text ?? "anonymous";
|
|
2667
|
+
}
|
|
2668
|
+
return "anonymous";
|
|
2669
|
+
}
|
|
2670
|
+
function applyDeclarationDiscriminatorToFields(fields, node, subjectType, checker, file, diagnostics) {
|
|
2671
|
+
const directive = validateDiscriminatorDirective(node, checker, file, diagnostics);
|
|
2672
|
+
if (directive === null) {
|
|
2673
|
+
return [...fields];
|
|
2674
|
+
}
|
|
2675
|
+
const discriminatorValue = resolveDiscriminatorValue(
|
|
2676
|
+
getConcreteTypeArgumentForDiscriminator(
|
|
2677
|
+
node,
|
|
2678
|
+
subjectType,
|
|
2679
|
+
checker,
|
|
2680
|
+
directive.typeParameterName
|
|
2681
|
+
),
|
|
2682
|
+
checker,
|
|
2683
|
+
directive.provenance,
|
|
2684
|
+
diagnostics
|
|
2685
|
+
);
|
|
2686
|
+
if (discriminatorValue === null) {
|
|
2687
|
+
return [...fields];
|
|
2688
|
+
}
|
|
2689
|
+
return fields.map(
|
|
2690
|
+
(field) => field.name === directive.fieldName ? {
|
|
2691
|
+
...field,
|
|
2692
|
+
type: {
|
|
2693
|
+
kind: "enum",
|
|
2694
|
+
members: [{ value: discriminatorValue }]
|
|
2695
|
+
}
|
|
2696
|
+
} : field
|
|
2697
|
+
);
|
|
2698
|
+
}
|
|
2699
|
+
function buildInstantiatedReferenceName(baseName, typeArguments, checker) {
|
|
2700
|
+
const renderedArguments = typeArguments.map(
|
|
2701
|
+
(typeArgument) => checker.typeToString(typeArgument).replace(/[^A-Za-z0-9]+/g, "_").replace(/^_+|_+$/g, "")
|
|
2702
|
+
).filter((value) => value !== "");
|
|
2703
|
+
return renderedArguments.length === 0 ? baseName : `${baseName}__${renderedArguments.join("__")}`;
|
|
2704
|
+
}
|
|
2705
|
+
function extractReferenceTypeArguments(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry, diagnostics) {
|
|
2706
|
+
const typeNode = sourceNode === void 0 ? void 0 : extractTypeNodeFromSource(sourceNode);
|
|
2707
|
+
if (typeNode === void 0) {
|
|
2708
|
+
return [];
|
|
2709
|
+
}
|
|
2710
|
+
const resolvedTypeNode = resolveAliasedTypeNode(typeNode, checker);
|
|
2711
|
+
if (!ts3.isTypeReferenceNode(resolvedTypeNode) || resolvedTypeNode.typeArguments === void 0) {
|
|
2712
|
+
return [];
|
|
2713
|
+
}
|
|
2714
|
+
return resolvedTypeNode.typeArguments.map((argumentNode) => {
|
|
2715
|
+
const argumentType = checker.getTypeFromTypeNode(argumentNode);
|
|
2716
|
+
return {
|
|
2717
|
+
tsType: argumentType,
|
|
2718
|
+
typeNode: resolveTypeNode(
|
|
2719
|
+
argumentType,
|
|
2720
|
+
checker,
|
|
2721
|
+
file,
|
|
2722
|
+
typeRegistry,
|
|
2723
|
+
visiting,
|
|
2724
|
+
argumentNode,
|
|
2725
|
+
extensionRegistry,
|
|
2726
|
+
diagnostics
|
|
2727
|
+
)
|
|
2728
|
+
};
|
|
2729
|
+
});
|
|
2730
|
+
}
|
|
2731
|
+
function applyDiscriminatorToObjectProperties(properties, node, subjectType, checker, file, diagnostics) {
|
|
2732
|
+
const directive = validateDiscriminatorDirective(node, checker, file, diagnostics);
|
|
2733
|
+
if (directive === null) {
|
|
2734
|
+
return properties;
|
|
2735
|
+
}
|
|
2736
|
+
const discriminatorValue = resolveDiscriminatorValue(
|
|
2737
|
+
getConcreteTypeArgumentForDiscriminator(
|
|
2738
|
+
node,
|
|
2739
|
+
subjectType,
|
|
2740
|
+
checker,
|
|
2741
|
+
directive.typeParameterName
|
|
2742
|
+
),
|
|
2743
|
+
checker,
|
|
2744
|
+
directive.provenance,
|
|
2745
|
+
diagnostics
|
|
2746
|
+
);
|
|
2747
|
+
if (discriminatorValue === null) {
|
|
2748
|
+
return properties;
|
|
2749
|
+
}
|
|
2750
|
+
return properties.map(
|
|
2751
|
+
(property) => property.name === directive.fieldName ? {
|
|
2752
|
+
...property,
|
|
2753
|
+
type: {
|
|
2754
|
+
kind: "enum",
|
|
2755
|
+
members: [{ value: discriminatorValue }]
|
|
2756
|
+
}
|
|
2757
|
+
} : property
|
|
2758
|
+
);
|
|
2759
|
+
}
|
|
2343
2760
|
function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting, diagnostics, hostType, extensionRegistry) {
|
|
2344
2761
|
if (!ts3.isIdentifier(prop.name)) {
|
|
2345
2762
|
return null;
|
|
@@ -2628,6 +3045,7 @@ function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode
|
|
|
2628
3045
|
file,
|
|
2629
3046
|
typeRegistry,
|
|
2630
3047
|
visiting,
|
|
3048
|
+
sourceNode,
|
|
2631
3049
|
extensionRegistry,
|
|
2632
3050
|
diagnostics
|
|
2633
3051
|
);
|
|
@@ -2891,35 +3309,60 @@ function typeNodeContainsReference(type, targetName) {
|
|
|
2891
3309
|
}
|
|
2892
3310
|
}
|
|
2893
3311
|
}
|
|
2894
|
-
function resolveObjectType(type, checker, file, typeRegistry, visiting, extensionRegistry, diagnostics) {
|
|
3312
|
+
function resolveObjectType(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry, diagnostics) {
|
|
3313
|
+
const collectedDiagnostics = diagnostics ?? [];
|
|
2895
3314
|
const typeName = getNamedTypeName(type);
|
|
2896
3315
|
const namedTypeName = typeName ?? void 0;
|
|
2897
3316
|
const namedDecl = getNamedTypeDeclaration(type);
|
|
2898
|
-
const
|
|
3317
|
+
const referenceTypeArguments = extractReferenceTypeArguments(
|
|
3318
|
+
type,
|
|
3319
|
+
checker,
|
|
3320
|
+
file,
|
|
3321
|
+
typeRegistry,
|
|
3322
|
+
visiting,
|
|
3323
|
+
sourceNode,
|
|
3324
|
+
extensionRegistry,
|
|
3325
|
+
collectedDiagnostics
|
|
3326
|
+
);
|
|
3327
|
+
const instantiatedTypeName = namedTypeName !== void 0 && referenceTypeArguments.length > 0 ? buildInstantiatedReferenceName(
|
|
3328
|
+
namedTypeName,
|
|
3329
|
+
referenceTypeArguments.map((argument) => argument.tsType),
|
|
3330
|
+
checker
|
|
3331
|
+
) : void 0;
|
|
3332
|
+
const registryTypeName = instantiatedTypeName ?? namedTypeName;
|
|
3333
|
+
const shouldRegisterNamedType = registryTypeName !== void 0 && !(registryTypeName === "Record" && namedDecl?.getSourceFile().fileName !== file);
|
|
2899
3334
|
const clearNamedTypeRegistration = () => {
|
|
2900
|
-
if (
|
|
3335
|
+
if (registryTypeName === void 0 || !shouldRegisterNamedType) {
|
|
2901
3336
|
return;
|
|
2902
3337
|
}
|
|
2903
|
-
Reflect.deleteProperty(typeRegistry,
|
|
3338
|
+
Reflect.deleteProperty(typeRegistry, registryTypeName);
|
|
2904
3339
|
};
|
|
2905
3340
|
if (visiting.has(type)) {
|
|
2906
|
-
if (
|
|
2907
|
-
return {
|
|
3341
|
+
if (registryTypeName !== void 0 && shouldRegisterNamedType) {
|
|
3342
|
+
return {
|
|
3343
|
+
kind: "reference",
|
|
3344
|
+
name: registryTypeName,
|
|
3345
|
+
typeArguments: referenceTypeArguments.map((argument) => argument.typeNode)
|
|
3346
|
+
};
|
|
2908
3347
|
}
|
|
2909
3348
|
return { kind: "object", properties: [], additionalProperties: false };
|
|
2910
3349
|
}
|
|
2911
|
-
if (
|
|
2912
|
-
typeRegistry[
|
|
2913
|
-
name:
|
|
3350
|
+
if (registryTypeName !== void 0 && shouldRegisterNamedType && !typeRegistry[registryTypeName]) {
|
|
3351
|
+
typeRegistry[registryTypeName] = {
|
|
3352
|
+
name: registryTypeName,
|
|
2914
3353
|
type: RESOLVING_TYPE_PLACEHOLDER,
|
|
2915
3354
|
provenance: provenanceForDeclaration(namedDecl, file)
|
|
2916
3355
|
};
|
|
2917
3356
|
}
|
|
2918
3357
|
visiting.add(type);
|
|
2919
|
-
if (
|
|
2920
|
-
if (typeRegistry[
|
|
3358
|
+
if (registryTypeName !== void 0 && shouldRegisterNamedType && typeRegistry[registryTypeName]?.type !== void 0) {
|
|
3359
|
+
if (typeRegistry[registryTypeName].type !== RESOLVING_TYPE_PLACEHOLDER) {
|
|
2921
3360
|
visiting.delete(type);
|
|
2922
|
-
return {
|
|
3361
|
+
return {
|
|
3362
|
+
kind: "reference",
|
|
3363
|
+
name: registryTypeName,
|
|
3364
|
+
typeArguments: referenceTypeArguments.map((argument) => argument.typeNode)
|
|
3365
|
+
};
|
|
2923
3366
|
}
|
|
2924
3367
|
}
|
|
2925
3368
|
const recordNode = tryResolveRecordType(
|
|
@@ -2929,24 +3372,28 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting, extensio
|
|
|
2929
3372
|
typeRegistry,
|
|
2930
3373
|
visiting,
|
|
2931
3374
|
extensionRegistry,
|
|
2932
|
-
|
|
3375
|
+
collectedDiagnostics
|
|
2933
3376
|
);
|
|
2934
3377
|
if (recordNode) {
|
|
2935
3378
|
visiting.delete(type);
|
|
2936
|
-
if (
|
|
2937
|
-
const isRecursiveRecord = typeNodeContainsReference(recordNode.valueType,
|
|
3379
|
+
if (registryTypeName !== void 0 && shouldRegisterNamedType) {
|
|
3380
|
+
const isRecursiveRecord = typeNodeContainsReference(recordNode.valueType, registryTypeName);
|
|
2938
3381
|
if (!isRecursiveRecord) {
|
|
2939
3382
|
clearNamedTypeRegistration();
|
|
2940
3383
|
return recordNode;
|
|
2941
3384
|
}
|
|
2942
3385
|
const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file, makeParseOptions(extensionRegistry)) : void 0;
|
|
2943
|
-
typeRegistry[
|
|
2944
|
-
name:
|
|
3386
|
+
typeRegistry[registryTypeName] = {
|
|
3387
|
+
name: registryTypeName,
|
|
2945
3388
|
type: recordNode,
|
|
2946
3389
|
...annotations !== void 0 && annotations.length > 0 && { annotations },
|
|
2947
3390
|
provenance: provenanceForDeclaration(namedDecl, file)
|
|
2948
3391
|
};
|
|
2949
|
-
return {
|
|
3392
|
+
return {
|
|
3393
|
+
kind: "reference",
|
|
3394
|
+
name: registryTypeName,
|
|
3395
|
+
typeArguments: referenceTypeArguments.map((argument) => argument.typeNode)
|
|
3396
|
+
};
|
|
2950
3397
|
}
|
|
2951
3398
|
return recordNode;
|
|
2952
3399
|
}
|
|
@@ -2957,7 +3404,7 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting, extensio
|
|
|
2957
3404
|
file,
|
|
2958
3405
|
typeRegistry,
|
|
2959
3406
|
visiting,
|
|
2960
|
-
|
|
3407
|
+
collectedDiagnostics,
|
|
2961
3408
|
extensionRegistry
|
|
2962
3409
|
);
|
|
2963
3410
|
for (const prop of type.getProperties()) {
|
|
@@ -2973,7 +3420,7 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting, extensio
|
|
|
2973
3420
|
visiting,
|
|
2974
3421
|
declaration,
|
|
2975
3422
|
extensionRegistry,
|
|
2976
|
-
|
|
3423
|
+
collectedDiagnostics
|
|
2977
3424
|
);
|
|
2978
3425
|
const fieldNodeInfo = fieldInfoMap?.get(prop.name);
|
|
2979
3426
|
properties.push({
|
|
@@ -2988,18 +3435,29 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting, extensio
|
|
|
2988
3435
|
visiting.delete(type);
|
|
2989
3436
|
const objectNode = {
|
|
2990
3437
|
kind: "object",
|
|
2991
|
-
properties
|
|
3438
|
+
properties: namedDecl !== void 0 && (ts3.isClassDeclaration(namedDecl) || ts3.isInterfaceDeclaration(namedDecl) || ts3.isTypeAliasDeclaration(namedDecl)) ? applyDiscriminatorToObjectProperties(
|
|
3439
|
+
properties,
|
|
3440
|
+
namedDecl,
|
|
3441
|
+
type,
|
|
3442
|
+
checker,
|
|
3443
|
+
file,
|
|
3444
|
+
collectedDiagnostics
|
|
3445
|
+
) : properties,
|
|
2992
3446
|
additionalProperties: true
|
|
2993
3447
|
};
|
|
2994
|
-
if (
|
|
3448
|
+
if (registryTypeName !== void 0 && shouldRegisterNamedType) {
|
|
2995
3449
|
const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file, makeParseOptions(extensionRegistry)) : void 0;
|
|
2996
|
-
typeRegistry[
|
|
2997
|
-
name:
|
|
3450
|
+
typeRegistry[registryTypeName] = {
|
|
3451
|
+
name: registryTypeName,
|
|
2998
3452
|
type: objectNode,
|
|
2999
3453
|
...annotations !== void 0 && annotations.length > 0 && { annotations },
|
|
3000
3454
|
provenance: provenanceForDeclaration(namedDecl, file)
|
|
3001
3455
|
};
|
|
3002
|
-
return {
|
|
3456
|
+
return {
|
|
3457
|
+
kind: "reference",
|
|
3458
|
+
name: registryTypeName,
|
|
3459
|
+
typeArguments: referenceTypeArguments.map((argument) => argument.typeNode)
|
|
3460
|
+
};
|
|
3003
3461
|
}
|
|
3004
3462
|
return objectNode;
|
|
3005
3463
|
}
|