@pithyjs/codex 0.1.0-beta.0
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 +4401 -0
- package/dist/annotations.d.ts +106 -0
- package/dist/annotations.js +306 -0
- package/dist/apply.d.ts +2 -0
- package/dist/apply.js +80 -0
- package/dist/changed-scope.d.ts +23 -0
- package/dist/changed-scope.js +31 -0
- package/dist/check.d.ts +2 -0
- package/dist/check.js +117 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +67 -0
- package/dist/env.d.ts +5 -0
- package/dist/env.js +35 -0
- package/dist/extract-cli.d.ts +23 -0
- package/dist/extract-cli.js +192 -0
- package/dist/extraction/breaking-changes.d.ts +66 -0
- package/dist/extraction/breaking-changes.js +352 -0
- package/dist/extraction/example-extractor.d.ts +55 -0
- package/dist/extraction/example-extractor.js +272 -0
- package/dist/extraction/index.d.ts +27 -0
- package/dist/extraction/index.js +31 -0
- package/dist/extraction/jsdoc-parser.d.ts +43 -0
- package/dist/extraction/jsdoc-parser.js +274 -0
- package/dist/extraction/pipeline.d.ts +54 -0
- package/dist/extraction/pipeline.js +526 -0
- package/dist/extraction/readme-sync.d.ts +108 -0
- package/dist/extraction/readme-sync.js +592 -0
- package/dist/extraction/snapshot-store.d.ts +40 -0
- package/dist/extraction/snapshot-store.js +153 -0
- package/dist/extraction/source-linker.d.ts +80 -0
- package/dist/extraction/source-linker.js +316 -0
- package/dist/extraction/test-example-extractor.d.ts +69 -0
- package/dist/extraction/test-example-extractor.js +400 -0
- package/dist/extraction/test-pattern-extractor.d.ts +68 -0
- package/dist/extraction/test-pattern-extractor.js +261 -0
- package/dist/extraction/testing-pyramid.d.ts +44 -0
- package/dist/extraction/testing-pyramid.js +163 -0
- package/dist/extraction/type-extractor.d.ts +34 -0
- package/dist/extraction/type-extractor.js +494 -0
- package/dist/extraction/types.d.ts +401 -0
- package/dist/extraction/types.js +34 -0
- package/dist/indexer.d.ts +1 -0
- package/dist/indexer.js +107 -0
- package/dist/llm.d.ts +8 -0
- package/dist/llm.js +75 -0
- package/dist/readme-sync-cli.d.ts +20 -0
- package/dist/readme-sync-cli.js +167 -0
- package/dist/review.d.ts +2 -0
- package/dist/review.js +93 -0
- package/dist/scan.d.ts +29 -0
- package/dist/scan.js +221 -0
- package/dist/schema.d.ts +169 -0
- package/dist/schema.js +70 -0
- package/dist/snapshot-cli.d.ts +45 -0
- package/dist/snapshot-cli.js +217 -0
- package/dist/sync-cli.d.ts +22 -0
- package/dist/sync-cli.js +154 -0
- package/dist/sync-pipeline.d.ts +58 -0
- package/dist/sync-pipeline.js +104 -0
- package/dist/sync.d.ts +2 -0
- package/dist/sync.js +318 -0
- package/dist/validate-cli.d.ts +20 -0
- package/dist/validate-cli.js +144 -0
- package/dist/validate.d.ts +76 -0
- package/dist/validate.js +183 -0
- package/dist/watch-cli.d.ts +21 -0
- package/dist/watch-cli.js +220 -0
- package/package.json +62 -0
|
@@ -0,0 +1,494 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @codex
|
|
3
|
+
* {
|
|
4
|
+
* "id": "pithy.codex.extraction.type-extractor",
|
|
5
|
+
* "title": "TypeScript Type Extractor",
|
|
6
|
+
* "category": "compiler"
|
|
7
|
+
* }
|
|
8
|
+
*/
|
|
9
|
+
import ts from 'typescript';
|
|
10
|
+
/**
|
|
11
|
+
* @codexApi {"parent":"pithy.codex.extraction.type-extractor","name":"extractTypesFromFile","stability":"stable","signature":"(filePath: string, content: string) => ExtractedTypeDefinition[]"}
|
|
12
|
+
*
|
|
13
|
+
* Extracts type definitions from a TypeScript file using the TypeScript Compiler API
|
|
14
|
+
* @param filePath - Path to the TypeScript file
|
|
15
|
+
* @param content - Content of the file
|
|
16
|
+
* @returns Array of extracted type definitions
|
|
17
|
+
*/
|
|
18
|
+
export function extractTypesFromFile(filePath, content) {
|
|
19
|
+
const types = [];
|
|
20
|
+
// Create a source file
|
|
21
|
+
const sourceFile = ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
|
|
22
|
+
// Visit all nodes
|
|
23
|
+
function visit(node) {
|
|
24
|
+
if (ts.isInterfaceDeclaration(node)) {
|
|
25
|
+
types.push(extractInterface(node, sourceFile, filePath));
|
|
26
|
+
}
|
|
27
|
+
else if (ts.isTypeAliasDeclaration(node)) {
|
|
28
|
+
types.push(extractTypeAlias(node, sourceFile, filePath));
|
|
29
|
+
}
|
|
30
|
+
else if (ts.isEnumDeclaration(node)) {
|
|
31
|
+
types.push(extractEnum(node, sourceFile, filePath));
|
|
32
|
+
}
|
|
33
|
+
else if (ts.isClassDeclaration(node) && node.name) {
|
|
34
|
+
types.push(extractClass(node, sourceFile, filePath));
|
|
35
|
+
}
|
|
36
|
+
ts.forEachChild(node, visit);
|
|
37
|
+
}
|
|
38
|
+
visit(sourceFile);
|
|
39
|
+
return types;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Extracts an interface declaration
|
|
43
|
+
*/
|
|
44
|
+
function extractInterface(node, sourceFile, filePath) {
|
|
45
|
+
const name = node.name.text;
|
|
46
|
+
const exported = hasExportModifier(node);
|
|
47
|
+
const location = getSourceLocation(node, sourceFile, filePath);
|
|
48
|
+
// Extract generics
|
|
49
|
+
const generics = node.typeParameters?.map(tp => tp.getText(sourceFile));
|
|
50
|
+
// Extract extends
|
|
51
|
+
const extendsClause = node.heritageClauses?.find(hc => hc.token === ts.SyntaxKind.ExtendsKeyword);
|
|
52
|
+
const extendsTypes = extendsClause?.types.map(t => t.getText(sourceFile));
|
|
53
|
+
// Extract members
|
|
54
|
+
const members = node.members.map(member => extractMember(member, sourceFile));
|
|
55
|
+
// Extract JSDoc
|
|
56
|
+
const jsdoc = extractJSDocFromNode(node, sourceFile, filePath);
|
|
57
|
+
return {
|
|
58
|
+
name,
|
|
59
|
+
kind: 'interface',
|
|
60
|
+
exported,
|
|
61
|
+
generics,
|
|
62
|
+
extends: extendsTypes,
|
|
63
|
+
members,
|
|
64
|
+
jsdoc,
|
|
65
|
+
location,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Extracts a type alias declaration
|
|
70
|
+
*/
|
|
71
|
+
function extractTypeAlias(node, sourceFile, filePath) {
|
|
72
|
+
const name = node.name.text;
|
|
73
|
+
const exported = hasExportModifier(node);
|
|
74
|
+
const location = getSourceLocation(node, sourceFile, filePath);
|
|
75
|
+
// Extract generics
|
|
76
|
+
const generics = node.typeParameters?.map(tp => tp.getText(sourceFile));
|
|
77
|
+
// For type aliases, we represent the type as a single "member"
|
|
78
|
+
const members = [];
|
|
79
|
+
// If it's an object type literal, extract its properties
|
|
80
|
+
if (ts.isTypeLiteralNode(node.type)) {
|
|
81
|
+
for (const member of node.type.members) {
|
|
82
|
+
members.push(extractMember(member, sourceFile));
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
// For other types (union, intersection, etc.), store the full type as a special member
|
|
87
|
+
members.push({
|
|
88
|
+
name: '__type',
|
|
89
|
+
type: node.type.getText(sourceFile),
|
|
90
|
+
optional: false,
|
|
91
|
+
readonly: false,
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
const jsdoc = extractJSDocFromNode(node, sourceFile, filePath);
|
|
95
|
+
return {
|
|
96
|
+
name,
|
|
97
|
+
kind: 'type',
|
|
98
|
+
exported,
|
|
99
|
+
generics,
|
|
100
|
+
members,
|
|
101
|
+
jsdoc,
|
|
102
|
+
location,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Extracts an enum declaration
|
|
107
|
+
*/
|
|
108
|
+
function extractEnum(node, sourceFile, filePath) {
|
|
109
|
+
const name = node.name.text;
|
|
110
|
+
const exported = hasExportModifier(node);
|
|
111
|
+
const location = getSourceLocation(node, sourceFile, filePath);
|
|
112
|
+
// Extract enum members
|
|
113
|
+
const members = node.members.map(member => {
|
|
114
|
+
const memberName = ts.isIdentifier(member.name)
|
|
115
|
+
? member.name.text
|
|
116
|
+
: member.name.getText(sourceFile);
|
|
117
|
+
const value = member.initializer?.getText(sourceFile) || '';
|
|
118
|
+
return {
|
|
119
|
+
name: memberName,
|
|
120
|
+
type: value || 'auto',
|
|
121
|
+
optional: false,
|
|
122
|
+
readonly: true,
|
|
123
|
+
};
|
|
124
|
+
});
|
|
125
|
+
const jsdoc = extractJSDocFromNode(node, sourceFile, filePath);
|
|
126
|
+
return {
|
|
127
|
+
name,
|
|
128
|
+
kind: 'enum',
|
|
129
|
+
exported,
|
|
130
|
+
members,
|
|
131
|
+
jsdoc,
|
|
132
|
+
location,
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Extracts a class declaration
|
|
137
|
+
*/
|
|
138
|
+
function extractClass(node, sourceFile, filePath) {
|
|
139
|
+
const name = node.name?.text || 'AnonymousClass';
|
|
140
|
+
const exported = hasExportModifier(node);
|
|
141
|
+
const location = getSourceLocation(node, sourceFile, filePath);
|
|
142
|
+
// Extract generics
|
|
143
|
+
const generics = node.typeParameters?.map(tp => tp.getText(sourceFile));
|
|
144
|
+
// Extract extends
|
|
145
|
+
const extendsClause = node.heritageClauses?.find(hc => hc.token === ts.SyntaxKind.ExtendsKeyword);
|
|
146
|
+
const extendsTypes = extendsClause?.types.map(t => t.getText(sourceFile));
|
|
147
|
+
// Extract implements
|
|
148
|
+
const implementsClause = node.heritageClauses?.find(hc => hc.token === ts.SyntaxKind.ImplementsKeyword);
|
|
149
|
+
const implementsTypes = implementsClause?.types.map(t => t.getText(sourceFile));
|
|
150
|
+
// Extract public members only (skip private, protected, and #private)
|
|
151
|
+
const members = [];
|
|
152
|
+
for (const member of node.members) {
|
|
153
|
+
if (hasPrivateModifier(member))
|
|
154
|
+
continue;
|
|
155
|
+
if (hasProtectedModifier(member))
|
|
156
|
+
continue;
|
|
157
|
+
// Skip ECMAScript #private fields/methods
|
|
158
|
+
if (member.name && ts.isPrivateIdentifier(member.name))
|
|
159
|
+
continue;
|
|
160
|
+
if (ts.isPropertyDeclaration(member) ||
|
|
161
|
+
ts.isMethodDeclaration(member) ||
|
|
162
|
+
ts.isGetAccessor(member) ||
|
|
163
|
+
ts.isSetAccessor(member)) {
|
|
164
|
+
members.push(extractClassMember(member, sourceFile));
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
const jsdoc = extractJSDocFromNode(node, sourceFile, filePath);
|
|
168
|
+
return {
|
|
169
|
+
name,
|
|
170
|
+
kind: 'class',
|
|
171
|
+
exported,
|
|
172
|
+
generics,
|
|
173
|
+
extends: extendsTypes,
|
|
174
|
+
implements: implementsTypes,
|
|
175
|
+
members,
|
|
176
|
+
jsdoc,
|
|
177
|
+
location,
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Extracts a member from an interface/type literal
|
|
182
|
+
*/
|
|
183
|
+
function extractMember(member, sourceFile) {
|
|
184
|
+
if (ts.isPropertySignature(member)) {
|
|
185
|
+
const name = member.name.getText(sourceFile);
|
|
186
|
+
const type = member.type?.getText(sourceFile) || 'unknown';
|
|
187
|
+
const optional = !!member.questionToken;
|
|
188
|
+
const readonly = hasReadonlyModifier(member);
|
|
189
|
+
const description = getJSDocDescription(member, sourceFile);
|
|
190
|
+
return { name, type, optional, readonly, description };
|
|
191
|
+
}
|
|
192
|
+
if (ts.isMethodSignature(member)) {
|
|
193
|
+
const name = member.name.getText(sourceFile);
|
|
194
|
+
const signature = member.getText(sourceFile);
|
|
195
|
+
const optional = !!member.questionToken;
|
|
196
|
+
const description = getJSDocDescription(member, sourceFile);
|
|
197
|
+
return {
|
|
198
|
+
name,
|
|
199
|
+
type: 'method',
|
|
200
|
+
optional,
|
|
201
|
+
readonly: false,
|
|
202
|
+
signature,
|
|
203
|
+
description,
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
if (ts.isIndexSignatureDeclaration(member)) {
|
|
207
|
+
return {
|
|
208
|
+
name: '[index]',
|
|
209
|
+
type: member.type?.getText(sourceFile) || 'unknown',
|
|
210
|
+
optional: false,
|
|
211
|
+
readonly: hasReadonlyModifier(member),
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
// Fallback
|
|
215
|
+
return {
|
|
216
|
+
name: member.getText(sourceFile).slice(0, 50),
|
|
217
|
+
type: 'unknown',
|
|
218
|
+
optional: false,
|
|
219
|
+
readonly: false,
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Builds a signature string for a class method/getter/setter without the body.
|
|
224
|
+
*/
|
|
225
|
+
function buildClassMemberSignature(member, sourceFile) {
|
|
226
|
+
const name = member.name.getText(sourceFile);
|
|
227
|
+
if (ts.isGetAccessor(member)) {
|
|
228
|
+
const returnType = member.type
|
|
229
|
+
? `: ${member.type.getText(sourceFile)}`
|
|
230
|
+
: '';
|
|
231
|
+
return `get ${name}()${returnType}`;
|
|
232
|
+
}
|
|
233
|
+
if (ts.isSetAccessor(member)) {
|
|
234
|
+
const params = member.parameters.map(p => p.getText(sourceFile)).join(', ');
|
|
235
|
+
return `set ${name}(${params})`;
|
|
236
|
+
}
|
|
237
|
+
// Method declaration
|
|
238
|
+
const typeParams = member.typeParameters
|
|
239
|
+
? `<${member.typeParameters.map(tp => tp.getText(sourceFile)).join(', ')}>`
|
|
240
|
+
: '';
|
|
241
|
+
const params = member.parameters.map(p => p.getText(sourceFile)).join(', ');
|
|
242
|
+
const returnType = member.type ? `: ${member.type.getText(sourceFile)}` : '';
|
|
243
|
+
return `${name}${typeParams}(${params})${returnType}`;
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Extracts a class member
|
|
247
|
+
*/
|
|
248
|
+
function extractClassMember(member, sourceFile) {
|
|
249
|
+
const name = member.name.getText(sourceFile);
|
|
250
|
+
const optional = ts.isPropertyDeclaration(member) && !!member.questionToken;
|
|
251
|
+
const readonly = hasReadonlyModifier(member);
|
|
252
|
+
const description = getJSDocDescription(member, sourceFile);
|
|
253
|
+
if (ts.isMethodDeclaration(member) ||
|
|
254
|
+
ts.isGetAccessor(member) ||
|
|
255
|
+
ts.isSetAccessor(member)) {
|
|
256
|
+
const signature = buildClassMemberSignature(member, sourceFile);
|
|
257
|
+
return {
|
|
258
|
+
name,
|
|
259
|
+
type: ts.isGetAccessor(member)
|
|
260
|
+
? 'getter'
|
|
261
|
+
: ts.isSetAccessor(member)
|
|
262
|
+
? 'setter'
|
|
263
|
+
: 'method',
|
|
264
|
+
optional,
|
|
265
|
+
readonly,
|
|
266
|
+
signature,
|
|
267
|
+
description,
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
const type = member.type?.getText(sourceFile) || 'unknown';
|
|
271
|
+
return { name, type, optional, readonly, description };
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* Gets source location from a node
|
|
275
|
+
*/
|
|
276
|
+
function getSourceLocation(node, sourceFile, filePath) {
|
|
277
|
+
const { line, character } = sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile));
|
|
278
|
+
const endPos = sourceFile.getLineAndCharacterOfPosition(node.getEnd());
|
|
279
|
+
return {
|
|
280
|
+
file: filePath,
|
|
281
|
+
line: line + 1, // 1-indexed
|
|
282
|
+
column: character + 1,
|
|
283
|
+
endLine: endPos.line + 1,
|
|
284
|
+
endColumn: endPos.character + 1,
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
/**
|
|
288
|
+
* Checks if a node has the export modifier
|
|
289
|
+
*/
|
|
290
|
+
function hasExportModifier(node) {
|
|
291
|
+
const modifiers = ts.canHaveModifiers(node)
|
|
292
|
+
? ts.getModifiers(node)
|
|
293
|
+
: undefined;
|
|
294
|
+
return modifiers?.some(m => m.kind === ts.SyntaxKind.ExportKeyword) || false;
|
|
295
|
+
}
|
|
296
|
+
/**
|
|
297
|
+
* Checks if a node has the private modifier
|
|
298
|
+
*/
|
|
299
|
+
function hasPrivateModifier(node) {
|
|
300
|
+
const modifiers = ts.canHaveModifiers(node)
|
|
301
|
+
? ts.getModifiers(node)
|
|
302
|
+
: undefined;
|
|
303
|
+
return modifiers?.some(m => m.kind === ts.SyntaxKind.PrivateKeyword) || false;
|
|
304
|
+
}
|
|
305
|
+
/**
|
|
306
|
+
* Checks if a node has the protected modifier
|
|
307
|
+
*/
|
|
308
|
+
function hasProtectedModifier(node) {
|
|
309
|
+
const modifiers = ts.canHaveModifiers(node)
|
|
310
|
+
? ts.getModifiers(node)
|
|
311
|
+
: undefined;
|
|
312
|
+
return (modifiers?.some(m => m.kind === ts.SyntaxKind.ProtectedKeyword) || false);
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
* Checks if a node has the readonly modifier
|
|
316
|
+
*/
|
|
317
|
+
function hasReadonlyModifier(node) {
|
|
318
|
+
const modifiers = ts.canHaveModifiers(node)
|
|
319
|
+
? ts.getModifiers(node)
|
|
320
|
+
: undefined;
|
|
321
|
+
return (modifiers?.some(m => m.kind === ts.SyntaxKind.ReadonlyKeyword) || false);
|
|
322
|
+
}
|
|
323
|
+
/**
|
|
324
|
+
* Gets the JSDoc description from a node
|
|
325
|
+
*/
|
|
326
|
+
function getJSDocDescription(node, sourceFile) {
|
|
327
|
+
const jsDocs = ts.getJSDocCommentsAndTags(node);
|
|
328
|
+
for (const doc of jsDocs) {
|
|
329
|
+
if (ts.isJSDoc(doc) && doc.comment) {
|
|
330
|
+
if (typeof doc.comment === 'string') {
|
|
331
|
+
return doc.comment;
|
|
332
|
+
}
|
|
333
|
+
return doc.comment.map(c => c.getText(sourceFile)).join('');
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
return undefined;
|
|
337
|
+
}
|
|
338
|
+
/**
|
|
339
|
+
* Extracts a comment string from a JSDoc comment node
|
|
340
|
+
*/
|
|
341
|
+
function extractCommentText(comment, sourceFile) {
|
|
342
|
+
if (!comment)
|
|
343
|
+
return '';
|
|
344
|
+
if (typeof comment === 'string')
|
|
345
|
+
return comment;
|
|
346
|
+
return comment.map(c => c.getText(sourceFile)).join('');
|
|
347
|
+
}
|
|
348
|
+
/**
|
|
349
|
+
* Extracts JSDoc from a node into our ParsedJSDoc format,
|
|
350
|
+
* including @param, @returns, @throws, @see, @since, @deprecated, and @example tags.
|
|
351
|
+
*/
|
|
352
|
+
function extractJSDocFromNode(node, sourceFile, filePath) {
|
|
353
|
+
const jsDocs = ts.getJSDocCommentsAndTags(node);
|
|
354
|
+
for (const doc of jsDocs) {
|
|
355
|
+
if (!ts.isJSDoc(doc))
|
|
356
|
+
continue;
|
|
357
|
+
const location = getSourceLocation(doc, sourceFile, filePath);
|
|
358
|
+
const description = extractCommentText(doc.comment, sourceFile);
|
|
359
|
+
const params = [];
|
|
360
|
+
const examples = [];
|
|
361
|
+
const tags = {};
|
|
362
|
+
let returns;
|
|
363
|
+
const throws = [];
|
|
364
|
+
const see = [];
|
|
365
|
+
let since;
|
|
366
|
+
let deprecated;
|
|
367
|
+
if (doc.tags) {
|
|
368
|
+
for (const tag of doc.tags) {
|
|
369
|
+
const tagName = tag.tagName.text;
|
|
370
|
+
const tagComment = extractCommentText(tag.comment, sourceFile);
|
|
371
|
+
switch (tagName) {
|
|
372
|
+
case 'param': {
|
|
373
|
+
if (ts.isJSDocParameterTag(tag)) {
|
|
374
|
+
params.push({
|
|
375
|
+
name: tag.name.getText(sourceFile),
|
|
376
|
+
type: tag.typeExpression?.getText(sourceFile),
|
|
377
|
+
description: tagComment,
|
|
378
|
+
optional: tag.isBracketed,
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
break;
|
|
382
|
+
}
|
|
383
|
+
case 'returns':
|
|
384
|
+
case 'return':
|
|
385
|
+
returns = {
|
|
386
|
+
type: tag.typeExpression?.getText(sourceFile),
|
|
387
|
+
description: tagComment,
|
|
388
|
+
};
|
|
389
|
+
break;
|
|
390
|
+
case 'throws':
|
|
391
|
+
case 'exception':
|
|
392
|
+
throws.push(tagComment);
|
|
393
|
+
break;
|
|
394
|
+
case 'see':
|
|
395
|
+
see.push(tagComment);
|
|
396
|
+
break;
|
|
397
|
+
case 'since':
|
|
398
|
+
since = tagComment;
|
|
399
|
+
break;
|
|
400
|
+
case 'deprecated':
|
|
401
|
+
deprecated = tagComment || true;
|
|
402
|
+
break;
|
|
403
|
+
case 'example':
|
|
404
|
+
examples.push({
|
|
405
|
+
code: tagComment,
|
|
406
|
+
language: 'typescript',
|
|
407
|
+
runnable: false,
|
|
408
|
+
isDoctest: false,
|
|
409
|
+
location: getSourceLocation(tag, sourceFile, filePath),
|
|
410
|
+
});
|
|
411
|
+
break;
|
|
412
|
+
default:
|
|
413
|
+
if (!tags[tagName])
|
|
414
|
+
tags[tagName] = [];
|
|
415
|
+
tags[tagName].push(tagComment);
|
|
416
|
+
break;
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
return {
|
|
421
|
+
description,
|
|
422
|
+
params,
|
|
423
|
+
returns,
|
|
424
|
+
examples,
|
|
425
|
+
throws: throws.length > 0 ? throws : undefined,
|
|
426
|
+
see: see.length > 0 ? see : undefined,
|
|
427
|
+
since,
|
|
428
|
+
deprecated,
|
|
429
|
+
tags,
|
|
430
|
+
location,
|
|
431
|
+
};
|
|
432
|
+
}
|
|
433
|
+
return undefined;
|
|
434
|
+
}
|
|
435
|
+
/**
|
|
436
|
+
* @codexApi {"parent":"pithy.codex.extraction.type-extractor","name":"getTypeSignature","stability":"stable","signature":"(def: ExtractedTypeDefinition) => string"}
|
|
437
|
+
*
|
|
438
|
+
* Generates a human-readable signature for a type definition
|
|
439
|
+
* @param def - The extracted type definition
|
|
440
|
+
* @returns Formatted type signature
|
|
441
|
+
*/
|
|
442
|
+
export function getTypeSignature(def) {
|
|
443
|
+
const exportPrefix = def.exported ? 'export ' : '';
|
|
444
|
+
const genericsStr = def.generics?.length
|
|
445
|
+
? `<${def.generics.join(', ')}>`
|
|
446
|
+
: '';
|
|
447
|
+
const extendsStr = def.extends?.length
|
|
448
|
+
? ` extends ${def.extends.join(', ')}`
|
|
449
|
+
: '';
|
|
450
|
+
const implementsStr = def.implements?.length
|
|
451
|
+
? ` implements ${def.implements.join(', ')}`
|
|
452
|
+
: '';
|
|
453
|
+
switch (def.kind) {
|
|
454
|
+
case 'interface':
|
|
455
|
+
return `${exportPrefix}interface ${def.name}${genericsStr}${extendsStr}`;
|
|
456
|
+
case 'type':
|
|
457
|
+
return `${exportPrefix}type ${def.name}${genericsStr}`;
|
|
458
|
+
case 'enum':
|
|
459
|
+
return `${exportPrefix}enum ${def.name}`;
|
|
460
|
+
case 'class':
|
|
461
|
+
return `${exportPrefix}class ${def.name}${genericsStr}${extendsStr}${implementsStr}`;
|
|
462
|
+
default:
|
|
463
|
+
return def.name;
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
/**
|
|
467
|
+
* @codexApi {"parent":"pithy.codex.extraction.type-extractor","name":"generateMethodTable","stability":"stable","signature":"(def: ExtractedTypeDefinition) => string"}
|
|
468
|
+
*
|
|
469
|
+
* Generates a markdown table of members for documentation
|
|
470
|
+
* @param def - The extracted type definition
|
|
471
|
+
* @returns Markdown table string
|
|
472
|
+
*/
|
|
473
|
+
export function generateMethodTable(def) {
|
|
474
|
+
if (def.members.length === 0)
|
|
475
|
+
return '';
|
|
476
|
+
const lines = [
|
|
477
|
+
'| Member | Type | Description |',
|
|
478
|
+
'|--------|------|-------------|',
|
|
479
|
+
];
|
|
480
|
+
for (const member of def.members) {
|
|
481
|
+
let nameCol = member.name;
|
|
482
|
+
if (member.readonly) {
|
|
483
|
+
nameCol = `readonly ${nameCol}`;
|
|
484
|
+
}
|
|
485
|
+
if (member.optional) {
|
|
486
|
+
nameCol = `${nameCol}?`;
|
|
487
|
+
}
|
|
488
|
+
const typeCol = member.signature || member.type;
|
|
489
|
+
const rawDesc = member.description ?? '';
|
|
490
|
+
const descCol = rawDesc.replace(/\|/g, '\\|').replace(/\r?\n/g, '<br/>');
|
|
491
|
+
lines.push(`| \`${nameCol}\` | \`${typeCol}\` | ${descCol} |`);
|
|
492
|
+
}
|
|
493
|
+
return lines.join('\n');
|
|
494
|
+
}
|