@webpieces/api-doc-model 0.0.1
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/README.md +21 -0
- package/package.json +28 -0
- package/src/extract/ApiDocExtractionError.d.ts +30 -0
- package/src/extract/ApiDocExtractionError.js +39 -0
- package/src/extract/ApiDocExtractionError.js.map +1 -0
- package/src/extract/ApiDocExtractor.d.ts +59 -0
- package/src/extract/ApiDocExtractor.js +220 -0
- package/src/extract/ApiDocExtractor.js.map +1 -0
- package/src/extract/ConstantFolder.d.ts +30 -0
- package/src/extract/ConstantFolder.js +103 -0
- package/src/extract/ConstantFolder.js.map +1 -0
- package/src/extract/JsDoc.d.ts +33 -0
- package/src/extract/JsDoc.js +88 -0
- package/src/extract/JsDoc.js.map +1 -0
- package/src/extract/SourceLocation.d.ts +11 -0
- package/src/extract/SourceLocation.js +23 -0
- package/src/extract/SourceLocation.js.map +1 -0
- package/src/extract/TypeResolver.d.ts +111 -0
- package/src/extract/TypeResolver.js +445 -0
- package/src/extract/TypeResolver.js.map +1 -0
- package/src/index.d.ts +14 -0
- package/src/index.js +29 -0
- package/src/index.js.map +1 -0
- package/src/model/ApiDocModel.d.ts +202 -0
- package/src/model/ApiDocModel.js +231 -0
- package/src/model/ApiDocModel.js.map +1 -0
- package/src/model/TypeRef.d.ts +48 -0
- package/src/model/TypeRef.js +83 -0
- package/src/model/TypeRef.js.map +1 -0
|
@@ -0,0 +1,445 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.TypeResolver = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const ts = tslib_1.__importStar(require("typescript"));
|
|
6
|
+
const ApiDocModel_1 = require("../model/ApiDocModel");
|
|
7
|
+
const TypeRef_1 = require("../model/TypeRef");
|
|
8
|
+
const ApiDocExtractionError_1 = require("./ApiDocExtractionError");
|
|
9
|
+
const JsDoc_1 = require("./JsDoc");
|
|
10
|
+
const SourceLocation_1 = require("./SourceLocation");
|
|
11
|
+
/** The decorators this resolver reads off a DTO property, by NAME — it imports none of them. */
|
|
12
|
+
const INT_DECORATOR = 'WpInt';
|
|
13
|
+
const MIN_DECORATOR = 'WpMin';
|
|
14
|
+
const MAX_DECORATOR = 'WpMax';
|
|
15
|
+
/** The type-alias name that DECLARES integer-ness. See the class doc and `responsibilities.md`. */
|
|
16
|
+
const INTEGER_ALIAS = 'Integer';
|
|
17
|
+
/**
|
|
18
|
+
* Resolve declared TypeScript types into {@link TypeRef}s, registering every NAMED type it meets as
|
|
19
|
+
* a {@link DocumentedType} a renderer can `$ref`.
|
|
20
|
+
*
|
|
21
|
+
* ## It walks TYPE NODES, not checker types, and that is load-bearing
|
|
22
|
+
*
|
|
23
|
+
* `type Integer = number` resolves, in the checker, to `number` — the alias is gone. Integer-ness is
|
|
24
|
+
* therefore invisible to a checker-driven walk, and the whole reason `Integer` is the PREFERRED
|
|
25
|
+
* spelling is that it COMPOSES: `Integer[]` and `Record<string, Integer>` say exactly which thing is
|
|
26
|
+
* an integer, where a decorator on `counts?: number[]` cannot (the same ambiguity the deleted
|
|
27
|
+
* `arrayItems` argument had). Walking the written syntax is what keeps that composition readable.
|
|
28
|
+
*
|
|
29
|
+
* ## Cycles terminate BY CONSTRUCTION
|
|
30
|
+
*
|
|
31
|
+
* Every named type is ONE entry in the model, registered before its fields are walked, so a type
|
|
32
|
+
* that refers back to itself resolves to a `$ref` at the second visit and stops. There is NO depth
|
|
33
|
+
* counter anywhere — a deep-but-finite graph of 7, 20 or 200 named hops is fully expanded, because
|
|
34
|
+
* truncating one would silently publish an incomplete document. The ONLY thing that is cut is a
|
|
35
|
+
* self-referential ANONYMOUS type, and it is cut by NODE IDENTITY (it has no name to `$ref`), with an
|
|
36
|
+
* {@link UnmappedType} recorded so #982's guard has something to name.
|
|
37
|
+
*/
|
|
38
|
+
class TypeResolver {
|
|
39
|
+
checker;
|
|
40
|
+
types = new Map();
|
|
41
|
+
unmapped = [];
|
|
42
|
+
/** Named types already registered (or mid-registration) — the cycle stop for the NAMED case. */
|
|
43
|
+
registered = new Set();
|
|
44
|
+
/** Anonymous type literals currently being expanded — the cycle stop for the ANONYMOUS case. */
|
|
45
|
+
expandingAnonymous = new Set();
|
|
46
|
+
constructor(checker) {
|
|
47
|
+
this.checker = checker;
|
|
48
|
+
}
|
|
49
|
+
/** Every named type reached so far, by name. */
|
|
50
|
+
collectedTypes() {
|
|
51
|
+
return this.types;
|
|
52
|
+
}
|
|
53
|
+
/** Everything that could not be represented — recorded, never dropped. */
|
|
54
|
+
collectedUnmapped() {
|
|
55
|
+
return this.unmapped;
|
|
56
|
+
}
|
|
57
|
+
/** Resolve one written type, registering whatever named types it reaches. */
|
|
58
|
+
resolve(node, ownerName) {
|
|
59
|
+
if (ts.isParenthesizedTypeNode(node)) {
|
|
60
|
+
return this.resolve(node.type, ownerName);
|
|
61
|
+
}
|
|
62
|
+
if (ts.isArrayTypeNode(node)) {
|
|
63
|
+
return TypeRef_1.TypeRef.array(this.resolve(node.elementType, ownerName));
|
|
64
|
+
}
|
|
65
|
+
if (ts.isUnionTypeNode(node)) {
|
|
66
|
+
return this.resolveUnion(node, ownerName);
|
|
67
|
+
}
|
|
68
|
+
if (ts.isTypeLiteralNode(node)) {
|
|
69
|
+
return this.resolveAnonymousObject(node, ownerName);
|
|
70
|
+
}
|
|
71
|
+
if (ts.isLiteralTypeNode(node)) {
|
|
72
|
+
return this.resolveLiteral(node);
|
|
73
|
+
}
|
|
74
|
+
if (ts.isTypeReferenceNode(node)) {
|
|
75
|
+
return this.resolveReference(node, ownerName);
|
|
76
|
+
}
|
|
77
|
+
const primitive = TypeResolver.keywordOf(node.kind);
|
|
78
|
+
if (primitive !== undefined) {
|
|
79
|
+
return TypeRef_1.TypeRef.primitiveOf(primitive);
|
|
80
|
+
}
|
|
81
|
+
return this.recordUnmapped(node, 'no model representation for this type form');
|
|
82
|
+
}
|
|
83
|
+
/** `string` / `number` / `boolean` / `null` / `unknown`, or undefined for anything else. */
|
|
84
|
+
// webpieces-disable no-function-outside-class -- private static lookup table of this class
|
|
85
|
+
static keywordOf(kind) {
|
|
86
|
+
switch (kind) {
|
|
87
|
+
case ts.SyntaxKind.StringKeyword:
|
|
88
|
+
return 'string';
|
|
89
|
+
case ts.SyntaxKind.NumberKeyword:
|
|
90
|
+
return 'number';
|
|
91
|
+
case ts.SyntaxKind.BooleanKeyword:
|
|
92
|
+
return 'boolean';
|
|
93
|
+
case ts.SyntaxKind.NullKeyword:
|
|
94
|
+
return 'null';
|
|
95
|
+
case ts.SyntaxKind.UnknownKeyword:
|
|
96
|
+
case ts.SyntaxKind.AnyKeyword:
|
|
97
|
+
case ts.SyntaxKind.VoidKeyword:
|
|
98
|
+
return 'unknown';
|
|
99
|
+
default:
|
|
100
|
+
return undefined;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
resolveLiteral(node) {
|
|
104
|
+
if (ts.isStringLiteral(node.literal)) {
|
|
105
|
+
return TypeRef_1.TypeRef.enumOf([node.literal.text]);
|
|
106
|
+
}
|
|
107
|
+
if (node.literal.kind === ts.SyntaxKind.NullKeyword) {
|
|
108
|
+
return TypeRef_1.TypeRef.primitiveOf('null');
|
|
109
|
+
}
|
|
110
|
+
if (node.literal.kind === ts.SyntaxKind.TrueKeyword ||
|
|
111
|
+
node.literal.kind === ts.SyntaxKind.FalseKeyword) {
|
|
112
|
+
return TypeRef_1.TypeRef.primitiveOf('boolean');
|
|
113
|
+
}
|
|
114
|
+
if (ts.isNumericLiteral(node.literal)) {
|
|
115
|
+
return TypeRef_1.TypeRef.primitiveOf('number');
|
|
116
|
+
}
|
|
117
|
+
return this.recordUnmapped(node, 'literal type is neither a string, a number nor a boolean');
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* A union, after `null` / `undefined` have been dropped (the FIELD records those as nullable /
|
|
121
|
+
* optional — see {@link fieldOf} — because `{}` and `{x: null}` are different wire documents).
|
|
122
|
+
*
|
|
123
|
+
* Three outcomes, in this order:
|
|
124
|
+
* - every branch a string literal -> an ENUM
|
|
125
|
+
* - one branch left -> that branch
|
|
126
|
+
* - every branch a named object -> a UNION, with a DERIVED discriminator when every branch
|
|
127
|
+
* carries the same property typed as ONE string literal
|
|
128
|
+
* - anything else -> an {@link UnmappedType}. No invented discriminator, ever:
|
|
129
|
+
* a union TypeScript itself cannot narrow is not one a
|
|
130
|
+
* renderer may claim to.
|
|
131
|
+
*/
|
|
132
|
+
resolveUnion(node, ownerName) {
|
|
133
|
+
const branches = node.types.filter((t) => !TypeResolver.isNullish(t));
|
|
134
|
+
const literals = branches.filter((t) => ts.isLiteralTypeNode(t) && ts.isStringLiteral(t.literal));
|
|
135
|
+
if (literals.length === branches.length && branches.length > 0) {
|
|
136
|
+
return TypeRef_1.TypeRef.enumOf(literals.map((t) => t.literal.text));
|
|
137
|
+
}
|
|
138
|
+
if (branches.length === 1) {
|
|
139
|
+
return this.resolve(branches[0], ownerName);
|
|
140
|
+
}
|
|
141
|
+
if (branches.length === 0) {
|
|
142
|
+
return TypeRef_1.TypeRef.primitiveOf('null');
|
|
143
|
+
}
|
|
144
|
+
const refs = branches.map((t) => this.resolve(t, ownerName));
|
|
145
|
+
if (!refs.every((r) => r.kind === 'ref')) {
|
|
146
|
+
return this.recordUnmapped(node, 'a union whose branches are not all NAMED object types has no discriminator a renderer could narrow on');
|
|
147
|
+
}
|
|
148
|
+
const names = refs.map((r) => r.refName);
|
|
149
|
+
const discriminator = this.deriveDiscriminator(names);
|
|
150
|
+
if (discriminator === undefined) {
|
|
151
|
+
return this.recordUnmapped(node, 'no property is typed as a single string literal on EVERY branch, so this union has no derivable discriminator');
|
|
152
|
+
}
|
|
153
|
+
this.registerUnionAlias(node, names, discriminator);
|
|
154
|
+
return TypeRef_1.TypeRef.union(names);
|
|
155
|
+
}
|
|
156
|
+
/** `null` and `undefined` branches — recorded as nullable/optional on the FIELD, not in the union. */
|
|
157
|
+
// webpieces-disable no-function-outside-class -- private static predicate of this class
|
|
158
|
+
static isNullish(node) {
|
|
159
|
+
if (node.kind === ts.SyntaxKind.UndefinedKeyword ||
|
|
160
|
+
node.kind === ts.SyntaxKind.NullKeyword) {
|
|
161
|
+
return true;
|
|
162
|
+
}
|
|
163
|
+
return ts.isLiteralTypeNode(node) && node.literal.kind === ts.SyntaxKind.NullKeyword;
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* The DERIVED discriminator: the property every branch declares as exactly ONE string literal,
|
|
167
|
+
* with a value no two branches share. Derived, never invented — if the source does not narrow,
|
|
168
|
+
* neither does the document.
|
|
169
|
+
*/
|
|
170
|
+
deriveDiscriminator(branchNames) {
|
|
171
|
+
const branches = branchNames.map((name) => this.types.get(name));
|
|
172
|
+
if (branches.some((b) => b === undefined)) {
|
|
173
|
+
return undefined;
|
|
174
|
+
}
|
|
175
|
+
const first = branches[0];
|
|
176
|
+
for (const field of first.fields) {
|
|
177
|
+
const values = new Map();
|
|
178
|
+
for (let i = 0; i < branches.length; i++) {
|
|
179
|
+
const candidate = branches[i].fields.find((f) => f.name === field.name);
|
|
180
|
+
const literal = candidate !== undefined &&
|
|
181
|
+
candidate.type.kind === 'enum' &&
|
|
182
|
+
candidate.type.enumValues.length === 1
|
|
183
|
+
? candidate.type.enumValues[0]
|
|
184
|
+
: undefined;
|
|
185
|
+
if (literal === undefined) {
|
|
186
|
+
values.clear();
|
|
187
|
+
break;
|
|
188
|
+
}
|
|
189
|
+
values.set(branchNames[i], literal);
|
|
190
|
+
}
|
|
191
|
+
if (values.size === branches.length && new Set(values.values()).size === values.size) {
|
|
192
|
+
return new ApiDocModel_1.UnionDiscriminator(field.name, values);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
return undefined;
|
|
196
|
+
}
|
|
197
|
+
/** A union written as a named `type X = A | B` becomes its own model entry, so #982 can `$ref` it. */
|
|
198
|
+
registerUnionAlias(node, branchNames, discriminator) {
|
|
199
|
+
const alias = node.parent;
|
|
200
|
+
if (!ts.isTypeAliasDeclaration(alias)) {
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
const name = alias.name.text;
|
|
204
|
+
if (this.registered.has(name)) {
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
this.registered.add(name);
|
|
208
|
+
this.types.set(name, new ApiDocModel_1.DocumentedType(name, JsDoc_1.JsDoc.read(alias).description, [], [], branchNames, discriminator, undefined));
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* `Integer`, `Array<T>`, `Record<string, V>`, `Promise<T>`, and everything named — an interface,
|
|
212
|
+
* a class, or a type alias. Anything else (a `Map`, a `Date`, a generic parameter) is recorded
|
|
213
|
+
* rather than guessed at.
|
|
214
|
+
*/
|
|
215
|
+
resolveReference(node, ownerName) {
|
|
216
|
+
const name = ts.isIdentifier(node.typeName) ? node.typeName.text : node.typeName.right.text;
|
|
217
|
+
const args = node.typeArguments ?? [];
|
|
218
|
+
if (name === INTEGER_ALIAS) {
|
|
219
|
+
return TypeRef_1.TypeRef.primitiveOf('number', /*integer*/ true);
|
|
220
|
+
}
|
|
221
|
+
if ((name === 'Array' || name === 'ReadonlyArray') && args.length === 1) {
|
|
222
|
+
return TypeRef_1.TypeRef.array(this.resolve(args[0], ownerName));
|
|
223
|
+
}
|
|
224
|
+
if ((name === 'Record' || name === 'Partial') && args.length === 2) {
|
|
225
|
+
return TypeRef_1.TypeRef.openMap(this.resolve(args[1], ownerName));
|
|
226
|
+
}
|
|
227
|
+
if (name === 'Promise' && args.length === 1) {
|
|
228
|
+
return this.resolve(args[0], ownerName);
|
|
229
|
+
}
|
|
230
|
+
const declaration = this.declarationOf(node.typeName);
|
|
231
|
+
if (declaration === undefined) {
|
|
232
|
+
return this.recordUnmapped(node, `no declaration found for '${name}'`);
|
|
233
|
+
}
|
|
234
|
+
if (ts.isInterfaceDeclaration(declaration) || ts.isClassDeclaration(declaration)) {
|
|
235
|
+
return this.registerNamedObject(name, declaration);
|
|
236
|
+
}
|
|
237
|
+
if (ts.isTypeAliasDeclaration(declaration)) {
|
|
238
|
+
return this.resolveAlias(name, declaration, ownerName);
|
|
239
|
+
}
|
|
240
|
+
if (ts.isEnumDeclaration(declaration)) {
|
|
241
|
+
return this.registerStringEnum(name, declaration);
|
|
242
|
+
}
|
|
243
|
+
return this.recordUnmapped(node, `'${name}' is declared as something with no document shape`);
|
|
244
|
+
}
|
|
245
|
+
/** `type X = ...` — registered under X when it has a shape of its own, else transparent. */
|
|
246
|
+
resolveAlias(name, alias, ownerName) {
|
|
247
|
+
if (this.registered.has(name)) {
|
|
248
|
+
return this.types.get(name)?.unionRefNames.length
|
|
249
|
+
? TypeRef_1.TypeRef.union(this.types.get(name).unionRefNames)
|
|
250
|
+
: TypeRef_1.TypeRef.ref(name);
|
|
251
|
+
}
|
|
252
|
+
if (ts.isUnionTypeNode(alias.type)) {
|
|
253
|
+
const resolved = this.resolve(alias.type, ownerName);
|
|
254
|
+
if (resolved.kind === 'enum') {
|
|
255
|
+
this.registered.add(name);
|
|
256
|
+
this.types.set(name, new ApiDocModel_1.DocumentedType(name, JsDoc_1.JsDoc.read(alias).description, [], resolved.enumValues, [], undefined, undefined));
|
|
257
|
+
return TypeRef_1.TypeRef.ref(name);
|
|
258
|
+
}
|
|
259
|
+
return resolved;
|
|
260
|
+
}
|
|
261
|
+
if (ts.isTypeLiteralNode(alias.type)) {
|
|
262
|
+
return this.registerNamedObject(name, alias.type, alias);
|
|
263
|
+
}
|
|
264
|
+
return this.resolve(alias.type, ownerName);
|
|
265
|
+
}
|
|
266
|
+
/** A TS `enum` of string members — the one non-union enum shape a document can carry. */
|
|
267
|
+
registerStringEnum(name, declaration) {
|
|
268
|
+
if (this.registered.has(name)) {
|
|
269
|
+
return TypeRef_1.TypeRef.ref(name);
|
|
270
|
+
}
|
|
271
|
+
const values = [];
|
|
272
|
+
for (const member of declaration.members) {
|
|
273
|
+
if (member.initializer && ts.isStringLiteral(member.initializer)) {
|
|
274
|
+
values.push(member.initializer.text);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
if (values.length !== declaration.members.length) {
|
|
278
|
+
return this.recordUnmapped(declaration, `enum '${name}' has members that are not string literals`);
|
|
279
|
+
}
|
|
280
|
+
this.registered.add(name);
|
|
281
|
+
this.types.set(name, new ApiDocModel_1.DocumentedType(name, JsDoc_1.JsDoc.read(declaration).description, [], values, [], undefined, undefined));
|
|
282
|
+
return TypeRef_1.TypeRef.ref(name);
|
|
283
|
+
}
|
|
284
|
+
/**
|
|
285
|
+
* Register a named object shape, RESERVING THE NAME BEFORE walking its fields. That order is the
|
|
286
|
+
* whole cycle story: a type that refers back to itself meets `registered.has(name)` on the second
|
|
287
|
+
* visit and resolves to a `$ref`, so the walk terminates with no counter and no truncation.
|
|
288
|
+
*/
|
|
289
|
+
registerNamedObject(name, shape, docSource) {
|
|
290
|
+
if (this.registered.has(name)) {
|
|
291
|
+
return TypeRef_1.TypeRef.ref(name);
|
|
292
|
+
}
|
|
293
|
+
this.registered.add(name);
|
|
294
|
+
const fields = [];
|
|
295
|
+
let indexSignature;
|
|
296
|
+
for (const member of shape.members) {
|
|
297
|
+
if (ts.isIndexSignatureDeclaration(member)) {
|
|
298
|
+
indexSignature = this.resolve(member.type, name);
|
|
299
|
+
continue;
|
|
300
|
+
}
|
|
301
|
+
const field = this.fieldOf(member, name);
|
|
302
|
+
if (field !== undefined) {
|
|
303
|
+
fields.push(field);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
this.types.set(name, new ApiDocModel_1.DocumentedType(name, JsDoc_1.JsDoc.read(docSource ?? shape).description, fields, [], [], undefined, indexSignature));
|
|
307
|
+
return TypeRef_1.TypeRef.ref(name);
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* An inline `{ ... }`. It has no name to `$ref`, so it is registered under an OWNER-DERIVED one
|
|
311
|
+
* (`Parent.field`) — a renderer needs a target, and a name derived from where the shape is
|
|
312
|
+
* written is the only honest one available.
|
|
313
|
+
*
|
|
314
|
+
* A self-referential anonymous type is cut HERE, by NODE IDENTITY. It cannot be a `$ref` (there
|
|
315
|
+
* is no declared name to point at) and it cannot be expanded (it would never end), so it is
|
|
316
|
+
* recorded as unmapped and the walk returns.
|
|
317
|
+
*/
|
|
318
|
+
resolveAnonymousObject(node, ownerName) {
|
|
319
|
+
if (this.expandingAnonymous.has(node)) {
|
|
320
|
+
return this.recordUnmapped(node, 'a self-referential ANONYMOUS object type has no name a renderer could $ref; give it a name');
|
|
321
|
+
}
|
|
322
|
+
const indexOnly = node.members.length === 1 && ts.isIndexSignatureDeclaration(node.members[0]);
|
|
323
|
+
if (indexOnly) {
|
|
324
|
+
const signature = node.members[0];
|
|
325
|
+
return TypeRef_1.TypeRef.openMap(this.resolve(signature.type, ownerName));
|
|
326
|
+
}
|
|
327
|
+
this.expandingAnonymous.add(node);
|
|
328
|
+
// eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- the stack entry must be dropped whatever the walk does
|
|
329
|
+
try {
|
|
330
|
+
return this.registerNamedObject(ownerName, node);
|
|
331
|
+
}
|
|
332
|
+
finally {
|
|
333
|
+
this.expandingAnonymous.delete(node);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
/** ONE property of a DTO — its type, its optionality, its prose and its numeric constraints. */
|
|
337
|
+
fieldOf(member, ownerName) {
|
|
338
|
+
if (!ts.isPropertySignature(member) && !ts.isPropertyDeclaration(member)) {
|
|
339
|
+
return undefined;
|
|
340
|
+
}
|
|
341
|
+
if (!ts.isIdentifier(member.name) && !ts.isStringLiteral(member.name)) {
|
|
342
|
+
return undefined;
|
|
343
|
+
}
|
|
344
|
+
const name = member.name.text;
|
|
345
|
+
if (member.type === undefined) {
|
|
346
|
+
return undefined;
|
|
347
|
+
}
|
|
348
|
+
const optional = member.questionToken !== undefined || TypeResolver.declaresUndefined(member.type);
|
|
349
|
+
const nullable = TypeResolver.declaresNull(member.type);
|
|
350
|
+
let type = this.resolve(member.type, `${ownerName}.${name}`);
|
|
351
|
+
if (TypeResolver.hasDecorator(member, INT_DECORATOR)) {
|
|
352
|
+
type = TypeResolver.markInteger(type);
|
|
353
|
+
}
|
|
354
|
+
const doc = JsDoc_1.JsDoc.read(member);
|
|
355
|
+
const min = this.numericArgument(member, MIN_DECORATOR);
|
|
356
|
+
const max = this.numericArgument(member, MAX_DECORATOR);
|
|
357
|
+
this.assertNumericConstraintsFit(member, name, type, min, max);
|
|
358
|
+
return new ApiDocModel_1.DocumentedField(name, type, optional, nullable, doc.description, doc.mcp, doc.format, min, max);
|
|
359
|
+
}
|
|
360
|
+
/**
|
|
361
|
+
* `@WpMin` / `@WpMax` on a non-numeric field is a BUILD FAILURE, not a warning. A minimum on a
|
|
362
|
+
* string is not something a renderer can emit sensibly, and a document that silently dropped it
|
|
363
|
+
* would publish a contract weaker than the one its author wrote down.
|
|
364
|
+
*/
|
|
365
|
+
assertNumericConstraintsFit(member, name, type, min, max) {
|
|
366
|
+
if (min === undefined && max === undefined) {
|
|
367
|
+
return;
|
|
368
|
+
}
|
|
369
|
+
const numeric = type.isNumeric() || (type.kind === 'array' && type.items.isNumeric());
|
|
370
|
+
if (numeric) {
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
throw new ApiDocExtractionError_1.ApiDocExtractionError(`@${MIN_DECORATOR}/@${MAX_DECORATOR} on non-numeric field '${name}'`, SourceLocation_1.SourceLocation.of(member), 'Put the constraint on a `number` / `Integer` field, or drop it.');
|
|
374
|
+
}
|
|
375
|
+
/** `@WpInt()` decorates the FIELD, so the integer flag is pushed onto the right leaf. */
|
|
376
|
+
// webpieces-disable no-function-outside-class -- private static helper of this class
|
|
377
|
+
static markInteger(type) {
|
|
378
|
+
if (type.kind === 'array') {
|
|
379
|
+
return TypeRef_1.TypeRef.array(type.items.asInteger());
|
|
380
|
+
}
|
|
381
|
+
if (type.kind === 'openMap') {
|
|
382
|
+
return TypeRef_1.TypeRef.openMap(type.values.asInteger());
|
|
383
|
+
}
|
|
384
|
+
return type.asInteger();
|
|
385
|
+
}
|
|
386
|
+
// webpieces-disable no-function-outside-class -- private static helper of this class
|
|
387
|
+
static declaresUndefined(node) {
|
|
388
|
+
return (ts.isUnionTypeNode(node) &&
|
|
389
|
+
node.types.some((t) => t.kind === ts.SyntaxKind.UndefinedKeyword));
|
|
390
|
+
}
|
|
391
|
+
// webpieces-disable no-function-outside-class -- private static helper of this class
|
|
392
|
+
static declaresNull(node) {
|
|
393
|
+
return (ts.isUnionTypeNode(node) &&
|
|
394
|
+
node.types.some((t) => t.kind === ts.SyntaxKind.NullKeyword ||
|
|
395
|
+
(ts.isLiteralTypeNode(t) && t.literal.kind === ts.SyntaxKind.NullKeyword)));
|
|
396
|
+
}
|
|
397
|
+
// webpieces-disable no-function-outside-class -- private static helper of this class
|
|
398
|
+
static hasDecorator(node, decoratorName) {
|
|
399
|
+
return TypeResolver.decoratorCall(node, decoratorName) !== undefined;
|
|
400
|
+
}
|
|
401
|
+
// webpieces-disable no-function-outside-class -- private static helper of this class
|
|
402
|
+
static decoratorCall(node, decoratorName) {
|
|
403
|
+
const decorators = ts.canHaveDecorators(node) ? (ts.getDecorators(node) ?? []) : [];
|
|
404
|
+
for (const decorator of decorators) {
|
|
405
|
+
const call = decorator.expression;
|
|
406
|
+
if (ts.isCallExpression(call) &&
|
|
407
|
+
ts.isIdentifier(call.expression) &&
|
|
408
|
+
call.expression.text === decoratorName) {
|
|
409
|
+
return call;
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
return undefined;
|
|
413
|
+
}
|
|
414
|
+
numericArgument(node, decoratorName) {
|
|
415
|
+
const call = TypeResolver.decoratorCall(node, decoratorName);
|
|
416
|
+
const argument = call?.arguments[0];
|
|
417
|
+
if (argument === undefined) {
|
|
418
|
+
return undefined;
|
|
419
|
+
}
|
|
420
|
+
if (ts.isNumericLiteral(argument)) {
|
|
421
|
+
return Number(argument.text);
|
|
422
|
+
}
|
|
423
|
+
if (ts.isPrefixUnaryExpression(argument) &&
|
|
424
|
+
argument.operator === ts.SyntaxKind.MinusToken &&
|
|
425
|
+
ts.isNumericLiteral(argument.operand)) {
|
|
426
|
+
return -Number(argument.operand.text);
|
|
427
|
+
}
|
|
428
|
+
throw new ApiDocExtractionError_1.ApiDocExtractionError(`@${decoratorName} argument is not a numeric literal: '${argument.getText()}'`, SourceLocation_1.SourceLocation.of(argument), 'Write the bound as a numeric literal. A value only known at runtime cannot be published.');
|
|
429
|
+
}
|
|
430
|
+
/** The declaration a type name points at, through imports and aliases. */
|
|
431
|
+
declarationOf(name) {
|
|
432
|
+
const symbol = this.checker.getSymbolAtLocation(name);
|
|
433
|
+
const resolved = symbol !== undefined && (symbol.flags & ts.SymbolFlags.Alias) !== 0
|
|
434
|
+
? this.checker.getAliasedSymbol(symbol)
|
|
435
|
+
: symbol;
|
|
436
|
+
return resolved?.declarations?.[0];
|
|
437
|
+
}
|
|
438
|
+
recordUnmapped(node, reason) {
|
|
439
|
+
const text = node.getText();
|
|
440
|
+
this.unmapped.push(new ApiDocModel_1.UnmappedType(text, SourceLocation_1.SourceLocation.of(node), reason));
|
|
441
|
+
return TypeRef_1.TypeRef.unmapped(text);
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
exports.TypeResolver = TypeResolver;
|
|
445
|
+
//# sourceMappingURL=TypeResolver.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TypeResolver.js","sourceRoot":"","sources":["../../../../../../packages/docs/api-doc-model/src/extract/TypeResolver.ts"],"names":[],"mappings":"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","sourcesContent":["import * as ts from 'typescript';\nimport {\n DocumentedField,\n DocumentedType,\n UnionDiscriminator,\n UnmappedType,\n} from '../model/ApiDocModel';\nimport { PrimitiveKind, TypeRef } from '../model/TypeRef';\nimport { ApiDocExtractionError } from './ApiDocExtractionError';\nimport { JsDoc } from './JsDoc';\nimport { SourceLocation } from './SourceLocation';\n\n/** The decorators this resolver reads off a DTO property, by NAME — it imports none of them. */\nconst INT_DECORATOR = 'WpInt';\nconst MIN_DECORATOR = 'WpMin';\nconst MAX_DECORATOR = 'WpMax';\n\n/** The type-alias name that DECLARES integer-ness. See the class doc and `responsibilities.md`. */\nconst INTEGER_ALIAS = 'Integer';\n\n/**\n * Resolve declared TypeScript types into {@link TypeRef}s, registering every NAMED type it meets as\n * a {@link DocumentedType} a renderer can `$ref`.\n *\n * ## It walks TYPE NODES, not checker types, and that is load-bearing\n *\n * `type Integer = number` resolves, in the checker, to `number` — the alias is gone. Integer-ness is\n * therefore invisible to a checker-driven walk, and the whole reason `Integer` is the PREFERRED\n * spelling is that it COMPOSES: `Integer[]` and `Record<string, Integer>` say exactly which thing is\n * an integer, where a decorator on `counts?: number[]` cannot (the same ambiguity the deleted\n * `arrayItems` argument had). Walking the written syntax is what keeps that composition readable.\n *\n * ## Cycles terminate BY CONSTRUCTION\n *\n * Every named type is ONE entry in the model, registered before its fields are walked, so a type\n * that refers back to itself resolves to a `$ref` at the second visit and stops. There is NO depth\n * counter anywhere — a deep-but-finite graph of 7, 20 or 200 named hops is fully expanded, because\n * truncating one would silently publish an incomplete document. The ONLY thing that is cut is a\n * self-referential ANONYMOUS type, and it is cut by NODE IDENTITY (it has no name to `$ref`), with an\n * {@link UnmappedType} recorded so #982's guard has something to name.\n */\nexport class TypeResolver {\n private readonly types = new Map<string, DocumentedType>();\n private readonly unmapped: UnmappedType[] = [];\n /** Named types already registered (or mid-registration) — the cycle stop for the NAMED case. */\n private readonly registered = new Set<string>();\n /** Anonymous type literals currently being expanded — the cycle stop for the ANONYMOUS case. */\n private readonly expandingAnonymous = new Set<ts.TypeNode>();\n\n constructor(private readonly checker: ts.TypeChecker) {}\n\n /** Every named type reached so far, by name. */\n collectedTypes(): ReadonlyMap<string, DocumentedType> {\n return this.types;\n }\n\n /** Everything that could not be represented — recorded, never dropped. */\n collectedUnmapped(): readonly UnmappedType[] {\n return this.unmapped;\n }\n\n /** Resolve one written type, registering whatever named types it reaches. */\n resolve(node: ts.TypeNode, ownerName: string): TypeRef {\n if (ts.isParenthesizedTypeNode(node)) {\n return this.resolve(node.type, ownerName);\n }\n if (ts.isArrayTypeNode(node)) {\n return TypeRef.array(this.resolve(node.elementType, ownerName));\n }\n if (ts.isUnionTypeNode(node)) {\n return this.resolveUnion(node, ownerName);\n }\n if (ts.isTypeLiteralNode(node)) {\n return this.resolveAnonymousObject(node, ownerName);\n }\n if (ts.isLiteralTypeNode(node)) {\n return this.resolveLiteral(node);\n }\n if (ts.isTypeReferenceNode(node)) {\n return this.resolveReference(node, ownerName);\n }\n const primitive = TypeResolver.keywordOf(node.kind);\n if (primitive !== undefined) {\n return TypeRef.primitiveOf(primitive);\n }\n return this.recordUnmapped(node, 'no model representation for this type form');\n }\n\n /** `string` / `number` / `boolean` / `null` / `unknown`, or undefined for anything else. */\n // webpieces-disable no-function-outside-class -- private static lookup table of this class\n private static keywordOf(kind: ts.SyntaxKind): PrimitiveKind | undefined {\n switch (kind) {\n case ts.SyntaxKind.StringKeyword:\n return 'string';\n case ts.SyntaxKind.NumberKeyword:\n return 'number';\n case ts.SyntaxKind.BooleanKeyword:\n return 'boolean';\n case ts.SyntaxKind.NullKeyword:\n return 'null';\n case ts.SyntaxKind.UnknownKeyword:\n case ts.SyntaxKind.AnyKeyword:\n case ts.SyntaxKind.VoidKeyword:\n return 'unknown';\n default:\n return undefined;\n }\n }\n\n private resolveLiteral(node: ts.LiteralTypeNode): TypeRef {\n if (ts.isStringLiteral(node.literal)) {\n return TypeRef.enumOf([node.literal.text]);\n }\n if (node.literal.kind === ts.SyntaxKind.NullKeyword) {\n return TypeRef.primitiveOf('null');\n }\n if (\n node.literal.kind === ts.SyntaxKind.TrueKeyword ||\n node.literal.kind === ts.SyntaxKind.FalseKeyword\n ) {\n return TypeRef.primitiveOf('boolean');\n }\n if (ts.isNumericLiteral(node.literal)) {\n return TypeRef.primitiveOf('number');\n }\n return this.recordUnmapped(\n node,\n 'literal type is neither a string, a number nor a boolean',\n );\n }\n\n /**\n * A union, after `null` / `undefined` have been dropped (the FIELD records those as nullable /\n * optional — see {@link fieldOf} — because `{}` and `{x: null}` are different wire documents).\n *\n * Three outcomes, in this order:\n * - every branch a string literal -> an ENUM\n * - one branch left -> that branch\n * - every branch a named object -> a UNION, with a DERIVED discriminator when every branch\n * carries the same property typed as ONE string literal\n * - anything else -> an {@link UnmappedType}. No invented discriminator, ever:\n * a union TypeScript itself cannot narrow is not one a\n * renderer may claim to.\n */\n private resolveUnion(node: ts.UnionTypeNode, ownerName: string): TypeRef {\n const branches = node.types.filter((t: ts.TypeNode) => !TypeResolver.isNullish(t));\n\n const literals = branches.filter(\n (t: ts.TypeNode) => ts.isLiteralTypeNode(t) && ts.isStringLiteral(t.literal),\n );\n if (literals.length === branches.length && branches.length > 0) {\n return TypeRef.enumOf(\n literals.map(\n (t: ts.TypeNode) =>\n ((t as ts.LiteralTypeNode).literal as ts.StringLiteral).text,\n ),\n );\n }\n\n if (branches.length === 1) {\n return this.resolve(branches[0], ownerName);\n }\n if (branches.length === 0) {\n return TypeRef.primitiveOf('null');\n }\n\n const refs: TypeRef[] = branches.map((t: ts.TypeNode) => this.resolve(t, ownerName));\n if (!refs.every((r: TypeRef) => r.kind === 'ref')) {\n return this.recordUnmapped(\n node,\n 'a union whose branches are not all NAMED object types has no discriminator a renderer could narrow on',\n );\n }\n\n const names = refs.map((r: TypeRef) => r.refName!);\n const discriminator = this.deriveDiscriminator(names);\n if (discriminator === undefined) {\n return this.recordUnmapped(\n node,\n 'no property is typed as a single string literal on EVERY branch, so this union has no derivable discriminator',\n );\n }\n this.registerUnionAlias(node, names, discriminator);\n return TypeRef.union(names);\n }\n\n /** `null` and `undefined` branches — recorded as nullable/optional on the FIELD, not in the union. */\n // webpieces-disable no-function-outside-class -- private static predicate of this class\n private static isNullish(node: ts.TypeNode): boolean {\n if (\n node.kind === ts.SyntaxKind.UndefinedKeyword ||\n node.kind === ts.SyntaxKind.NullKeyword\n ) {\n return true;\n }\n return ts.isLiteralTypeNode(node) && node.literal.kind === ts.SyntaxKind.NullKeyword;\n }\n\n /**\n * The DERIVED discriminator: the property every branch declares as exactly ONE string literal,\n * with a value no two branches share. Derived, never invented — if the source does not narrow,\n * neither does the document.\n */\n private deriveDiscriminator(branchNames: readonly string[]): UnionDiscriminator | undefined {\n const branches = branchNames.map((name: string) => this.types.get(name));\n if (branches.some((b: DocumentedType | undefined) => b === undefined)) {\n return undefined;\n }\n const first = branches[0]!;\n for (const field of first.fields) {\n const values = new Map<string, string>();\n for (let i = 0; i < branches.length; i++) {\n const candidate = branches[i]!.fields.find(\n (f: DocumentedField) => f.name === field.name,\n );\n const literal =\n candidate !== undefined &&\n candidate.type.kind === 'enum' &&\n candidate.type.enumValues.length === 1\n ? candidate.type.enumValues[0]\n : undefined;\n if (literal === undefined) {\n values.clear();\n break;\n }\n values.set(branchNames[i], literal);\n }\n if (values.size === branches.length && new Set(values.values()).size === values.size) {\n return new UnionDiscriminator(field.name, values);\n }\n }\n return undefined;\n }\n\n /** A union written as a named `type X = A | B` becomes its own model entry, so #982 can `$ref` it. */\n private registerUnionAlias(\n node: ts.UnionTypeNode,\n branchNames: readonly string[],\n discriminator: UnionDiscriminator,\n ): void {\n const alias = node.parent;\n if (!ts.isTypeAliasDeclaration(alias)) {\n return;\n }\n const name = alias.name.text;\n if (this.registered.has(name)) {\n return;\n }\n this.registered.add(name);\n this.types.set(\n name,\n new DocumentedType(\n name,\n JsDoc.read(alias).description,\n [],\n [],\n branchNames,\n discriminator,\n undefined,\n ),\n );\n }\n\n /**\n * `Integer`, `Array<T>`, `Record<string, V>`, `Promise<T>`, and everything named — an interface,\n * a class, or a type alias. Anything else (a `Map`, a `Date`, a generic parameter) is recorded\n * rather than guessed at.\n */\n private resolveReference(node: ts.TypeReferenceNode, ownerName: string): TypeRef {\n const name = ts.isIdentifier(node.typeName) ? node.typeName.text : node.typeName.right.text;\n const args = node.typeArguments ?? [];\n\n if (name === INTEGER_ALIAS) {\n return TypeRef.primitiveOf('number', /*integer*/ true);\n }\n if ((name === 'Array' || name === 'ReadonlyArray') && args.length === 1) {\n return TypeRef.array(this.resolve(args[0], ownerName));\n }\n if ((name === 'Record' || name === 'Partial') && args.length === 2) {\n return TypeRef.openMap(this.resolve(args[1], ownerName));\n }\n if (name === 'Promise' && args.length === 1) {\n return this.resolve(args[0], ownerName);\n }\n\n const declaration = this.declarationOf(node.typeName);\n if (declaration === undefined) {\n return this.recordUnmapped(node, `no declaration found for '${name}'`);\n }\n if (ts.isInterfaceDeclaration(declaration) || ts.isClassDeclaration(declaration)) {\n return this.registerNamedObject(name, declaration);\n }\n if (ts.isTypeAliasDeclaration(declaration)) {\n return this.resolveAlias(name, declaration, ownerName);\n }\n if (ts.isEnumDeclaration(declaration)) {\n return this.registerStringEnum(name, declaration);\n }\n return this.recordUnmapped(\n node,\n `'${name}' is declared as something with no document shape`,\n );\n }\n\n /** `type X = ...` — registered under X when it has a shape of its own, else transparent. */\n private resolveAlias(name: string, alias: ts.TypeAliasDeclaration, ownerName: string): TypeRef {\n if (this.registered.has(name)) {\n return this.types.get(name)?.unionRefNames.length\n ? TypeRef.union(this.types.get(name)!.unionRefNames)\n : TypeRef.ref(name);\n }\n\n if (ts.isUnionTypeNode(alias.type)) {\n const resolved = this.resolve(alias.type, ownerName);\n if (resolved.kind === 'enum') {\n this.registered.add(name);\n this.types.set(\n name,\n new DocumentedType(\n name,\n JsDoc.read(alias).description,\n [],\n resolved.enumValues,\n [],\n undefined,\n undefined,\n ),\n );\n return TypeRef.ref(name);\n }\n return resolved;\n }\n\n if (ts.isTypeLiteralNode(alias.type)) {\n return this.registerNamedObject(name, alias.type, alias);\n }\n return this.resolve(alias.type, ownerName);\n }\n\n /** A TS `enum` of string members — the one non-union enum shape a document can carry. */\n private registerStringEnum(name: string, declaration: ts.EnumDeclaration): TypeRef {\n if (this.registered.has(name)) {\n return TypeRef.ref(name);\n }\n const values: string[] = [];\n for (const member of declaration.members) {\n if (member.initializer && ts.isStringLiteral(member.initializer)) {\n values.push(member.initializer.text);\n }\n }\n if (values.length !== declaration.members.length) {\n return this.recordUnmapped(\n declaration,\n `enum '${name}' has members that are not string literals`,\n );\n }\n this.registered.add(name);\n this.types.set(\n name,\n new DocumentedType(\n name,\n JsDoc.read(declaration).description,\n [],\n values,\n [],\n undefined,\n undefined,\n ),\n );\n return TypeRef.ref(name);\n }\n\n /**\n * Register a named object shape, RESERVING THE NAME BEFORE walking its fields. That order is the\n * whole cycle story: a type that refers back to itself meets `registered.has(name)` on the second\n * visit and resolves to a `$ref`, so the walk terminates with no counter and no truncation.\n */\n private registerNamedObject(\n name: string,\n shape: ts.InterfaceDeclaration | ts.ClassDeclaration | ts.TypeLiteralNode,\n docSource?: ts.Node,\n ): TypeRef {\n if (this.registered.has(name)) {\n return TypeRef.ref(name);\n }\n this.registered.add(name);\n\n const fields: DocumentedField[] = [];\n let indexSignature: TypeRef | undefined;\n for (const member of shape.members) {\n if (ts.isIndexSignatureDeclaration(member)) {\n indexSignature = this.resolve(member.type, name);\n continue;\n }\n const field = this.fieldOf(member, name);\n if (field !== undefined) {\n fields.push(field);\n }\n }\n\n this.types.set(\n name,\n new DocumentedType(\n name,\n JsDoc.read(docSource ?? shape).description,\n fields,\n [],\n [],\n undefined,\n indexSignature,\n ),\n );\n return TypeRef.ref(name);\n }\n\n /**\n * An inline `{ ... }`. It has no name to `$ref`, so it is registered under an OWNER-DERIVED one\n * (`Parent.field`) — a renderer needs a target, and a name derived from where the shape is\n * written is the only honest one available.\n *\n * A self-referential anonymous type is cut HERE, by NODE IDENTITY. It cannot be a `$ref` (there\n * is no declared name to point at) and it cannot be expanded (it would never end), so it is\n * recorded as unmapped and the walk returns.\n */\n private resolveAnonymousObject(node: ts.TypeLiteralNode, ownerName: string): TypeRef {\n if (this.expandingAnonymous.has(node)) {\n return this.recordUnmapped(\n node,\n 'a self-referential ANONYMOUS object type has no name a renderer could $ref; give it a name',\n );\n }\n const indexOnly =\n node.members.length === 1 && ts.isIndexSignatureDeclaration(node.members[0]);\n if (indexOnly) {\n const signature = node.members[0] as ts.IndexSignatureDeclaration;\n return TypeRef.openMap(this.resolve(signature.type, ownerName));\n }\n\n this.expandingAnonymous.add(node);\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- the stack entry must be dropped whatever the walk does\n try {\n return this.registerNamedObject(ownerName, node);\n } finally {\n this.expandingAnonymous.delete(node);\n }\n }\n\n /** ONE property of a DTO — its type, its optionality, its prose and its numeric constraints. */\n private fieldOf(\n member: ts.TypeElement | ts.ClassElement,\n ownerName: string,\n ): DocumentedField | undefined {\n if (!ts.isPropertySignature(member) && !ts.isPropertyDeclaration(member)) {\n return undefined;\n }\n if (!ts.isIdentifier(member.name) && !ts.isStringLiteral(member.name)) {\n return undefined;\n }\n const name = member.name.text;\n if (member.type === undefined) {\n return undefined;\n }\n\n const optional =\n member.questionToken !== undefined || TypeResolver.declaresUndefined(member.type);\n const nullable = TypeResolver.declaresNull(member.type);\n\n let type = this.resolve(member.type, `${ownerName}.${name}`);\n if (TypeResolver.hasDecorator(member, INT_DECORATOR)) {\n type = TypeResolver.markInteger(type);\n }\n\n const doc = JsDoc.read(member);\n const min = this.numericArgument(member, MIN_DECORATOR);\n const max = this.numericArgument(member, MAX_DECORATOR);\n this.assertNumericConstraintsFit(member, name, type, min, max);\n\n return new DocumentedField(\n name,\n type,\n optional,\n nullable,\n doc.description,\n doc.mcp,\n doc.format,\n min,\n max,\n );\n }\n\n /**\n * `@WpMin` / `@WpMax` on a non-numeric field is a BUILD FAILURE, not a warning. A minimum on a\n * string is not something a renderer can emit sensibly, and a document that silently dropped it\n * would publish a contract weaker than the one its author wrote down.\n */\n private assertNumericConstraintsFit(\n member: ts.Node,\n name: string,\n type: TypeRef,\n min: number | undefined,\n max: number | undefined,\n ): void {\n if (min === undefined && max === undefined) {\n return;\n }\n const numeric = type.isNumeric() || (type.kind === 'array' && type.items!.isNumeric());\n if (numeric) {\n return;\n }\n throw new ApiDocExtractionError(\n `@${MIN_DECORATOR}/@${MAX_DECORATOR} on non-numeric field '${name}'`,\n SourceLocation.of(member),\n 'Put the constraint on a `number` / `Integer` field, or drop it.',\n );\n }\n\n /** `@WpInt()` decorates the FIELD, so the integer flag is pushed onto the right leaf. */\n // webpieces-disable no-function-outside-class -- private static helper of this class\n private static markInteger(type: TypeRef): TypeRef {\n if (type.kind === 'array') {\n return TypeRef.array(type.items!.asInteger());\n }\n if (type.kind === 'openMap') {\n return TypeRef.openMap(type.values!.asInteger());\n }\n return type.asInteger();\n }\n\n // webpieces-disable no-function-outside-class -- private static helper of this class\n private static declaresUndefined(node: ts.TypeNode): boolean {\n return (\n ts.isUnionTypeNode(node) &&\n node.types.some((t: ts.TypeNode) => t.kind === ts.SyntaxKind.UndefinedKeyword)\n );\n }\n\n // webpieces-disable no-function-outside-class -- private static helper of this class\n private static declaresNull(node: ts.TypeNode): boolean {\n return (\n ts.isUnionTypeNode(node) &&\n node.types.some(\n (t: ts.TypeNode) =>\n t.kind === ts.SyntaxKind.NullKeyword ||\n (ts.isLiteralTypeNode(t) && t.literal.kind === ts.SyntaxKind.NullKeyword),\n )\n );\n }\n\n // webpieces-disable no-function-outside-class -- private static helper of this class\n private static hasDecorator(node: ts.Node, decoratorName: string): boolean {\n return TypeResolver.decoratorCall(node, decoratorName) !== undefined;\n }\n\n // webpieces-disable no-function-outside-class -- private static helper of this class\n private static decoratorCall(\n node: ts.Node,\n decoratorName: string,\n ): ts.CallExpression | undefined {\n const decorators = ts.canHaveDecorators(node) ? (ts.getDecorators(node) ?? []) : [];\n for (const decorator of decorators) {\n const call = decorator.expression;\n if (\n ts.isCallExpression(call) &&\n ts.isIdentifier(call.expression) &&\n call.expression.text === decoratorName\n ) {\n return call;\n }\n }\n return undefined;\n }\n\n private numericArgument(node: ts.Node, decoratorName: string): number | undefined {\n const call = TypeResolver.decoratorCall(node, decoratorName);\n const argument = call?.arguments[0];\n if (argument === undefined) {\n return undefined;\n }\n if (ts.isNumericLiteral(argument)) {\n return Number(argument.text);\n }\n if (\n ts.isPrefixUnaryExpression(argument) &&\n argument.operator === ts.SyntaxKind.MinusToken &&\n ts.isNumericLiteral(argument.operand)\n ) {\n return -Number(argument.operand.text);\n }\n throw new ApiDocExtractionError(\n `@${decoratorName} argument is not a numeric literal: '${argument.getText()}'`,\n SourceLocation.of(argument),\n 'Write the bound as a numeric literal. A value only known at runtime cannot be published.',\n );\n }\n\n /** The declaration a type name points at, through imports and aliases. */\n private declarationOf(name: ts.EntityName): ts.Declaration | undefined {\n const symbol = this.checker.getSymbolAtLocation(name);\n const resolved =\n symbol !== undefined && (symbol.flags & ts.SymbolFlags.Alias) !== 0\n ? this.checker.getAliasedSymbol(symbol)\n : symbol;\n return resolved?.declarations?.[0];\n }\n\n private recordUnmapped(node: ts.Node, reason: string): TypeRef {\n const text = node.getText();\n this.unmapped.push(new UnmappedType(text, SourceLocation.of(node), reason));\n return TypeRef.unmapped(text);\n }\n}\n"]}
|
package/src/index.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@webpieces/api-doc-model` — read a webpieces API contract with the TypeScript compiler API and
|
|
3
|
+
* produce ONE in-memory {@link ApiDocModel}.
|
|
4
|
+
*
|
|
5
|
+
* This is the single extraction pass both the OpenAPI documents and the MCP tool list (#982) are
|
|
6
|
+
* rendered from. It emits nothing itself, and it depends on `typescript` and nothing else, so it can
|
|
7
|
+
* be pointed at any upstream project's contract — see `responsibilities.md` for why that constraint
|
|
8
|
+
* is worth stating rather than leaving to be discovered.
|
|
9
|
+
*/
|
|
10
|
+
export { ApiDocExtractor } from './extract/ApiDocExtractor';
|
|
11
|
+
export { ApiDocExtractionError } from './extract/ApiDocExtractionError';
|
|
12
|
+
export { ApiDocModel, DocumentedAuth, DocumentedEndpoint, DocumentedEndpointOptions, DocumentedField, DocumentedMcpTool, DocumentedType, UnionDiscriminator, UnmappedType, } from './model/ApiDocModel';
|
|
13
|
+
export { TypeRef } from './model/TypeRef';
|
|
14
|
+
export type { PrimitiveKind, TypeRefKind } from './model/TypeRef';
|
package/src/index.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.TypeRef = exports.UnmappedType = exports.UnionDiscriminator = exports.DocumentedType = exports.DocumentedMcpTool = exports.DocumentedField = exports.DocumentedEndpointOptions = exports.DocumentedEndpoint = exports.DocumentedAuth = exports.ApiDocModel = exports.ApiDocExtractionError = exports.ApiDocExtractor = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* `@webpieces/api-doc-model` — read a webpieces API contract with the TypeScript compiler API and
|
|
6
|
+
* produce ONE in-memory {@link ApiDocModel}.
|
|
7
|
+
*
|
|
8
|
+
* This is the single extraction pass both the OpenAPI documents and the MCP tool list (#982) are
|
|
9
|
+
* rendered from. It emits nothing itself, and it depends on `typescript` and nothing else, so it can
|
|
10
|
+
* be pointed at any upstream project's contract — see `responsibilities.md` for why that constraint
|
|
11
|
+
* is worth stating rather than leaving to be discovered.
|
|
12
|
+
*/
|
|
13
|
+
var ApiDocExtractor_1 = require("./extract/ApiDocExtractor");
|
|
14
|
+
Object.defineProperty(exports, "ApiDocExtractor", { enumerable: true, get: function () { return ApiDocExtractor_1.ApiDocExtractor; } });
|
|
15
|
+
var ApiDocExtractionError_1 = require("./extract/ApiDocExtractionError");
|
|
16
|
+
Object.defineProperty(exports, "ApiDocExtractionError", { enumerable: true, get: function () { return ApiDocExtractionError_1.ApiDocExtractionError; } });
|
|
17
|
+
var ApiDocModel_1 = require("./model/ApiDocModel");
|
|
18
|
+
Object.defineProperty(exports, "ApiDocModel", { enumerable: true, get: function () { return ApiDocModel_1.ApiDocModel; } });
|
|
19
|
+
Object.defineProperty(exports, "DocumentedAuth", { enumerable: true, get: function () { return ApiDocModel_1.DocumentedAuth; } });
|
|
20
|
+
Object.defineProperty(exports, "DocumentedEndpoint", { enumerable: true, get: function () { return ApiDocModel_1.DocumentedEndpoint; } });
|
|
21
|
+
Object.defineProperty(exports, "DocumentedEndpointOptions", { enumerable: true, get: function () { return ApiDocModel_1.DocumentedEndpointOptions; } });
|
|
22
|
+
Object.defineProperty(exports, "DocumentedField", { enumerable: true, get: function () { return ApiDocModel_1.DocumentedField; } });
|
|
23
|
+
Object.defineProperty(exports, "DocumentedMcpTool", { enumerable: true, get: function () { return ApiDocModel_1.DocumentedMcpTool; } });
|
|
24
|
+
Object.defineProperty(exports, "DocumentedType", { enumerable: true, get: function () { return ApiDocModel_1.DocumentedType; } });
|
|
25
|
+
Object.defineProperty(exports, "UnionDiscriminator", { enumerable: true, get: function () { return ApiDocModel_1.UnionDiscriminator; } });
|
|
26
|
+
Object.defineProperty(exports, "UnmappedType", { enumerable: true, get: function () { return ApiDocModel_1.UnmappedType; } });
|
|
27
|
+
var TypeRef_1 = require("./model/TypeRef");
|
|
28
|
+
Object.defineProperty(exports, "TypeRef", { enumerable: true, get: function () { return TypeRef_1.TypeRef; } });
|
|
29
|
+
//# sourceMappingURL=index.js.map
|
package/src/index.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../packages/docs/api-doc-model/src/index.ts"],"names":[],"mappings":";;;AAAA;;;;;;;;GAQG;AACH,6DAA4D;AAAnD,kHAAA,eAAe,OAAA;AACxB,yEAAwE;AAA/D,8HAAA,qBAAqB,OAAA;AAC9B,mDAU6B;AATzB,0GAAA,WAAW,OAAA;AACX,6GAAA,cAAc,OAAA;AACd,iHAAA,kBAAkB,OAAA;AAClB,wHAAA,yBAAyB,OAAA;AACzB,8GAAA,eAAe,OAAA;AACf,gHAAA,iBAAiB,OAAA;AACjB,6GAAA,cAAc,OAAA;AACd,iHAAA,kBAAkB,OAAA;AAClB,2GAAA,YAAY,OAAA;AAEhB,2CAA0C;AAAjC,kGAAA,OAAO,OAAA","sourcesContent":["/**\n * `@webpieces/api-doc-model` — read a webpieces API contract with the TypeScript compiler API and\n * produce ONE in-memory {@link ApiDocModel}.\n *\n * This is the single extraction pass both the OpenAPI documents and the MCP tool list (#982) are\n * rendered from. It emits nothing itself, and it depends on `typescript` and nothing else, so it can\n * be pointed at any upstream project's contract — see `responsibilities.md` for why that constraint\n * is worth stating rather than leaving to be discovered.\n */\nexport { ApiDocExtractor } from './extract/ApiDocExtractor';\nexport { ApiDocExtractionError } from './extract/ApiDocExtractionError';\nexport {\n ApiDocModel,\n DocumentedAuth,\n DocumentedEndpoint,\n DocumentedEndpointOptions,\n DocumentedField,\n DocumentedMcpTool,\n DocumentedType,\n UnionDiscriminator,\n UnmappedType,\n} from './model/ApiDocModel';\nexport { TypeRef } from './model/TypeRef';\nexport type { PrimitiveKind, TypeRefKind } from './model/TypeRef';\n"]}
|