diff --git a/.config/mocha.fast.json b/.config/mocha.fast.json index d546079ba..922306dfc 100644 --- a/.config/mocha.fast.json +++ b/.config/mocha.fast.json @@ -1,5 +1,8 @@ { + "$schema": "https://json.schemastore.org/mocharc.json", "timeout": 5000, "spec": ["src/test/**/*.test.ts"], - "exclude": ["src/test/packages/**", "src/test/slow/**"] + "exclude": ["src/test/packages/**", "src/test/slow/**"], + "watch-files": ["src/**/*.ts"], + "extension": ["ts", "tsx"] } diff --git a/CHANGELOG.md b/CHANGELOG.md index 887c6bf8e..5c7bdc7e4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,22 @@ # Unreleased +## v0.22.14 (2022-04-07) + +### Bug Fixes + +- Fixed missing comments on `@enum` style enum members defined in declaration files, #1880. +- Fixed `--validation.notDocumented` warnings for functions/methods/type aliases, #1895, #1898. +- Search results will no longer include random items when the search bar is empty, #1881. +- Comments on overloaded constructors will now be detected in the same way that overloaded functions/methods are. +- Fixed `removeReflection` not completely removing reflections from the project, #1898. +- Fixed `@hidden` / `@ignore` / `@exclude` comments on default exports with no associated variable, #1903. +- `makeRecursiveVisitor` will now correctly call the `intersection` callback, #1910. + +### Thanks! + +- @nlepage +- @ychan167 + ## v0.22.13 (2022-03-06) ### Features diff --git a/package-lock.json b/package-lock.json index c7c6ae0d0..bce3c2d8d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "typedoc", - "version": "0.22.13", + "version": "0.22.14", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "typedoc", - "version": "0.22.13", + "version": "0.22.14", "license": "Apache-2.0", "dependencies": { "glob": "^7.2.0", diff --git a/package.json b/package.json index e5575c2d0..5eb4c700e 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "typedoc", "description": "Create api documentation for TypeScript projects.", - "version": "0.22.13", + "version": "0.22.14", "homepage": "https://typedoc.org", "main": "./dist/index.js", "exports": "./dist/index.js", diff --git a/src/lib/converter/converter.ts b/src/lib/converter/converter.ts index a8c4c7ed9..04f3d96c1 100644 --- a/src/lib/converter/converter.ts +++ b/src/lib/converter/converter.ts @@ -187,8 +187,12 @@ export class Converter extends ChildableComponent< [ReflectionKind.EnumMember]: [ ts.SyntaxKind.EnumMember, ts.SyntaxKind.PropertyAssignment, + ts.SyntaxKind.PropertySignature, + ], + [ReflectionKind.Variable]: [ + ts.SyntaxKind.VariableDeclaration, + ts.SyntaxKind.ExportAssignment, ], - [ReflectionKind.Variable]: [ts.SyntaxKind.VariableDeclaration], [ReflectionKind.Function]: [ ts.SyntaxKind.FunctionDeclaration, ts.SyntaxKind.VariableDeclaration, @@ -201,6 +205,7 @@ export class Converter extends ChildableComponent< [ReflectionKind.Constructor]: [ts.SyntaxKind.Constructor], [ReflectionKind.Property]: [ ts.SyntaxKind.PropertyDeclaration, + ts.SyntaxKind.PropertyAssignment, ts.SyntaxKind.PropertySignature, ts.SyntaxKind.JSDocPropertyTag, ts.SyntaxKind.BinaryExpression, diff --git a/src/lib/converter/plugins/CommentPlugin.ts b/src/lib/converter/plugins/CommentPlugin.ts index 7609c42d0..fb260f34e 100644 --- a/src/lib/converter/plugins/CommentPlugin.ts +++ b/src/lib/converter/plugins/CommentPlugin.ts @@ -175,7 +175,11 @@ export class CommentPlugin extends ConverterComponent { reflection: Reflection, node?: ts.Node ) { - if (reflection.kindOf(ReflectionKind.FunctionOrMethod)) { + if ( + reflection.kindOf( + ReflectionKind.FunctionOrMethod | ReflectionKind.Constructor + ) + ) { // We only want a comment on functions/methods if this is a set of overloaded functions. // In that case, TypeDoc lets you put a comment on the implementation, and will copy it over to // the available signatures so that you can avoid documenting things multiple times. @@ -184,7 +188,9 @@ export class CommentPlugin extends ConverterComponent { let specialOverloadCase = false; if ( node && - (ts.isFunctionDeclaration(node) || ts.isMethodDeclaration(node)) + (ts.isFunctionDeclaration(node) || + ts.isMethodDeclaration(node) || + ts.isConstructorDeclaration(node)) ) { const symbol = node.name && context.checker.getSymbolAtLocation(node.name); @@ -192,7 +198,8 @@ export class CommentPlugin extends ConverterComponent { const declarations = symbol.declarations.filter( (d) => ts.isFunctionDeclaration(d) || - ts.isMethodDeclaration(d) + ts.isMethodDeclaration(d) || + ts.isConstructorDeclaration(d) ); if ( declarations.length > 1 && @@ -274,9 +281,9 @@ export class CommentPlugin extends ConverterComponent { ) as DeclarationReflection[], (method) => method.signatures?.length === 0 ); - allRemoved.forEach((reflection) => - project.removeReflection(reflection) - ); + allRemoved.forEach((reflection) => { + project.removeReflection(reflection); + }); someRemoved.forEach((reflection) => { reflection.sources = unique( reflection.signatures!.reduce( diff --git a/src/lib/converter/symbols.ts b/src/lib/converter/symbols.ts index 3bd7e2840..348a34722 100644 --- a/src/lib/converter/symbols.ts +++ b/src/lib/converter/symbols.ts @@ -170,6 +170,18 @@ export function convertSymbol( flags = removeFlag(flags, ts.SymbolFlags.Property); } + // A default exported function with no associated variable is a property, but + // we should really convert it as a variable for documentation purposes + // export default () => {} + // export default 123 + if ( + flags === ts.SymbolFlags.Property && + symbol.name === "default" && + context.scope.kindOf(ReflectionKind.Module | ReflectionKind.Project) + ) { + flags = ts.SymbolFlags.BlockScopedVariable; + } + for (const flag of getEnumFlags(flags ^ allConverterFlags)) { if (!(flag & allConverterFlags)) { context.logger.verbose( @@ -178,8 +190,8 @@ export function convertSymbol( } } - // Note: This method does not allow skipping earlier converters, defined according to the order of - // the ts.SymbolFlags enum. For now, this is fine... might not be flexible enough in the future. + // Note: This method does not allow skipping earlier converters. + // For now, this is fine... might not be flexible enough in the future. let skip = 0; for (const flag of conversionOrder) { if (!(flag & flags)) continue; @@ -509,10 +521,12 @@ function convertClassOrInterface( reflection ); reflectionContext.addChild(constructMember); - // The symbol is already taken by the class. - context.registerReflection(constructMember, undefined); const ctors = staticType.getConstructSignatures(); + context.registerReflection( + constructMember, + ctors?.[0]?.declaration?.symbol + ); // Modifiers are the same for all constructors if (ctors.length && ctors[0].declaration) { diff --git a/src/lib/models/reflections/abstract.ts b/src/lib/models/reflections/abstract.ts index 51f7b62a7..9a1a76ad7 100644 --- a/src/lib/models/reflections/abstract.ts +++ b/src/lib/models/reflections/abstract.ts @@ -364,7 +364,8 @@ export abstract class Reflection { } /** - * Return the full name of this reflection. + * Return the full name of this reflection. Intended for use in debugging. For log messages + * intended to be displayed to the user for them to fix, prefer {@link getFriendlyFullName} instead. * * The full name contains the name of this reflection and the names of all parent reflections. * @@ -379,6 +380,29 @@ export abstract class Reflection { } } + /** + * Return the full name of this reflection, with signature names dropped if possible without + * introducing ambiguity in the name. + */ + getFriendlyFullName(): string { + if (this.parent && !this.parent.isProject()) { + if ( + this.kindOf( + ReflectionKind.ConstructorSignature | + ReflectionKind.CallSignature | + ReflectionKind.GetSignature | + ReflectionKind.SetSignature + ) + ) { + return this.parent.getFriendlyFullName(); + } + + return this.parent.getFriendlyFullName() + "." + this.name; + } else { + return this.name; + } + } + /** * Set a flag on this reflection. */ diff --git a/src/lib/models/reflections/container.ts b/src/lib/models/reflections/container.ts index 840e04060..ae4455fcd 100644 --- a/src/lib/models/reflections/container.ts +++ b/src/lib/models/reflections/container.ts @@ -39,7 +39,7 @@ export class ContainerReflection extends Reflection { * @param callback The callback function that should be applied for each child reflection. */ override traverse(callback: TraverseCallback) { - for (const child of this.children ?? []) { + for (const child of this.children?.slice() || []) { if (callback(child, TraverseProperty.Children) === false) { return; } diff --git a/src/lib/models/reflections/declaration.ts b/src/lib/models/reflections/declaration.ts index e16647cba..bcd8d1e54 100644 --- a/src/lib/models/reflections/declaration.ts +++ b/src/lib/models/reflections/declaration.ts @@ -170,7 +170,7 @@ export class DeclarationReflection extends ContainerReflection { * @param callback The callback function that should be applied for each child reflection. */ override traverse(callback: TraverseCallback) { - for (const parameter of this.typeParameters ?? []) { + for (const parameter of this.typeParameters?.slice() || []) { if (callback(parameter, TraverseProperty.TypeParameter) === false) { return; } @@ -187,7 +187,7 @@ export class DeclarationReflection extends ContainerReflection { } } - for (const signature of this.signatures ?? []) { + for (const signature of this.signatures?.slice() || []) { if (callback(signature, TraverseProperty.Signatures) === false) { return; } diff --git a/src/lib/models/reflections/signature.ts b/src/lib/models/reflections/signature.ts index 1cae16971..8ef0ba4b7 100644 --- a/src/lib/models/reflections/signature.ts +++ b/src/lib/models/reflections/signature.ts @@ -73,13 +73,13 @@ export class SignatureReflection extends Reflection { } } - for (const parameter of this.typeParameters ?? []) { + for (const parameter of this.typeParameters?.slice() || []) { if (callback(parameter, TraverseProperty.TypeParameter) === false) { return; } } - for (const parameter of this.parameters ?? []) { + for (const parameter of this.parameters?.slice() || []) { if (callback(parameter, TraverseProperty.Parameters) === false) { return; } diff --git a/src/lib/models/types.ts b/src/lib/models/types.ts index b7bfaed05..fb8b80f44 100644 --- a/src/lib/models/types.ts +++ b/src/lib/models/types.ts @@ -89,6 +89,7 @@ export function makeRecursiveVisitor( visitor.inferred?.(type); }, intersection(type) { + visitor.intersection?.(type); type.types.forEach((t) => t.visit(recursiveVisitor)); }, intrinsic(type) { diff --git a/src/lib/output/plugins/MarkedLinksPlugin.ts b/src/lib/output/plugins/MarkedLinksPlugin.ts index c54a8cca0..d078068f6 100644 --- a/src/lib/output/plugins/MarkedLinksPlugin.ts +++ b/src/lib/output/plugins/MarkedLinksPlugin.ts @@ -135,7 +135,7 @@ export class MarkedLinksPlugin extends ContextAwareRendererComponent { } } else { const fullName = (this.reflection || - this.project)!.getFullName(); + this.project)!.getFriendlyFullName(); this.warnings.push(`In ${fullName}: ${original}`); return original; } diff --git a/src/lib/output/themes/default/assets/typedoc/components/Search.ts b/src/lib/output/themes/default/assets/typedoc/components/Search.ts index 1fe99fc99..95147f5e2 100644 --- a/src/lib/output/themes/default/assets/typedoc/components/Search.ts +++ b/src/lib/output/themes/default/assets/typedoc/components/Search.ts @@ -152,7 +152,9 @@ function updateResults( const searchText = query.value.trim(); // Perform a wildcard search - const res = state.index.search(`*${searchText}*`); + // Set empty `res` to prevent getting random results with wildcard search + // when the `searchText` is empty. + const res = searchText ? state.index.search(`*${searchText}*`) : []; for (let i = 0, c = Math.min(10, res.length); i < c; i++) { const row = state.data.rows[Number(res[i].ref)]; diff --git a/src/lib/utils/options/sources/typedoc.ts b/src/lib/utils/options/sources/typedoc.ts index 039cfe4e9..b3e1b5f88 100644 --- a/src/lib/utils/options/sources/typedoc.ts +++ b/src/lib/utils/options/sources/typedoc.ts @@ -372,8 +372,7 @@ export function addTypeDocOptions(options: Pick) { "Interface", "Property", "Method", - "GetSignature", - "SetSignature", + "Accessor", "TypeAlias", ], }); diff --git a/src/lib/validation/documentation.ts b/src/lib/validation/documentation.ts index 5b657f99c..722038861 100644 --- a/src/lib/validation/documentation.ts +++ b/src/lib/validation/documentation.ts @@ -1,23 +1,91 @@ import * as path from "path"; import * as ts from "typescript"; -import { ProjectReflection, ReflectionKind } from "../models"; +import { + DeclarationReflection, + ProjectReflection, + Reflection, + ReflectionKind, + ReflectionType, + SignatureReflection, +} from "../models"; import { Logger, normalizePath } from "../utils"; +import { removeFlag } from "../utils/enum"; export function validateDocumentation( project: ProjectReflection, logger: Logger, requiredToBeDocumented: readonly (keyof typeof ReflectionKind)[] ): void { - const kinds = requiredToBeDocumented.reduce( - (prev, cur) => (prev |= ReflectionKind[cur]), + let kinds = requiredToBeDocumented.reduce( + (prev, cur) => prev | ReflectionKind[cur], 0 ); - for (const ref of project.getReflectionsByKind(kinds)) { - const symbol = project.getSymbolFromReflection(ref); - if (!ref.comment && symbol?.declarations) { - const decl = symbol.declarations[0]; + // Functions, Constructors, and Accessors never have comments directly on them. + // If they are required to be documented, what's really required is that their + // contained signatures have a comment. + if (kinds & ReflectionKind.FunctionOrMethod) { + kinds |= ReflectionKind.CallSignature; + kinds = removeFlag(kinds, ReflectionKind.FunctionOrMethod); + } + if (kinds & ReflectionKind.Constructor) { + kinds |= ReflectionKind.ConstructorSignature; + kinds = removeFlag(kinds, ReflectionKind.Constructor); + } + if (kinds & ReflectionKind.Accessor) { + kinds |= ReflectionKind.GetSignature | ReflectionKind.SetSignature; + kinds = removeFlag(kinds, ReflectionKind.Accessor); + } + + const toProcess = project.getReflectionsByKind(kinds); + const seen = new Set(); + + while (toProcess.length) { + const ref = toProcess.shift()!; + if (seen.has(ref)) continue; + seen.add(ref); + + if (ref instanceof DeclarationReflection) { + const signatures = + ref.type instanceof ReflectionType + ? ref.type.declaration.getNonIndexSignatures() + : ref.getNonIndexSignatures(); + + if (signatures.length) { + // We maybe used to have a comment, but the comment plugin has removed it. + // See CommentPlugin.onResolve. We've been asked to validate this reflection, + // (it's probably a type alias) so we should validate that signatures all have + // comments, but we shouldn't produce a warning here. + toProcess.push(...signatures); + continue; + } + } + + let symbol = project.getSymbolFromReflection(ref); + let index = 0; + + // Signatures don't have symbols associated with them, so get the parent and then + // maybe also adjust the declaration index that we care about. + if (!symbol && ref.kindOf(ReflectionKind.SomeSignature)) { + symbol = project.getSymbolFromReflection(ref.parent!); + + const parentIndex = ( + ref.parent as DeclarationReflection + ).signatures?.indexOf(ref as SignatureReflection); + if (parentIndex) { + index = parentIndex; + } + } + + const decl = symbol?.declarations?.[index]; + + if (!ref.hasComment() && decl) { const sourceFile = decl.getSourceFile(); + + if (sourceFile.fileName.includes("node_modules")) { + continue; + } + const { line } = ts.getLineAndCharacterOfPosition( sourceFile, decl.getStart() @@ -26,13 +94,9 @@ export function validateDocumentation( path.relative(process.cwd(), sourceFile.fileName) ); - if (file.includes("node_modules")) { - continue; - } - const loc = `${file}:${line + 1}`; logger.warn( - `${ref.name}, defined at ${loc}, does not have any documentation.` + `${ref.getFriendlyFullName()}, defined at ${loc}, does not have any documentation.` ); } } diff --git a/src/test/TestLogger.ts b/src/test/TestLogger.ts new file mode 100644 index 000000000..aa62239b5 --- /dev/null +++ b/src/test/TestLogger.ts @@ -0,0 +1,37 @@ +import { Logger, LogLevel, removeIf } from "../lib/utils"; +import { deepStrictEqual as equal, fail } from "assert"; + +const levelMap: Record = { + [LogLevel.Error]: "error: ", + [LogLevel.Warn]: "warn: ", + [LogLevel.Info]: "info: ", + [LogLevel.Verbose]: "debug: ", +}; + +export class TestLogger extends Logger { + messages: string[] = []; + + discardDebugMessages() { + removeIf(this.messages, (msg) => msg.startsWith("debug")); + } + + expectMessage(message: string) { + const index = this.messages.indexOf(message); + if (index === -1) { + const messages = this.messages.join("\n\t") || "(none logged)"; + fail( + `Expected "${message}" to be logged. The logged messages were:\n\t${messages}` + ); + } + this.messages.splice(index, 1); + } + + expectNoOtherMessages() { + equal(this.messages, [], "Expected no other messages to be logged."); + } + + override log(message: string, level: LogLevel): void { + super.log(message, level); + this.messages.push(levelMap[level] + message); + } +} diff --git a/src/test/behaviorTests.ts b/src/test/behaviorTests.ts index 8ef0a11bb..bb286671d 100644 --- a/src/test/behaviorTests.ts +++ b/src/test/behaviorTests.ts @@ -70,4 +70,13 @@ export const behaviorTests: Record< equal(foo.comment, undefined); }, + + removeReflection(project) { + const foo = query(project, "foo"); + project.removeReflection(foo); + equal( + Object.values(project.reflections).map((r) => r.name), + ["typedoc"] + ); + }, }; diff --git a/src/test/converter/exports/mod.ts b/src/test/converter/exports/mod.ts index 6d2786601..9781d2919 100644 --- a/src/test/converter/exports/mod.ts +++ b/src/test/converter/exports/mod.ts @@ -20,9 +20,7 @@ export { a as c } from "./mod"; /** * Will not be re-exported from export.ts using export * from... */ -export default function () { - console.log("Default"); -} +export default function () {} /** * This is annotated with the hidden tag and will therefore not be included in the generated documentation. diff --git a/src/test/converter2.test.ts b/src/test/converter2.test.ts index 6b16a421d..d490ca495 100644 --- a/src/test/converter2.test.ts +++ b/src/test/converter2.test.ts @@ -9,6 +9,7 @@ import { getConverter2Base, getConverter2Program, } from "./programs"; +import { TestLogger } from "./TestLogger"; const base = getConverter2Base(); const app = getConverter2App(); @@ -16,7 +17,7 @@ const app = getConverter2App(); function runTest( title: string, entry: string, - check: (project: ProjectReflection) => void + check: (project: ProjectReflection, logger: TestLogger) => void ) { it(title, () => { const program = getConverter2Program(); @@ -33,6 +34,7 @@ function runTest( const sourceFile = program.getSourceFile(entryPoint); ok(sourceFile, `No source file found for ${entryPoint}`); + app.logger = new TestLogger(); const project = app.converter.convert([ { displayName: entry, @@ -40,7 +42,7 @@ function runTest( sourceFile, }, ]); - check(project); + check(project, app.logger as TestLogger); }); } diff --git a/src/test/converter2/behavior/removeReflection.ts b/src/test/converter2/behavior/removeReflection.ts new file mode 100644 index 000000000..e076de0fa --- /dev/null +++ b/src/test/converter2/behavior/removeReflection.ts @@ -0,0 +1,5 @@ +export function foo(first: string, second: string, third: string) { + first; + second; + third; +} diff --git a/src/test/converter2/issues/gh1547.ts b/src/test/converter2/issues/gh1547.ts index 972100b89..1000fa660 100644 --- a/src/test/converter2/issues/gh1547.ts +++ b/src/test/converter2/issues/gh1547.ts @@ -19,7 +19,5 @@ export class Test { * * @param things - Array of things or a thing. */ - log_thing(things: ValueOrArray): void { - console.log(things); - } + log_thing(things: ValueOrArray): void {} } diff --git a/src/test/converter2/issues/gh1580.ts b/src/test/converter2/issues/gh1580.ts index 63a048973..2caef2c83 100644 --- a/src/test/converter2/issues/gh1580.ts +++ b/src/test/converter2/issues/gh1580.ts @@ -5,9 +5,7 @@ class A { prop!: string; /** Run docs */ - run(): void { - console.log("A"); - } + run(): void {} } class B extends A { @@ -15,6 +13,5 @@ class B extends A { run(): void { super.run(); - console.log("B"); } } diff --git a/src/test/converter2/issues/gh1880.d.ts b/src/test/converter2/issues/gh1880.d.ts new file mode 100644 index 000000000..87dfa5ef7 --- /dev/null +++ b/src/test/converter2/issues/gh1880.d.ts @@ -0,0 +1,11 @@ +/** + * This enumeration defines some values. + * + * @enum + */ +export declare const SomeEnum: { + /** + * This is the auto property. + */ + readonly AUTO: "auto"; +}; diff --git a/src/test/converter2/issues/gh1898.ts b/src/test/converter2/issues/gh1898.ts new file mode 100644 index 000000000..593fd481f --- /dev/null +++ b/src/test/converter2/issues/gh1898.ts @@ -0,0 +1,34 @@ +/** I am documented, but the validator thinks otherwise. */ +export type FunctionType = (foo: string) => string; + +export type UnDocFn = (foo: string) => string; + +/** This docblock works fine. */ +export class ExampleClass { + /** This wrongly complains about not being documented */ + [Symbol.iterator]() { + return { + /** This also says it's not documented. */ + index: 0, + /** This one too. */ + next(): IteratorResult { + return { done: false, value: this.index++ }; + }, + }; + } + + /** + * @hidden + */ + [Symbol.asyncIterator]() { + return { + // This does _not_ complain, which is what I would expect. + index: 0, + // Even though it's not rendered in the final docs, it still complains + // that this isn't documented. + async next(): Promise> { + return { done: false, value: this.index++ }; + }, + }; + } +} diff --git a/src/test/converter2/issues/gh1903.ts b/src/test/converter2/issues/gh1903.ts new file mode 100644 index 000000000..c95663935 --- /dev/null +++ b/src/test/converter2/issues/gh1903.ts @@ -0,0 +1,4 @@ +/** + * @hidden + */ +export default () => {}; diff --git a/src/test/converter2/issues/gh1903b.ts b/src/test/converter2/issues/gh1903b.ts new file mode 100644 index 000000000..9759ffb9e --- /dev/null +++ b/src/test/converter2/issues/gh1903b.ts @@ -0,0 +1,2 @@ +/** @hidden */ +export default 123; diff --git a/src/test/converter2/validation/class.ts b/src/test/converter2/validation/class.ts new file mode 100644 index 000000000..20689a559 --- /dev/null +++ b/src/test/converter2/validation/class.ts @@ -0,0 +1,8 @@ +export class Foo { + /** Comment */ + constructor(); + constructor(x: string); + constructor(x?: string) {} +} + +export class Bar {} diff --git a/src/test/converter2/validation/function.ts b/src/test/converter2/validation/function.ts new file mode 100644 index 000000000..f21370b09 --- /dev/null +++ b/src/test/converter2/validation/function.ts @@ -0,0 +1,7 @@ +/** Foo docs */ +export function foo() {} + +export function bar(); +/** Bar docs */ +export function bar(x: string); +export function bar() {} diff --git a/src/test/issueTests.ts b/src/test/issueTests.ts index 00687e7ef..0d63f726c 100644 --- a/src/test/issueTests.ts +++ b/src/test/issueTests.ts @@ -10,6 +10,8 @@ import { CommentTag, UnionType, } from "../lib/models"; +import { getConverter2App } from "./programs"; +import type { TestLogger } from "./TestLogger"; function query(project: ProjectReflection, name: string) { const reflection = project.getChildByName(name); @@ -18,7 +20,7 @@ function query(project: ProjectReflection, name: string) { } export const issueTests: { - [issue: string]: (project: ProjectReflection) => void; + [issue: string]: (project: ProjectReflection, logger: TestLogger) => void; } = { gh869(project) { const classFoo = project.children?.find( @@ -335,4 +337,37 @@ export const issueTests: { "Nested\n" ); }, + + gh1880(project) { + const SomeEnum = query(project, "SomeEnum"); + equal(SomeEnum.kind, ReflectionKind.Enum); + ok(SomeEnum.hasComment(), "Missing @enum variable comment"); + + const auto = query(project, "SomeEnum.AUTO"); + ok(auto.hasComment(), "Missing @enum member comment"); + }, + + gh1898(project, logger) { + const app = getConverter2App(); + app.validate(project); + logger.discardDebugMessages(); + logger.expectMessage( + "warn: UnDocFn.__type, defined at src/test/converter2/issues/gh1898.ts:4, does not have any documentation." + ); + logger.expectNoOtherMessages(); + }, + + gh1903(project) { + equal( + Object.values(project.reflections).map((r) => r.name), + ["typedoc"] + ); + }, + + gh1903b(project) { + equal( + Object.values(project.reflections).map((r) => r.name), + ["typedoc"] + ); + }, }; diff --git a/src/test/programs.ts b/src/test/programs.ts index 61d0aa620..86e2bab30 100644 --- a/src/test/programs.ts +++ b/src/test/programs.ts @@ -58,6 +58,7 @@ export function getConverter2App() { excludeExternals: true, tsconfig: join(getConverter2Base(), "tsconfig.json"), plugin: [], + validation: true, }); converter2App.options.freeze(); } diff --git a/src/test/validation.test.ts b/src/test/validation.test.ts index 23e6bbb63..6c50dedfb 100644 --- a/src/test/validation.test.ts +++ b/src/test/validation.test.ts @@ -1,15 +1,12 @@ import { equal, fail, ok } from "assert"; import { join, relative } from "path"; import { Logger, LogLevel, normalizePath } from ".."; +import { validateDocumentation } from "../lib/validation/documentation"; import { validateExports } from "../lib/validation/exports"; import { getConverter2App, getConverter2Program } from "./programs"; +import { TestLogger } from "./TestLogger"; -function expectWarning( - typeName: string, - file: string, - referencingName: string, - intentionallyNotExported: readonly string[] = [] -) { +function convertValidationFile(file: string) { const app = getConverter2App(); const program = getConverter2Program(); const sourceFile = program.getSourceFile( @@ -26,6 +23,17 @@ function expectWarning( }, ]); + return project; +} + +function expectWarning( + typeName: string, + file: string, + referencingName: string, + intentionallyNotExported: readonly string[] = [] +) { + const project = convertValidationFile(file); + let sawWarning = false; const regex = /(.*?), defined at (.*?):\d+, is referenced by (.*?) but not included in the documentation\./; @@ -186,3 +194,38 @@ describe("validateExports", () => { ok(sawWarning, "Never saw warning."); }); }); + +describe("validateDocumentation", () => { + it("Should correctly handle functions", () => { + const project = convertValidationFile("function.ts"); + const logger = new TestLogger(); + validateDocumentation(project, logger, ["Function"]); + + logger.expectMessage( + "warn: bar, defined at src/test/converter2/validation/function.ts:4, does not have any documentation." + ); + logger.expectNoOtherMessages(); + }); + + it("Should correctly handle accessors", () => { + const project = convertValidationFile("getSignature.ts"); + const logger = new TestLogger(); + validateDocumentation(project, logger, ["Accessor"]); + + logger.expectMessage( + "warn: Foo.foo, defined at src/test/converter2/validation/getSignature.ts:2, does not have any documentation." + ); + logger.expectNoOtherMessages(); + }); + + it("Should correctly handle constructors", () => { + const project = convertValidationFile("class.ts"); + const logger = new TestLogger(); + validateDocumentation(project, logger, ["Constructor"]); + + logger.expectMessage( + "warn: Foo.constructor, defined at src/test/converter2/validation/class.ts:4, does not have any documentation." + ); + logger.expectNoOtherMessages(); + }); +});