@formspec/build 0.1.0-alpha.47 → 0.1.0-alpha.49
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/analyzer/class-analyzer.d.ts.map +1 -1
- package/dist/analyzer/tsdoc-parser.d.ts.map +1 -1
- package/dist/cli.cjs +120 -4
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +120 -4
- package/dist/cli.js.map +1 -1
- package/dist/generators/discovered-schema.d.ts.map +1 -1
- package/dist/index.cjs +119 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +119 -3
- package/dist/index.js.map +1 -1
- package/dist/internals.cjs +98 -1
- package/dist/internals.cjs.map +1 -1
- package/dist/internals.js +98 -1
- package/dist/internals.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -2469,6 +2469,90 @@ function supportsConstraintCapability(type, checker, capability) {
|
|
|
2469
2469
|
}
|
|
2470
2470
|
return false;
|
|
2471
2471
|
}
|
|
2472
|
+
function stripHintNullishUnion(type) {
|
|
2473
|
+
if (!type.isUnion()) {
|
|
2474
|
+
return type;
|
|
2475
|
+
}
|
|
2476
|
+
const nonNullish = type.types.filter(
|
|
2477
|
+
(member) => (member.flags & (ts.TypeFlags.Null | ts.TypeFlags.Undefined)) === 0
|
|
2478
|
+
);
|
|
2479
|
+
if (nonNullish.length === 1 && nonNullish[0] !== void 0) {
|
|
2480
|
+
return nonNullish[0];
|
|
2481
|
+
}
|
|
2482
|
+
return type;
|
|
2483
|
+
}
|
|
2484
|
+
function isCallableType(type) {
|
|
2485
|
+
return type.getCallSignatures().length > 0 || type.getConstructSignatures().length > 0;
|
|
2486
|
+
}
|
|
2487
|
+
function isUserEmittableHintProperty(property, declaration) {
|
|
2488
|
+
if (property.name.startsWith("__")) {
|
|
2489
|
+
return false;
|
|
2490
|
+
}
|
|
2491
|
+
if ("name" in declaration && declaration.name !== void 0) {
|
|
2492
|
+
const name = declaration.name;
|
|
2493
|
+
if (ts.isComputedPropertyName(name) || ts.isPrivateIdentifier(name)) {
|
|
2494
|
+
return false;
|
|
2495
|
+
}
|
|
2496
|
+
if (!ts.isIdentifier(name) && !ts.isStringLiteral(name) && !ts.isNumericLiteral(name)) {
|
|
2497
|
+
return false;
|
|
2498
|
+
}
|
|
2499
|
+
}
|
|
2500
|
+
return true;
|
|
2501
|
+
}
|
|
2502
|
+
function collectObjectSubfieldCandidates(type, checker, capability) {
|
|
2503
|
+
const out = [];
|
|
2504
|
+
const visit = (current, prefix, depth) => {
|
|
2505
|
+
if (depth > MAX_HINT_DEPTH) {
|
|
2506
|
+
return;
|
|
2507
|
+
}
|
|
2508
|
+
const stripped = stripHintNullishUnion(current);
|
|
2509
|
+
if (isCallableType(stripped)) {
|
|
2510
|
+
return;
|
|
2511
|
+
}
|
|
2512
|
+
if (!hasTypeSemanticCapability(stripped, checker, "object-like")) {
|
|
2513
|
+
return;
|
|
2514
|
+
}
|
|
2515
|
+
for (const property of stripped.getProperties()) {
|
|
2516
|
+
const declaration = property.valueDeclaration ?? property.declarations?.[0];
|
|
2517
|
+
if (declaration === void 0) {
|
|
2518
|
+
continue;
|
|
2519
|
+
}
|
|
2520
|
+
if (!isUserEmittableHintProperty(property, declaration)) {
|
|
2521
|
+
continue;
|
|
2522
|
+
}
|
|
2523
|
+
const propertyType = checker.getTypeOfSymbolAtLocation(property, declaration);
|
|
2524
|
+
const path5 = [...prefix, property.name];
|
|
2525
|
+
if (supportsConstraintCapability(propertyType, checker, capability)) {
|
|
2526
|
+
out.push(path5.join("."));
|
|
2527
|
+
continue;
|
|
2528
|
+
}
|
|
2529
|
+
const strippedPropertyType = stripHintNullishUnion(propertyType);
|
|
2530
|
+
if (!isCallableType(strippedPropertyType) && hasTypeSemanticCapability(strippedPropertyType, checker, "object-like")) {
|
|
2531
|
+
visit(strippedPropertyType, path5, depth + 1);
|
|
2532
|
+
}
|
|
2533
|
+
}
|
|
2534
|
+
};
|
|
2535
|
+
visit(type, [], 0);
|
|
2536
|
+
return out;
|
|
2537
|
+
}
|
|
2538
|
+
function buildPathTargetHint(subjectType, checker, capability, tagName, argumentText) {
|
|
2539
|
+
if (!hasTypeSemanticCapability(subjectType, checker, "object-like")) {
|
|
2540
|
+
return null;
|
|
2541
|
+
}
|
|
2542
|
+
const candidates = collectObjectSubfieldCandidates(subjectType, checker, capability);
|
|
2543
|
+
const primary = candidates[0];
|
|
2544
|
+
if (primary === void 0) {
|
|
2545
|
+
return null;
|
|
2546
|
+
}
|
|
2547
|
+
const argText = argumentText?.trim() ?? "";
|
|
2548
|
+
const renderExample = (path5) => argText === "" ? `@${tagName} :${path5}` : `@${tagName} :${path5} ${argText}`;
|
|
2549
|
+
if (candidates.length === 1) {
|
|
2550
|
+
return `Hint: use a path target to constrain a subfield, e.g. ${renderExample(primary)}`;
|
|
2551
|
+
}
|
|
2552
|
+
const shown = candidates.slice(0, MAX_HINT_CANDIDATES);
|
|
2553
|
+
const overflow = candidates.length > MAX_HINT_CANDIDATES ? ", \u2026" : "";
|
|
2554
|
+
return `Hint: use a path target to constrain a subfield (candidates: ${shown.join(", ")}${overflow}), e.g. ${renderExample(primary)}`;
|
|
2555
|
+
}
|
|
2472
2556
|
function makeDiagnostic(code, message, provenance) {
|
|
2473
2557
|
return {
|
|
2474
2558
|
code,
|
|
@@ -2636,10 +2720,18 @@ function buildCompilerBackedConstraintDiagnostics(node, sourceFile, tagName, par
|
|
|
2636
2720
|
const requiredCapability = definition.capabilities[0];
|
|
2637
2721
|
if (requiredCapability !== void 0 && !supportsConstraintCapability(subjectType, checker, requiredCapability)) {
|
|
2638
2722
|
const actualType = checker.typeToString(subjectType, node, SYNTHETIC_TYPE_FORMAT_FLAGS);
|
|
2723
|
+
const baseMessage = `Target "${node.getText(sourceFile)}": constraint "${tagName}" is only valid on ${capabilityLabel(requiredCapability)} targets, but field type is "${actualType}"`;
|
|
2724
|
+
const hint = buildPathTargetHint(
|
|
2725
|
+
subjectType,
|
|
2726
|
+
checker,
|
|
2727
|
+
requiredCapability,
|
|
2728
|
+
tagName,
|
|
2729
|
+
parsedTag?.argumentText
|
|
2730
|
+
);
|
|
2639
2731
|
return [
|
|
2640
2732
|
makeDiagnostic(
|
|
2641
2733
|
"TYPE_MISMATCH",
|
|
2642
|
-
|
|
2734
|
+
hint === null ? baseMessage : `${baseMessage}. ${hint}`,
|
|
2643
2735
|
provenance
|
|
2644
2736
|
)
|
|
2645
2737
|
];
|
|
@@ -3015,11 +3107,13 @@ function getTagCommentText(tag) {
|
|
|
3015
3107
|
}
|
|
3016
3108
|
return ts.getTextOfJSDocComment(tag.comment);
|
|
3017
3109
|
}
|
|
3018
|
-
var SYNTHETIC_TYPE_FORMAT_FLAGS, parseResultCache;
|
|
3110
|
+
var SYNTHETIC_TYPE_FORMAT_FLAGS, MAX_HINT_CANDIDATES, MAX_HINT_DEPTH, parseResultCache;
|
|
3019
3111
|
var init_tsdoc_parser = __esm({
|
|
3020
3112
|
"src/analyzer/tsdoc-parser.ts"() {
|
|
3021
3113
|
"use strict";
|
|
3022
3114
|
SYNTHETIC_TYPE_FORMAT_FLAGS = ts.TypeFormatFlags.NoTruncation | ts.TypeFormatFlags.UseAliasDefinedOutsideCurrentScope;
|
|
3115
|
+
MAX_HINT_CANDIDATES = 5;
|
|
3116
|
+
MAX_HINT_DEPTH = 3;
|
|
3023
3117
|
parseResultCache = /* @__PURE__ */ new Map();
|
|
3024
3118
|
}
|
|
3025
3119
|
});
|
|
@@ -4303,6 +4397,9 @@ function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode
|
|
|
4303
4397
|
if (type.flags & ts3.TypeFlags.Undefined) {
|
|
4304
4398
|
return { kind: "primitive", primitiveKind: "null" };
|
|
4305
4399
|
}
|
|
4400
|
+
if (type.flags & ts3.TypeFlags.Void) {
|
|
4401
|
+
return { kind: "primitive", primitiveKind: "null" };
|
|
4402
|
+
}
|
|
4306
4403
|
if (type.isStringLiteral()) {
|
|
4307
4404
|
return {
|
|
4308
4405
|
kind: "enum",
|
|
@@ -6296,7 +6393,13 @@ function toStandaloneJsonSchema(root, typeRegistry, resolved) {
|
|
|
6296
6393
|
const syntheticField = {
|
|
6297
6394
|
kind: "field",
|
|
6298
6395
|
name: "__result",
|
|
6299
|
-
|
|
6396
|
+
metadata: {
|
|
6397
|
+
...syntheticFieldMetadata,
|
|
6398
|
+
// Pin apiName so metadata-policy transforms (e.g. toStripeApiCase)
|
|
6399
|
+
// cannot rename this synthetic wrapper field. The lookup below
|
|
6400
|
+
// expects "__result" in the output schema properties.
|
|
6401
|
+
apiName: { value: "__result", source: "explicit" }
|
|
6402
|
+
},
|
|
6300
6403
|
type: root.type,
|
|
6301
6404
|
required: true,
|
|
6302
6405
|
constraints: [],
|
|
@@ -6539,7 +6642,20 @@ function unwrapPromiseType(checker, type) {
|
|
|
6539
6642
|
if (!("getAwaitedType" in checker) || typeof checker.getAwaitedType !== "function") {
|
|
6540
6643
|
return type;
|
|
6541
6644
|
}
|
|
6542
|
-
|
|
6645
|
+
const awaited = checker.getAwaitedType(type) ?? type;
|
|
6646
|
+
if (awaited === type && looksLikePromiseType(checker, type)) {
|
|
6647
|
+
throw new Error(
|
|
6648
|
+
`FormSpec could not unwrap the awaited type from "${checker.typeToString(type)}". This usually indicates the TypeScript compiler host cannot resolve its default lib files (for example "lib.es2015.promise.d.ts"). Ensure the program is configured with the standard library available to \`ts.createProgram\`.`
|
|
6649
|
+
);
|
|
6650
|
+
}
|
|
6651
|
+
return awaited;
|
|
6652
|
+
}
|
|
6653
|
+
function looksLikePromiseType(checker, type) {
|
|
6654
|
+
const symbolName = type.getSymbol()?.getName();
|
|
6655
|
+
if (symbolName === "Promise" || symbolName === "PromiseLike") {
|
|
6656
|
+
return true;
|
|
6657
|
+
}
|
|
6658
|
+
return /^(?:Promise|PromiseLike)\b/.test(checker.typeToString(type));
|
|
6543
6659
|
}
|
|
6544
6660
|
function unwrapPromiseTypeNode(typeNode) {
|
|
6545
6661
|
if (typeNode === void 0) {
|