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