Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 7e36ceb

Browse files
committed
Merge branch 'migrate-plugin-to-ts' into contributors
# Conflicts: # packages/eslint-plugin/src/rules/no-unnecessary-type-assertion.ts
2 parents c0c84bf + 2f6ff83 commit 7e36ceb

File tree

16 files changed

+1026
-14
lines changed

16 files changed

+1026
-14
lines changed

packages/eslint-plugin-tslint/tsconfig.build.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"extends": "../../tsconfig.json",
2+
"extends": "../../tsconfig.base.json",
33
"compilerOptions": {
44
"outDir": "./dist"
55
},

packages/eslint-plugin/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,7 @@ Then you should add `airbnb` (or `airbnb-base`) to your `extends` section of `.e
144144
| [`@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: | |
145145
| [`@typescript-eslint/no-useless-constructor`](./docs/rules/no-useless-constructor.md) | Disallow unnecessary constructors | | |
146146
| [`@typescript-eslint/no-var-requires`](./docs/rules/no-var-requires.md) | Disallows the use of require statements except in import statements (`no-var-requires` from TSLint) | :heavy_check_mark: | |
147+
| [`@typescript-eslint/prefer-function-type`](./docs/rules/prefer-function-type.md) | Use function types instead of interfaces with call signatures (`callable-types` from TSLint) | | :wrench: |
147148
| [`@typescript-eslint/prefer-interface`](./docs/rules/prefer-interface.md) | Prefer an interface declaration over a type literal (type T = { ... }) (`interface-over-type-literal` from TSLint) | :heavy_check_mark: | :wrench: |
148149
| [`@typescript-eslint/prefer-namespace-keyword`](./docs/rules/prefer-namespace-keyword.md) | Require the use of the `namespace` keyword instead of the `module` keyword to declare custom TypeScript modules. (`no-internal-module` from TSLint) | :heavy_check_mark: | :wrench: |
149150
| [`@typescript-eslint/promise-function-async`](./docs/rules/promise-function-async.md) | Requires any function or method that returns a Promise to be marked async. (`promise-function-async` from TSLint) | :heavy_check_mark: | |

