1import * as Configuration from "@effect/docgen/Configuration"
2import * as Domain from "@effect/docgen/Domain"
3import * as Parser from "@effect/docgen/Parser"
4import * as Printer from "@effect/docgen/Printer"
5import { assert, describe, it } from "@effect/vitest"
6import { Effect, Exit, Predicate } from "effect"
7import * as Path from "effect/Path"
8import * as ast from "ts-morph"
9
10let testCounter = 0
11
12const project = new ast.Project({
13 compilerOptions: { strict: true },
14 useInMemoryFileSystem: true
15})
16
17const defaultConfig: Configuration.ConfigurationShape = {
18 projectName: "docgen",
19 projectHomepage: "https://github.com/effect-ts/docgen",
20 srcLink: "https://github.com/effect-ts/docgen/blob/main/src/",
21 srcDir: "src",
22 outDir: "docs",
23 theme: "mikearnaldi/just-the-docs",
24 enableSearch: true,
25 enforceDescriptions: false,
26 enforceExamples: false,
27 enforceVersion: true,
28 runExamples: false,
29 tscExecutable: "tsc",
30 exclude: [],
31 parseCompilerOptions: {},
32 examplesCompilerOptions: {}
33}
34
35const makeSourcefile = (source: string | ast.SourceFile) => {
36 if (Predicate.isString(source)) {
37 const filename = `test.ts`
38 const existing = project.getSourceFile(filename)
39 if (existing) {
40 project.removeSourceFile(existing)
41 }
42 return project.createSourceFile(filename, source)
43 }
44 return source
45}
46
47const makeSource = (source: string | ast.SourceFile) => {
48 const sourceFile = makeSourcefile(source)
49 const filename = sourceFile.getBaseName()
50 return Parser.Source.of({
51 path: [filename],
52 sourceFile
53 })
54}
55
56const print = (printables: ReadonlyArray<Printer.Printable>) => {
57 return Effect.gen(function*() {
58 const strings = yield* Effect.forEach(printables, (printable) => Printer.print(printable))
59 return strings.join("\n")
60 })
61}
62
63const isModule = (printableOr: ReadonlyArray<Printer.Printable> | Domain.Module): printableOr is Domain.Module => {
64 return !Array.isArray(printableOr)
65}
66
67const expectMarkdown = Effect.fnUntraced(function*<E>(
68 eff: Effect.Effect<
69 ReadonlyArray<Printer.Printable> | Domain.Module,
70 E,
71 Parser.Source | Configuration.Configuration | Path.Path
72 >,
73 sourceText: string,
74 expected: string,
75 config?: Partial<Configuration.ConfigurationShape>
76) {
77 const exit = yield* Effect.exit(eff.pipe(
78 Effect.flatMap((printableOr) => {
79 if (isModule(printableOr)) {
80 return Printer.printModule(printableOr)
81 }
82 return print(printableOr)
83 }),
84 Effect.provideService(Parser.Source, makeSource(sourceText)),
85 Effect.provideService(Configuration.Configuration, { ...defaultConfig, ...config }),
86 Effect.provide(Path.layer)
87 ))
88 assert.ok(exit._tag === "Success")
89 if (exit.value !== expected) {
90 console.log(exit.value)
91 }
92 assert.strictEqual(exit.value, expected)
93})
94
95describe("Parser", () => {
96 describe("parseModule", () => {
97 it.effect("should not require an example for modules when `enforceExamples` is set to true", () =>
98 Effect.gen(function*() {
99 yield* expectMarkdown(
100 Parser.parseModule,
101 `/**
102* This is the assert module.
103*
104* @since 1.0.0
105*/
106import * as assert from 'assert'
107
108/**
109 * This is the foo export.
110 *
111 * @example
112 * import { foo } from 'test'
113 *
114 * console.log(foo)
115 *
116 * @category category
117 * @since 1.0.0
118 */
119export const foo = 'foo'`,
120 `## test.ts overview
121
122This is the assert module.
123
124Since v1.0.0
125
126<!-- toc -->
127
128# category
129
130## foo
131
132This is the foo export.
133
134**Example**
135
136\`\`\`ts
137import { foo } from 'test'
138
139console.log(foo)
140\`\`\`
141
142**Signature**
143
144\`\`\`ts
145declare const foo: "foo"
146\`\`\`
147
148[Source](https://github.com/effect-ts/docgen/blob/main/src/test.ts#L19)
149
150Since v1.0.0`
151 )
152 }))
153
154 it.effect("should ignore non-JSDoc comments above JSDoc comments", () =>
155 Effect.gen(function*() {
156 yield* expectMarkdown(
157 Parser.parseModule,
158 `/**
159* This is the assert module.
160*
161* @since 1.0.0
162*/
163import * as assert from 'assert'
164
165// This comment should be ignored
166
167/**
168 * This is the foo export.
169 *
170 * @example
171 * import { foo } from 'test'
172 *
173 * console.log(foo)
174 *
175 * @category category
176 * @since 1.0.0
177 */
178export const foo = 'foo'`,
179 `## test.ts overview
180
181This is the assert module.
182
183Since v1.0.0
184
185<!-- toc -->
186
187# category
188
189## foo
190
191This is the foo export.
192
193**Example**
194
195\`\`\`ts
196import { foo } from 'test'
197
198console.log(foo)
199\`\`\`
200
201**Signature**
202
203\`\`\`ts
204declare const foo: "foo"
205\`\`\`
206
207[Source](https://github.com/effect-ts/docgen/blob/main/src/test.ts#L21)
208
209Since v1.0.0`
210 )
211 }))
212 })
213
214 describe("parseFunctions", () => {
215 it.effect("omits internal properties from signatures", () =>
216 Effect.gen(function*() {
217 yield* expectMarkdown(
218 Parser.parseFunctions,
219 `/**
220 * @since 1.0.0
221 */
222 export const myfunc: (options: {
223 readonly visible?: string
224 /** @internal */
225 readonly internal?: boolean
226 }) => void = () => {}`,
227 `## myfunc
228
229**Signature**
230
231\`\`\`ts
232declare const myfunc: (options: { readonly visible?: string; }) => void
233\`\`\`
234
235[Source](https://github.com/effect-ts/docgen/blob/main/src/test.ts#L4)
236
237Since v1.0.0`
238 )
239 }))
240
241 it.effect(`should remove all metadata from typedcript code blocks when the theme is ${Configuration.DEFAULT_THEME}`, () =>
242 Effect.gen(function*() {
243 yield* expectMarkdown(
244 Parser.parseFunctions,
245 `/**
246 * \`\`\`ts skip-type-checking a=1 showLineNumbers=true
247 * const a: string = 1
248 * \`\`\`
249 *
250 * @since 1.0.0
251 */
252 export function myfunc<A>() {}`,
253 `## myfunc
254
255\`\`\`ts
256const a: string = 1
257\`\`\`
258
259**Signature**
260
261\`\`\`ts
262declare const myfunc: <A>() => void
263\`\`\`
264
265[Source](https://github.com/effect-ts/docgen/blob/main/src/test.ts#L8)
266
267Since v1.0.0`
268 )
269 }))
270 it.effect("generics", () =>
271 Effect.gen(function*() {
272 yield* expectMarkdown(
273 Parser.parseFunctions,
274 `/**
275 * This is a description containing two links to {@link foo} and {@link bar}.
276 *
277 * @since 1.2.0
278 */
279 export function myfunc<A>() {}`,
280 `## myfunc
281
282This is a description containing two links to \`foo\` and \`bar\`.
283
284**Signature**
285
286\`\`\`ts
287declare const myfunc: <A>() => void
288\`\`\`
289
290[Source](https://github.com/effect-ts/docgen/blob/main/src/test.ts#L6)
291
292Since v1.2.0`
293 )
294 }))
295
296 it.effect("description", () =>
297 Effect.gen(function*() {
298 yield* expectMarkdown(
299 Parser.parseFunctions,
300 `/**
301 * This is a description containing two links to {@link foo} and {@link bar}.
302 *
303 * @since 1.2.0
304 */
305 export function myfunc() {}`,
306 `## myfunc
307
308This is a description containing two links to \`foo\` and \`bar\`.
309
310**Signature**
311
312\`\`\`ts
313declare const myfunc: () => void
314\`\`\`
315
316[Source](https://github.com/effect-ts/docgen/blob/main/src/test.ts#L6)
317
318Since v1.2.0`
319 )
320 }))
321
322 it.effect("throws", () =>
323 Effect.gen(function*() {
324 yield* expectMarkdown(
325 Parser.parseFunctions,
326 `/**
327 * description...
328 * @throws \`Error1\` - Description 1
329 * @throws \`Error2\` - Description 2
330 * @since 1.2.0
331 */
332 export function myfunc() {}`,
333 `## myfunc
334
335description...
336
337**Throws**
338
339\`Error1\` - Description 1
340\`Error2\` - Description 2
341
342**Signature**
343
344\`\`\`ts
345declare const myfunc: () => void
346\`\`\`
347
348[Source](https://github.com/effect-ts/docgen/blob/main/src/test.ts#L7)
349
350Since v1.2.0`
351 )
352 }))
353
354 it.effect("sees", () =>
355 Effect.gen(function*() {
356 yield* expectMarkdown(
357 Parser.parseFunctions,
358 `/**
359 * description...
360 * @see \`foo\` Description 1
361 * @see {@link bar} Description 2
362 * @see {@link baz quux} Description 2
363 * @since 1.2.0
364 */
365 export function myfunc() {}`,
366 `## myfunc
367
368description...
369
370**See**
371
372- \`foo\` Description 1
373- \`bar\` Description 2
374- \`quux\` Description 2
375
376**Signature**
377
378\`\`\`ts
379declare const myfunc: () => void
380\`\`\`
381
382[Source](https://github.com/effect-ts/docgen/blob/main/src/test.ts#L8)
383
384Since v1.2.0`
385 )
386 }))
387
388 it.effect("example without fence", () =>
389 Effect.gen(function*() {
390 yield* expectMarkdown(
391 Parser.parseFunctions,
392 `/**
393 * description...
394 * @example
395 * const x = 1
396 * @since 1.0.0
397 */
398 export function myfunc() {}`,
399 `## myfunc
400
401description...
402
403**Example**
404
405\`\`\`ts
406const x = 1
407\`\`\`
408
409**Signature**
410
411\`\`\`ts
412declare const myfunc: () => void
413\`\`\`
414
415[Source](https://github.com/effect-ts/docgen/blob/main/src/test.ts#L7)
416
417Since v1.0.0`
418 )
419 }))
420
421 it.effect("example with backtick fence", () =>
422 Effect.gen(function*() {
423 yield* expectMarkdown(
424 Parser.parseFunctions,
425 `/**
426 * description...
427 * @example
428 * \`\`\`ts
429 * const x = 1
430 * \`\`\`
431 * @since 1.0.0
432 */
433 export function myfunc() {}`,
434 `## myfunc
435
436description...
437
438**Example**
439
440\`\`\`ts
441const x = 1
442\`\`\`
443
444**Signature**
445
446\`\`\`ts
447declare const myfunc: () => void
448\`\`\`
449
450[Source](https://github.com/effect-ts/docgen/blob/main/src/test.ts#L9)
451
452Since v1.0.0`
453 )
454 }))
455
456 it.effect("2 examples", () =>
457 Effect.gen(function*() {
458 yield* expectMarkdown(
459 Parser.parseFunctions,
460 `/**
461 * description...
462 * @example
463 * \`\`\`ts
464 * const x = 1
465 * \`\`\`
466 * @example
467 * \`\`\`ts
468 * const x = 2
469 * \`\`\`
470 * @since 1.0.0
471 */
472 export function myfunc() {}`,
473 `## myfunc
474
475description...
476
477**Example**
478
479\`\`\`ts
480const x = 1
481\`\`\`
482
483**Example**
484
485\`\`\`ts
486const x = 2
487\`\`\`
488
489**Signature**
490
491\`\`\`ts
492declare const myfunc: () => void
493\`\`\`
494
495[Source](https://github.com/effect-ts/docgen/blob/main/src/test.ts#L13)
496
497Since v1.0.0`
498 )
499 }))
500
501 it.effect("example with metas", () =>
502 Effect.gen(function*() {
503 yield* expectMarkdown(
504 Parser.parseFunctions,
505 `/**
506 * description...
507 * @example
508 * \`\`\`ts a=1
509 * const x = 1
510 * \`\`\`
511 * @since 1.0.0
512 */
513 export function myfunc() {}`,
514 `## myfunc
515
516description...
517
518**Example**
519
520\`\`\`ts a=1
521const x = 1
522\`\`\`
523
524**Signature**
525
526\`\`\`ts
527declare const myfunc: () => void
528\`\`\`
529
530[Source](https://github.com/effect-ts/docgen/blob/main/src/test.ts#L9)
531
532Since v1.0.0`
533 )
534 }))
535
536 it.effect("example with titde fence", () =>
537 Effect.gen(function*() {
538 yield* expectMarkdown(
539 Parser.parseFunctions,
540 `/**
541 * description...
542 * @example
543 * ~~~ts
544 * const x = 1
545 * ~~~
546 * @since 1.0.0
547 */
548 export function myfunc() {}`,
549 `## myfunc
550
551description...
552
553**Example**
554
555~~~ts
556const x = 1
557~~~
558
559**Signature**
560
561\`\`\`ts
562declare const myfunc: () => void
563\`\`\`
564
565[Source](https://github.com/effect-ts/docgen/blob/main/src/test.ts#L9)
566
567Since v1.0.0`
568 )
569 }))
570
571 it.effect("should not return private function declarations", () =>
572 Effect.gen(function*() {
573 yield* expectMarkdown(
574 Parser.parseFunctions,
575 `/**
576 * description...
577 */
578 function myfunc() {}`,
579 ""
580 )
581 }))
582
583 it.effect("should not return ignored function declarations", () =>
584 Effect.gen(function*() {
585 yield* expectMarkdown(
586 Parser.parseFunctions,
587 `/**
588 * @ignore
589 */
590 export function myfunc() {}`,
591 ""
592 )
593 }))
594
595 it.effect("should not return ignored function declarations with overloads", () =>
596 Effect.gen(function*() {
597 yield* expectMarkdown(
598 Parser.parseFunctions,
599 `/**
600 * @ignore
601 */
602 export function sum(a: number, b: number)
603 export function sum(a: number, b: number): number { return a + b }`,
604 ""
605 )
606 }))
607
608 it.effect("should not return internal function declarations", () =>
609 Effect.gen(function*() {
610 yield* expectMarkdown(
611 Parser.parseFunctions,
612 `/**
613 * @internal
614 */
615 export function sum(a: number, b: number): number { return a + b }`,
616 ""
617 )
618 }))
619
620 it.effect("should not return internal function declarations even with overloads", () =>
621 Effect.gen(function*() {
622 yield* expectMarkdown(
623 Parser.parseFunctions,
624 `/**
625 * @internal
626 */
627 export function sum(a: number, b: number)
628 export function sum(a: number, b: number): number { return a + b }`,
629 ""
630 )
631 }))
632
633 it.effect("should not return private const function declarations", () =>
634 Effect.gen(function*() {
635 yield* expectMarkdown(
636 Parser.parseFunctions,
637 `const sum = (a: number, b: number): number => a + b `,
638 ""
639 )
640 }))
641
642 it.effect("should not return internal const function declarations", () =>
643 Effect.gen(function*() {
644 yield* expectMarkdown(
645 Parser.parseFunctions,
646 `/**
647 * @internal
648 */
649 export const sum = (a: number, b: number): number => a + b `,
650 ""
651 )
652 }))
653
654 it.effect("should account for nullable polymorphic return types", () =>
655 Effect.gen(function*() {
656 yield* expectMarkdown(
657 Parser.parseFunctions,
658 `/**
659 * @since 1.0.0
660 */
661 export const toNullable = <A>(ma: A | null): A | null => ma`,
662 `## toNullable
663
664**Signature**
665
666\`\`\`ts
667declare const toNullable: <A>(ma: A | null) => A | null
668\`\`\`
669
670[Source](https://github.com/effect-ts/docgen/blob/main/src/test.ts#L4)
671
672Since v1.0.0`
673 )
674 }))
675
676 it.effect("should handle a const function declaration", () =>
677 Effect.gen(function*() {
678 yield* expectMarkdown(
679 Parser.parseFunctions,
680 `/**
681 * a description...
682 * @since 1.0.0
683 * @example
684 * assert.deepStrictEqual(f(1, 2), { a: 1, b: 2 })
685 * @example
686 * assert.deepStrictEqual(f(3, 4), { a: 3, b: 4 })
687 * @deprecated
688 */
689 export const f = (a: number, b: number): { [key: string]: number } => ({ a, b })`,
690 `## ~~f~~
691
692a description...
693
694**Example**
695
696\`\`\`ts
697assert.deepStrictEqual(f(1, 2), { a: 1, b: 2 })
698\`\`\`
699
700**Example**
701
702\`\`\`ts
703assert.deepStrictEqual(f(3, 4), { a: 3, b: 4 })
704\`\`\`
705
706**Signature**
707
708\`\`\`ts
709declare const f: (a: number, b: number) => { [key: string]: number; }
710\`\`\`
711
712[Source](https://github.com/effect-ts/docgen/blob/main/src/test.ts#L10)
713
714Since v1.0.0`
715 )
716 }))
717
718 it.effect("should handle a function declaration", () =>
719 Effect.gen(function*() {
720 yield* expectMarkdown(
721 Parser.parseFunctions,
722 `/**
723 * @since 1.0.0
724 */
725 export function f(a: number, b: number): { [key: string]: number } { return { a, b } }`,
726 `## f
727
728**Signature**
729
730\`\`\`ts
731declare const f: (a: number, b: number) => { [key: string]: number; }
732\`\`\`
733
734[Source](https://github.com/effect-ts/docgen/blob/main/src/test.ts#L4)
735
736Since v1.0.0`
737 )
738 }))
739
740 it.effect("should handle overloadings", () =>
741 Effect.gen(function*() {
742 yield* expectMarkdown(
743 Parser.parseFunctions,
744 `/**
745 * a description...
746 * @since 1.0.0
747 * @deprecated
748 */
749 export function f(a: Int, b: Int): { [key: string]: number }
750 export function f(a: number, b: number): { [key: string]: number }
751 export function f(a: any, b: any): { [key: string]: number } { return { a, b } }`,
752 `## ~~f~~
753
754a description...
755
756**Signature**
757
758\`\`\`ts
759declare const f: { (a: Int, b: Int): { [key: string]: number; }; (a: number, b: number): { [key: string]: number; }; }
760\`\`\`
761
762[Source](https://github.com/effect-ts/docgen/blob/main/src/test.ts#L8)
763
764Since v1.0.0`
765 )
766 }))
767 })
768
769 describe("parseConstants", () => {
770 it.effect("should handle a constant value", () =>
771 Effect.gen(function*() {
772 yield* expectMarkdown(
773 Parser.parseConstants,
774 `/**
775 * a description...
776 * @since 1.0.0
777 * @deprecated
778 */
779 export const s: string = ''`,
780 `## ~~s~~
781
782a description...
783
784**Signature**
785
786\`\`\`ts
787declare const s: string
788\`\`\`
789
790[Source](https://github.com/effect-ts/docgen/blob/main/src/test.ts#L6)
791
792Since v1.0.0`
793 )
794 }))
795
796 it.effect("should support constants with default type parameters", () =>
797 Effect.gen(function*() {
798 yield* expectMarkdown(
799 Parser.parseConstants,
800 `/**
801 * @since 1.0.0
802 */
803 export const left: <E = never, A = never>(l: E) => string = T.left`,
804 `## left
805
806**Signature**
807
808\`\`\`ts
809declare const left: <E = never, A = never>(l: E) => string
810\`\`\`
811
812[Source](https://github.com/effect-ts/docgen/blob/main/src/test.ts#L4)
813
814Since v1.0.0`
815 )
816 }))
817
818 it.effect("should support untyped constants", () =>
819 Effect.gen(function*() {
820 yield* expectMarkdown(
821 Parser.parseConstants,
822 `
823 class A {}
824 /**
825 * @since 1.0.0
826 */
827 export const empty = new A()`,
828 `## empty
829
830**Signature**
831
832\`\`\`ts
833declare const empty: A
834\`\`\`
835
836[Source](https://github.com/effect-ts/docgen/blob/main/src/test.ts#L6)
837
838Since v1.0.0`
839 )
840 }))
841
842 it.effect("should handle constants with typeof annotations", () =>
843 Effect.gen(function*() {
844 yield* expectMarkdown(
845 Parser.parseConstants,
846 ` const task: { a: number } = {
847 a: 1
848 }
849 /**
850 * @since 1.0.0
851 */
852 export const taskSeq: typeof task = {
853 ...task,
854 ap: (mab, ma) => () => mab().then(f => ma().then(a => f(a)))
855 }`,
856 `## taskSeq
857
858**Signature**
859
860\`\`\`ts
861declare const taskSeq: { a: number; }
862\`\`\`
863
864[Source](https://github.com/effect-ts/docgen/blob/main/src/test.ts#L7)
865
866Since v1.0.0`
867 )
868 }))
869
870 it.effect("should not include variables declared in for loops", () =>
871 Effect.gen(function*() {
872 yield* expectMarkdown(
873 Parser.parseConstants,
874 ` const object = { a: 1, b: 2, c: 3 };
875
876 for (const property in object) {
877 console.log(property);
878 }`,
879 ""
880 )
881 }))
882 })
883
884 describe("parseTypeAliases", () => {
885 it.effect("should return a type alias", () =>
886 Effect.gen(function*() {
887 yield* expectMarkdown(
888 Parser.parseTypeAliases,
889 `
890 type None<A> = { readonly _tag: "None" }
891 type Some<A> = { readonly _tag: "Some"; readonly value: A }
892 /**
893 * a description...
894 * @since 1.0.0
895 * @deprecated
896 */
897 export type Option<A> = None<A> | Some<A>`,
898 `## ~~Option~~ (type alias)
899
900a description...
901
902**Signature**
903
904\`\`\`ts
905type Option<A> = None<A> | Some<A>
906\`\`\`
907
908[Source](https://github.com/effect-ts/docgen/blob/main/src/test.ts#L9)
909
910Since v1.0.0`
911 )
912 }))
913 })
914
915 describe("parseExports", () => {
916 it.effect("should return no exports if the file is empty", () =>
917 Effect.gen(function*() {
918 yield* expectMarkdown(
919 Parser.parseExports,
920 "",
921 ""
922 )
923 }))
924
925 it.effect("should return an `Export`", () =>
926 Effect.gen(function*() {
927 yield* expectMarkdown(
928 Parser.parseExports,
929 `
930 const a = 1;
931 const b = 2;
932 export {
933 /**
934 * description_of_a
935 * \`\`\`ts
936 * const a: string = 1
937 * \`\`\`
938 *
939 * @since 1.0.0
940 */
941 a,
942 /**
943 * description_of_b
944 * @since 2.0.0
945 */
946 b
947 }`,
948 `## a
949
950description_of_a
951\`\`\`ts
952const a: string = 1
953\`\`\`
954
955**Signature**
956
957\`\`\`ts
958declare const a: 1
959\`\`\`
960
961[Source](https://github.com/effect-ts/docgen/blob/main/src/test.ts#L13)
962
963Since v1.0.0
964## b
965
966description_of_b
967
968**Signature**
969
970\`\`\`ts
971declare const b: 2
972\`\`\`
973
974[Source](https://github.com/effect-ts/docgen/blob/main/src/test.ts#L18)
975
976Since v2.0.0`
977 )
978 }))
979
980 it.effect("should handle renamimg", () =>
981 Effect.gen(function*() {
982 yield* expectMarkdown(
983 Parser.parseExports,
984 `const a = 1;
985 export {
986 /**
987 * @since 1.0.0
988 */
989 a as b
990 }`,
991 `## b
992
993**Signature**
994
995\`\`\`ts
996declare const b: 1
997\`\`\`
998
999[Source](https://github.com/effect-ts/docgen/blob/main/src/test.ts#L6)
1000
1001Since v1.0.0`
1002 )
1003 }))
1004
1005 it.effect("should handle a single re-export", () =>
1006 Effect.gen(function*() {
1007 project.createSourceFile("a.ts", `export const a = 1`)
1008 const sourceFile = project.createSourceFile(
1009 "b.ts",
1010 `import { a } from './a'
1011 const b = a
1012 export {
1013 /**
1014 * @since 1.0.0
1015 */
1016 b
1017 }`
1018 )
1019 const actual = yield* Effect.exit(Parser.parseExports.pipe(
1020 Effect.provideService(Parser.Source, makeSource(sourceFile)),
1021 Effect.provideService(Configuration.Configuration, defaultConfig)
1022 ))
1023 assert.deepStrictEqual(
1024 actual,
1025 Exit.succeed([
1026 new Domain.Export(
1027 "b",
1028 new Domain.Doc(
1029 undefined,
1030 ["1.0.0"],
1031 [],
1032 [],
1033 [],
1034 [],
1035 [],
1036 {
1037 "since": ["1.0.0"]
1038 }
1039 ),
1040 "declare const b: 1",
1041 {
1042 "column": 11,
1043 "line": 7
1044 },
1045 false
1046 )
1047 ])
1048 )
1049 }))
1050
1051 it.effect("should handle `export * from ...`", () =>
1052 Effect.gen(function*() {
1053 project.createSourceFile("example.ts", `export const a = 1`, { overwrite: true })
1054
1055 const sourceFile = project.createSourceFile(
1056 "export-all.ts",
1057 `
1058 /**
1059 * @since 1.0.0
1060 */
1061 export * from './example'
1062 `
1063 )
1064
1065 const actual = yield* Effect.exit(Parser.parseExports.pipe(
1066 Effect.provideService(Parser.Source, makeSource(sourceFile)),
1067 Effect.provideService(Configuration.Configuration, defaultConfig)
1068 ))
1069
1070 assert.deepStrictEqual(
1071 actual,
1072 Exit.succeed([
1073 new Domain.Export(
1074 "'./example'",
1075 new Domain.Doc(
1076 "Re-exports all named exports from the './example' module.",
1077 ["1.0.0"],
1078 [],
1079 [],
1080 [],
1081 [],
1082 [],
1083 {
1084 "since": ["1.0.0"]
1085 }
1086 ),
1087 "export * from './example'",
1088 {
1089 "column": 10,
1090 "line": 5
1091 },
1092 true
1093 )
1094 ])
1095 )
1096 }))
1097
1098 it.effect("should handle `export * as ... from ...`", () =>
1099 Effect.gen(function*() {
1100 project.createSourceFile("example.ts", `export const a = 1`, { overwrite: true })
1101
1102 const sourceFile = project.createSourceFile(
1103 "export-all-namespace.ts",
1104 `
1105 /**
1106 * @since 1.0.0
1107 */
1108 export * as example from './example'
1109 `
1110 )
1111
1112 const actual = yield* Effect.exit(Parser.parseExports.pipe(
1113 Effect.provideService(Parser.Source, makeSource(sourceFile)),
1114 Effect.provideService(Configuration.Configuration, defaultConfig)
1115 ))
1116
1117 assert.deepStrictEqual(
1118 actual,
1119 Exit.succeed([
1120 new Domain.Export(
1121 "example",
1122 new Domain.Doc(
1123 "Re-exports all named exports from the './example' module as `example`.",
1124 ["1.0.0"],
1125 [],
1126 [],
1127 [],
1128 [],
1129 [],
1130 {
1131 "since": ["1.0.0"]
1132 }
1133 ),
1134 "export * as example from './example'",
1135 {
1136 "column": 11,
1137 "line": 5
1138 },
1139 true
1140 )
1141 ])
1142 )
1143 }))
1144 })
1145
1146 describe("parseInterfaces", () => {
1147 it.effect("should return no interfaces if the file is empty", () =>
1148 Effect.gen(function*() {
1149 yield* expectMarkdown(
1150 Parser.parseInterfaces,
1151 "",
1152 ""
1153 )
1154 }))
1155
1156 it.effect("should return no interfaces if there are no exported interfaces", () =>
1157 Effect.gen(function*() {
1158 yield* expectMarkdown(
1159 Parser.parseInterfaces,
1160 "interface A {}",
1161 ""
1162 )
1163 }))
1164
1165 it.effect("should return an interface", () =>
1166 Effect.gen(function*() {
1167 yield* expectMarkdown(
1168 Parser.parseInterfaces,
1169 `/**
1170 * a description...
1171 * @since 1.0.0
1172 * @deprecated
1173 */
1174 export interface A {}`,
1175 `## ~~A~~ (interface)
1176
1177a description...
1178
1179**Signature**
1180
1181\`\`\`ts
1182export interface A {}
1183\`\`\`
1184
1185[Source](https://github.com/effect-ts/docgen/blob/main/src/test.ts#L6)
1186
1187Since v1.0.0`
1188 )
1189 }))
1190 })
1191
1192 describe("parseNamespaces", () => {
1193 it.effect("should return no namespaces if the file is empty", () =>
1194 Effect.gen(function*() {
1195 yield* expectMarkdown(
1196 Parser.parseNamespaces,
1197 "",
1198 ""
1199 )
1200 }))
1201
1202 it.effect("should return no namespaces if there are no exported namespaces", () =>
1203 Effect.gen(function*() {
1204 yield* expectMarkdown(
1205 Parser.parseNamespaces,
1206 "namespace A {}",
1207 ""
1208 )
1209 }))
1210
1211 it.effect("should parse an empty Namespace", () =>
1212 Effect.gen(function*() {
1213 yield* expectMarkdown(
1214 Parser.parseNamespaces,
1215 `
1216 /**
1217 * @since 1.0.0
1218 */
1219 export namespace A {}
1220 `,
1221 `## A (namespace)
1222
1223[Source](https://github.com/effect-ts/docgen/blob/main/src/test.ts#L5)
1224
1225Since v1.0.0`
1226 )
1227 }))
1228
1229 describe("namespace > interfaces", () => {
1230 it.effect("should ignore not exported interfaces", () =>
1231 Effect.gen(function*() {
1232 yield* expectMarkdown(
1233 Parser.parseNamespaces,
1234 `
1235 /**
1236 * @since 1.0.0
1237 */
1238 export namespace A {
1239 interface C {}
1240 }
1241 `,
1242 `## A (namespace)
1243
1244[Source](https://github.com/effect-ts/docgen/blob/main/src/test.ts#L5)
1245
1246Since v1.0.0`
1247 )
1248 }))
1249
1250 it.effect("should parse an interface", () =>
1251 Effect.gen(function*() {
1252 yield* expectMarkdown(
1253 Parser.parseNamespaces,
1254 `
1255/**
1256 * @since 1.0.0
1257 */
1258export namespace A {
1259 /**
1260 * @since 1.0.1
1261 */
1262 export interface B {
1263 readonly d: boolean
1264 }
1265}
1266 `,
1267 `## A (namespace)
1268
1269[Source](https://github.com/effect-ts/docgen/blob/main/src/test.ts#L5)
1270
1271Since v1.0.0
1272
1273### B (interface)
1274
1275**Signature**
1276
1277\`\`\`ts
1278export interface B {
1279 readonly d: boolean
1280 }
1281\`\`\`
1282
1283[Source](https://github.com/effect-ts/docgen/blob/main/src/test.ts#L9)
1284
1285Since v1.0.1`
1286 )
1287 }))
1288 })
1289
1290 describe("namespace > type aliases", () => {
1291 it.effect("should ignore not exported type aliases", () =>
1292 Effect.gen(function*() {
1293 yield* expectMarkdown(
1294 Parser.parseNamespaces,
1295 `
1296 /**
1297 * @since 1.0.0
1298 */
1299 export namespace A {
1300 type C = number
1301 }
1302 `,
1303 `## A (namespace)
1304
1305[Source](https://github.com/effect-ts/docgen/blob/main/src/test.ts#L5)
1306
1307Since v1.0.0`
1308 )
1309 }))
1310
1311 it.effect("should parse a type alias", () =>
1312 Effect.gen(function*() {
1313 yield* expectMarkdown(
1314 Parser.parseNamespaces,
1315 `
1316 /**
1317 * @since 1.0.0
1318 */
1319 export namespace A {
1320 /**
1321 * @since 1.0.1
1322 */
1323 export type B = string
1324 }
1325 `,
1326 `## A (namespace)
1327
1328[Source](https://github.com/effect-ts/docgen/blob/main/src/test.ts#L5)
1329
1330Since v1.0.0
1331
1332### B (type alias)
1333
1334**Signature**
1335
1336\`\`\`ts
1337type B = string
1338\`\`\`
1339
1340[Source](https://github.com/effect-ts/docgen/blob/main/src/test.ts#L9)
1341
1342Since v1.0.1`
1343 )
1344 }))
1345 })
1346
1347 describe("namespace > nested namespaces", () => {
1348 it.effect("should ignore not exported namespaces", () =>
1349 Effect.gen(function*() {
1350 yield* expectMarkdown(
1351 Parser.parseNamespaces,
1352 `
1353 /**
1354 * @since 1.0.0
1355 */
1356 export namespace A {
1357 namespace B {}
1358 }
1359 `,
1360 `## A (namespace)
1361
1362[Source](https://github.com/effect-ts/docgen/blob/main/src/test.ts#L5)
1363
1364Since v1.0.0`
1365 )
1366 }))
1367
1368 it.effect("should parse a namespace", () =>
1369 Effect.gen(function*() {
1370 yield* expectMarkdown(
1371 Parser.parseNamespaces,
1372 `
1373 /**
1374 * @since 1.0.0
1375 */
1376 export namespace A {
1377 /**
1378 * @since 1.0.1
1379 */
1380 export namespace B {
1381 /**
1382 * @since 1.0.2
1383 */
1384 export type C = string
1385 }
1386 }
1387 `,
1388 `## A (namespace)
1389
1390[Source](https://github.com/effect-ts/docgen/blob/main/src/test.ts#L5)
1391
1392Since v1.0.0
1393
1394### B (namespace)
1395
1396[Source](https://github.com/effect-ts/docgen/blob/main/src/test.ts#L9)
1397
1398Since v1.0.1
1399
1400#### C (type alias)
1401
1402**Signature**
1403
1404\`\`\`ts
1405type C = string
1406\`\`\`
1407
1408[Source](https://github.com/effect-ts/docgen/blob/main/src/test.ts#L13)
1409
1410Since v1.0.2`
1411 )
1412 }))
1413 })
1414 })
1415
1416 describe("parseClasses", () => {
1417 it.effect("should ignore `@internal` classes", () =>
1418 Effect.gen(function*() {
1419 yield* expectMarkdown(
1420 Parser.parseClasses,
1421 `/** @internal */export class MyClass {}`,
1422 ""
1423 )
1424 }))
1425
1426 it.effect("should ignore `@ignore` classes", () =>
1427 Effect.gen(function*() {
1428 yield* expectMarkdown(
1429 Parser.parseClasses,
1430 `
1431 /** @ignore */
1432 export class MyClass {}
1433 `,
1434 ""
1435 )
1436 }))
1437
1438 it.effect("should ignore not exported classes", () =>
1439 Effect.gen(function*() {
1440 yield* expectMarkdown(
1441 Parser.parseClasses,
1442 `
1443 class MyClass {}
1444 `,
1445 ""
1446 )
1447 }))
1448
1449 it.effect("should skip ignored properties", () =>
1450 Effect.gen(function*() {
1451 yield* expectMarkdown(
1452 Parser.parseClasses,
1453 `/**
1454 * @since 1.0.0
1455 */
1456 export class MyClass<A> {
1457 /**
1458 * @ignore
1459 */
1460 readonly _A!: A
1461 }`,
1462 `## MyClass (class)
1463
1464**Signature**
1465
1466\`\`\`ts
1467declare class MyClass<A>
1468\`\`\`
1469
1470[Source](https://github.com/effect-ts/docgen/blob/main/src/test.ts#L4)
1471
1472Since v1.0.0`
1473 )
1474 }))
1475
1476 it.effect("should skip the constructor body", () =>
1477 Effect.gen(function*() {
1478 yield* expectMarkdown(
1479 Parser.parseClasses,
1480 `/**
1481 * description
1482 * @since 1.0.0
1483 */
1484 export class C { constructor() {} }`,
1485 `## C (class)
1486
1487description
1488
1489**Signature**
1490
1491\`\`\`ts
1492declare class C { constructor() }
1493\`\`\`
1494
1495[Source](https://github.com/effect-ts/docgen/blob/main/src/test.ts#L5)
1496
1497Since v1.0.0`
1498 )
1499 }))
1500
1501 it("should get a constructor declaration signature", () => {
1502 const sourceFile = project.createSourceFile(
1503 `test-${testCounter++}.ts`,
1504 `
1505 /**
1506 * @since 1.0.0
1507 */
1508 declare class A {
1509 constructor()
1510 }
1511 `
1512 )
1513
1514 const constructorDeclaration = sourceFile
1515 .getClass("A")!
1516 .getConstructors()[0]
1517
1518 assert.deepStrictEqual(
1519 Parser.getConstructorDeclarationSignature(constructorDeclaration),
1520 "constructor()"
1521 )
1522 })
1523
1524 it.effect("should handle non-readonly properties", () =>
1525 Effect.gen(function*() {
1526 yield* expectMarkdown(
1527 Parser.parseClasses,
1528 `/**
1529 * description
1530 * @since 1.0.0
1531 */
1532 export class C {
1533 /**
1534 * @since 1.0.0
1535 */
1536 a: string
1537 }`,
1538 `## C (class)
1539
1540description
1541
1542**Signature**
1543
1544\`\`\`ts
1545declare class C
1546\`\`\`
1547
1548[Source](https://github.com/effect-ts/docgen/blob/main/src/test.ts#L5)
1549
1550Since v1.0.0
1551
1552### a (property)
1553
1554**Signature**
1555
1556\`\`\`ts
1557a: string
1558\`\`\`
1559
1560[Source](https://github.com/effect-ts/docgen/blob/main/src/test.ts#L9)
1561
1562Since v1.0.0`
1563 )
1564 }))
1565
1566 it.effect("should return a `Class`", () =>
1567 Effect.gen(function*() {
1568 yield* expectMarkdown(
1569 Parser.parseClasses,
1570 `/**
1571 * a class description...
1572 * @since 1.0.0
1573 * @deprecated
1574 */
1575 export class Test {
1576 /**
1577 * a property...
1578 * @since 1.1.0
1579 * @deprecated
1580 */
1581 readonly a: string
1582 private readonly b: number
1583 /**
1584 * a static method description...
1585 * @since 1.1.0
1586 * @deprecated
1587 */
1588 static f(): void {}
1589 constructor(readonly value: string) { }
1590 /**
1591 * a method description...
1592 * @since 1.1.0
1593 * @deprecated
1594 */
1595 g(a: number, b: number): { [key: string]: number } {
1596 return { a, b }
1597 }
1598 }`,
1599 `## ~~Test~~ (class)
1600
1601a class description...
1602
1603**Signature**
1604
1605\`\`\`ts
1606declare class Test { constructor(readonly value: string) }
1607\`\`\`
1608
1609[Source](https://github.com/effect-ts/docgen/blob/main/src/test.ts#L6)
1610
1611Since v1.0.0
1612
1613### ~~f~~ (static method)
1614
1615a static method description...
1616
1617**Signature**
1618
1619\`\`\`ts
1620declare const f: () => void
1621\`\`\`
1622
1623[Source](https://github.com/effect-ts/docgen/blob/main/src/test.ts#L19)
1624
1625Since v1.1.0
1626
1627### ~~g~~ (method)
1628
1629a method description...
1630
1631**Signature**
1632
1633\`\`\`ts
1634declare const g: (a: number, b: number) => { [key: string]: number; }
1635\`\`\`
1636
1637[Source](https://github.com/effect-ts/docgen/blob/main/src/test.ts#L26)
1638
1639Since v1.1.0
1640
1641### ~~a~~ (property)
1642
1643a property...
1644
1645**Signature**
1646
1647\`\`\`ts
1648readonly a: string
1649\`\`\`
1650
1651[Source](https://github.com/effect-ts/docgen/blob/main/src/test.ts#L12)
1652
1653Since v1.1.0`
1654 )
1655 }))
1656
1657 it.effect("should handle method overloadings", () =>
1658 Effect.gen(function*() {
1659 yield* expectMarkdown(
1660 Parser.parseClasses,
1661 `/**
1662 * a class description...
1663 * @since 1.0.0
1664 * @deprecated
1665 */
1666 export class Test<A> {
1667 /**
1668 * a static method description...
1669 * @since 1.1.0
1670 * @deprecated
1671 */
1672 static f(x: number): number
1673 static f(x: string): string
1674 static f(x: any): any {}
1675 constructor(readonly value: A) { }
1676 /**
1677 * a method description...
1678 * @since 1.1.0
1679 * @deprecated
1680 */
1681 map(f: (a: number) => number): Test
1682 map(f: (a: string) => string): Test
1683 map(f: (a: any) => any): any {
1684 return new Test(f(this.value))
1685 }
1686 }`,
1687 `## ~~Test~~ (class)
1688
1689a class description...
1690
1691**Signature**
1692
1693\`\`\`ts
1694declare class Test<A> { constructor(readonly value: A) }
1695\`\`\`
1696
1697[Source](https://github.com/effect-ts/docgen/blob/main/src/test.ts#L6)
1698
1699Since v1.0.0
1700
1701### ~~f~~ (static method)
1702
1703a static method description...
1704
1705**Signature**
1706
1707\`\`\`ts
1708declare const f: { (x: number): number; (x: string): string; }
1709\`\`\`
1710
1711[Source](https://github.com/effect-ts/docgen/blob/main/src/test.ts#L14)
1712
1713Since v1.1.0
1714
1715### ~~map~~ (method)
1716
1717a method description...
1718
1719**Signature**
1720
1721\`\`\`ts
1722declare const map: { (f: (a: number) => number): Test; (f: (a: string) => string): Test; }
1723\`\`\`
1724
1725[Source](https://github.com/effect-ts/docgen/blob/main/src/test.ts#L23)
1726
1727Since v1.1.0`
1728 )
1729 }))
1730
1731 it.effect("should ignore internal/ignored methods (#42)", () =>
1732 Effect.gen(function*() {
1733 yield* expectMarkdown(
1734 Parser.parseClasses,
1735 `/**
1736 * a class description...
1737 * @since 1.0.0
1738 */
1739 export class Test<A> {
1740 /**
1741 * @since 0.0.1
1742 * @internal
1743 **/
1744 private foo(): void {}
1745 /**
1746 * @since 0.0.1
1747 * @ignore
1748 **/
1749 private bar(): void {}
1750 }`,
1751 `## Test (class)
1752
1753a class description...
1754
1755**Signature**
1756
1757\`\`\`ts
1758declare class Test<A>
1759\`\`\`
1760
1761[Source](https://github.com/effect-ts/docgen/blob/main/src/test.ts#L5)
1762
1763Since v1.0.0`
1764 )
1765 }))
1766 })
1767
1768 describe("parseFile", () => {
1769 it.effect("should not parse a non-existent file", () =>
1770 Effect.gen(function*() {
1771 const file = new Domain.File("non-existent.ts", "")
1772 const project = new ast.Project({ useInMemoryFileSystem: true })
1773
1774 assert.deepStrictEqual(
1775 yield* Effect.exit(
1776 Parser.parseFile(project)(file).pipe(
1777 Effect.provideService(Configuration.Configuration, defaultConfig),
1778 Effect.provide(Path.layer)
1779 )
1780 ),
1781 Exit.fail(["Unable to locate file: non-existent.ts"])
1782 )
1783 }))
1784 })
1785
1786 describe("utils", () => {
1787 it("parseComment", () => {
1788 assert.deepStrictEqual(Parser.parseComment(""), {
1789 description: undefined,
1790 tags: {}
1791 })
1792
1793 assert.deepStrictEqual(Parser.parseComment("/** description */"), {
1794 description: "description",
1795 tags: {}
1796 })
1797
1798 assert.deepStrictEqual(
1799 Parser.parseComment("/** description\n * @since 1.0.0\n */"),
1800 {
1801 description: "description",
1802 tags: {
1803 since: ["1.0.0"]
1804 }
1805 }
1806 )
1807
1808 assert.deepStrictEqual(
1809 Parser.parseComment("/** description\n * @deprecated\n */"),
1810 {
1811 description: "description",
1812 tags: {
1813 deprecated: [""]
1814 }
1815 }
1816 )
1817
1818 assert.deepStrictEqual(
1819 Parser.parseComment("/** description\n * @category instance\n */"),
1820 {
1821 description: "description",
1822 tags: {
1823 category: ["instance"]
1824 }
1825 }
1826 )
1827 })
1828 })
1829})