@xyd-js/sources 0.0.1-xyd.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.
@@ -0,0 +1,572 @@
1
+ import * as fs from "node:fs";
2
+ import * as path from "node:path";
3
+
4
+ import type {Reference, Definition} from "@xyd-js/uniform";
5
+
6
+ import type {
7
+ JSONOutput,
8
+ ContainerReflection,
9
+ DeclarationReflection,
10
+ SomeType,
11
+ ReflectionSymbolId,
12
+ Comment,
13
+ } from 'typedoc';
14
+ import {ReflectionKind} from "typedoc";
15
+
16
+ import {
17
+ MultiSignatureLoader,
18
+ signatureTextByLine,
19
+ signatureSourceCodeByLine
20
+ } from "./SignatureText";
21
+
22
+ class TypeDocSignatureTextLoader extends MultiSignatureLoader {
23
+ constructor(
24
+ private project: JSONOutput.ProjectReflection,
25
+ private packagePathMap: { [id: number]: string }
26
+ ) {
27
+ super();
28
+ }
29
+
30
+ public signatureText(
31
+ id: number,
32
+ line: number
33
+ ) {
34
+ const loader = this.getSignatuerLoader(id)
35
+ if (!loader) {
36
+ return
37
+ }
38
+
39
+ const signTxt = signatureTextByLine(loader, line)
40
+ if (!signTxt) {
41
+ console.warn('(TypeDocSignatureTextLoader.signatureText): Signature text is empty', id)
42
+ return
43
+ }
44
+
45
+ return signTxt
46
+ }
47
+
48
+ public signatureSourceCode(
49
+ id: number,
50
+ line: number
51
+ ) {
52
+ const loader = this.getSignatuerLoader(id)
53
+ if (!loader) {
54
+ return
55
+ }
56
+
57
+ const sourceCode = signatureSourceCodeByLine(loader, line)
58
+ if (!sourceCode) {
59
+ console.warn('(TypeDocSignatureTextLoader.signatureSourceCode): Source code is empty', id)
60
+ return
61
+ }
62
+
63
+ return sourceCode
64
+ }
65
+
66
+ private getSignatuerLoader(id: number) {
67
+ const symbolMap = this.project.symbolIdMap[id] as ReflectionSymbolId
68
+ if (!symbolMap) {
69
+ console.warn('(TypeDocSignatureTextLoader.getSignatuerLoader): Symbol not found', id)
70
+ return
71
+ }
72
+
73
+ const fullPath = this.packagePathMap[id]
74
+ if (!fullPath) {
75
+ console.warn('(TypeDocSignatureTextLoader.getSignatuerLoader): Package path not found for symbol', symbolMap.packageName)
76
+ return
77
+ }
78
+
79
+ const loader = this.load(fullPath)
80
+ if (!loader) {
81
+ console.warn('(TypeDocSignatureTextLoader.getSignatuerLoader): Loader not found', fullPath)
82
+ return
83
+ }
84
+
85
+ return loader
86
+ }
87
+ }
88
+
89
+ class Transformer {
90
+ public packagePathMap: { [id: number]: string } = {};
91
+ public signatureTextLoader!: TypeDocSignatureTextLoader;
92
+
93
+ constructor(
94
+ private rootPath: string,
95
+ protected project: JSONOutput.ProjectReflection,
96
+ protected references: Reference[] = []
97
+ ) {
98
+ const packagePathMap = this.createPackagePathMap()
99
+ if (packagePathMap) {
100
+ this.packagePathMap = packagePathMap
101
+ }
102
+ }
103
+
104
+ private createPackagePathMap() {
105
+ const packagePathMap: { [id: number]: string } = {};
106
+ const packageJsonPaths = this.findPackageJsonPaths(this.rootPath);
107
+
108
+ if (!packageJsonPaths.length) {
109
+ console.warn('(Transformer.createPackagePathMap): No package.json found in rootPath', this.rootPath)
110
+ return {packageMap: null, moduleRootMap: null}
111
+ }
112
+
113
+ for (const packageJsonPath of packageJsonPaths) {
114
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
115
+ const packageName = packageJson.name;
116
+ const moduleRoot = path.dirname(packageJsonPath);
117
+
118
+ if (!packageName) {
119
+ console.warn('(Transformer.createPackagePathMap): Package name not found in package.json', packageJsonPath)
120
+ continue
121
+ }
122
+
123
+ for (const id in this.project.symbolIdMap) {
124
+ const symbolMap = this.project.symbolIdMap[id] as ReflectionSymbolId;
125
+
126
+ if (symbolMap.packageName === packageName) {
127
+ const fullPath = path.join(moduleRoot, symbolMap.packagePath);
128
+ packagePathMap[Number.parseInt(id)] = fullPath;
129
+ }
130
+ }
131
+ }
132
+
133
+ return packagePathMap
134
+ }
135
+
136
+ private findPackageJsonPaths(dir: string): string[] {
137
+ let results: string[] = [];
138
+ const list = fs.readdirSync(dir);
139
+
140
+ for (const file of list) {
141
+ const filePath = path.join(dir, file);
142
+ const stat = fs.statSync(filePath);
143
+ if (stat && stat.isDirectory()) {
144
+ results = results.concat(this.findPackageJsonPaths(filePath));
145
+ } else if (file === 'package.json') {
146
+ results.push(filePath);
147
+ }
148
+ }
149
+ return results;
150
+ }
151
+ }
152
+
153
+ export function typedocToUniform(
154
+ rootPath: string,
155
+ project: JSONOutput.ProjectReflection
156
+ ): Reference[] {
157
+ const references: Reference[] = []
158
+ const transformer = new Transformer(
159
+ rootPath,
160
+ project,
161
+ references
162
+ )
163
+ const signatureTextLoader = new TypeDocSignatureTextLoader(
164
+ project,
165
+ transformer.packagePathMap,
166
+ )
167
+ transformer.signatureTextLoader = signatureTextLoader
168
+
169
+ // TODO: in the future abstraction?
170
+ if (project.kind !== ReflectionKind.Project) {
171
+ throw new Error('Project reflection expected');
172
+ }
173
+
174
+ for (const child of project.children || []) {
175
+ if (!("kind" in child)) {
176
+ throw new Error('(typedocToUniform): Child reflection expected in project childrens');
177
+ }
178
+
179
+ if (typeof child.kind != "number") {
180
+ throw new Error('(typedocToUniform): Child reflection kind expected to be a number');
181
+ }
182
+
183
+ if (!(child.kind satisfies ReflectionKind)) {
184
+ throw new Error('(typedocToUniform): Child reflection kind expected to be a valid ReflectionKind');
185
+ }
186
+
187
+ const kind = child.kind as ReflectionKind
188
+
189
+ switch (kind) {
190
+ case ReflectionKind.Module: {
191
+ const container = child as ContainerReflection
192
+
193
+ for (const group of container.children || []) {
194
+ const ref = typedocGroupToUniform.call(
195
+ transformer,
196
+ group
197
+ )
198
+
199
+ if (!ref) {
200
+ continue
201
+ }
202
+
203
+ ref.context = {
204
+ ...ref.context,
205
+ package: container.name,
206
+ }
207
+ references.push(ref)
208
+ }
209
+
210
+ break
211
+ }
212
+
213
+ case ReflectionKind.Function:
214
+ case ReflectionKind.Class: {
215
+ if (!(child satisfies DeclarationReflection)) {
216
+ throw new Error('(typedocToUniform): Function reflection expected to be a DeclarationReflection');
217
+ }
218
+
219
+ const ref = typedocGroupToUniform.call(
220
+ transformer,
221
+ child as DeclarationReflection
222
+ )
223
+
224
+ if (!ref) {
225
+ break
226
+ }
227
+
228
+ ref.context = {
229
+ ...ref.context,
230
+ package: project.name,
231
+ }
232
+ references.push(ref)
233
+
234
+ break
235
+ }
236
+
237
+ default: {
238
+ console.warn("(typedocToUniform): Another children project kind not supported", child.kind)
239
+ }
240
+ }
241
+ }
242
+
243
+ return references
244
+ }
245
+
246
+ function typedocGroupToUniform(
247
+ this: Transformer,
248
+ group: DeclarationReflection
249
+ ) {
250
+ let ref: Reference | undefined
251
+
252
+ switch (group.kind) {
253
+ case ReflectionKind.Class: {
254
+ ref = jsClassToUniformRef.call(this, group)
255
+
256
+ break
257
+ }
258
+ case ReflectionKind.Function: {
259
+ ref = jsFunctionToUniformRef.call(this, group)
260
+
261
+ break
262
+ }
263
+ default: {
264
+ console.warn('(typedocGroupToUniform): Unhandled reflection kind', group.kind)
265
+ }
266
+ }
267
+
268
+ return ref
269
+ }
270
+
271
+ function jsClassToUniformRef(
272
+ this: Transformer,
273
+ dec: DeclarationReflection
274
+ ) {
275
+ const definitions: Definition[] = []
276
+
277
+ const ref: Reference = {
278
+ title: `Class ${dec.name}`,
279
+ canonical: `class-${dec.name}`,
280
+ description: '',
281
+ context: {},
282
+ examples: {
283
+ groups: []
284
+ },
285
+ definitions
286
+ }
287
+
288
+ const declarationCtx = declarationUniformContext.call(this, dec)
289
+ if (declarationCtx) {
290
+ ref.context = {
291
+ ...ref.context,
292
+ ...declarationCtx
293
+ }
294
+ }
295
+
296
+ if (dec.comment) {
297
+ const description = commentToUniform(dec.comment)
298
+ const group = (declarationCtx?.packageName.split('/') || [])
299
+ .map(name => `"${name}"`)
300
+ .join(",")
301
+
302
+ ref.description = `---
303
+ title: ${dec.name}
304
+ group: [${group}, Classes]
305
+ ---
306
+ ${description}`
307
+ }
308
+
309
+ // handle constructor
310
+ {
311
+ const constructor = dec.children?.find(child => child.name === 'constructor')
312
+ if (constructor?.signatures?.[0]) {
313
+ const constructorDef: Definition = {
314
+ title: 'Constructor',
315
+ properties: []
316
+ }
317
+
318
+ const constructorSign = constructor.signatures[0]
319
+ for (const param of constructorSign.parameters || []) {
320
+ if (!param.type) {
321
+ console.warn('(jsClassToUniformRef): Constructor parameter type not found', param.name)
322
+ continue
323
+ }
324
+
325
+ let description = ""
326
+ if (param.comment) {
327
+ description = commentToUniform(param.comment)
328
+ }
329
+ constructorDef.properties.push({
330
+ name: param.name,
331
+ type: someTypeToUniformType(param.type),
332
+ description
333
+ })
334
+ }
335
+ definitions.push(constructorDef)
336
+ }
337
+ }
338
+
339
+ // handle methods
340
+ {
341
+ const methods = dec.children?.filter(child =>
342
+ child.kind === ReflectionKind.Method && child.name !== 'constructor'
343
+ ) || []
344
+
345
+ if (methods.length > 0) {
346
+ const methodsDef: Definition = {
347
+ title: 'Methods',
348
+ properties: []
349
+ }
350
+
351
+ for (const method of methods) {
352
+ if (!method.signatures?.[0]) continue
353
+
354
+ const methodSign = method.signatures[0]
355
+ let methodDesc = ""
356
+ if (methodSign.comment) {
357
+ methodDesc = commentToUniform(methodSign.comment)
358
+ }
359
+
360
+ methodsDef.properties.push({
361
+ name: method.name,
362
+ type: methodSign.type ? someTypeToUniformType(methodSign.type) : "void",
363
+ description: methodDesc
364
+ })
365
+ }
366
+
367
+ definitions.push(methodsDef)
368
+ }
369
+ }
370
+
371
+ return ref
372
+ }
373
+
374
+ function jsFunctionToUniformRef(
375
+ this: Transformer,
376
+ dec: DeclarationReflection
377
+ ) {
378
+ const definitions: Definition[] = []
379
+ const ref: Reference = {
380
+ title: `Function ${dec.name}`,
381
+ canonical: `fn-${dec.name}`,
382
+ description: '',
383
+ context: {},
384
+ examples: {
385
+ groups: [],
386
+ },
387
+ definitions,
388
+ }
389
+
390
+ const declarationCtx = declarationUniformContext.call(this, dec)
391
+ if (declarationCtx) {
392
+ ref.context = {
393
+ ...ref.context,
394
+ ...declarationCtx
395
+ }
396
+ }
397
+
398
+ const signatures = dec.signatures || []
399
+ if (signatures.length > 1) {
400
+ console.error('(jsFunctionToUniformRef): Multiple signatures not supported for function declaration', dec.name)
401
+ return
402
+ }
403
+
404
+ for (const sign of dec.signatures || []) {
405
+ {
406
+ if (sign.comment) {
407
+ const description = commentToUniform(sign.comment)
408
+ const group = (declarationCtx?.packageName.split('/') || [])
409
+ .map(name => `"${name}"`)
410
+ .join(",")
411
+
412
+ ref.description = `---
413
+ title: ${dec.name}
414
+ group: [${group}, Functions]
415
+ ---
416
+ ${description}`
417
+ }
418
+ }
419
+
420
+ // handle returns
421
+ {
422
+ const returnsUniformDef: Definition = {
423
+ title: 'Returns',
424
+ properties: [],
425
+ }
426
+
427
+ if (sign.type) {
428
+ let desc = ""
429
+
430
+ if (sign.comment) {
431
+ desc = returnCommentToUniform(sign.comment) || ""
432
+ }
433
+ returnsUniformDef.properties.push({
434
+ name: "",
435
+ type: someTypeToUniformType(sign.type),
436
+ description: desc
437
+ })
438
+ }
439
+
440
+ ref.definitions.push(returnsUniformDef)
441
+ }
442
+
443
+ // handle parameters
444
+ {
445
+ const parametersUniformDef: Definition = {
446
+ title: 'Parameters',
447
+ properties: [],
448
+ }
449
+
450
+ for (const param of sign.parameters || []) {
451
+ if (!param.type) {
452
+ console.warn('(jsFunctionToUniformRef): Parameter type not found', param.name)
453
+ continue
454
+ }
455
+
456
+ let description = ""
457
+ if (param.comment) {
458
+ description = commentToUniform(param.comment)
459
+ }
460
+ parametersUniformDef.properties.push({
461
+ name: param.name,
462
+ type: someTypeToUniformType(param.type),
463
+ description
464
+ })
465
+ }
466
+
467
+ ref.definitions.push(parametersUniformDef)
468
+ }
469
+ }
470
+
471
+ return ref
472
+ }
473
+
474
+
475
+ function declarationUniformContext(
476
+ this: Transformer,
477
+ dec: DeclarationReflection,
478
+ ) {
479
+ if (!dec.sources || !dec.sources.length) {
480
+ return
481
+ }
482
+
483
+ if (dec.sources.length > 1) {
484
+ console.warn('(declarationUniformContext): Multiple sources not supported for function declaration', dec.name)
485
+ return
486
+ }
487
+
488
+ const source = dec.sources[0]
489
+ if (!source.fileName) {
490
+ return
491
+ }
492
+
493
+ const signTxt = this.signatureTextLoader.signatureText(
494
+ dec.id,
495
+ source.line
496
+ ) || ""
497
+
498
+ const sourceCode = this.signatureTextLoader.signatureSourceCode(
499
+ dec.id,
500
+ source.line
501
+ ) || ""
502
+
503
+ // Get the symbol map to find the package path
504
+ const symbolMap = this.project.symbolIdMap[dec.id] as ReflectionSymbolId
505
+ if (!symbolMap) {
506
+ console.warn('(declarationUniformContext): Symbol not found', dec.id)
507
+ return
508
+ }
509
+
510
+ // Use the packagePath directly as it's already relative to the module root
511
+ const fileFullPath = symbolMap.packagePath
512
+
513
+ return {
514
+ packageName: symbolMap.packageName,
515
+ fileName: source.fileName,
516
+ fileFullPath,
517
+ line: source.line,
518
+ col: source.character,
519
+ signatureText: {
520
+ code: signTxt,
521
+ lang: "ts",
522
+ },
523
+ sourcecode: {
524
+ code: sourceCode,
525
+ lang: "ts"
526
+ }
527
+ }
528
+ }
529
+
530
+ function someTypeToUniformType(someType: SomeType) {
531
+ if (!("name" in someType)) {
532
+ console.warn('SomeType does not have name property', someType)
533
+ return ""
534
+ }
535
+
536
+ switch (someType.type) {
537
+ case "reference": {
538
+ // TODO: abstract definition properties like GenericDefinitionProperty extends DefinitionProperty?
539
+ return `<${someType.name}>`
540
+ }
541
+ default: {
542
+ return someType.name
543
+ }
544
+ }
545
+ }
546
+
547
+ function commentToUniform(comment: Comment) {
548
+ let desc = ""
549
+
550
+ for (const summary of comment?.summary || []) {
551
+ desc += `${summary.text}\n`
552
+ }
553
+
554
+ return desc
555
+ }
556
+
557
+ function returnCommentToUniform(comment: Comment) {
558
+ if (!comment.blockTags || !comment.blockTags.length) {
559
+ return
560
+ }
561
+
562
+ let desc = ""
563
+ for (const tag of comment.blockTags) {
564
+ if (tag.tag === "@returns") {
565
+ for (const content of tag.content || []) {
566
+ desc += `${content.text}\n`
567
+ }
568
+ }
569
+ }
570
+
571
+ return desc
572
+ }
package/src/index.ts ADDED
@@ -0,0 +1,45 @@
1
+ import * as TypeDoc from 'typedoc';
2
+ import type {TypeDocOptions} from "typedoc";
3
+
4
+ import {
5
+ typedocToUniform
6
+ } from "./TypeDocTransformer"
7
+
8
+ export async function sourcesToUniform(
9
+ root: string,
10
+ entryPoints: string[],
11
+ ) {
12
+ // TODO: support another strategies
13
+ // TODO: support entry points from github?
14
+ const options = {
15
+ entryPoints,
16
+ plugin: [],
17
+ readme: "none",
18
+ disableSources: "true",
19
+ entryPointStrategy: TypeDoc.EntryPointStrategy.Packages,
20
+ } satisfies Partial<TypeDocOptions>;
21
+
22
+ const app = await TypeDoc.Application.bootstrapWithPlugins(options);
23
+ const project = await app.convert()
24
+ if (!project) {
25
+ console.error('Failed to generate documentation.');
26
+ return
27
+ }
28
+
29
+ const jsonOutput = await app.serializer.projectToObject(project, root);
30
+ const projectJson = jsonOutput as unknown as TypeDoc.JSONOutput.ProjectReflection;
31
+
32
+ // TODO: do better validation
33
+ if (!projectJson.schemaVersion || !projectJson.children || !projectJson.children.length) {
34
+ console.error('Failed to generate documentation.');
35
+ return
36
+ }
37
+
38
+ const ref = typedocToUniform(root, projectJson)
39
+ if (!ref) {
40
+ console.error('Failed to generate documentation.');
41
+ return
42
+ }
43
+
44
+ return ref
45
+ }
@@ -0,0 +1,62 @@
1
+ import * as fs from "node:fs";
2
+ import * as path from "node:path";
3
+
4
+ import * as TypeDoc from 'typedoc';
5
+ import type {TypeDocOptions} from "typedoc";
6
+ //@ts-ignore
7
+ import {PluginOptions} from 'typedoc-plugin-markdown'
8
+
9
+ import {typedocToUniform} from "../src/TypeDocTransformer"
10
+
11
+ async function generateDocs() {
12
+ const options = {
13
+ entryPoints: [
14
+ "example/package-a",
15
+ // "example/package-b",
16
+ ],
17
+ plugin: [
18
+ "typedoc-plugin-markdown"
19
+ ],
20
+ readme: "none",
21
+ disableSources: true,
22
+ entryPointStrategy: TypeDoc.EntryPointStrategy.Packages,
23
+
24
+ indexFormat: "table",
25
+ useCodeBlocks: true,
26
+ } satisfies Partial<PluginOptions | TypeDocOptions>;
27
+
28
+ const app = await TypeDoc.Application.bootstrapWithPlugins(options);
29
+ const project = await app.convert()
30
+
31
+ if (!project) {
32
+ throw new Error('Failed to generate documentation.');
33
+ }
34
+
35
+ const jsonOutput = await app.serializer.projectToObject(project, path.resolve("./example"));
36
+ fs.writeFileSync('docs.json', JSON.stringify(jsonOutput, null, 2));
37
+
38
+ await app.generateOutputs(project);
39
+ // await app.generateJson(project, 'docs.json');
40
+
41
+ {
42
+ const projectRaw = fs.readFileSync('docs.json', 'utf-8');
43
+ const projectJson = JSON.parse(projectRaw);
44
+ const ref = typedocToUniform(
45
+ path.resolve("./example"),
46
+ projectJson
47
+ );
48
+
49
+ fs.writeFileSync('references_todo.json', JSON.stringify(ref, null, 2));
50
+ }
51
+
52
+ // {
53
+ // const loader = new SignatureTextLoader(
54
+ // path.resolve('./example/package-a/src/index.ts')
55
+ // );
56
+ //
57
+ // // TODO: some issues if line number higher than file lines
58
+ // const signature = signatureTextByLine(loader, 15);
59
+ // }
60
+ }
61
+
62
+ generateDocs().catch(console.error);
package/tsconfig.json ADDED
@@ -0,0 +1,30 @@
1
+ {
2
+ "compilerOptions": {
3
+ "paths": {
4
+ "@xyd-js/uniform": [
5
+ "../xyd-uniform"
6
+ ]
7
+ },
8
+ "target": "ES2020",
9
+ "module": "ESNext",
10
+ "moduleResolution": "node",
11
+ "strict": true,
12
+ "esModuleInterop": true,
13
+ "skipLibCheck": true,
14
+ "forceConsistentCasingInFileNames": true,
15
+ "outDir": "./dist",
16
+ "declaration": true,
17
+ "declarationMap": true,
18
+ "incremental": true,
19
+ "tsBuildInfoFile": "./dist/tsconfig.tsbuildinfo",
20
+ "resolveJsonModule": true
21
+ // Add this line
22
+ },
23
+ "include": [
24
+ "src/**/*.ts"
25
+ ],
26
+ "exclude": [
27
+ "node_modules",
28
+ "dist"
29
+ ]
30
+ }