diff --git a/src/exports.ts b/src/exports.ts new file mode 100644 index 0000000..9e3f370 --- /dev/null +++ b/src/exports.ts @@ -0,0 +1,4 @@ +export * from "./lib"; +export * from "./types"; +export { closePopover } from "./utils/popover"; +export { getMessageIframe } from "./utils/postMessage"; diff --git a/src/index.ts b/src/index.ts index 68f9809..6489d59 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,11 +1,4 @@ -import * as methods from "./lib"; +import * as prismatic from "./exports"; -export * from "./lib"; - -export * from "./types"; -export { closePopover } from "./utils/popover"; -export { getMessageIframe } from "./utils/postMessage"; - -export default { - ...methods, -}; +export * from "./exports"; +export default prismatic; diff --git a/src/jsdoc.test.ts b/src/jsdoc.test.ts new file mode 100644 index 0000000..5daeeee --- /dev/null +++ b/src/jsdoc.test.ts @@ -0,0 +1,98 @@ +import { resolve } from "node:path"; +import ts from "typescript"; +import { describe, expect, it } from "vitest"; + +const indexPath = resolve(__dirname, "index.ts"); + +const createProgram = () => { + const configPath = ts.findConfigFile(indexPath, ts.sys.fileExists); + if (!configPath) throw new Error("tsconfig not found"); + const { config } = ts.readConfigFile(configPath, ts.sys.readFile); + const { options } = ts.parseJsonConfigFileContent( + config, + ts.sys, + resolve(__dirname, ".."), + ); + return ts.createProgram([indexPath], options); +}; + +const getDocumentation = (symbol: ts.Symbol, checker: ts.TypeChecker): string => + ts.displayPartsToString(symbol.getDocumentationComment(checker)); + +const resolveSymbol = ( + symbol: ts.Symbol, + checker: ts.TypeChecker, +): ts.Symbol => + symbol.flags & ts.SymbolFlags.Alias + ? checker.getAliasedSymbol(symbol) + : symbol; + +/** + * Extract the JSDoc text for every named export and every property on the + * default export, then return them keyed by name. + */ +const collectDocs = () => { + const program = createProgram(); + const checker = program.getTypeChecker(); + const sourceFile = program.getSourceFile(indexPath); + if (!sourceFile) throw new Error(`Source file not found: ${indexPath}`); + const moduleSymbol = checker.getSymbolAtLocation(sourceFile); + if (!moduleSymbol) throw new Error("Module symbol not found"); + const exports = checker.getExportsOfModule(moduleSymbol); + + const namedDocs = new Map(); + const defaultDocs = new Map(); + + for (const exp of exports) { + if (exp.getName() === "default") { + const resolved = resolveSymbol(exp, checker); + const declaration = + resolved.valueDeclaration ?? resolved.declarations?.[0]; + if (!declaration) throw new Error("Default export has no declaration"); + const defaultType = checker.getTypeOfSymbolAtLocation( + resolved, + declaration, + ); + + for (const prop of defaultType.getProperties()) { + const resolvedProp = resolveSymbol(prop, checker); + defaultDocs.set( + prop.getName(), + getDocumentation(resolvedProp, checker), + ); + } + } else { + const resolved = resolveSymbol(exp, checker); + if (resolved.flags & ts.SymbolFlags.Value) { + namedDocs.set(exp.getName(), getDocumentation(resolved, checker)); + } + } + } + + return { namedDocs, defaultDocs }; +}; + +describe("default export JSDoc parity", () => { + const { namedDocs, defaultDocs } = collectDocs(); + + const namedKeys = new Set(namedDocs.keys()); + const defaultKeys = new Set(defaultDocs.keys()); + + it("default export has the same keys as the named value exports", () => { + const missingFromDefault = new Set( + [...namedKeys].filter((k) => !defaultKeys.has(k)), + ); + const extraOnDefault = new Set( + [...defaultKeys].filter((k) => !namedKeys.has(k)), + ); + + expect(missingFromDefault).toEqual(new Set()); + expect(extraOnDefault).toEqual(new Set()); + }); + + it.each([ + ...namedKeys, + ])("%s has matching JSDoc on the default export", (name) => { + expect(defaultDocs.get(name)).toBe(namedDocs.get(name)); + }); +}); diff --git a/tests/types/default-export.test-d.ts b/tests/types/default-export.test-d.ts index 97462a5..9d28443 100644 --- a/tests/types/default-export.test-d.ts +++ b/tests/types/default-export.test-d.ts @@ -1,5 +1,5 @@ import { expectTypeOf } from "expect-type"; import defaultExport from "../../src"; -import * as methods from "../../src/lib"; +import * as allExports from "../../src/exports"; -expectTypeOf(defaultExport).toEqualTypeOf(); +expectTypeOf(defaultExport).toEqualTypeOf();