Skip to content

Commit baa6bec

Browse files
authored
lint @inheritdoc and MDX for correct canonical references (#12785)
* lint `@inheritDoc` tags for correct canonical references * also lint mdx, add rule for canonicalReferences * fix up incorrect canonical references * format
1 parent 2d1e3ea commit baa6bec

File tree

15 files changed

+1658
-263
lines changed

15 files changed

+1658
-263
lines changed

.circleci/config.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ jobs:
3636
- checkout
3737
- run: npm version
3838
- run: npm ci
39+
- run: npm run docmodel
3940
- run: npm run build
4041
- run: npm run lint
4142

config/apiExtractor.ts

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { spawnSync } from "node:child_process";
22
import fs from "node:fs";
3-
import { join } from "node:path";
3+
import { readFile, writeFile } from "node:fs/promises";
4+
import { format, join, parse } from "node:path";
45
import { parseArgs } from "node:util";
56
import * as path from "path";
67

@@ -84,10 +85,36 @@ try {
8485
})
8586
);
8687

87-
await buildReport("@apollo/client", entryPointFile, "docModel");
88+
const result = await buildReport(
89+
"@apollo/client",
90+
entryPointFile,
91+
"docModel"
92+
);
8893
if (process.exitCode === 50) {
8994
process.exitCode = 0; // if there were only warnings, we still want to exit with 0
9095
}
96+
97+
console.log("Creating file with all possible canonical references...");
98+
const canonicalReferences = new Set<string>();
99+
const file = await readFile(result.extractorConfig.apiJsonFilePath, "utf8");
100+
JSON.parse(file, (key, value) => {
101+
if (
102+
key === "canonicalReference" &&
103+
typeof value === "string" &&
104+
value.startsWith("@apollo/client")
105+
) {
106+
canonicalReferences.add(value);
107+
}
108+
return undefined;
109+
});
110+
await writeFile(
111+
format({
112+
...parse(result.extractorConfig.apiJsonFilePath),
113+
base: "canonical-references.json",
114+
}),
115+
JSON.stringify([...canonicalReferences.values()], null, 2),
116+
"utf8"
117+
);
91118
}
92119

93120
if (parsed.values.generate?.includes("apiReport")) {
@@ -185,4 +212,5 @@ async function buildReport(
185212
);
186213
}
187214
}
215+
return extractorResult;
188216
}

