Skip to content

Commit 747bfcb

Browse files
scottoharabradzacher
authored andcommitted
feat(eslint-plugin): add new rule no-empty-function (typescript-eslint#626)
1 parent aa206c4 commit 747bfcb

File tree

6 files changed

+241
-0
lines changed

6 files changed

+241
-0
lines changed

packages/eslint-plugin/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,7 @@ Then you should add `airbnb` (or `airbnb-base`) to your `extends` section of `.e
142142
| [`@typescript-eslint/member-ordering`](./docs/rules/member-ordering.md) | Require a consistent member declaration order | | | |
143143
| [`@typescript-eslint/no-angle-bracket-type-assertion`](./docs/rules/no-angle-bracket-type-assertion.md) | Enforces the use of `as Type` assertions instead of `<Type>` assertions | :heavy_check_mark: | | |
144144
| [`@typescript-eslint/no-array-constructor`](./docs/rules/no-array-constructor.md) | Disallow generic `Array` constructors | :heavy_check_mark: | :wrench: | |
145+
| [`@typescript-eslint/no-empty-function`](./docs/rules/no-empty-function.md) | Disallow empty functions | | | |
145146
| [`@typescript-eslint/no-empty-interface`](./docs/rules/no-empty-interface.md) | Disallow the declaration of empty interfaces | :heavy_check_mark: | | |
146147
| [`@typescript-eslint/no-explicit-any`](./docs/rules/no-explicit-any.md) | Disallow usage of the `any` type | :heavy_check_mark: | | |
147148
| [`@typescript-eslint/no-extra-parens`](./docs/rules/no-extra-parens.md) | Disallow unnecessary parentheses | | :wrench: | |
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# Disallow Empty Functions (@typescript-eslint/no-empty-function)
2+
3+
Empty functions can reduce readability because readers need to guess whether it’s intentional or not. So writing a clear comment for empty functions is a good practice.
4+
5+
## Rule Details
6+
7+
The `@typescript-eslint/no-empty-function` rule extends the `no-empty-function` rule from ESLint core, and adds support for handling Typescript specific code that would otherwise trigger the rule.
8+
9+
One example of valid Typescript specific code that would otherwise trigger the `no-empty-function` rule is the use of [parameter properties](https://www.typescriptlang.org/docs/handbook/classes.html#parameter-properties) in constructor functions:
10+
11+
```typescript
12+
class Person {
13+
constructor(private firstName: string, private surname: string) {}
14+
}
15+
```
16+
17+
The above code is functionally equivalent to:
18+
19+
```typescript
20+
class Person {
21+
private firstName: string;
22+
private surname: string;
23+
24+
constructor(firstName: string, surname: string) {
25+
this.firstName = firstName;
26+
this.surname = surname;
27+
}
28+
}
29+
```
30+
31+
Parameter properties enable both the _declaration_ and _initialization_ of member properties in a single location, avoiding the boilerplate & duplication that is common when initializing member properties from parameter values in a constructor function.
32+
33+
In these cases, although the constructor has an empty function body, it is technically valid and should not trigger an error.
34+
35+
See the [ESLint documentation](https://eslint.org/docs/rules/no-empty-function) for more details on the `no-empty-function` rule.
36+
37+
## Rule Changes
38+
39+
```cjson
40+
{
41+
// note you must disable the base rule as it can report incorrect errors
42+
"no-empty-function": "off",
43+
"@typescript-eslint/no-empty-function": "error"
44+
}
45+
```
46+
47+
<sup>Taken with ❤️ [from ESLint core](https://github.com/eslint/eslint/blob/master/docs/rules/no-empty-function.md)</sup>

packages/eslint-plugin/src/rules/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import memberNaming from './member-naming';
1616
import memberOrdering from './member-ordering';
1717
import noAngleBracketTypeAssertion from './no-angle-bracket-type-assertion';
1818
import noArrayConstructor from './no-array-constructor';
19+
import noEmptyFunction from './no-empty-function';
1920
import noEmptyInterface from './no-empty-interface';
2021
import noExplicitAny from './no-explicit-any';
2122
import noExtraParens from './no-extra-parens';
@@ -73,6 +74,7 @@ export default {
7374
'member-ordering': memberOrdering,
7475
'no-angle-bracket-type-assertion': noAngleBracketTypeAssertion,
7576
'no-array-constructor': noArrayConstructor,
77+
'no-empty-function': noEmptyFunction,
7678
'no-empty-interface': noEmptyInterface,
7779
'no-explicit-any': noExplicitAny,
7880
'no-extra-parens': noExtraParens,
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
import {
2+
TSESTree,
3+
AST_NODE_TYPES,
4+
} from '@typescript-eslint/experimental-utils';
5+
import baseRule from 'eslint/lib/rules/no-empty-function';
6+
import * as util from '../util';
7+
8+
type Options = util.InferOptionsTypeFromRule<typeof baseRule>;
9+
type MessageIds = util.InferMessageIdsTypeFromRule<typeof baseRule>;
10+
11+
export default util.createRule<Options, MessageIds>({
12+
name: 'no-empty-function',
13+
meta: {
14+
type: 'suggestion',
15+
docs: {
16+
description: 'Disallow empty functions',
17+
category: 'Best Practices',
18+
recommended: false,
19+
},
20+
schema: baseRule.meta.schema,
21+
messages: baseRule.meta.messages,
22+
},
23+
defaultOptions: [
24+
{
25+
allow: [],
26+
},
27+
],
28+
create(context) {
29+
const rules = baseRule.create(context);
30+
31+
/**
32+
* Checks if the node is a constructor
33+
* @param node the node to ve validated
34+
* @returns true if the node is a constructor
35+
* @private
36+
*/
37+
function isConstructor(
38+
node: TSESTree.FunctionDeclaration | TSESTree.FunctionExpression,
39+
): boolean {
40+
return !!(
41+
node.parent &&
42+
node.parent.type === 'MethodDefinition' &&
43+
node.parent.kind === 'constructor'
44+
);
45+
}
46+
47+
/**
48+
* Check if the method body is empty
49+
* @param node the node to be validated
50+
* @returns true if the body is empty
51+
* @private
52+
*/
53+
function isBodyEmpty(
54+
node: TSESTree.FunctionDeclaration | TSESTree.FunctionExpression,
55+
): boolean {
56+
return !node.body || node.body.body.length === 0;
57+
}
58+
59+
/**
60+
* Check if method has parameter properties
61+
* @param node the node to be validated
62+
* @returns true if the body has parameter properties
63+
* @private
64+
*/
65+
function hasParameterProperties(
66+
node: TSESTree.FunctionDeclaration | TSESTree.FunctionExpression,
67+
): boolean {
68+
return (
69+
node.params &&
70+
node.params.some(
71+
param => param.type === AST_NODE_TYPES.TSParameterProperty,
72+
)
73+
);
74+
}
75+
76+
/**
77+
* Checks if the method is a concise constructor (no function body, but has parameter properties)
78+
* @param node the node to be validated
79+
* @returns true if the method is a concise constructor
80+
* @private
81+
*/
82+
function isConciseConstructor(
83+
node: TSESTree.FunctionDeclaration | TSESTree.FunctionExpression,
84+
) {
85+
// Check TypeScript specific nodes
86+
return (
87+
isConstructor(node) && isBodyEmpty(node) && hasParameterProperties(node)
88+
);
89+
}
90+
91+
return {
92+
FunctionDeclaration(node: TSESTree.FunctionDeclaration) {
93+
if (!isConciseConstructor(node)) {
94+
rules.FunctionDeclaration(node);
95+
}
96+
},
97+
FunctionExpression(node: TSESTree.FunctionExpression) {
98+
if (!isConciseConstructor(node)) {
99+
rules.FunctionExpression(node);
100+
}
101+
},
102+
};
103+
},
104+
});
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import rule from '../../src/rules/no-empty-function';
2+
import { RuleTester } from '../RuleTester';
3+
4+
const ruleTester = new RuleTester({
5+
parser: '@typescript-eslint/parser',
6+
});
7+
8+
ruleTester.run('no-empty-function', rule, {
9+
valid: [
10+
{
11+
code: `class Person {
12+
private name: string
13+
constructor(name: string) {
14+
this.name = name;
15+
}
16+
}`,
17+
},
18+
{
19+
code: `class Person {
20+
constructor(private name: string) {}
21+
}`,
22+
},
23+
{
24+
code: `class Person {
25+
constructor(name: string) {}
26+
}`,
27+
options: [{ allow: ['constructors'] }],
28+
},
29+
{
30+
code: `class Person {
31+
otherMethod(name: string) {}
32+
}`,
33+
options: [{ allow: ['methods'] }],
34+
},
35+
],
36+
37+
invalid: [
38+
{
39+
code: `class Person {
40+
constructor(name: string) {}
41+
}`,
42+
errors: [
43+
{
44+
messageId: 'unexpected',
45+
data: {
46+
name: 'constructor',
47+
},
48+
line: 2,
49+
column: 35,
50+
},
51+
],
52+
},
53+
{
54+
code: `class Person {
55+
otherMethod(name: string) {}
56+
}`,
57+
errors: [
58+
{
59+
messageId: 'unexpected',
60+
data: {
61+
name: "method 'otherMethod'",
62+
},
63+
line: 2,
64+
column: 35,
65+
},
66+
],
67+
},
68+
],
69+
});

packages/eslint-plugin/typings/eslint-rules.d.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,24 @@ declare module 'eslint/lib/rules/no-dupe-args' {
155155
export = rule;
156156
}
157157

158+
declare module 'eslint/lib/rules/no-empty-function' {
159+
import { TSESLint, TSESTree } from '@typescript-eslint/experimental-utils';
160+
161+
const rule: TSESLint.RuleModule<
162+
'unexpected',
163+
[
164+
{
165+
allow?: string[];
166+
}
167+
],
168+
{
169+
FunctionDeclaration(node: TSESTree.FunctionDeclaration): void;
170+
FunctionExpression(node: TSESTree.FunctionExpression): void;
171+
}
172+
>;
173+
export = rule;
174+
}
175+
158176
declare module 'eslint/lib/rules/no-implicit-globals' {
159177
import { TSESLint, TSESTree } from '@typescript-eslint/experimental-utils';
160178

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy