Skip to content

Commit 02d9d73

Browse files
authored
feat(eslint-plugin): [unified-signatures] support ignoring overload signatures with different JSDoc comments (#10781)
* initial implementation * add a test for interfaces * remove some redundant 'export function ...' tests * check for block comments instead of a heuristic to check jsdoc comments
1 parent 84af50e commit 02d9d73

File tree

5 files changed

+439
-3
lines changed

5 files changed

+439
-3
lines changed

packages/eslint-plugin/docs/rules/unified-signatures.mdx

+44
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,50 @@ function f(b: string): void;
7878
</TabItem>
7979
</Tabs>
8080

81+
### `ignoreOverloadsWithDifferentJSDoc`
82+
83+
{/* insert option description */}
84+
85+
Examples of code for this rule with `ignoreOverloadsWithDifferentJSDoc`:
86+
87+
<Tabs>
88+
<TabItem value="❌ Incorrect">
89+
90+
```ts option='{ "ignoreOverloadsWithDifferentJSDoc": true }'
91+
declare function f(x: string): void;
92+
declare function f(x: boolean): void;
93+
/**
94+
* @deprecate
95+
*/
96+
declare function f(x: number): void;
97+
/**
98+
* @deprecate
99+
*/
100+
declare function f(x: null): void;
101+
```
102+
103+
</TabItem>
104+
<TabItem value="✅ Correct">
105+
106+
```ts option='{ "ignoreOverloadsWithDifferentJSDoc": true }'
107+
declare function f(x: string): void;
108+
/**
109+
* This signature does something else.
110+
*/
111+
declare function f(x: boolean): void;
112+
/**
113+
* @async
114+
*/
115+
declare function f(x: number): void;
116+
/**
117+
* @deprecate
118+
*/
119+
declare function f(x: null): void;
120+
```
121+
122+
</TabItem>
123+
</Tabs>
124+
81125
## When Not To Use It
82126

83127
This is purely a stylistic rule to help with readability of function signature overloads.

packages/eslint-plugin/src/rules/unified-signatures.ts

+34-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import type { TSESTree } from '@typescript-eslint/utils';
22

3-
import { AST_NODE_TYPES } from '@typescript-eslint/utils';
3+
import { AST_NODE_TYPES, AST_TOKEN_TYPES } from '@typescript-eslint/utils';
44

55
import type { Equal } from '../util';
66

@@ -61,6 +61,7 @@ export type MessageIds =
6161
export type Options = [
6262
{
6363
ignoreDifferentlyNamedParameters?: boolean;
64+
ignoreOverloadsWithDifferentJSDoc?: boolean;
6465
},
6566
];
6667

@@ -91,16 +92,25 @@ export default createRule<Options, MessageIds>({
9192
description:
9293
'Whether two parameters with different names at the same index should be considered different even if their types are the same.',
9394
},
95+
ignoreOverloadsWithDifferentJSDoc: {
96+
type: 'boolean',
97+
description:
98+
'Whether two overloads with different JSDoc comments should be considered different even if their parameter and return types are the same.',
99+
},
94100
},
95101
},
96102
],
97103
},
98104
defaultOptions: [
99105
{
100106
ignoreDifferentlyNamedParameters: false,
107+
ignoreOverloadsWithDifferentJSDoc: false,
101108
},
102109
],
103-
create(context, [{ ignoreDifferentlyNamedParameters }]) {
110+
create(
111+
context,
112+
[{ ignoreDifferentlyNamedParameters, ignoreOverloadsWithDifferentJSDoc }],
113+
) {
104114
//----------------------------------------------------------------------
105115
// Helpers
106116
//----------------------------------------------------------------------
@@ -230,6 +240,15 @@ export default createRule<Options, MessageIds>({
230240
}
231241
}
232242

243+
if (ignoreOverloadsWithDifferentJSDoc) {
244+
const aComment = getBlockCommentForNode(getExportingNode(a) ?? a);
245+
const bComment = getBlockCommentForNode(getExportingNode(b) ?? b);
246+
247+
if (aComment?.value !== bComment?.value) {
248+
return false;
249+
}
250+
}
251+
233252
return (
234253
typesAreEqual(a.returnType, b.returnType) &&
235254
// Must take the same type parameters.
@@ -522,6 +541,18 @@ export default createRule<Options, MessageIds>({
522541
currentScope = scopes.pop();
523542
}
524543

544+
/**
545+
* @returns the first valid JSDoc comment annotating `node`
546+
*/
547+
function getBlockCommentForNode(
548+
node: TSESTree.Node,
549+
): TSESTree.Comment | undefined {
550+
return context.sourceCode
551+
.getCommentsBefore(node)
552+
.reverse()
553+
.find(comment => comment.type === AST_TOKEN_TYPES.Block);
554+
}
555+
525556
function addOverload(
526557
signature: OverloadNode,
527558
key?: string,
@@ -590,7 +621,7 @@ export default createRule<Options, MessageIds>({
590621
});
591622

592623
function getExportingNode(
593-
node: TSESTree.TSDeclareFunction,
624+
node: SignatureDefinition,
594625
):
595626
| TSESTree.ExportDefaultDeclaration
596627
| TSESTree.ExportNamedDeclaration

packages/eslint-plugin/tests/docs-eslint-output-snapshots/unified-signatures.shot

+39
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)