import { computeJSDocInputHash, extractJSDocsSync, parseJSDoc } from "@effect/jsdocs" import { assert, describe, it } from "@effect/vitest" import * as fs from "node:fs" import * as os from "node:os" import * as path from "node:path" import * as ts from "typescript" interface SourceFileWithParseDiagnostics extends ts.SourceFile { readonly parseDiagnostics: ReadonlyArray } const signatureParseDiagnostics = (signature: string): ReadonlyArray => (ts.createSourceFile( "signature.ts", signature, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS ) as SourceFileWithParseDiagnostics) .parseDiagnostics.map((diagnostic) => ts.flattenDiagnosticMessageText(diagnostic.messageText, "\n")) describe("jsdocs", () => { it("parses a raw JSDoc block", () => { const result = parseJSDoc(`/** * Creates a value. * * @category constructors * @since 1.0.0 */`) assert.strictEqual(result._tag, "Success") if (result._tag === "Success") { assert.strictEqual(result.value.description.short, "Creates a value.") } }) it("accepts practical When to use forms", () => { const result = parseJSDoc(`/** * Creates a value. * * **When to use** * * Use to create a value when construction should be explicit. * * @category constructors * @since 1.0.0 */`) assert.strictEqual(result._tag, "Success") if (result._tag === "Success") { assert.strictEqual( result.value.description.whenToUse, "Use to create a value when construction should be explicit." ) } }) it("flags unsupported When to use forms", () => { const result = parseJSDoc(`/** * Creates a value. * * **When to use** * * - Creating a value when construction should be explicit. * * @category constructors * @since 1.0.0 */`) assert.strictEqual(result._tag, "Failure") if (result._tag === "Failure") { assert.deepStrictEqual( result.error.diagnostics.map((diagnostic) => diagnostic.code), ["when-to-use-format"] ) } }) it("extracts docs with TypeScript", () => { const cwd = fs.mkdtempSync(path.join(os.tmpdir(), "jsdocs-")) fs.mkdirSync(path.join(cwd, "src"), { recursive: true }) fs.writeFileSync( path.join(cwd, "tsconfig.json"), JSON.stringify({ compilerOptions: { module: "NodeNext", moduleResolution: "NodeNext", target: "ES2022" }, include: ["src/**/*.ts"] }) ) fs.writeFileSync( path.join(cwd, "package.json"), JSON.stringify({ name: "@effect/sample", type: "module", exports: { ".": "./src/index.ts", "./*": "./src/*.ts" } }) ) fs.writeFileSync(path.join(cwd, "src/index.ts"), `export * as Foo from "./Foo.ts"\n`) fs.writeFileSync( path.join(cwd, "src/Foo.ts"), `/** * Creates a value. * * @category constructors * @since 1.0.0 */ export const makeValue = () => 1 ` ) const model = extractJSDocsSync({ cwd, tsconfig: "tsconfig.json", include: ["src/**/*.ts"], output: ".data/jsdocs.json" }) assert.strictEqual(model.version, 2) assert.strictEqual(model.files.length, 1) assert.strictEqual(model.files[0]?.declarations[0]?.name, "makeValue") assert.strictEqual(model.apis[0]?.apiFqn, "@effect/sample/Foo.makeValue") assert.deepStrictEqual(model.apis[0]?.importGuidance, { style: "namespace-barrel", importDeclaration: "import { Foo } from \"@effect/sample\"", usage: "Foo.makeValue" }) }) it("stores a stable input hash for cache checks", () => { const cwd = fs.mkdtempSync(path.join(os.tmpdir(), "jsdocs-")) fs.mkdirSync(path.join(cwd, "src"), { recursive: true }) fs.writeFileSync( path.join(cwd, "tsconfig.json"), JSON.stringify({ compilerOptions: { module: "NodeNext", moduleResolution: "NodeNext", target: "ES2022" }, include: ["src/**/*.ts"] }) ) fs.writeFileSync( path.join(cwd, "jsdocs.config.json"), JSON.stringify({ tsconfig: "tsconfig.json", include: ["src/**/*.ts"], output: ".data/jsdocs.json" }) ) fs.writeFileSync( path.join(cwd, "package.json"), JSON.stringify({ name: "@effect/sample", type: "module", exports: { ".": "./src/index.ts", "./*": "./src/*.ts" } }) ) fs.writeFileSync(path.join(cwd, "src/index.ts"), `export * as Foo from "./Foo.ts"\n`) fs.writeFileSync( path.join(cwd, "src/Foo.ts"), `/** * Creates a value. * * @category constructors * @since 1.0.0 */ export const makeValue = () => 1 ` ) const options = { cwd, tsconfig: "tsconfig.json", include: ["src/**/*.ts"], output: ".data/jsdocs.json" } const inputHash = computeJSDocInputHash(options) const model = extractJSDocsSync(options) assert.strictEqual(model.inputHash, inputHash) assert.strictEqual(computeJSDocInputHash(options), inputHash) fs.appendFileSync(path.join(cwd, "src/Foo.ts"), "\n") assert.notStrictEqual(computeJSDocInputHash(options), inputHash) }) it("extracts typechecker signatures for top-level functions", () => { const cwd = fs.mkdtempSync(path.join(os.tmpdir(), "jsdocs-")) fs.mkdirSync(path.join(cwd, "src"), { recursive: true }) fs.writeFileSync( path.join(cwd, "tsconfig.json"), JSON.stringify({ compilerOptions: { module: "NodeNext", moduleResolution: "NodeNext", target: "ES2022" }, include: ["src/**/*.ts"] }) ) fs.writeFileSync( path.join(cwd, "package.json"), JSON.stringify({ name: "@effect/sample", type: "module", exports: { ".": "./src/index.ts", "./*": "./src/*.ts" } }) ) fs.writeFileSync(path.join(cwd, "src/index.ts"), `export * as Foo from "./Foo.ts"\n`) fs.writeFileSync( path.join(cwd, "src/External.ts"), `/** * External value type. * * @category models * @since 1.0.0 */ export interface External { readonly value: string } /** * Makes an external value. * * @category constructors * @since 1.0.0 */ export const makeExternal = (value: string): External => ({ value }) ` ) fs.writeFileSync( path.join(cwd, "src/Foo.ts"), `/** * Parses a value. * * @category parsing * @since 1.0.0 */ export function parse(value: string): string export function parse(value: number): number export function parse(value: string | number) { return value } /** * Converts a value. * * @category constructors * @since 1.0.0 */ export const inferred = (value: number) => String(value) const aliased = (value: boolean) => !value export { /** * Negates a value. * * @category constructors * @since 1.0.0 */ aliased as renamed } export { /** * Makes an external value. * * @category constructors * @since 1.0.0 */ makeExternal as external } from "./External.ts" const attempt = (value: string) => value.length export { /** * Attempts a value. * * @category constructors * @since 1.0.0 */ attempt as try } /** * Handles many overloads. * * @category constructors * @since 1.0.0 */ export function many(value: 1): 1 export function many(value: 2): 2 export function many(value: 3): 3 export function many(value: 4): 4 export function many(value: 5): 5 export function many(value: 6): 6 export function many(value: 7): 7 export function many(value: 8): 8 export function many(value: 9): 9 export function many(value: 10): 10 export function many(value: 11): 11 export function many(value: 12): 12 export function many(value: 13): 13 export function many(value: number) { return value } /** * Runs a value through a transform chain. * * @category constructors * @since 1.0.0 */ export function pipeline(a: A): A export function pipeline(a: A, ab: (a: A) => B): B export function pipeline(a: A, ab: (a: A) => B, bc: (b: B) => C): C export function pipeline(a: A, ab: (a: A) => B, bc: (b: B) => C, cd: (c: C) => D): D export function pipeline( a: A, ab: (a: A) => B, bc: (b: B) => C, cd: (c: C) => D, de: (d: D) => E ): E export function pipeline( a: A, ab: (a: A) => B, bc: (b: B) => C, cd: (c: C) => D, de: (d: D) => E, ef: (e: E) => F ): F export function pipeline( a: A, ab: (a: A) => B, bc: (b: B) => C, cd: (c: C) => D, de: (d: D) => E, ef: (e: E) => F, fg: (f: F) => G ): G export function pipeline( a: A, ab: (a: A) => B, bc: (b: B) => C, cd: (c: C) => D, de: (d: D) => E, ef: (e: E) => F, fg: (f: F) => G, gh: (g: G) => H ): H export function pipeline( a: A, ab: (a: A) => B, bc: (b: B) => C, cd: (c: C) => D, de: (d: D) => E, ef: (e: E) => F, fg: (f: F) => G, gh: (g: G) => H, hi: (h: H) => I ): I export function pipeline(...args: Array) { return args.length === 1 ? args[0] : args.slice(1).reduce((value, f) => f(value), args[0]) } interface Box {} declare namespace Box { export type Inner> = A extends Box ? T : never } interface Check {} interface Ctor> { (value: Box.Inner): A } /** * Builds a boxed constructor. * * @category constructors * @since 1.0.0 */ export function boxed>( ...value: readonly [Check>, ...Array>>] ): Ctor { return null as any } /** * Handles distinct overload modes. * * @category constructors * @since 1.0.0 */ export function modes(body: () => string): string export function modes(body: (value: number) => string): string export function modes(body: (this: string) => string): string export function modes(body: () => string, map: (value: string) => number): number export function modes(body: () => string, map: (value: string) => number, next: (value: number) => boolean): boolean export function modes(options: { readonly self: string }, body: (this: string) => string): string export function modes(name: string): (body: () => string) => string export function modes(self: string, body: () => string): string export function modes(predicate: (value: number) => boolean, body: () => number): number export function modes(refinement: (value: string | number) => value is string, body: () => string): string export function modes(...args: Array) { return args[0] } /** * Service for sample values. * * @category services * @since 1.0.0 */ export class Service { readonly value: string /** * Runs the service. */ run(input: number): string { return \`\${this.value}:\${input}\` } } /** * Default count. * * @category constants * @since 1.0.0 */ export const count = 1 /** * Sample value type. * * @category models * @since 1.0.0 */ export interface Sample { readonly value: string } ` ) const model = extractJSDocsSync({ cwd, tsconfig: "tsconfig.json", include: ["src/**/*.ts"], output: ".data/jsdocs.json" }) const signatureByName = new Map(model.apis.map((api) => [api.apiName, api.signature])) assert.deepStrictEqual(model.files.flatMap((file) => file.diagnostics), []) for (const api of model.apis) { if (api.signature !== null) { const diagnostics = signatureParseDiagnostics(api.signature) if (diagnostics.length > 0) { assert.fail(`${api.apiName}: ${diagnostics.join(", ")}`) } } } assert.strictEqual( signatureByName.get("parse"), "declare function parse(value: string): string\ndeclare function parse(value: number): number" ) assert.strictEqual(signatureByName.get("inferred"), "declare function inferred(value: number): string") assert.strictEqual(signatureByName.get("renamed"), "declare function renamed(value: boolean): boolean") assert.strictEqual( signatureByName.get("try"), `declare const _try: { (value: string): number; } export { _try as try }` ) assert.strictEqual(signatureByName.get("external"), "declare function external(value: string): External") const manyLines = signatureByName.get("many")?.split("\n") assert.strictEqual(manyLines?.length, 1) assert.strictEqual(manyLines?.[0], "declare function many(value: 1): 1") const pipelineLines = signatureByName.get("pipeline")?.split("\n") ?? [] assert.strictEqual(pipelineLines.length, 6) assert.deepStrictEqual(pipelineLines.map((line) => line.match(/=>/g)?.length ?? 0), [0, 1, 2, 3, 4, 8]) const boxedSignature = signatureByName.get("boxed") assert.strictEqual( boxedSignature, "declare const boxed: >(value_0: Check>, ...value: Check>[]) => Ctor" ) const modeLines = signatureByName.get("modes")?.split("\n") assert.deepStrictEqual(modeLines, [ "declare function modes(body: () => string): string", "declare function modes(body: (this: string) => string): string", "declare function modes(body: () => string, map: (value: string) => number): number", "declare function modes(options: { readonly self: string; }, body: (this: string) => string): string", "declare function modes(name: string): (body: () => string) => string", "declare function modes(self: string, body: () => string): string", "declare function modes(predicate: (value: number) => boolean, body: () => number): number", "declare function modes(refinement: (value: string | number) => value is string, body: () => string): string" ]) assert.strictEqual(signatureByName.get("Service"), null) assert.strictEqual(signatureByName.get("Service.run"), null) assert.strictEqual(signatureByName.get("count"), null) assert.strictEqual(signatureByName.get("Sample"), null) }) it("stores valid top-of-file module JSDoc", () => { const cwd = fs.mkdtempSync(path.join(os.tmpdir(), "jsdocs-")) fs.mkdirSync(path.join(cwd, "src"), { recursive: true }) fs.writeFileSync( path.join(cwd, "tsconfig.json"), JSON.stringify({ compilerOptions: { module: "NodeNext", moduleResolution: "NodeNext", target: "ES2022" }, include: ["src/**/*.ts"] }) ) fs.writeFileSync( path.join(cwd, "package.json"), JSON.stringify({ name: "@effect/sample", type: "module", exports: { ".": "./src/index.ts", "./*": "./src/*.ts" } }) ) fs.writeFileSync(path.join(cwd, "src/index.ts"), `export * as Foo from "./Foo.ts"\n`) fs.writeFileSync( path.join(cwd, "src/Foo.ts"), `/** * The Foo module provides helpers for sample values. Use {@link makeValue} to * create the default value. * * **Example** (Using the module) * * \`\`\`ts * import { Foo } from "@effect/sample" * * Foo.makeValue() * \`\`\` * * @see {@link makeValue} * @since 1.0.0 */ import type { Buffer } from "node:buffer" /** * Creates a value. * * @category constructors * @since 1.0.0 */ export const makeValue = () => 1 ` ) const model = extractJSDocsSync({ cwd, tsconfig: "tsconfig.json", include: ["src/**/*.ts"], output: ".data/jsdocs.json" }) assert.strictEqual(model.files[0]?.moduleJSDoc?.raw.includes("The Foo module provides helpers"), true) assert.deepStrictEqual(model.files[0]?.diagnostics, []) }) it("does not treat the first exported declaration JSDoc as module JSDoc", () => { const cwd = fs.mkdtempSync(path.join(os.tmpdir(), "jsdocs-")) fs.mkdirSync(path.join(cwd, "src"), { recursive: true }) fs.writeFileSync( path.join(cwd, "tsconfig.json"), JSON.stringify({ compilerOptions: { module: "NodeNext", moduleResolution: "NodeNext", target: "ES2022" }, include: ["src/**/*.ts"] }) ) fs.writeFileSync( path.join(cwd, "package.json"), JSON.stringify({ name: "@effect/sample", type: "module", exports: { ".": "./src/index.ts", "./*": "./src/*.ts" } }) ) fs.writeFileSync(path.join(cwd, "src/index.ts"), `export * as Foo from "./Foo.ts"\n`) fs.writeFileSync( path.join(cwd, "src/Foo.ts"), `/** * Creates a value. * * @category constructors * @since 1.0.0 */ export const makeValue = () => 1 ` ) const model = extractJSDocsSync({ cwd, tsconfig: "tsconfig.json", include: ["src/**/*.ts"], output: ".data/jsdocs.json" }) assert.strictEqual(model.files[0]?.moduleJSDoc, undefined) assert.deepStrictEqual(model.files[0]?.diagnostics, []) }) it("flags inline links that TypeScript does not bind", () => { const cwd = fs.mkdtempSync(path.join(os.tmpdir(), "jsdocs-")) fs.mkdirSync(path.join(cwd, "src"), { recursive: true }) fs.writeFileSync( path.join(cwd, "tsconfig.json"), JSON.stringify({ compilerOptions: { module: "NodeNext", moduleResolution: "NodeNext", target: "ES2022" }, include: ["src/**/*.ts"] }) ) fs.writeFileSync( path.join(cwd, "package.json"), JSON.stringify({ name: "@effect/sample", type: "module", exports: { ".": "./src/index.ts", "./*": "./src/*.ts" } }) ) fs.writeFileSync(path.join(cwd, "src/index.ts"), `export * as Foo from "./Foo.ts"\n`) fs.writeFileSync(path.join(cwd, "src/Schema.ts"), `export {}\n`) fs.writeFileSync( path.join(cwd, "src/Foo.ts"), `/** * Creates a value with the {@link Schema} module. * * @category constructors * @since 1.0.0 */ export const makeValue = () => 1 ` ) const model = extractJSDocsSync({ cwd, tsconfig: "tsconfig.json", include: ["src/**/*.ts"], output: ".data/jsdocs.json" }) assert.deepStrictEqual( model.files[0]?.diagnostics.map((diagnostic) => diagnostic.code), ["unresolved-link"] ) assert.deepStrictEqual( model.files[0]?.diagnostics.map((diagnostic) => diagnostic.message), ["Unresolved JSDoc inline link: {@link Schema}"] ) }) it("flags module JSDoc tag, example, and public @see diagnostics", () => { const cwd = fs.mkdtempSync(path.join(os.tmpdir(), "jsdocs-")) fs.mkdirSync(path.join(cwd, "src"), { recursive: true }) fs.writeFileSync( path.join(cwd, "tsconfig.json"), JSON.stringify({ compilerOptions: { module: "NodeNext", moduleResolution: "NodeNext", target: "ES2022" }, include: ["src/**/*.ts"] }) ) fs.writeFileSync( path.join(cwd, "package.json"), JSON.stringify({ name: "@effect/sample", type: "module", exports: { ".": "./src/index.ts", "./*": "./src/*.ts" } }) ) fs.writeFileSync(path.join(cwd, "src/index.ts"), `export * as Foo from "./Foo.ts"\n`) fs.writeFileSync( path.join(cwd, "src/Foo.ts"), `/** * The Foo module provides helpers. * * \`\`\`ts * const value = 1 * \`\`\` * * @see {@link Hidden} */ import type { Buffer } from "node:buffer" class Hidden {} /** * Creates a value. * * @category constructors * @since 1.0.0 */ export const makeValue = () => 1 ` ) const model = extractJSDocsSync({ cwd, tsconfig: "tsconfig.json", include: ["src/**/*.ts"], output: ".data/jsdocs.json" }) assert.deepStrictEqual( model.files[0]?.diagnostics.map((diagnostic) => diagnostic.code).sort(), ["loose-ts-fence", "missing-tag", "public-see-target"].sort() ) assert.strictEqual(model.files[0]?.moduleJSDoc?.raw.includes("The Foo module provides helpers"), true) }) it("flags @see links to targets without public JSDoc", () => { const cwd = fs.mkdtempSync(path.join(os.tmpdir(), "jsdocs-")) fs.mkdirSync(path.join(cwd, "src"), { recursive: true }) fs.writeFileSync( path.join(cwd, "tsconfig.json"), JSON.stringify({ compilerOptions: { module: "NodeNext", moduleResolution: "NodeNext", target: "ES2022" }, include: ["src/**/*.ts"] }) ) fs.writeFileSync( path.join(cwd, "package.json"), JSON.stringify({ name: "@effect/sample", type: "module", exports: { ".": "./src/index.ts", "./*": "./src/*.ts" } }) ) fs.writeFileSync(path.join(cwd, "src/index.ts"), `export * as Foo from "./Foo.ts"\n`) fs.writeFileSync( path.join(cwd, "src/Foo.ts"), `/** * A documented container. * * @category models * @since 1.0.0 */ export interface Box { documented: string hidden: string } /** * Uses the hidden member. * * @see {@link Box.hidden} * @category constructors * @since 1.0.0 */ export const useHidden = () => undefined ` ) const model = extractJSDocsSync({ cwd, tsconfig: "tsconfig.json", include: ["src/**/*.ts"], output: ".data/jsdocs.json" }) assert.deepStrictEqual( model.files[0]?.diagnostics.map((diagnostic) => diagnostic.code), ["undocumented-see-target"] ) assert.deepStrictEqual(model.files[0]?.imports?.barrel, { type: "namespace", module: "@effect/sample", name: "Foo" }) }) it("resolves @see links through TypeScript symbols before local-name lookup", () => { const cwd = fs.mkdtempSync(path.join(os.tmpdir(), "jsdocs-")) fs.mkdirSync(path.join(cwd, "src"), { recursive: true }) fs.writeFileSync( path.join(cwd, "tsconfig.json"), JSON.stringify({ compilerOptions: { module: "NodeNext", moduleResolution: "NodeNext", target: "ES2022" }, include: ["src/**/*.ts"] }) ) fs.writeFileSync( path.join(cwd, "package.json"), JSON.stringify({ name: "@effect/sample", type: "module", exports: { ".": "./src/index.ts", "./*": "./src/*.ts" } }) ) fs.writeFileSync( path.join(cwd, "src/index.ts"), `export * as Eq from "./Eq.ts"\nexport * as Ordering from "./Ordering.ts"\nexport * as Reducer from "./Reducer.ts"\n` ) fs.writeFileSync( path.join(cwd, "src/Reducer.ts"), `/** * A reusable reducer. * * @category models * @since 1.0.0 */ export interface Reducer { /** * Combines two values. */ combine: string } ` ) fs.writeFileSync( path.join(cwd, "src/Ordering.ts"), `/** * Ordering reducer value. * * @category constants * @since 1.0.0 */ export const Reducer = "ordering" ` ) fs.writeFileSync( path.join(cwd, "src/Eq.ts"), `import * as Reducer from "./Reducer.ts" /** * Creates an equivalence reducer. * * @see {@link Reducer} * @category constructors * @since 1.0.0 */ export const makeReducer = () => Reducer ` ) const model = extractJSDocsSync({ cwd, tsconfig: "tsconfig.json", include: ["src/**/*.ts"], output: ".data/jsdocs.json" }) const makeReducer = model.apis.find((api) => api.apiFqn === "@effect/sample/Eq.makeReducer") const link = makeReducer?.see[0]?.links[0] assert.deepStrictEqual(model.files.flatMap((file) => file.diagnostics), []) assert.deepStrictEqual(link?.resolution, { _tag: "Resolved", apiId: "root-declaration:type:@effect/sample/Reducer.Reducer", apiFqn: "@effect/sample/Reducer.Reducer" }) }) it("flags @see links to targets outside the public JSDoc API model", () => { const cwd = fs.mkdtempSync(path.join(os.tmpdir(), "jsdocs-")) fs.mkdirSync(path.join(cwd, "src"), { recursive: true }) fs.writeFileSync( path.join(cwd, "tsconfig.json"), JSON.stringify({ compilerOptions: { module: "NodeNext", moduleResolution: "NodeNext", target: "ES2022" }, include: ["src/**/*.ts"] }) ) fs.writeFileSync( path.join(cwd, "package.json"), JSON.stringify({ name: "@effect/sample", type: "module", exports: { ".": "./src/index.ts", "./*": "./src/*.ts" } }) ) fs.writeFileSync(path.join(cwd, "src/index.ts"), `export * as Foo from "./Foo.ts"\n`) fs.writeFileSync( path.join(cwd, "src/Foo.ts"), `/** * Private helper. * * @category models * @since 1.0.0 */ class Hidden {} /** * Uses a hidden helper. * * @see {@link Hidden} * @category constructors * @since 1.0.0 */ export const useHidden = () => Hidden ` ) const model = extractJSDocsSync({ cwd, tsconfig: "tsconfig.json", include: ["src/**/*.ts"], output: ".data/jsdocs.json" }) assert.deepStrictEqual( model.files[0]?.diagnostics.map((diagnostic) => diagnostic.code), ["public-see-target"] ) assert.deepStrictEqual( model.files[0]?.diagnostics.map((diagnostic) => diagnostic.message), [ "@see link {@link Hidden} does not resolve to a public JSDoc API. Check that the target is exported and has valid public JSDoc." ] ) }) it("keeps valid APIs from files with diagnostics for @see resolution", () => { const cwd = fs.mkdtempSync(path.join(os.tmpdir(), "jsdocs-")) fs.mkdirSync(path.join(cwd, "src"), { recursive: true }) fs.writeFileSync( path.join(cwd, "tsconfig.json"), JSON.stringify({ compilerOptions: { module: "NodeNext", moduleResolution: "NodeNext", target: "ES2022" }, include: ["src/**/*.ts"] }) ) fs.writeFileSync( path.join(cwd, "package.json"), JSON.stringify({ name: "@effect/sample", type: "module", exports: { ".": "./src/index.ts", "./*": "./src/*.ts" } }) ) fs.writeFileSync( path.join(cwd, "src/index.ts"), `export * as Bar from "./Bar.ts"\nexport * as Foo from "./Foo.ts"\n` ) fs.writeFileSync( path.join(cwd, "src/Foo.ts"), `/** * Target value. * * @category constants * @since 1.0.0 */ export const Target = "target" /** * Broken value. * * **Example** * * \`\`\`ts * const value = "broken" * \`\`\` * * @category constants * @since 1.0.0 */ export const Broken = "broken" ` ) fs.writeFileSync( path.join(cwd, "src/Bar.ts"), `import * as Foo from "./Foo.ts" /** * Uses the target value. * * @see {@link Foo.Target} for the target value * @category constants * @since 1.0.0 */ export const useTarget = () => Foo.Target ` ) const model = extractJSDocsSync({ cwd, tsconfig: "tsconfig.json", include: ["src/**/*.ts"], output: ".data/jsdocs.json" }) const foo = model.files.find((file) => file.file === "src/Foo.ts") const bar = model.files.find((file) => file.file === "src/Bar.ts") assert.deepStrictEqual( foo?.diagnostics.map((diagnostic) => diagnostic.code), ["malformed-example"] ) assert.deepStrictEqual(bar?.diagnostics, []) assert.strictEqual( model.apis.some((api) => api.apiFqn === "@effect/sample/Foo.Target"), true ) }) it("resolves @see links to aliased export specifiers", () => { const cwd = fs.mkdtempSync(path.join(os.tmpdir(), "jsdocs-")) fs.mkdirSync(path.join(cwd, "src"), { recursive: true }) fs.writeFileSync( path.join(cwd, "tsconfig.json"), JSON.stringify({ compilerOptions: { module: "NodeNext", moduleResolution: "NodeNext", target: "ES2022" }, include: ["src/**/*.ts"] }) ) fs.writeFileSync( path.join(cwd, "package.json"), JSON.stringify({ name: "@effect/sample", type: "module", exports: { ".": "./src/index.ts", "./*": "./src/*.ts" } }) ) fs.writeFileSync(path.join(cwd, "src/index.ts"), `export * as Foo from "./Foo.ts"\n`) fs.writeFileSync( path.join(cwd, "src/Foo.ts"), `/** * @since 1.0.0 */ const Array_ = "array" export { /** * Public array helper. * * @category constructors * @since 1.0.0 */ Array_ as Array } /** * Public tuple helper. * * @see {@link Array_} * @see {@link Array} * @category constructors * @since 1.0.0 */ export const Tuple = Array ` ) const model = extractJSDocsSync({ cwd, tsconfig: "tsconfig.json", include: ["src/**/*.ts"], output: ".data/jsdocs.json" }) const tuple = model.apis.find((api) => api.apiFqn === "@effect/sample/Foo.Tuple") const links = tuple?.see.flatMap((tag) => tag.links) ?? [] assert.deepStrictEqual(model.files.flatMap((file) => file.diagnostics), []) assert.deepStrictEqual(links.map((link) => link.resolution), [{ _tag: "Resolved", apiId: "root-declaration:value:@effect/sample/Foo.Array", apiFqn: "@effect/sample/Foo.Array" }, { _tag: "Resolved", apiId: "root-declaration:value:@effect/sample/Foo.Array", apiFqn: "@effect/sample/Foo.Array" }]) }) })