1import * as Checker from "@effect/docgen/Checker"
2import * as Configuration from "@effect/docgen/Configuration"
3import * as Parser from "@effect/docgen/Parser"
4import { assert, describe, it } from "@effect/vitest"
5import { Effect, Predicate } from "effect"
6import * as Path from "effect/Path"
7import * as ast from "ts-morph"
8
9const project = new ast.Project({
10 compilerOptions: { strict: true },
11 useInMemoryFileSystem: true
12})
13
14const defaultConfig: Configuration.ConfigurationShape = {
15 projectName: "docgen",
16 projectHomepage: "https://github.com/effect-ts/docgen",
17 srcLink: "https://github.com/effect-ts/docgen/blob/main/src/",
18 srcDir: "src",
19 outDir: "docs",
20 theme: "mikearnaldi/just-the-docs",
21 enableSearch: true,
22 enforceDescriptions: false,
23 enforceExamples: false,
24 enforceVersion: true,
25 runExamples: false,
26 tscExecutable: "tsc",
27 exclude: [],
28 parseCompilerOptions: {},
29 examplesCompilerOptions: {}
30}
31
32const makeSourcefile = (source: string | ast.SourceFile) => {
33 if (Predicate.isString(source)) {
34 const filename = `test.ts`
35 const existing = project.getSourceFile(filename)
36 if (existing) {
37 project.removeSourceFile(existing)
38 }
39 return project.createSourceFile(filename, source)
40 }
41 return source
42}
43
44const makeSource = (source: string | ast.SourceFile) => {
45 const sourceFile = makeSourcefile(source)
46 const filename = sourceFile.getBaseName()
47 return Parser.Source.of({
48 path: [filename],
49 sourceFile
50 })
51}
52
53const expectFailure = <A>(
54 config: Partial<Configuration.ConfigurationShape>,
55 sourceText: string,
56 parser: Effect.Effect<A, never, Parser.Source | Configuration.Configuration | Path.Path>,
57 checker: (a: A) => Effect.Effect<Array<string>, never, Configuration.Configuration | Parser.Source>,
58 failure: ReadonlyArray<string>
59) => {
60 return Effect.gen(function*() {
61 const actual = yield* Effect.exit(parser.pipe(
62 Effect.flatMap(checker),
63 Effect.provideService(Parser.Source, makeSource(sourceText)),
64 Effect.provideService(Configuration.Configuration, { ...defaultConfig, ...config }),
65 Effect.provide(Path.layer)
66 ))
67 assert.ok(actual._tag === "Success")
68 assert.deepStrictEqual(actual.value, failure)
69 })
70}
71
72describe("Checker", () => {
73 describe("checkFunctions", () => {
74 it.effect("should raise an error if `@since` tag is missing", () =>
75 expectFailure(
76 {},
77 `
78/** @since 1.0.0 */
79export function a() {}
80
81/** description */
82export function b() {}
83 `,
84 Parser.parseFunctions,
85 Checker.checkFunctions,
86 [
87 "Missing `@since` tag in file /test.ts:\n" +
88 "\n" +
89 " 4 |\n" +
90 " 5 | /** description */\n" +
91 "> 6 | export function b() {}\n" +
92 " | ^\n" +
93 " 7 | "
94 ]
95 ))
96 })
97
98 describe("checkExports", () => {
99 it.effect("should raise an error if `@since` tag is missing", () =>
100 expectFailure(
101 {},
102 "export { a }",
103 Parser.parseExports,
104 Checker.checkExports,
105 [
106 "Missing `@since` tag in file /test.ts:\n" +
107 "\n" +
108 "> 1 | export { a }\n" +
109 " | ^"
110 ]
111 ))
112 })
113
114 describe("checkNamespaces", () => {
115 it.effect("should raise an error if `@since` tag is missing", () =>
116 expectFailure(
117 {},
118 "export namespace A {}",
119 Parser.parseNamespaces,
120 Checker.checkNamespaces,
121 [
122 "Missing `@since` tag in file /test.ts:\n" +
123 "\n" +
124 "> 1 | export namespace A {}\n" +
125 " | ^"
126 ]
127 ))
128
129 it.effect("should raise an error if `@since` tag is missing on a nested interface", () =>
130 expectFailure(
131 {},
132 `
133 /**
134 * @since 1.0.0
135 */
136 export namespace A {
137 export interface B {}
138 }
139 `,
140 Parser.parseNamespaces,
141 Checker.checkNamespaces,
142 [
143 "Missing `@since` tag in file /test.ts:\n" +
144 "\n" +
145 " 4 | */\n" +
146 " 5 | export namespace A {\n" +
147 "> 6 | export interface B {}\n" +
148 " | ^\n" +
149 " 7 | }\n" +
150 " 8 | "
151 ]
152 ))
153
154 it.effect("should raise an error if `@since` tag is missing on a nested type alias", () =>
155 expectFailure(
156 {},
157 `
158 /**
159 * @since 1.0.0
160 */
161 export namespace A {
162 export type B = string
163 }
164 `,
165 Parser.parseNamespaces,
166 Checker.checkNamespaces,
167 [
168 "Missing `@since` tag in file /test.ts:\n" +
169 "\n" +
170 " 4 | */\n" +
171 " 5 | export namespace A {\n" +
172 "> 6 | export type B = string\n" +
173 " | ^\n" +
174 " 7 | }\n" +
175 " 8 | "
176 ]
177 ))
178
179 it.effect("should raise an error if `@since` tag is missing on a nested namespace", () =>
180 expectFailure(
181 {},
182 `
183 /**
184 * @since 1.0.0
185 */
186 export namespace A {
187 export namespace B {}
188 }
189 `,
190 Parser.parseNamespaces,
191 Checker.checkNamespaces,
192 [
193 "Missing `@since` tag in file /test.ts:\n" +
194 "\n" +
195 " 4 | */\n" +
196 " 5 | export namespace A {\n" +
197 "> 6 | export namespace B {}\n" +
198 " | ^\n" +
199 " 7 | }\n" +
200 " 8 | "
201 ]
202 ))
203 })
204
205 describe("checkClasses", () => {
206 it.effect("should raise an error if `@since` tag is missing", () =>
207 expectFailure(
208 {},
209 `export class MyClass {}`,
210 Parser.parseClasses,
211 Checker.checkClasses,
212 [
213 "Missing `@since` tag in file /test.ts:\n" +
214 "\n" +
215 "> 1 | export class MyClass {}\n" +
216 " | ^"
217 ]
218 ))
219 })
220})