Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add option to warn on undocumented items #1819

Merged
merged 11 commits into from Jan 23, 2022
4 changes: 4 additions & 0 deletions CHANGELOG.md
@@ -1,5 +1,9 @@
# Unreleased

### Features

- Added `--validation.notDocumented` option to warn on items that are not documented

### Bug Fixes

- Fixed line height of `h1` and `h2` elements being too low, #1796.
Expand Down
9 changes: 9 additions & 0 deletions src/lib/application.ts
Expand Up @@ -34,6 +34,7 @@ import {
} from "./utils/entry-point";
import { nicePath } from "./utils/paths";
import { hasBeenLoadedMultipleTimes } from "./utils/general";
import { validateDocumentation } from "./validation/documentation";

// eslint-disable-next-line @typescript-eslint/no-var-requires
const packageInfo = require("../../package.json") as {
Expand Down Expand Up @@ -416,6 +417,14 @@ export class Application extends ChildableComponent<
);
}

if (checks.notDocumented) {
validateDocumentation(
project,
this.logger,
this.options.getValue("requiredToBeDocumented")
);
}

// checks.invalidLink is currently handled when rendering by the MarkedLinksPlugin.
// It should really move here, but I'm putting that off until done refactoring the comment
// parsing so that we don't have duplicate parse logic all over the place.
Expand Down
6 changes: 6 additions & 0 deletions src/lib/utils/options/declaration.ts
Expand Up @@ -3,6 +3,7 @@ import type { LogLevel } from "../loggers";
import type { SortStrategy } from "../sort";
import { isAbsolute, join, resolve } from "path";
import type { EntryPointStrategy } from "../entry-point";
import type { ReflectionKind } from "../../models";

