@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/index.js
CHANGED
|
@@ -1212,6 +1212,9 @@ import * as path from "path";
|
|
|
1212
1212
|
|
|
1213
1213
|
// src/analyzer/class-analyzer.ts
|
|
1214
1214
|
import * as ts3 from "typescript";
|
|
1215
|
+
import {
|
|
1216
|
+
parseCommentBlock as parseCommentBlock2
|
|
1217
|
+
} from "@formspec/analysis/internal";
|
|
1215
1218
|
|
|
1216
1219
|
// src/analyzer/jsdoc-constraints.ts
|
|
1217
1220
|
import * as ts2 from "typescript";
|
|
@@ -2159,9 +2162,17 @@ function analyzeClassToIR(classDecl, checker, file = "", extensionRegistry) {
|
|
|
2159
2162
|
}
|
|
2160
2163
|
}
|
|
2161
2164
|
}
|
|
2165
|
+
const specializedFields = applyDeclarationDiscriminatorToFields(
|
|
2166
|
+
fields,
|
|
2167
|
+
classDecl,
|
|
2168
|
+
classType,
|
|
2169
|
+
checker,
|
|
2170
|
+
file,
|
|
2171
|
+
diagnostics
|
|
2172
|
+
);
|
|
2162
2173
|
return {
|
|
2163
2174
|
name,
|
|
2164
|
-
fields,
|
|
2175
|
+
fields: specializedFields,
|
|
2165
2176
|
fieldLayouts,
|
|
2166
2177
|
typeRegistry,
|
|
2167
2178
|
...annotations.length > 0 && { annotations },
|
|
@@ -2201,10 +2212,18 @@ function analyzeInterfaceToIR(interfaceDecl, checker, file = "", extensionRegist
|
|
|
2201
2212
|
}
|
|
2202
2213
|
}
|
|
2203
2214
|
}
|
|
2204
|
-
const
|
|
2215
|
+
const specializedFields = applyDeclarationDiscriminatorToFields(
|
|
2216
|
+
fields,
|
|
2217
|
+
interfaceDecl,
|
|
2218
|
+
interfaceType,
|
|
2219
|
+
checker,
|
|
2220
|
+
file,
|
|
2221
|
+
diagnostics
|
|
2222
|
+
);
|
|
2223
|
+
const fieldLayouts = specializedFields.map(() => ({}));
|
|
2205
2224
|
return {
|
|
2206
2225
|
name,
|
|
2207
|
-
fields,
|
|
2226
|
+
fields: specializedFields,
|
|
2208
2227
|
fieldLayouts,
|
|
2209
2228
|
typeRegistry,
|
|
2210
2229
|
...annotations.length > 0 && { annotations },
|
|
@@ -2253,12 +2272,20 @@ function analyzeTypeAliasToIR(typeAlias, checker, file = "", extensionRegistry)
|
|
|
2253
2272
|
}
|
|
2254
2273
|
}
|
|
2255
2274
|
}
|
|
2275
|
+
const specializedFields = applyDeclarationDiscriminatorToFields(
|
|
2276
|
+
fields,
|
|
2277
|
+
typeAlias,
|
|
2278
|
+
aliasType,
|
|
2279
|
+
checker,
|
|
2280
|
+
file,
|
|
2281
|
+
diagnostics
|
|
2282
|
+
);
|
|
2256
2283
|
return {
|
|
2257
2284
|
ok: true,
|
|
2258
2285
|
analysis: {
|
|
2259
2286
|
name,
|
|
2260
|
-
fields,
|
|
2261
|
-
fieldLayouts:
|
|
2287
|
+
fields: specializedFields,
|
|
2288
|
+
fieldLayouts: specializedFields.map(() => ({})),
|
|
2262
2289
|
typeRegistry,
|
|
2263
2290
|
...annotations.length > 0 && { annotations },
|
|
2264
2291
|
...diagnostics.length > 0 && { diagnostics },
|
|
@@ -2267,6 +2294,396 @@ function analyzeTypeAliasToIR(typeAlias, checker, file = "", extensionRegistry)
|
|
|
2267
2294
|
}
|
|
2268
2295
|
};
|
|
2269
2296
|
}
|
|
2297
|
+
function makeAnalysisDiagnostic(code, message, primaryLocation, relatedLocations = []) {
|
|
2298
|
+
return {
|
|
2299
|
+
code,
|
|
2300
|
+
message,
|
|
2301
|
+
severity: "error",
|
|
2302
|
+
primaryLocation,
|
|
2303
|
+
relatedLocations
|
|
2304
|
+
};
|
|
2305
|
+
}
|
|
2306
|
+
function getLeadingParsedTags(node) {
|
|
2307
|
+
const sourceFile = node.getSourceFile();
|
|
2308
|
+
const sourceText = sourceFile.getFullText();
|
|
2309
|
+
const commentRanges = ts3.getLeadingCommentRanges(sourceText, node.getFullStart());
|
|
2310
|
+
if (commentRanges === void 0) {
|
|
2311
|
+
return [];
|
|
2312
|
+
}
|
|
2313
|
+
const parsedTags = [];
|
|
2314
|
+
for (const range of commentRanges) {
|
|
2315
|
+
if (range.kind !== ts3.SyntaxKind.MultiLineCommentTrivia) {
|
|
2316
|
+
continue;
|
|
2317
|
+
}
|
|
2318
|
+
const commentText = sourceText.slice(range.pos, range.end);
|
|
2319
|
+
if (!commentText.startsWith("/**")) {
|
|
2320
|
+
continue;
|
|
2321
|
+
}
|
|
2322
|
+
parsedTags.push(...parseCommentBlock2(commentText, { offset: range.pos }).tags);
|
|
2323
|
+
}
|
|
2324
|
+
return parsedTags;
|
|
2325
|
+
}
|
|
2326
|
+
function findDiscriminatorProperty(node, fieldName) {
|
|
2327
|
+
if (ts3.isClassDeclaration(node)) {
|
|
2328
|
+
for (const member of node.members) {
|
|
2329
|
+
if (ts3.isPropertyDeclaration(member) && ts3.isIdentifier(member.name) && member.name.text === fieldName) {
|
|
2330
|
+
return member;
|
|
2331
|
+
}
|
|
2332
|
+
}
|
|
2333
|
+
return null;
|
|
2334
|
+
}
|
|
2335
|
+
if (ts3.isInterfaceDeclaration(node)) {
|
|
2336
|
+
for (const member of node.members) {
|
|
2337
|
+
if (ts3.isPropertySignature(member) && ts3.isIdentifier(member.name) && member.name.text === fieldName) {
|
|
2338
|
+
return member;
|
|
2339
|
+
}
|
|
2340
|
+
}
|
|
2341
|
+
return null;
|
|
2342
|
+
}
|
|
2343
|
+
if (ts3.isTypeLiteralNode(node.type)) {
|
|
2344
|
+
for (const member of node.type.members) {
|
|
2345
|
+
if (ts3.isPropertySignature(member) && ts3.isIdentifier(member.name) && member.name.text === fieldName) {
|
|
2346
|
+
return member;
|
|
2347
|
+
}
|
|
2348
|
+
}
|
|
2349
|
+
}
|
|
2350
|
+
return null;
|
|
2351
|
+
}
|
|
2352
|
+
function isLocalTypeParameterName(node, typeParameterName) {
|
|
2353
|
+
return node.typeParameters?.some((typeParameter) => typeParameter.name.text === typeParameterName) ?? false;
|
|
2354
|
+
}
|
|
2355
|
+
function isNullishSemanticType(type) {
|
|
2356
|
+
if (type.flags & (ts3.TypeFlags.Null | ts3.TypeFlags.Undefined | ts3.TypeFlags.Void | ts3.TypeFlags.Unknown | ts3.TypeFlags.Any)) {
|
|
2357
|
+
return true;
|
|
2358
|
+
}
|
|
2359
|
+
return type.isUnion() && type.types.some((member) => isNullishSemanticType(member));
|
|
2360
|
+
}
|
|
2361
|
+
function isStringLikeSemanticType(type) {
|
|
2362
|
+
if (type.flags & ts3.TypeFlags.StringLike) {
|
|
2363
|
+
return true;
|
|
2364
|
+
}
|
|
2365
|
+
if (type.isUnion()) {
|
|
2366
|
+
return type.types.length > 0 && type.types.every((member) => isStringLikeSemanticType(member));
|
|
2367
|
+
}
|
|
2368
|
+
return false;
|
|
2369
|
+
}
|
|
2370
|
+
function extractDiscriminatorDirective(node, file, diagnostics) {
|
|
2371
|
+
const discriminatorTags = getLeadingParsedTags(node).filter(
|
|
2372
|
+
(tag) => tag.normalizedTagName === "discriminator"
|
|
2373
|
+
);
|
|
2374
|
+
if (discriminatorTags.length === 0) {
|
|
2375
|
+
return null;
|
|
2376
|
+
}
|
|
2377
|
+
const [firstTag, ...duplicateTags] = discriminatorTags;
|
|
2378
|
+
for (const _duplicateTag of duplicateTags) {
|
|
2379
|
+
diagnostics.push(
|
|
2380
|
+
makeAnalysisDiagnostic(
|
|
2381
|
+
"DUPLICATE_TAG",
|
|
2382
|
+
'Duplicate "@discriminator" tag. Only one discriminator declaration is allowed per declaration.',
|
|
2383
|
+
provenanceForNode(node, file)
|
|
2384
|
+
)
|
|
2385
|
+
);
|
|
2386
|
+
}
|
|
2387
|
+
if (firstTag === void 0) {
|
|
2388
|
+
return null;
|
|
2389
|
+
}
|
|
2390
|
+
const firstTarget = firstTag.target;
|
|
2391
|
+
if (firstTarget?.path === null || firstTarget?.valid !== true) {
|
|
2392
|
+
diagnostics.push(
|
|
2393
|
+
makeAnalysisDiagnostic(
|
|
2394
|
+
"INVALID_TAG_ARGUMENT",
|
|
2395
|
+
'Tag "@discriminator" requires a direct path target like ":kind".',
|
|
2396
|
+
provenanceForNode(node, file)
|
|
2397
|
+
)
|
|
2398
|
+
);
|
|
2399
|
+
return null;
|
|
2400
|
+
}
|
|
2401
|
+
if (firstTarget.path.segments.length !== 1) {
|
|
2402
|
+
diagnostics.push(
|
|
2403
|
+
makeAnalysisDiagnostic(
|
|
2404
|
+
"INVALID_TAG_ARGUMENT",
|
|
2405
|
+
'Tag "@discriminator" only supports direct property targets in v1; nested paths are out of scope.',
|
|
2406
|
+
provenanceForNode(node, file)
|
|
2407
|
+
)
|
|
2408
|
+
);
|
|
2409
|
+
return null;
|
|
2410
|
+
}
|
|
2411
|
+
const typeParameterName = firstTag.argumentText.trim();
|
|
2412
|
+
if (!/^[A-Za-z_$][\w$]*$/u.test(typeParameterName)) {
|
|
2413
|
+
diagnostics.push(
|
|
2414
|
+
makeAnalysisDiagnostic(
|
|
2415
|
+
"INVALID_TAG_ARGUMENT",
|
|
2416
|
+
'Tag "@discriminator" requires a local type parameter name as its source operand.',
|
|
2417
|
+
provenanceForNode(node, file)
|
|
2418
|
+
)
|
|
2419
|
+
);
|
|
2420
|
+
return null;
|
|
2421
|
+
}
|
|
2422
|
+
return {
|
|
2423
|
+
fieldName: firstTarget.path.segments[0] ?? firstTarget.rawText,
|
|
2424
|
+
typeParameterName,
|
|
2425
|
+
provenance: provenanceForNode(node, file)
|
|
2426
|
+
};
|
|
2427
|
+
}
|
|
2428
|
+
function validateDiscriminatorDirective(node, checker, file, diagnostics) {
|
|
2429
|
+
const directive = extractDiscriminatorDirective(node, file, diagnostics);
|
|
2430
|
+
if (directive === null) {
|
|
2431
|
+
return null;
|
|
2432
|
+
}
|
|
2433
|
+
if (!isLocalTypeParameterName(node, directive.typeParameterName)) {
|
|
2434
|
+
diagnostics.push(
|
|
2435
|
+
makeAnalysisDiagnostic(
|
|
2436
|
+
"INVALID_TAG_ARGUMENT",
|
|
2437
|
+
`Tag "@discriminator" references "${directive.typeParameterName}", but the source operand must be a type parameter declared on the same declaration.`,
|
|
2438
|
+
directive.provenance
|
|
2439
|
+
)
|
|
2440
|
+
);
|
|
2441
|
+
return null;
|
|
2442
|
+
}
|
|
2443
|
+
const propertyDecl = findDiscriminatorProperty(node, directive.fieldName);
|
|
2444
|
+
if (propertyDecl === null) {
|
|
2445
|
+
diagnostics.push(
|
|
2446
|
+
makeAnalysisDiagnostic(
|
|
2447
|
+
"UNKNOWN_PATH_TARGET",
|
|
2448
|
+
`Tag "@discriminator" targets "${directive.fieldName}", but no direct property with that name exists on this declaration.`,
|
|
2449
|
+
directive.provenance
|
|
2450
|
+
)
|
|
2451
|
+
);
|
|
2452
|
+
return null;
|
|
2453
|
+
}
|
|
2454
|
+
if (propertyDecl.questionToken !== void 0) {
|
|
2455
|
+
diagnostics.push(
|
|
2456
|
+
makeAnalysisDiagnostic(
|
|
2457
|
+
"TYPE_MISMATCH",
|
|
2458
|
+
`Discriminator field "${directive.fieldName}" must be required; optional discriminator fields are not supported.`,
|
|
2459
|
+
directive.provenance,
|
|
2460
|
+
[provenanceForNode(propertyDecl, file)]
|
|
2461
|
+
)
|
|
2462
|
+
);
|
|
2463
|
+
return null;
|
|
2464
|
+
}
|
|
2465
|
+
const propertyType = checker.getTypeAtLocation(propertyDecl);
|
|
2466
|
+
if (isNullishSemanticType(propertyType)) {
|
|
2467
|
+
diagnostics.push(
|
|
2468
|
+
makeAnalysisDiagnostic(
|
|
2469
|
+
"TYPE_MISMATCH",
|
|
2470
|
+
`Discriminator field "${directive.fieldName}" must not be nullable.`,
|
|
2471
|
+
directive.provenance,
|
|
2472
|
+
[provenanceForNode(propertyDecl, file)]
|
|
2473
|
+
)
|
|
2474
|
+
);
|
|
2475
|
+
return null;
|
|
2476
|
+
}
|
|
2477
|
+
if (!isStringLikeSemanticType(propertyType)) {
|
|
2478
|
+
diagnostics.push(
|
|
2479
|
+
makeAnalysisDiagnostic(
|
|
2480
|
+
"TYPE_MISMATCH",
|
|
2481
|
+
`Discriminator field "${directive.fieldName}" must be string-like.`,
|
|
2482
|
+
directive.provenance,
|
|
2483
|
+
[provenanceForNode(propertyDecl, file)]
|
|
2484
|
+
)
|
|
2485
|
+
);
|
|
2486
|
+
return null;
|
|
2487
|
+
}
|
|
2488
|
+
return directive;
|
|
2489
|
+
}
|
|
2490
|
+
function getConcreteTypeArgumentForDiscriminator(node, subjectType, checker, typeParameterName) {
|
|
2491
|
+
const typeParameterIndex = node.typeParameters?.findIndex(
|
|
2492
|
+
(typeParameter) => typeParameter.name.text === typeParameterName
|
|
2493
|
+
) ?? -1;
|
|
2494
|
+
if (typeParameterIndex < 0) {
|
|
2495
|
+
return null;
|
|
2496
|
+
}
|
|
2497
|
+
const referenceTypeArguments = (isTypeReference(subjectType) ? subjectType.typeArguments : void 0) ?? subjectType.aliasTypeArguments;
|
|
2498
|
+
if (referenceTypeArguments?.[typeParameterIndex] !== void 0) {
|
|
2499
|
+
return referenceTypeArguments[typeParameterIndex] ?? null;
|
|
2500
|
+
}
|
|
2501
|
+
const localTypeParameter = node.typeParameters?.[typeParameterIndex];
|
|
2502
|
+
return localTypeParameter === void 0 ? null : checker.getTypeAtLocation(localTypeParameter);
|
|
2503
|
+
}
|
|
2504
|
+
function extractDeclarationApiName(node) {
|
|
2505
|
+
for (const tag of getLeadingParsedTags(node)) {
|
|
2506
|
+
if (tag.normalizedTagName !== "apiName") {
|
|
2507
|
+
continue;
|
|
2508
|
+
}
|
|
2509
|
+
if (tag.target === null && tag.argumentText.trim() !== "") {
|
|
2510
|
+
return tag.argumentText.trim();
|
|
2511
|
+
}
|
|
2512
|
+
if (tag.target?.kind === "variant" && tag.target.rawText === "singular") {
|
|
2513
|
+
const value = tag.argumentText.trim();
|
|
2514
|
+
if (value !== "") {
|
|
2515
|
+
return value;
|
|
2516
|
+
}
|
|
2517
|
+
}
|
|
2518
|
+
}
|
|
2519
|
+
return null;
|
|
2520
|
+
}
|
|
2521
|
+
function inferJsonFacingName(name) {
|
|
2522
|
+
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();
|
|
2523
|
+
}
|
|
2524
|
+
function resolveNamedDiscriminatorDeclaration(type, checker, seen = /* @__PURE__ */ new Set()) {
|
|
2525
|
+
if (seen.has(type)) {
|
|
2526
|
+
return null;
|
|
2527
|
+
}
|
|
2528
|
+
seen.add(type);
|
|
2529
|
+
const symbol = type.aliasSymbol ?? type.getSymbol();
|
|
2530
|
+
if (symbol !== void 0) {
|
|
2531
|
+
const aliased = symbol.flags & ts3.SymbolFlags.Alias ? checker.getAliasedSymbol(symbol) : void 0;
|
|
2532
|
+
const targetSymbol = aliased ?? symbol;
|
|
2533
|
+
const declaration = targetSymbol.declarations?.find(
|
|
2534
|
+
(candidate) => ts3.isClassDeclaration(candidate) || ts3.isInterfaceDeclaration(candidate) || ts3.isTypeAliasDeclaration(candidate) || ts3.isEnumDeclaration(candidate)
|
|
2535
|
+
);
|
|
2536
|
+
if (declaration !== void 0) {
|
|
2537
|
+
if (ts3.isTypeAliasDeclaration(declaration) && ts3.isTypeReferenceNode(declaration.type) && checker.getTypeFromTypeNode(declaration.type) !== type) {
|
|
2538
|
+
return resolveNamedDiscriminatorDeclaration(
|
|
2539
|
+
checker.getTypeFromTypeNode(declaration.type),
|
|
2540
|
+
checker,
|
|
2541
|
+
seen
|
|
2542
|
+
);
|
|
2543
|
+
}
|
|
2544
|
+
return declaration;
|
|
2545
|
+
}
|
|
2546
|
+
}
|
|
2547
|
+
return null;
|
|
2548
|
+
}
|
|
2549
|
+
function resolveDiscriminatorValue(boundType, checker, provenance, diagnostics) {
|
|
2550
|
+
if (boundType === null) {
|
|
2551
|
+
diagnostics.push(
|
|
2552
|
+
makeAnalysisDiagnostic(
|
|
2553
|
+
"INVALID_TAG_ARGUMENT",
|
|
2554
|
+
"Discriminator resolution failed because no concrete type argument is available for the referenced type parameter.",
|
|
2555
|
+
provenance
|
|
2556
|
+
)
|
|
2557
|
+
);
|
|
2558
|
+
return null;
|
|
2559
|
+
}
|
|
2560
|
+
if (boundType.isStringLiteral()) {
|
|
2561
|
+
return boundType.value;
|
|
2562
|
+
}
|
|
2563
|
+
if (boundType.isUnion()) {
|
|
2564
|
+
const nonNullMembers = boundType.types.filter(
|
|
2565
|
+
(member) => !(member.flags & (ts3.TypeFlags.Null | ts3.TypeFlags.Undefined))
|
|
2566
|
+
);
|
|
2567
|
+
if (nonNullMembers.every((member) => member.isStringLiteral())) {
|
|
2568
|
+
diagnostics.push(
|
|
2569
|
+
makeAnalysisDiagnostic(
|
|
2570
|
+
"INVALID_TAG_ARGUMENT",
|
|
2571
|
+
"Discriminator resolution for unions of string literals is out of scope for v1.",
|
|
2572
|
+
provenance
|
|
2573
|
+
)
|
|
2574
|
+
);
|
|
2575
|
+
return null;
|
|
2576
|
+
}
|
|
2577
|
+
}
|
|
2578
|
+
const declaration = resolveNamedDiscriminatorDeclaration(boundType, checker);
|
|
2579
|
+
if (declaration !== null) {
|
|
2580
|
+
return extractDeclarationApiName(declaration) ?? inferJsonFacingName(getDeclarationName(declaration));
|
|
2581
|
+
}
|
|
2582
|
+
diagnostics.push(
|
|
2583
|
+
makeAnalysisDiagnostic(
|
|
2584
|
+
"INVALID_TAG_ARGUMENT",
|
|
2585
|
+
"Discriminator resolution could not derive a JSON-facing discriminator value from the referenced type argument.",
|
|
2586
|
+
provenance
|
|
2587
|
+
)
|
|
2588
|
+
);
|
|
2589
|
+
return null;
|
|
2590
|
+
}
|
|
2591
|
+
function getDeclarationName(node) {
|
|
2592
|
+
if (ts3.isClassDeclaration(node) || ts3.isInterfaceDeclaration(node) || ts3.isTypeAliasDeclaration(node) || ts3.isEnumDeclaration(node)) {
|
|
2593
|
+
return node.name?.text ?? "anonymous";
|
|
2594
|
+
}
|
|
2595
|
+
return "anonymous";
|
|
2596
|
+
}
|
|
2597
|
+
function applyDeclarationDiscriminatorToFields(fields, node, subjectType, checker, file, diagnostics) {
|
|
2598
|
+
const directive = validateDiscriminatorDirective(node, checker, file, diagnostics);
|
|
2599
|
+
if (directive === null) {
|
|
2600
|
+
return [...fields];
|
|
2601
|
+
}
|
|
2602
|
+
const discriminatorValue = resolveDiscriminatorValue(
|
|
2603
|
+
getConcreteTypeArgumentForDiscriminator(
|
|
2604
|
+
node,
|
|
2605
|
+
subjectType,
|
|
2606
|
+
checker,
|
|
2607
|
+
directive.typeParameterName
|
|
2608
|
+
),
|
|
2609
|
+
checker,
|
|
2610
|
+
directive.provenance,
|
|
2611
|
+
diagnostics
|
|
2612
|
+
);
|
|
2613
|
+
if (discriminatorValue === null) {
|
|
2614
|
+
return [...fields];
|
|
2615
|
+
}
|
|
2616
|
+
return fields.map(
|
|
2617
|
+
(field) => field.name === directive.fieldName ? {
|
|
2618
|
+
...field,
|
|
2619
|
+
type: {
|
|
2620
|
+
kind: "enum",
|
|
2621
|
+
members: [{ value: discriminatorValue }]
|
|
2622
|
+
}
|
|
2623
|
+
} : field
|
|
2624
|
+
);
|
|
2625
|
+
}
|
|
2626
|
+
function buildInstantiatedReferenceName(baseName, typeArguments, checker) {
|
|
2627
|
+
const renderedArguments = typeArguments.map(
|
|
2628
|
+
(typeArgument) => checker.typeToString(typeArgument).replace(/[^A-Za-z0-9]+/g, "_").replace(/^_+|_+$/g, "")
|
|
2629
|
+
).filter((value) => value !== "");
|
|
2630
|
+
return renderedArguments.length === 0 ? baseName : `${baseName}__${renderedArguments.join("__")}`;
|
|
2631
|
+
}
|
|
2632
|
+
function extractReferenceTypeArguments(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry, diagnostics) {
|
|
2633
|
+
const typeNode = sourceNode === void 0 ? void 0 : extractTypeNodeFromSource(sourceNode);
|
|
2634
|
+
if (typeNode === void 0) {
|
|
2635
|
+
return [];
|
|
2636
|
+
}
|
|
2637
|
+
const resolvedTypeNode = resolveAliasedTypeNode(typeNode, checker);
|
|
2638
|
+
if (!ts3.isTypeReferenceNode(resolvedTypeNode) || resolvedTypeNode.typeArguments === void 0) {
|
|
2639
|
+
return [];
|
|
2640
|
+
}
|
|
2641
|
+
return resolvedTypeNode.typeArguments.map((argumentNode) => {
|
|
2642
|
+
const argumentType = checker.getTypeFromTypeNode(argumentNode);
|
|
2643
|
+
return {
|
|
2644
|
+
tsType: argumentType,
|
|
2645
|
+
typeNode: resolveTypeNode(
|
|
2646
|
+
argumentType,
|
|
2647
|
+
checker,
|
|
2648
|
+
file,
|
|
2649
|
+
typeRegistry,
|
|
2650
|
+
visiting,
|
|
2651
|
+
argumentNode,
|
|
2652
|
+
extensionRegistry,
|
|
2653
|
+
diagnostics
|
|
2654
|
+
)
|
|
2655
|
+
};
|
|
2656
|
+
});
|
|
2657
|
+
}
|
|
2658
|
+
function applyDiscriminatorToObjectProperties(properties, node, subjectType, checker, file, diagnostics) {
|
|
2659
|
+
const directive = validateDiscriminatorDirective(node, checker, file, diagnostics);
|
|
2660
|
+
if (directive === null) {
|
|
2661
|
+
return properties;
|
|
2662
|
+
}
|
|
2663
|
+
const discriminatorValue = resolveDiscriminatorValue(
|
|
2664
|
+
getConcreteTypeArgumentForDiscriminator(
|
|
2665
|
+
node,
|
|
2666
|
+
subjectType,
|
|
2667
|
+
checker,
|
|
2668
|
+
directive.typeParameterName
|
|
2669
|
+
),
|
|
2670
|
+
checker,
|
|
2671
|
+
directive.provenance,
|
|
2672
|
+
diagnostics
|
|
2673
|
+
);
|
|
2674
|
+
if (discriminatorValue === null) {
|
|
2675
|
+
return properties;
|
|
2676
|
+
}
|
|
2677
|
+
return properties.map(
|
|
2678
|
+
(property) => property.name === directive.fieldName ? {
|
|
2679
|
+
...property,
|
|
2680
|
+
type: {
|
|
2681
|
+
kind: "enum",
|
|
2682
|
+
members: [{ value: discriminatorValue }]
|
|
2683
|
+
}
|
|
2684
|
+
} : property
|
|
2685
|
+
);
|
|
2686
|
+
}
|
|
2270
2687
|
function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting, diagnostics, hostType, extensionRegistry) {
|
|
2271
2688
|
if (!ts3.isIdentifier(prop.name)) {
|
|
2272
2689
|
return null;
|
|
@@ -2555,6 +2972,7 @@ function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode
|
|
|
2555
2972
|
file,
|
|
2556
2973
|
typeRegistry,
|
|
2557
2974
|
visiting,
|
|
2975
|
+
sourceNode,
|
|
2558
2976
|
extensionRegistry,
|
|
2559
2977
|
diagnostics
|
|
2560
2978
|
);
|
|
@@ -2818,35 +3236,60 @@ function typeNodeContainsReference(type, targetName) {
|
|
|
2818
3236
|
}
|
|
2819
3237
|
}
|
|
2820
3238
|
}
|
|
2821
|
-
function resolveObjectType(type, checker, file, typeRegistry, visiting, extensionRegistry, diagnostics) {
|
|
3239
|
+
function resolveObjectType(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry, diagnostics) {
|
|
3240
|
+
const collectedDiagnostics = diagnostics ?? [];
|
|
2822
3241
|
const typeName = getNamedTypeName(type);
|
|
2823
3242
|
const namedTypeName = typeName ?? void 0;
|
|
2824
3243
|
const namedDecl = getNamedTypeDeclaration(type);
|
|
2825
|
-
const
|
|
3244
|
+
const referenceTypeArguments = extractReferenceTypeArguments(
|
|
3245
|
+
type,
|
|
3246
|
+
checker,
|
|
3247
|
+
file,
|
|
3248
|
+
typeRegistry,
|
|
3249
|
+
visiting,
|
|
3250
|
+
sourceNode,
|
|
3251
|
+
extensionRegistry,
|
|
3252
|
+
collectedDiagnostics
|
|
3253
|
+
);
|
|
3254
|
+
const instantiatedTypeName = namedTypeName !== void 0 && referenceTypeArguments.length > 0 ? buildInstantiatedReferenceName(
|
|
3255
|
+
namedTypeName,
|
|
3256
|
+
referenceTypeArguments.map((argument) => argument.tsType),
|
|
3257
|
+
checker
|
|
3258
|
+
) : void 0;
|
|
3259
|
+
const registryTypeName = instantiatedTypeName ?? namedTypeName;
|
|
3260
|
+
const shouldRegisterNamedType = registryTypeName !== void 0 && !(registryTypeName === "Record" && namedDecl?.getSourceFile().fileName !== file);
|
|
2826
3261
|
const clearNamedTypeRegistration = () => {
|
|
2827
|
-
if (
|
|
3262
|
+
if (registryTypeName === void 0 || !shouldRegisterNamedType) {
|
|
2828
3263
|
return;
|
|
2829
3264
|
}
|
|
2830
|
-
Reflect.deleteProperty(typeRegistry,
|
|
3265
|
+
Reflect.deleteProperty(typeRegistry, registryTypeName);
|
|
2831
3266
|
};
|
|
2832
3267
|
if (visiting.has(type)) {
|
|
2833
|
-
if (
|
|
2834
|
-
return {
|
|
3268
|
+
if (registryTypeName !== void 0 && shouldRegisterNamedType) {
|
|
3269
|
+
return {
|
|
3270
|
+
kind: "reference",
|
|
3271
|
+
name: registryTypeName,
|
|
3272
|
+
typeArguments: referenceTypeArguments.map((argument) => argument.typeNode)
|
|
3273
|
+
};
|
|
2835
3274
|
}
|
|
2836
3275
|
return { kind: "object", properties: [], additionalProperties: false };
|
|
2837
3276
|
}
|
|
2838
|
-
if (
|
|
2839
|
-
typeRegistry[
|
|
2840
|
-
name:
|
|
3277
|
+
if (registryTypeName !== void 0 && shouldRegisterNamedType && !typeRegistry[registryTypeName]) {
|
|
3278
|
+
typeRegistry[registryTypeName] = {
|
|
3279
|
+
name: registryTypeName,
|
|
2841
3280
|
type: RESOLVING_TYPE_PLACEHOLDER,
|
|
2842
3281
|
provenance: provenanceForDeclaration(namedDecl, file)
|
|
2843
3282
|
};
|
|
2844
3283
|
}
|
|
2845
3284
|
visiting.add(type);
|
|
2846
|
-
if (
|
|
2847
|
-
if (typeRegistry[
|
|
3285
|
+
if (registryTypeName !== void 0 && shouldRegisterNamedType && typeRegistry[registryTypeName]?.type !== void 0) {
|
|
3286
|
+
if (typeRegistry[registryTypeName].type !== RESOLVING_TYPE_PLACEHOLDER) {
|
|
2848
3287
|
visiting.delete(type);
|
|
2849
|
-
return {
|
|
3288
|
+
return {
|
|
3289
|
+
kind: "reference",
|
|
3290
|
+
name: registryTypeName,
|
|
3291
|
+
typeArguments: referenceTypeArguments.map((argument) => argument.typeNode)
|
|
3292
|
+
};
|
|
2850
3293
|
}
|
|
2851
3294
|
}
|
|
2852
3295
|
const recordNode = tryResolveRecordType(
|
|
@@ -2856,24 +3299,28 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting, extensio
|
|
|
2856
3299
|
typeRegistry,
|
|
2857
3300
|
visiting,
|
|
2858
3301
|
extensionRegistry,
|
|
2859
|
-
|
|
3302
|
+
collectedDiagnostics
|
|
2860
3303
|
);
|
|
2861
3304
|
if (recordNode) {
|
|
2862
3305
|
visiting.delete(type);
|
|
2863
|
-
if (
|
|
2864
|
-
const isRecursiveRecord = typeNodeContainsReference(recordNode.valueType,
|
|
3306
|
+
if (registryTypeName !== void 0 && shouldRegisterNamedType) {
|
|
3307
|
+
const isRecursiveRecord = typeNodeContainsReference(recordNode.valueType, registryTypeName);
|
|
2865
3308
|
if (!isRecursiveRecord) {
|
|
2866
3309
|
clearNamedTypeRegistration();
|
|
2867
3310
|
return recordNode;
|
|
2868
3311
|
}
|
|
2869
3312
|
const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file, makeParseOptions(extensionRegistry)) : void 0;
|
|
2870
|
-
typeRegistry[
|
|
2871
|
-
name:
|
|
3313
|
+
typeRegistry[registryTypeName] = {
|
|
3314
|
+
name: registryTypeName,
|
|
2872
3315
|
type: recordNode,
|
|
2873
3316
|
...annotations !== void 0 && annotations.length > 0 && { annotations },
|
|
2874
3317
|
provenance: provenanceForDeclaration(namedDecl, file)
|
|
2875
3318
|
};
|
|
2876
|
-
return {
|
|
3319
|
+
return {
|
|
3320
|
+
kind: "reference",
|
|
3321
|
+
name: registryTypeName,
|
|
3322
|
+
typeArguments: referenceTypeArguments.map((argument) => argument.typeNode)
|
|
3323
|
+
};
|
|
2877
3324
|
}
|
|
2878
3325
|
return recordNode;
|
|
2879
3326
|
}
|
|
@@ -2884,7 +3331,7 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting, extensio
|
|
|
2884
3331
|
file,
|
|
2885
3332
|
typeRegistry,
|
|
2886
3333
|
visiting,
|
|
2887
|
-
|
|
3334
|
+
collectedDiagnostics,
|
|
2888
3335
|
extensionRegistry
|
|
2889
3336
|
);
|
|
2890
3337
|
for (const prop of type.getProperties()) {
|
|
@@ -2900,7 +3347,7 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting, extensio
|
|
|
2900
3347
|
visiting,
|
|
2901
3348
|
declaration,
|
|
2902
3349
|
extensionRegistry,
|
|
2903
|
-
|
|
3350
|
+
collectedDiagnostics
|
|
2904
3351
|
);
|
|
2905
3352
|
const fieldNodeInfo = fieldInfoMap?.get(prop.name);
|
|
2906
3353
|
properties.push({
|
|
@@ -2915,18 +3362,29 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting, extensio
|
|
|
2915
3362
|
visiting.delete(type);
|
|
2916
3363
|
const objectNode = {
|
|
2917
3364
|
kind: "object",
|
|
2918
|
-
properties
|
|
3365
|
+
properties: namedDecl !== void 0 && (ts3.isClassDeclaration(namedDecl) || ts3.isInterfaceDeclaration(namedDecl) || ts3.isTypeAliasDeclaration(namedDecl)) ? applyDiscriminatorToObjectProperties(
|
|
3366
|
+
properties,
|
|
3367
|
+
namedDecl,
|
|
3368
|
+
type,
|
|
3369
|
+
checker,
|
|
3370
|
+
file,
|
|
3371
|
+
collectedDiagnostics
|
|
3372
|
+
) : properties,
|
|
2919
3373
|
additionalProperties: true
|
|
2920
3374
|
};
|
|
2921
|
-
if (
|
|
3375
|
+
if (registryTypeName !== void 0 && shouldRegisterNamedType) {
|
|
2922
3376
|
const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file, makeParseOptions(extensionRegistry)) : void 0;
|
|
2923
|
-
typeRegistry[
|
|
2924
|
-
name:
|
|
3377
|
+
typeRegistry[registryTypeName] = {
|
|
3378
|
+
name: registryTypeName,
|
|
2925
3379
|
type: objectNode,
|
|
2926
3380
|
...annotations !== void 0 && annotations.length > 0 && { annotations },
|
|
2927
3381
|
provenance: provenanceForDeclaration(namedDecl, file)
|
|
2928
3382
|
};
|
|
2929
|
-
return {
|
|
3383
|
+
return {
|
|
3384
|
+
kind: "reference",
|
|
3385
|
+
name: registryTypeName,
|
|
3386
|
+
typeArguments: referenceTypeArguments.map((argument) => argument.typeNode)
|
|
3387
|
+
};
|
|
2930
3388
|
}
|
|
2931
3389
|
return objectNode;
|
|
2932
3390
|
}
|