Skip to content

Commit 22a7b7e

Browse files
committed
enforce presence of DocumentationTypes for type *Options or type *Result
1 parent f5b754f commit 22a7b7e

File tree

3 files changed

+75
-0
lines changed

3 files changed

+75
-0
lines changed

eslint-local-rules/index.mjs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
} from "./import-from-export.ts";
1313
import { rule as requireDisableActEnvironment } from "./require-disable-act-environment.ts";
1414
import { rule as requireUsingDisposable } from "./require-using-disposable.ts";
15+
import { enforceDocumentationTypes } from "./namespace-documentationTypes.ts";
1516

1617
export default {
1718
"require-using-disposable": requireUsingDisposable,
@@ -24,4 +25,5 @@ export default {
2425
"no-relative-imports": noRelativeImports,
2526
"valid-inherit-doc": validInheritDoc,
2627
"mdx-valid-canonical-references": validMdxCanonicalReferences,
28+
"enforce-documentation-types": enforceDocumentationTypes,
2729
};
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import type { TSESTree as AST } from "@typescript-eslint/types";
2+
import { ESLintUtils } from "@typescript-eslint/utils";
3+
4+
export const enforceDocumentationTypes = ESLintUtils.RuleCreator.withoutDocs({
5+
create(context) {
6+
const namespaces = [];
7+
const shouldBeDocumented: Record<
8+
string,
9+
{
10+
namespaces: string[];
11+
node: AST.TSTypeAliasDeclaration;
12+
}
13+
> = {};
14+
return {
15+
TSModuleDeclaration(node) {
16+
if (node.kind !== "namespace") {
17+
return;
18+
}
19+
namespaces.push(
20+
node.id.type === "Identifier" ? node.id.name : "<unknown>"
21+
);
22+
},
23+
"TSModuleDeclaration:exit"(node) {
24+
if (node.kind !== "namespace") {
25+
return;
26+
}
27+
namespaces.pop();
28+
for (const [name, entry] of Object.entries(shouldBeDocumented)) {
29+
if (entry.namespaces.length > namespaces.length) {
30+
delete shouldBeDocumented[name];
31+
context.report({
32+
node: entry.node.id,
33+
messageId: "shouldBeDocumented",
34+
});
35+
}
36+
}
37+
},
38+
ExportNamedDeclaration(node) {
39+
if (!node.declaration) {
40+
return;
41+
}
42+
if (node.declaration.type === "TSTypeAliasDeclaration") {
43+
const name = node.declaration.id.name;
44+
if (name.endsWith("Result") || name.endsWith("Options")) {
45+
shouldBeDocumented[name] = {
46+
node: node.declaration,
47+
namespaces: [...namespaces],
48+
};
49+
}
50+
} else if (node.declaration.type === "TSInterfaceDeclaration") {
51+
const name = node.declaration.id.name;
52+
if (
53+
name in shouldBeDocumented &&
54+
namespaces.at(-1) === "DocumentationTypes"
55+
) {
56+
delete shouldBeDocumented[name];
57+
}
58+
}
59+
},
60+
};
61+
},
62+
meta: {
63+
messages: {
64+
shouldBeDocumented:
65+
"This type should have an interface with the same name in a nested `DocumentationTypes` namespace.",
66+
},
67+
type: "problem",
68+
schema: [],
69+
fixable: "code",
70+
},
71+
defaultOptions: [],
72+
});

eslint.config.mjs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,7 @@ export default [
199199
"import/consistent-type-specifier-style": ["error", "prefer-top-level"],
200200
"local-rules/require-using-disposable": "error",
201201
"local-rules/valid-inherit-doc": "error",
202+
"local-rules/enforce-documentation-types": "error",
202203
},
203204
},
204205
...compat.extends("plugin:testing-library/react").map((config) => ({

0 commit comments

Comments
 (0)