Skip to content

Commit

Permalink
feat(eslint-plugin): [no-empty-function] add decoratedFunctions opt…
Browse files Browse the repository at this point in the history
…ion (#2295)
  • Loading branch information
a-tarasyuk committed Jul 20, 2020
1 parent 156d058 commit 88f08f4
Show file tree
Hide file tree
Showing 4 changed files with 86 additions and 2 deletions.
2 changes: 1 addition & 1 deletion packages/eslint-plugin/docs/rules/no-empty-function.md
Expand Up @@ -5,7 +5,7 @@
This rule extends the base [`eslint/no-empty-function`](https://eslint.org/docs/rules/no-empty-function) rule.
It adds support for handling TypeScript specific code that would otherwise trigger the rule.

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:
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.

## How to use

Expand Down
35 changes: 34 additions & 1 deletion packages/eslint-plugin/src/rules/no-empty-function.ts
Expand Up @@ -29,6 +29,7 @@ const schema = util.deepMerge(
'protected-constructors',
'asyncFunctions',
'asyncMethods',
'decoratedFunctions',
],
},
},
Expand Down Expand Up @@ -61,6 +62,7 @@ export default util.createRule<Options, MessageIds>({
'protected-constructors',
);
const isAllowedPrivateConstructors = allow.includes('private-constructors');
const isAllowedDecoratedFunctions = allow.includes('decoratedFunctions');

/**
* Check if the method body is empty
Expand Down Expand Up @@ -117,15 +119,46 @@ export default util.createRule<Options, MessageIds>({
return false;
}

/**
* @param node the node to be validated
* @returns true if a function has decorators
* @private
*/
function isAllowedEmptyDecoratedFunctions(
node: TSESTree.FunctionExpression | TSESTree.FunctionDeclaration,
): boolean {
if (isAllowedDecoratedFunctions && isBodyEmpty(node)) {
const decorators =
node.type === AST_NODE_TYPES.FunctionDeclaration
? node.decorators
: node.parent?.type === AST_NODE_TYPES.MethodDefinition
? node.parent.decorators
: undefined;
return !!decorators && !!decorators.length;
}

return false;
}

return {
...rules,
FunctionExpression(node): void {
if (isAllowedEmptyConstructor(node)) {
if (
isAllowedEmptyConstructor(node) ||
isAllowedEmptyDecoratedFunctions(node)
) {
return;
}

rules.FunctionExpression(node);
},
FunctionDeclaration(node): void {
if (isAllowedEmptyDecoratedFunctions(node)) {
return;
}

rules.FunctionDeclaration(node);
},
};
},
});
50 changes: 50 additions & 0 deletions packages/eslint-plugin/tests/rules/no-empty-function.test.ts
Expand Up @@ -63,6 +63,22 @@ function foo() {
}
`,
},
{
code: `
@decorator()
function foo() {}
`,
options: [{ allow: ['decoratedFunctions'] }],
},
{
code: `
class Foo {
@decorator()
foo() {}
}
`,
options: [{ allow: ['decoratedFunctions'] }],
},
],

invalid: [
Expand Down Expand Up @@ -149,5 +165,39 @@ function foo() {}
},
],
},
{
code: `
@decorator()
function foo() {}
`,
errors: [
{
messageId: 'unexpected',
data: {
name: "function 'foo'",
},
line: 3,
column: 16,
},
],
},
{
code: `
class Foo {
@decorator()
foo() {}
}
`,
errors: [
{
messageId: 'unexpected',
data: {
name: "method 'foo'",
},
line: 4,
column: 9,
},
],
},
],
});
1 change: 1 addition & 0 deletions packages/types/src/ts-estree.ts
Expand Up @@ -946,6 +946,7 @@ export interface ForStatement extends BaseNode {
export interface FunctionDeclaration extends FunctionDeclarationBase {
type: AST_NODE_TYPES.FunctionDeclaration;
body: BlockStatement;
decorators?: Decorator[];
}

export interface FunctionExpression extends FunctionDeclarationBase {
Expand Down

0 comments on commit 88f08f4

Please sign in to comment.