Skip to content

Commit 9e4c6ae

Browse files
committed
enforce presence of DocumentationTypes for type *Options or type *Result
1 parent 4179446 commit 9e4c6ae

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
@@ -16,6 +16,7 @@ import {
1616
TVariablesShouldExtendOperationVariables,
1717
TDataTVariablesOrder,
1818
} from "./generics.ts";
19+
import { enforceDocumentationTypes } from "./namespace-documentationTypes.ts";
1920

2021
export default {
2122
"require-using-disposable": requireUsingDisposable,
@@ -31,4 +32,5 @@ export default {
3132
"variables-should-extend-operation-variables":
3233
TVariablesShouldExtendOperationVariables,
3334
"tdata-tvariables-order": TDataTVariablesOrder,
35+
"enforce-documentation-types": enforceDocumentationTypes,
3436
};
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
@@ -201,6 +201,7 @@ export default [
201201
"local-rules/valid-inherit-doc": "error",
202202
"local-rules/variables-should-extend-operation-variables": "error",
203203
"local-rules/tdata-tvariables-order": "error",
204+
"local-rules/enforce-documentation-types": "error",
204205
},
205206
},
206207
...compat.extends("plugin:testing-library/react").map((config) => ({

0 commit comments

Comments
 (0)