docs/source/api/core/ApolloClient.mdx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ For more information on the `defaultOptions` object, see the [Default Options](#
4545
## Types
4646

4747
<InterfaceDetails
48-
canonicalReference="@apollo/client!ApolloClientOptions:interface"
48+
canonicalReference="@apollo/client!ApolloClient.Options:interface"
4949
headingLevel={3}
5050
customPropertyOrder={[
5151
"cache",
@@ -58,7 +58,7 @@ customPropertyOrder={[
5858
]}
5959
/>
6060

61-
<InterfaceDetails canonicalReference="@apollo/client!DefaultOptions:interface" headingLevel={3} />
61+
<InterfaceDetails canonicalReference="@apollo/client!ApolloClient.DefaultOptions:interface" headingLevel={3} />
6262

6363
##### Example `defaultOptions` object
6464

docs/source/api/react/useLazyQuery.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ description: Apollo Client API reference
2222
<ManualTupleItem
2323
name="result"
2424
type="QueryResult<TData, TVariables>"
25-
canonicalReference="@apollo/client!QueryResult:interface"
25+
canonicalReference="@apollo/client!useLazyQuery.DocumentationTypes.useLazyQuery.Result:interface"
2626
>
2727
The result of the query. See the `useQuery` hook for more details.
2828
</ManualTupleItem>

docs/source/api/react/useMutation.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ description: Apollo Client API reference
4545
<ManualTupleItem
4646
name="result"
4747
type="MutationResult<TData>"
48-
canonicalReference="@apollo/client!MutationResult:interface"
48+
canonicalReference="@apollo/client!useMutation.Result:interface"
4949
idPrefix="usemutation-result"
5050
>
5151
The result of the mutation.

docs/source/data/queries.mdx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -528,13 +528,13 @@ Most calls to `useQuery` can omit the majority of these options, but it's useful
528528
529529
The `useQuery` hook accepts the following options:
530530
531-
<PropertySignatureTable canonicalReference="@apollo/client!useQuery.DocumentationTypes.Options:interface" idPrefix="queryhookoptions-interface" />
531+
<PropertySignatureTable canonicalReference="@apollo/client!useQuery.DocumentationTypes.useQuery.Options:interface" idPrefix="queryhookoptions-interface" />
532532
533533
### Result
534534
535535
After being called, the `useQuery` hook returns a result object with the following properties. This object contains your query result, plus some helpful functions for refetching, dynamic polling, and pagination.
536536
537-
<PropertySignatureTable canonicalReference="@apollo/client!useQuery.DocumentationTypes.Result:interface" idPrefix="queryresult-interface" />
537+
<PropertySignatureTable canonicalReference="@apollo/client!useQuery.DocumentationTypes.useQuery.Result:interface" idPrefix="queryresult-interface" />
538538
539539
## Next steps
540540

docs/source/data/subscriptions.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -518,7 +518,7 @@ In the example above, we pass three options to `subscribeToMore`:
518518

519519
The `useSubscription` Hook accepts the following options:
520520

521-
<PropertySignatureTable canonicalReference="@apollo/client!useSubscription.DocumentationTypes.Result:interface" idPrefix="subscriptionhookoptions-interface" />
521+
<PropertySignatureTable canonicalReference="@apollo/client!useSubscription.DocumentationTypes.useSubscription.Result:interface" idPrefix="subscriptionhookoptions-interface" />
522522

523523
### Result
524524

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
import type { TSESTree as AST } from "@typescript-eslint/types";
2+
import { ESLintUtils } from "@typescript-eslint/utils";
3+
import type { SourceCode } from "@typescript-eslint/utils/ts-eslint";
4+
5+
import references from "../docs/public/canonical-references.json" with { type: "json" };
6+
7+
const referenceSet = new Set(references);
8+
9+
export const validInheritDoc = ESLintUtils.RuleCreator.withoutDocs({
10+
create(context) {
11+
const source = context.sourceCode;
12+
let handled = new Set();
13+
return {
14+
"*"(node) {
15+
for (const comment of source.getCommentsBefore(node)) {
16+
if (handled.has(comment)) {
17+
continue;
18+
}
19+
handled.add(comment);
20+
if (comment.type === "Block") {
21+
const text = source.getText(comment);
22+
let match: RegExpMatchArray | null;
23+
if ((match = text.match(/@inheritDoc\s+([^\s}]+)/d))) {
24+
const canonicalReference = match[1];
25+
if (!referenceSet.has(canonicalReference)) {
26+
context.report({
27+
node: comment,
28+
loc: locForMatch(source, comment, match, 1),
29+
messageId: "invalidCanonicalReference",
30+
});
31+
}
32+
}
33+
if (
34+
(match = text.match(/@inheritdoc/di)) &&
35+
match[0] !== "@inheritDoc"
36+
) {
37+
const loc = locForMatch(source, comment, match, 0);
38+
context.report({
39+
node: comment,
40+
loc,
41+
messageId: "invalidSpelling",
42+
fix(fixer) {
43+
return fixer.replaceTextRange(
44+
[
45+
source.getIndexFromLoc(loc.start),
46+
source.getIndexFromLoc(loc.end),
47+
],
48+
"@inheritDoc"
49+
);
50+
},
51+
});
52+
}
53+
}
54+
}
55+
},
56+
};
57+
},
58+
meta: {
59+
messages: {
60+
invalidCanonicalReference: "Unknown canonical reference.",
61+
invalidSpelling: "Invalid spelling of @inheritDoc.",
62+
},
63+
type: "problem",
64+
schema: [],
65+
fixable: "code",
66+
},
67+
defaultOptions: [],
68+
});
69+
70+
export const validMdxCanonicalReferences = ESLintUtils.RuleCreator.withoutDocs({
71+
create(context, optionsWithDefault) {
72+
return {
73+
JSXAttribute(node: AST.JSXAttribute) {
74+
if (node.name.name == "canonicalReference") {
75+
const ref =
76+
node.value.type === "JSXExpressionContainer" ?
77+
node.value.expression
78+
: node.value;
79+
if (!ref || ref.type !== "Literal" || typeof ref.value !== "string") {
80+
context.report({
81+
node: ref || node,
82+
messageId: "shouldBeString",
83+
});
84+
} else if (!referenceSet.has(ref.value)) {
85+
context.report({
86+
node: ref,
87+
messageId: "invalidCanonicalReference",
88+
});
89+
}
90+
}
91+
},
92+
};
93+
},
94+
meta: {
95+
messages: {
96+
shouldBeString: "The canonicalReference value should be a string.",
97+
invalidCanonicalReference: "Unknown canonical reference.",
98+
},
99+
type: "problem",
100+
schema: [],
101+
},
102+
defaultOptions: [],
103+
});
104+
105+
function locForMatch(
106+
source: SourceCode,
107+
node: AST.NodeOrTokenData,
108+
match: RegExpMatchArray,
109+
index: number
110+
) {
111+
return {
112+
start: source.getLocFromIndex(
113+
source.getIndexFromLoc(node.loc.start) + match.indices[index][0]
114+
),
115+
end: source.getLocFromIndex(
116+
source.getIndexFromLoc(node.loc.start) + match.indices[index][1]
117+
),
118+
};
119+
}

eslint-local-rules/index.mjs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
import {
2+
validInheritDoc,
3+
validMdxCanonicalReferences,
4+
} from "./canonical-references.ts";
15
import { rule as forbidActInDisabledActEnvironment } from "./forbid-act-in-disabled-act-environment.ts";
26
import {
37
importFromExport,
@@ -18,4 +22,6 @@ export default {
1822
"no-internal-import-official-export": noInternalImportOfficialExport,
1923
"no-duplicate-exports": noDuplicateExports,
2024
"no-relative-imports": noRelativeImports,
25+
"valid-inherit-doc": validInheritDoc,
26+
"mdx-valid-canonical-references": validMdxCanonicalReferences,
2127
};

eslint-local-rules/tsconfig.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66
"noEmit": true,
77
"allowImportingTsExtensions": true,
88
"rewriteRelativeImportExtensions": true,
9-
"verbatimModuleSyntax": true
9+
"verbatimModuleSyntax": true,
10+
"resolveJsonModule": true
1011
},
1112
"include": ["**/*.ts"]
1213
}

0 commit comments

Comments
 (0)