export const EmitStrategy = {
true: true, // Alias for both, for backwards compatibility until 0.23
Expand Down Expand Up @@ -115,6 +116,7 @@ export interface TypeDocOptionMap {
/** @deprecated use validation.invalidLink */
listInvalidSymbolLinks: boolean;
validation: ValidationOptions;
requiredToBeDocumented: (keyof typeof ReflectionKind)[];
}

export type ValidationOptions = {
Expand All @@ -127,6 +129,10 @@ export type ValidationOptions = {
* If set, TypeDoc will produce warnings about \{&amp;link\} tags which will produce broken links.
*/
invalidLink: boolean;
/**
* If set, TypeDoc will produce warnings about declarations that do not have doc comments
*/
notDocumented: boolean;
};

/**
Expand Down
40 changes: 40 additions & 0 deletions src/lib/utils/options/sources/typedoc.ts
Expand Up @@ -4,6 +4,8 @@ import { ParameterType, ParameterHint, EmitStrategy } from "../declaration";
import { BUNDLED_THEMES, Theme } from "shiki";
import { SORT_STRATEGIES } from "../../sort";
import { EntryPointStrategy } from "../../entry-point";
import { ReflectionKind } from "../../../models";
import { toOrdinal } from "../../ordinal-numbers";

export function addTypeDocOptions(options: Pick<Options, "addDeclaration">) {
options.addDeclaration({
Expand Down Expand Up @@ -338,6 +340,43 @@ export function addTypeDocOptions(options: Pick<Options, "addDeclaration">) {
help: "A list of types which should not produce 'referenced but not documented' warnings.",
type: ParameterType.Array,
});
options.addDeclaration({
name: "requiredToBeDocumented",
help: "A list of reflection kinds that must be documented",
type: ParameterType.Array,
validate(values) {
const validValues = Object.values(ReflectionKind)
// this is good enough because the values of the ReflectionKind enum are all numbers
.filter((v) => typeof v === "string")
.join(", ");
for (
let i = 0, kind = values[i];
i < values.length;
i += 1, kind = values[i]
) {
if (!(kind in ReflectionKind)) {
throw new Error(
`The ${toOrdinal(
i + 1
)} 'requiredToBeDocumented' value is invalid. Must be one of: ${validValues}`
);
}
}
},
defaultValue: [
"Enum",
"EnumMember",
"Variable",
"Function",
"Class",
"Interface",
"Property",
"Method",
"GetSignature",
"SetSignature",
"TypeAlias",
],
});

options.addDeclaration({
name: "validation",
Expand All @@ -346,6 +385,7 @@ export function addTypeDocOptions(options: Pick<Options, "addDeclaration">) {
defaults: {
notExported: true,
invalidLink: false,
notDocumented: false,
},
});
}
25 changes: 25 additions & 0 deletions src/lib/utils/ordinal-numbers.ts
@@ -0,0 +1,25 @@
/**
* Format an integer value as an ordinal string. Throwing if the value is not a
* positive integer
* @param value The integer value to format as its ordinal version
*/
export function toOrdinal(value: number): string {
if (!Number.isInteger(value)) {
throw new TypeError("value must be an integer number");
}

if (value < 0) {
throw new TypeError("value must be a positive integer");
}

const onesDigit = value % 10;
const tensDigit = ((value % 100) - onesDigit) / 10;

if (tensDigit === 1) {
return `${value}th`;
}

const ordinal = onesDigit === 1 ? "st" : onesDigit === 2 ? "nd" : "th";

return `${value}${ordinal}`;
}
39 changes: 39 additions & 0 deletions src/lib/validation/documentation.ts
@@ -0,0 +1,39 @@
import * as path from "path";
import * as ts from "typescript";
import { ProjectReflection, ReflectionKind } from "../models";
import { Logger, normalizePath } from "../utils";

export function validateDocumentation(
project: ProjectReflection,
logger: Logger,
requiredToBeDocumented: readonly (keyof typeof ReflectionKind)[]
): void {
const 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];
const sourceFile = decl.getSourceFile();
const { line } = ts.getLineAndCharacterOfPosition(
Nokel81 marked this conversation as resolved.
Show resolved Hide resolved
sourceFile,
decl.getStart()
);
const file = normalizePath(
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.`
);
}
}
}
2 changes: 1 addition & 1 deletion src/lib/validation/exports.ts
Expand Up @@ -107,7 +107,7 @@ export function validateExports(
logger.warn(
`${
type.name
}, defined at ${file}:${line}, is referenced by ${current!.getFullName()} but not included in the documentation.`
}, defined at ${file}:${line+1}, is referenced by ${current!.getFullName()} but not included in the documentation.`
);
}
}
Expand Down
2 changes: 2 additions & 0 deletions src/test/utils/options/options.test.ts
Expand Up @@ -115,12 +115,14 @@ describe("Options", () => {
options.setValue("validation", true);
equal(options.getValue("validation"), {
notExported: true,
notDocumented: true,
invalidLink: true,
});

options.setValue("validation", false);
equal(options.getValue("validation"), {
notExported: false,
notDocumented: false,
invalidLink: false,
});
});
Expand Down
5 changes: 5 additions & 0 deletions src/test/utils/options/readers/arguments.test.ts
Expand Up @@ -164,6 +164,7 @@ describe("Options - ArgumentsReader", () => {
equal(logger.hasWarnings(), false);
equal(options.getValue("validation"), {
notExported: true,
notDocumented: false,
invalidLink: true,
});
}
Expand All @@ -182,6 +183,7 @@ describe("Options - ArgumentsReader", () => {
equal(logger.hasWarnings(), false);
equal(options.getValue("validation"), {
notExported: false,
notDocumented: false,
invalidLink: true,
});
}
Expand All @@ -195,6 +197,7 @@ describe("Options - ArgumentsReader", () => {
equal(logger.hasWarnings(), false);
equal(options.getValue("validation"), {
notExported: true,
notDocumented: true,
invalidLink: true,
});
}
Expand All @@ -208,6 +211,7 @@ describe("Options - ArgumentsReader", () => {
equal(logger.hasWarnings(), false);
equal(options.getValue("validation"), {
notExported: true,
notDocumented: true,
invalidLink: true,
});
}
Expand All @@ -221,6 +225,7 @@ describe("Options - ArgumentsReader", () => {
equal(logger.hasWarnings(), false);
equal(options.getValue("validation"), {
notExported: false,
notDocumented: false,
invalidLink: false,
});
}
Expand Down