packages/eslint-plugin/ROADMAP.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@
132132
| [`arrow-parens`] | 🌟 | [`arrow-parens`][arrow-parens] |
133133
| [`arrow-return-shorthand`] | 🌟 | [`arrow-body-style`][arrow-body-style] |
134134
| [`binary-expression-operand-order`] | 🌟 | [`yoda`][yoda] |
135-
| [`callable-types`] | 🛑 | N/A |
135+
| [`callable-types`] | | [`@typescript-eslint/prefer-function-type`] |
136136
| [`class-name`] || [`@typescript-eslint/class-name-casing`] |
137137
| [`comment-format`] | 🌟 | [`capitalized-comments`][capitalized-comments] & [`spaced-comment`][spaced-comment] |
138138
| [`completed-docs`] | 🔌 | [`eslint-plugin-jsdoc`][plugin:jsdoc] |
@@ -587,6 +587,7 @@ Relevant plugins: [`chai-expect-keywords`](https://github.com/gavinaiken/eslint-
587587
[`@typescript-eslint/member-delimiter-style`]: https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/member-delimiter-style.md
588588
[`@typescript-eslint/prefer-interface`]: https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/prefer-interface.md
589589
[`@typescript-eslint/no-array-constructor`]: https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-array-constructor.md
590+
[`@typescript-eslint/prefer-function-type`]: https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/prefer-function-type.md
590591
[`@typescript-eslint/no-for-in-array`]: https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-for-in-array.md
591592

592593
<!-- eslint-plugin-import -->
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
# Use function types instead of interfaces with call signatures (prefer-function-type)
2+
3+
## Rule Details
4+
5+
This rule suggests using a function type instead of an interface or object type literal with a single call signature.
6+
7+
Examples of **incorrect** code for this rule:
8+
9+
```ts
10+
interface Foo {
11+
(): string;
12+
}
13+
```
14+
15+
```ts
16+
function foo(bar: { (): number }): number {
17+
return bar();
18+
}
19+
```
20+
21+
```ts
22+
interface Foo extends Function {
23+
(): void;
24+
}
25+
```
26+
27+
Examples of **correct** code for this rule:
28+
29+
```ts
30+
interface Foo {
31+
(): void;
32+
bar: number;
33+
}
34+
```
35+
36+
```ts
37+
function foo(bar: { (): string; baz: number }): string {
38+
return bar();
39+
}
40+
```
41+
42+
```ts
43+
interface Foo {
44+
bar: string;
45+
}
46+
interface Bar extends Foo {
47+
(): void;
48+
}
49+
```
50+
51+
## When Not To Use It
52+
53+
If you specifically want to use an interface or type literal with a single call signature for stylistic reasons, you can disable this rule.
54+
55+
## Further Reading
56+
57+
- TSLint: [`callable-types`](https://palantir.github.io/tslint/rules/callable-types/)

packages/eslint-plugin/src/rules/no-unnecessary-type-assertion.ts

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ export default util.createRule<Options, MessageIds>({
4242
},
4343
defaultOptions: [{}],
4444
create(context, [options]) {
45+
const sourceCode = context.getSourceCode();
4546
const parserServices = util.getParserServices(context);
4647

4748
/**
@@ -79,9 +80,6 @@ export default util.createRule<Options, MessageIds>({
7980
node: TSESTree.Node,
8081
checker: ts.TypeChecker
8182
): void {
82-
/**
83-
* Corresponding TSNode is guaranteed to be in map
84-
*/
8583
const originalNode = parserServices.esTreeNodeToTSNodeMap.get<
8684
ts.NonNullExpression
8785
>(node);
@@ -101,16 +99,23 @@ export default util.createRule<Options, MessageIds>({
10199
}
102100
}
103101

104-
function verifyCast(node: TSESTree.Node, checker: ts.TypeChecker): void {
105-
const originalNode = parserServices.esTreeNodeToTSNodeMap.get<
106-
ts.AssertionExpression
107-
>(node);
102+
function verifyCast(
103+
node: TSESTree.TSTypeAssertion | TSESTree.TSAsExpression,
104+
checker: ts.TypeChecker
105+
): void {
108106
if (
107+
options &&
109108
options.typesToIgnore &&
110-
options.typesToIgnore.indexOf(originalNode.type.getText()) !== -1
109+
options.typesToIgnore.indexOf(
110+
sourceCode.getText(node.typeAnnotation)
111+
) !== -1
111112
) {
112113
return;
113114
}
115+
116+
const originalNode = parserServices.esTreeNodeToTSNodeMap.get<
117+
ts.AssertionExpression
118+
>(node);
114119
const castType = checker.getTypeAtLocation(originalNode);
115120

116121
if (
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
import {
2+
AST_NODE_TYPES,
3+
TSESTree,
4+
AST_TOKEN_TYPES
5+
} from '@typescript-eslint/typescript-estree';
6+
import * as util from '../util';
7+
8+
export default util.createRule({
9+
name: 'prefer-function-type',
10+
meta: {
11+
docs: {
12+
description:
13+
'Use function types instead of interfaces with call signatures',
14+
category: 'Best Practices',
15+
recommended: false,
16+
tslintName: 'callable-types'
17+
},
18+
fixable: 'code',
19+
messages: {
20+
functionTypeOverCallableType:
21+
"{{ type }} has only a call signature - use '{{ sigSuggestion }}' instead."
22+
},
23+
schema: [],
24+
type: 'suggestion'
25+
},
26+
defaultOptions: [],
27+
create(context) {
28+
const sourceCode = context.getSourceCode();
29+
30+
/**
31+
* Checks if there the interface has exactly one supertype that isn't named 'Function'
32+
* @param node The node being checked
33+
*/
34+
function hasOneSupertype(node: TSESTree.TSInterfaceDeclaration): boolean {
35+
if (!node.extends || node.extends.length === 0) {
36+
return false;
37+
}
38+
if (node.extends.length !== 1) {
39+
return true;
40+
}
41+
const expr = node.extends[0].expression;
42+
43+
return (
44+
expr.type !== AST_NODE_TYPES.Identifier || expr.name !== 'Function'
45+
);
46+
}
47+
48+
/**
49+
* @param parent The parent of the call signature causing the diagnostic
50+
*/
51+
function shouldWrapSuggestion(parent: TSESTree.Node | undefined): boolean {
52+
if (!parent) {
53+
return false;
54+
}
55+
56+
switch (parent.type) {
57+
case AST_NODE_TYPES.TSUnionType:
58+
case AST_NODE_TYPES.TSIntersectionType:
59+
case AST_NODE_TYPES.TSArrayType:
60+
return true;
61+
default:
62+
return false;
63+
}
64+
}
65+
66+
/**
67+
* @param call The call signature causing the diagnostic
68+
* @param parent The parent of the call
69+
* @returns The suggestion to report
70+
*/
71+
function renderSuggestion(
72+
call:
73+
| TSESTree.TSCallSignatureDeclaration
74+
| TSESTree.TSConstructSignatureDeclaration,
75+
parent: TSESTree.Node
76+
) {
77+
const start = call.range[0];
78+
const colonPos = call.returnType!.range[0] - start;
79+
const text = sourceCode.getText().slice(start, call.range[1]);
80+
81+
let suggestion = `${text.slice(0, colonPos)} =>${text.slice(
82+
colonPos + 1
83+
)}`;
84+
85+
if (shouldWrapSuggestion(parent.parent)) {
86+
suggestion = `(${suggestion})`;
87+
}
88+
if (parent.type === AST_NODE_TYPES.TSInterfaceDeclaration) {
89+
if (typeof parent.typeParameters !== 'undefined') {
90+
return `type ${sourceCode
91+
.getText()
92+
.slice(
93+
parent.id.range[0],
94+
parent.typeParameters.range[1]
95+
)} = ${suggestion}`;
96+
}
97+
return `type ${parent.id.name} = ${suggestion}`;
98+
}
99+
return suggestion.endsWith(';') ? suggestion.slice(0, -1) : suggestion;
100+
}
101+
102+
/**
103+
* @param member The TypeElement being checked
104+
* @param node The parent of member being checked
105+
*/
106+
function checkMember(member: TSESTree.TypeElement, node: TSESTree.Node) {
107+
if (
108+
(member.type === AST_NODE_TYPES.TSCallSignatureDeclaration ||
109+
member.type === AST_NODE_TYPES.TSConstructSignatureDeclaration) &&
110+
typeof member.returnType !== 'undefined'
111+
) {
112+
const suggestion = renderSuggestion(member, node);
113+
const fixStart =
114+
node.type === AST_NODE_TYPES.TSTypeLiteral
115+
? node.range[0]
116+
: sourceCode
117+
.getTokens(node)
118+
.filter(
119+
token =>
120+
token.type === AST_TOKEN_TYPES.Keyword &&
121+
token.value === 'interface'
122+
)[0].range[0];
123+
124+
context.report({
125+
node: member,
126+
messageId: 'functionTypeOverCallableType',
127+
data: {
128+
type:
129+
node.type === AST_NODE_TYPES.TSTypeLiteral
130+
? 'Type literal'
131+
: 'Interface',
132+
sigSuggestion: suggestion
133+
},
134+
fix(fixer) {
135+
return fixer.replaceTextRange(
136+
[fixStart, node.range[1]],
137+
suggestion
138+
);
139+
}
140+
});
141+
}
142+
}
143+
144+
return {
145+
TSInterfaceDeclaration(node: TSESTree.TSInterfaceDeclaration) {
146+
if (!hasOneSupertype(node) && node.body.body.length === 1) {
147+
checkMember(node.body.body[0], node);
148+
}
149+
},
150+
'TSTypeLiteral[members.length = 1]'(node: TSESTree.TSTypeLiteral) {
151+
checkMember(node.members[0], node);
152+
}
153+
};
154+
}
155+
});

packages/eslint-plugin/tests/rules/no-unnecessary-type-assertion.test.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,18 @@ type Foo = number;
3737
const foo = (3 + 5) as Foo;`,
3838
options: [{ typesToIgnore: ['Foo'] }]
3939
},
40+
{
41+
code: `const foo = (3 + 5) as any;`,
42+
options: [{ typesToIgnore: ['any'] }]
43+
},
44+
{
45+
code: `((Syntax as any).ArrayExpression = 'foo')`,
46+
options: [{ typesToIgnore: ['any'] }]
47+
},
48+
{
49+
code: `const foo = (3 + 5) as string;`,
50+
options: [{ typesToIgnore: ['string'] }]
51+
},
4052
{
4153
code: `
4254
type Foo = number;

0 commit comments

Comments
 (0)