Skip to content

Commit c5835f3

Browse files
eranshabibradzacher
andcommitted
feat(eslint-plugin): added new rule no-untyped-public-signature (#801)
Co-authored-by: Brad Zacher <[email protected]>
1 parent db1aa18 commit c5835f3

File tree

6 files changed

+391
-0
lines changed

6 files changed

+391
-0
lines changed

Diff for: packages/eslint-plugin/README.md

+1
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,7 @@ Then you should add `airbnb` (or `airbnb-base`) to your `extends` section of `.e
182182
| [`@typescript-eslint/no-unnecessary-qualifier`](./docs/rules/no-unnecessary-qualifier.md) | Warns when a namespace qualifier is unnecessary | | :wrench: | :thought_balloon: |
183183
| [`@typescript-eslint/no-unnecessary-type-arguments`](./docs/rules/no-unnecessary-type-arguments.md) | Warns if an explicitly specified type argument is the default for that type parameter | | :wrench: | :thought_balloon: |
184184
| [`@typescript-eslint/no-unnecessary-type-assertion`](./docs/rules/no-unnecessary-type-assertion.md) | Warns if a type assertion does not change the type of an expression | :heavy_check_mark: | :wrench: | :thought_balloon: |
185+
| [`@typescript-eslint/no-untyped-public-signature`](./docs/rules/no-untyped-public-signature.md) | Requires that all public method arguments and return type will be explicitly typed | | | |
185186
| [`@typescript-eslint/no-unused-expressions`](./docs/rules/no-unused-expressions.md) | Disallow unused expressions | | | |
186187
| [`@typescript-eslint/no-unused-vars`](./docs/rules/no-unused-vars.md) | Disallow unused variables | :heavy_check_mark: | | |
187188
| [`@typescript-eslint/no-use-before-define`](./docs/rules/no-use-before-define.md) | Disallow the use of variables before they are defined | :heavy_check_mark: | | |
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
# Disallow untyped public methods (no-untyped-public-signature)
2+
3+
public methods are meant to be used by code outside of your class. By typing both the parameters and the return type of public methods they will be more readable and easy to use.
4+
5+
## Rule Details
6+
7+
This rule aims to ensure that only typed public methods are declared in the code.
8+
9+
The following patterns are considered warnings:
10+
11+
```ts
12+
// untyped parameter
13+
public foo(param1): void {
14+
}
15+
16+
// untyped parameter
17+
public foo(param1: any): void {
18+
}
19+
20+
// untyped return type
21+
public foo(param1: string) {
22+
}
23+
24+
// untyped return type
25+
public foo(param1: string): any {
26+
}
27+
```
28+
29+
The following patterns are not warnings:
30+
31+
```ts
32+
// typed public method
33+
public foo(param1: string): void {
34+
}
35+
36+
// untyped private method
37+
private foo(param1) {
38+
}
39+
```
40+
41+
## Options
42+
43+
This rule, in its default state, does not require any argument.
44+
45+
### ignoredMethods
46+
47+
You may pass method names you would like this rule to ignore, like so:
48+
49+
```cjson
50+
{
51+
"@typescript-eslint/no-untyped-public-signature": ["error", { "ignoredMethods": ["ignoredMethodName"] }]
52+
}
53+
```
54+
55+
## When Not To Use It
56+
57+
If you don't wish to type public methods.

Diff for: packages/eslint-plugin/src/configs/all.json

+1
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
"@typescript-eslint/no-unnecessary-qualifier": "error",
5252
"@typescript-eslint/no-unnecessary-type-arguments": "error",
5353
"@typescript-eslint/no-unnecessary-type-assertion": "error",
54+
"@typescript-eslint/no-untyped-public-signature": "error",
5455
"@typescript-eslint/no-unused-expressions": "error",
5556
"no-unused-vars": "off",
5657
"@typescript-eslint/no-unused-vars": "error",

Diff for: packages/eslint-plugin/src/rules/index.ts

+2
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ import noUnnecessaryCondition from './no-unnecessary-condition';
4040
import noUnnecessaryQualifier from './no-unnecessary-qualifier';
4141
import noUnnecessaryTypeAssertion from './no-unnecessary-type-assertion';
4242
import noUnusedVars from './no-unused-vars';
43+
import noUntypedPublicSignature from './no-untyped-public-signature';
4344
import noUnusedExpressions from './no-unused-expressions';
4445
import noUseBeforeDefine from './no-use-before-define';
4546
import noUselessConstructor from './no-useless-constructor';
@@ -108,6 +109,7 @@ export default {
108109
'no-unnecessary-qualifier': noUnnecessaryQualifier,
109110
'no-unnecessary-type-arguments': useDefaultTypeParameter,
110111
'no-unnecessary-type-assertion': noUnnecessaryTypeAssertion,
112+
'no-untyped-public-signature': noUntypedPublicSignature,
111113
'no-unused-vars': noUnusedVars,
112114
'no-unused-expressions': noUnusedExpressions,
113115
'no-use-before-define': noUseBeforeDefine,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
import * as util from '../util';
2+
import {
3+
AST_NODE_TYPES,
4+
TSESTree,
5+
} from '@typescript-eslint/experimental-utils';
6+
7+
type MessageIds = 'noReturnType' | 'untypedParameter';
8+
9+
type Options = [{ ignoredMethods: string[] }];
10+
11+
export default util.createRule<Options, MessageIds>({
12+
name: 'no-unused-public-signature',
13+
meta: {
14+
docs: {
15+
description:
16+
'Requires that all public method arguments and return type will be explicitly typed',
17+
category: 'Best Practices',
18+
recommended: false,
19+
},
20+
messages: {
21+
noReturnType: 'Public method has no return type',
22+
untypedParameter: 'Public method parameters should be typed',
23+
},
24+
schema: [
25+
{
26+
allowAdditionalProperties: false,
27+
properties: {
28+
ignoredMethods: {
29+
type: 'array',
30+
items: {
31+
type: 'string',
32+
},
33+
},
34+
},
35+
type: 'object',
36+
},
37+
],
38+
type: 'suggestion',
39+
},
40+
defaultOptions: [{ ignoredMethods: [] }],
41+
create(context, [options]) {
42+
const ignoredMethods = new Set(options.ignoredMethods);
43+
44+
function isPublicMethod(
45+
node: TSESTree.MethodDefinition | TSESTree.TSAbstractMethodDefinition,
46+
): boolean {
47+
return node.accessibility === 'public' || !node.accessibility;
48+
}
49+
50+
function isIgnoredMethod(
51+
node: TSESTree.MethodDefinition | TSESTree.TSAbstractMethodDefinition,
52+
ignoredMethods: Set<string>,
53+
): boolean {
54+
if (
55+
node.key.type === AST_NODE_TYPES.Literal &&
56+
typeof node.key.value === 'string'
57+
) {
58+
return ignoredMethods.has(node.key.value);
59+
}
60+
if (
61+
node.key.type === AST_NODE_TYPES.TemplateLiteral &&
62+
node.key.expressions.length === 0
63+
) {
64+
return ignoredMethods.has(node.key.quasis[0].value.raw);
65+
}
66+
if (node.key.type === AST_NODE_TYPES.Identifier && !node.computed) {
67+
return ignoredMethods.has(node.key.name);
68+
}
69+
70+
return false;
71+
}
72+
73+
function isParamTyped(node: TSESTree.Identifier): boolean {
74+
return (
75+
!!node.typeAnnotation &&
76+
node.typeAnnotation.typeAnnotation.type !== AST_NODE_TYPES.TSAnyKeyword
77+
);
78+
}
79+
80+
function isReturnTyped(
81+
node: TSESTree.TSTypeAnnotation | undefined,
82+
): boolean {
83+
if (!node) {
84+
return false;
85+
}
86+
return (
87+
node.typeAnnotation &&
88+
node.typeAnnotation.type !== AST_NODE_TYPES.TSAnyKeyword
89+
);
90+
}
91+
92+
return {
93+
'TSAbstractMethodDefinition, MethodDefinition'(
94+
node: TSESTree.MethodDefinition | TSESTree.TSAbstractMethodDefinition,
95+
): void {
96+
if (isPublicMethod(node) && !isIgnoredMethod(node, ignoredMethods)) {
97+
const paramIdentifiers = node.value.params.filter(
98+
param => param.type === AST_NODE_TYPES.Identifier,
99+
) as TSESTree.Identifier[];
100+
const identifiersHaveTypes = paramIdentifiers.every(isParamTyped);
101+
if (!identifiersHaveTypes) {
102+
context.report({
103+
node,
104+
messageId: 'untypedParameter',
105+
data: {},
106+
});
107+
}
108+
109+
if (!isReturnTyped(node.value.returnType)) {
110+
context.report({
111+
node,
112+
messageId: 'noReturnType',
113+
data: {},
114+
});
115+
}
116+
}
117+
},
118+
};
119+
},
120+
});

0 commit comments

Comments
 (0)