Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(eslint-plugin): added new rule no-untyped-public-signature #801

Merged
merged 36 commits into from
Nov 12, 2019
Merged
Show file tree
Hide file tree
Changes from 29 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
ff6fae2
fix prefer-readonly rule TypeError when class has computed member exp…
eranshabi Jul 25, 2019
3e60496
fix lint
eranshabi Jul 25, 2019
24a8dd1
chore: tighter linting (#535)
bradzacher Jul 25, 2019
e649022
feat(typescript-estree)!: throw error on file not in project when `pr…
uniqueiniquity Jul 28, 2019
eb5c6c3
test: ensure integration tests can fail, add vue-sfc (#768)
JamesHenry Jul 28, 2019
fcd1d27
docs: fix typo in documentation for explicit-function-return-type (#772)
Jul 29, 2019
d0c1171
fix(eslint-plugin): [no-explicit-any] Fix ignoreRestArgs for interfac…
Dilatorily Jul 29, 2019
067b938
docs(eslint-plugin): Improve ban-types description (#773)
glenwinters Jul 29, 2019
44aef10
docs(prefer-readonly): add rule name to title (#779)
cherryblossom000 Jul 30, 2019
f0cdb5a
fix(eslint-plugin): [typedef] support default value for parameter (#785)
octogonz Aug 2, 2019
1af3102
fix(eslint-plugin): [typedef] support "for..in", "for..of" (#787)
octogonz Aug 2, 2019
fe533f3
Merge pull request #2 from typescript-eslint/master
eranshabi Aug 4, 2019
5a83f2f
add rule no-untyped-public-signature
eranshabi Aug 4, 2019
519560f
add ignoredMethods option to no-untyped-public-signature
eranshabi Aug 6, 2019
ce19131
Merge branch 'master' into master
eranshabi Aug 8, 2019
3860608
Merge branch 'master' into master
eranshabi Aug 9, 2019
7a3ff11
Merge branch 'master' into master
eranshabi Aug 10, 2019
e9b1d15
Merge branch 'master' into master
eranshabi Aug 13, 2019
e037488
fix format
eranshabi Aug 13, 2019
1d98ad3
Merge branch 'master' into master
eranshabi Aug 14, 2019
43db5b3
Merge branch 'master' into master
eranshabi Aug 18, 2019
3071a9c
Merge branch 'master' into master
eranshabi Aug 26, 2019
dcf12ef
Merge branch 'master' into master
eranshabi Sep 4, 2019
6a782ba
fix lint
eranshabi Sep 4, 2019
dcab7e7
format
eranshabi Sep 4, 2019
05761ab
Merge branch 'master' into master
eranshabi Sep 8, 2019
aa7a7b2
Merge branch 'master' into master
eranshabi Sep 15, 2019
86dc356
Merge branch 'master' into master
eranshabi Sep 25, 2019
74ba76a
Merge branch 'master' into master
eranshabi Sep 26, 2019
4773b5f
Merge branch 'master' into master
eranshabi Oct 30, 2019
a4677b0
fix some code review feedback
eranshabi Nov 4, 2019
b49f298
support new cases
eranshabi Nov 4, 2019
1699283
Merge branch 'master' into master
eranshabi Nov 4, 2019
6338cf3
test support for abstract method
eranshabi Nov 4, 2019
886c2c1
Merge branch 'master' into master
eranshabi Nov 4, 2019
f1bac82
Merge branch 'master' into master
bradzacher Nov 12, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/eslint-plugin/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,7 @@ Then you should add `airbnb` (or `airbnb-base`) to your `extends` section of `.e
| [`@typescript-eslint/no-unnecessary-qualifier`](./docs/rules/no-unnecessary-qualifier.md) | Warns when a namespace qualifier is unnecessary | | :wrench: | :thought_balloon: |
| [`@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: |
| [`@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: |
| [`@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 | | | |
| [`@typescript-eslint/no-unused-vars`](./docs/rules/no-unused-vars.md) | Disallow unused variables | :heavy_check_mark: | | |
| [`@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: | | |
| [`@typescript-eslint/no-useless-constructor`](./docs/rules/no-useless-constructor.md) | Disallow unnecessary constructors | | | |
Expand Down
49 changes: 49 additions & 0 deletions packages/eslint-plugin/docs/rules/no-untyped-public-signature.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# Disallow untyped public methods (no-untyped-public-signature)

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.

## Rule Details

This rule aims to ensure that only typed public methods are declared in the code.

The following patterns are considered warnings:

```ts
// untyped parameter
public foo(param1): void {
}

// untyped return type
public foo(param1: string) {
}
```
eranshabi marked this conversation as resolved.
Show resolved Hide resolved

The following patterns are not warnings:

```ts
// typed public method
public foo(param1: string): void {
}

// untyped private method
private foo(param1) {
}
```

## Options

This rule, in its default state, does not require any argument.

### ignoredMethods

You may pass method names you would like this rule to ignore, like so:

```cjson
{
"@typescript-eslint/no-untyped-public-signature": ["error", { "ignoredMethods": ["ignoredMethodName"] }]
}
```

## When Not To Use It

If you don't wish to type public methods.
1 change: 1 addition & 0 deletions packages/eslint-plugin/src/configs/all.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
"@typescript-eslint/no-unnecessary-qualifier": "error",
"@typescript-eslint/no-unnecessary-type-arguments": "error",
"@typescript-eslint/no-unnecessary-type-assertion": "error",
"@typescript-eslint/no-untyped-public-signature": "error",
"no-unused-vars": "off",
"@typescript-eslint/no-unused-vars": "error",
"no-use-before-define": "off",
Expand Down
2 changes: 2 additions & 0 deletions packages/eslint-plugin/src/rules/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import noUnnecessaryCondition from './no-unnecessary-condition';
import noUnnecessaryQualifier from './no-unnecessary-qualifier';
import noUnnecessaryTypeAssertion from './no-unnecessary-type-assertion';
import noUnusedVars from './no-unused-vars';
import noUntypedPublicSignature from './no-untyped-public-signature';
import noUseBeforeDefine from './no-use-before-define';
import noUselessConstructor from './no-useless-constructor';
import noVarRequires from './no-var-requires';
Expand Down Expand Up @@ -105,6 +106,7 @@ export default {
'no-unnecessary-qualifier': noUnnecessaryQualifier,
'no-unnecessary-type-arguments': useDefaultTypeParameter,
'no-unnecessary-type-assertion': noUnnecessaryTypeAssertion,
'no-untyped-public-signature': noUntypedPublicSignature,
'no-unused-vars': noUnusedVars,
'no-use-before-define': noUseBeforeDefine,
'no-useless-constructor': noUselessConstructor,
Expand Down
98 changes: 98 additions & 0 deletions packages/eslint-plugin/src/rules/no-untyped-public-signature.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import * as util from '../util';
import {
AST_NODE_TYPES,
TSESTree,
} from '@typescript-eslint/experimental-utils';

type MessageIds = 'noReturnType' | 'untypedParameter';

type Options = [{ ignoredMethods: string[] }];

export default util.createRule<Options, MessageIds>({
name: 'no-unused-public-signature',
meta: {
docs: {
description:
'Requires that all public method arguments and return type will be explicitly typed',
category: 'Best Practices',
recommended: false,
},
messages: {
noReturnType: 'Public method has no return type',
untypedParameter: 'Public method parameters should be typed',
},
schema: [
{
allowAdditionalProperties: false,
properties: {
ignoredMethods: {
type: 'array',
items: {
type: 'string',
},
},
},
type: 'object',
},
],
type: 'suggestion',
},
defaultOptions: [{ ignoredMethods: [] }],
create(context, [{ ignoredMethods }]) {
eranshabi marked this conversation as resolved.
Show resolved Hide resolved
function isPublicMethod(node: TSESTree.MethodDefinition): boolean {
return node.accessibility === 'public' || !node.accessibility;
}

function isIgnoredMethod(
node: TSESTree.MethodDefinition,
ignoredMethods: string[],
): boolean {
return ignoredMethods.includes((node.key as TSESTree.Identifier).name);
eranshabi marked this conversation as resolved.
Show resolved Hide resolved
}

function isParamTyped(node: TSESTree.Identifier): boolean {
return (
!!node.typeAnnotation &&
node.typeAnnotation.typeAnnotation.type !== AST_NODE_TYPES.TSAnyKeyword
);
}

function isReturnTyped(
node: TSESTree.TSTypeAnnotation | undefined,
): boolean {
if (!node) {
return false;
}
return (
node.typeAnnotation &&
node.typeAnnotation.type !== AST_NODE_TYPES.TSAnyKeyword
);
}

return {
MethodDefinition(node: TSESTree.MethodDefinition): void {
eranshabi marked this conversation as resolved.
Show resolved Hide resolved
if (isPublicMethod(node) && !isIgnoredMethod(node, ignoredMethods)) {
const paramIdentifiers = node.value.params.filter(
param => param.type === AST_NODE_TYPES.Identifier,
) as TSESTree.Identifier[];
const identifiersHaveTypes = paramIdentifiers.every(isParamTyped);
if (!identifiersHaveTypes) {
context.report({
node,
messageId: 'untypedParameter',
data: {},
});
}

if (!isReturnTyped(node.value.returnType)) {
context.report({
node,
messageId: 'noReturnType',
data: {},
});
}
}
},
};
},
});
145 changes: 145 additions & 0 deletions packages/eslint-plugin/tests/rules/no-untyped-public-signature.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
import rule from '../../src/rules/no-untyped-public-signature';
import { RuleTester } from '../RuleTester';

const ruleTester = new RuleTester({
parserOptions: {
ecmaVersion: 6,
sourceType: 'module',
ecmaFeatures: {},
},
parser: '@typescript-eslint/parser',
});

ruleTester.run('no-untyped-public-signature', rule, {
valid: [
{
code: `class A {
private a(c) {
}
}`,
},
{
code: `class A {
private async a(c) {
}
}`,
},
{
code: `
class A {
public b(c: string):void {

}
}`,
},
{
code: `
class A {
public b(...c):void {

}
}`,
},
{
code: `
class A {
b(c):void {

}
}`,
options: [{ ignoredMethods: ['b'] }],
},
{
code: `
class A {
b(...c):void {

}

d(c):void {

}
}`,
options: [{ ignoredMethods: ['b', 'd'] }],
},
],
invalid: [
//untyped parameter
{
code: `class A {
public b(c):void {

}
}`,
errors: [{ messageId: 'untypedParameter' }],
},
//untyped parameter (any)
{
code: `class A {
public b(c: any):void {

}
}`,
errors: [{ messageId: 'untypedParameter' }],
},
//implicit public method
{
code: `class A {
b(c):void {

}
}`,
errors: [{ messageId: 'untypedParameter' }],
},
//implicit async public method
{
code: `class A {
async a(c): void {
}
}`,
errors: [{ messageId: 'untypedParameter' }],
},
//no return type
{
code: `class A {
public a(c: number) {
}
}`,
errors: [{ messageId: 'noReturnType' }],
},
//no return type + untyped parameter
{
code: `class A {
public b(c) {

}
}`,
errors: [
{ messageId: 'untypedParameter' },
{ messageId: 'noReturnType' },
],
},
//any return type
{
code: `class A {
public b(c: number): any {

}
}`,
errors: [{ messageId: 'noReturnType' }],
},
//with ignored methods
{
code: `class A {
public b(c: number): any {

}

c() {
}
}`,
options: [{ ignoredMethods: ['c'] }],
errors: [{ messageId: 'noReturnType' }],
},
],
});