diff --git a/packages/eslint-plugin/docs/rules/no-empty-function.md b/packages/eslint-plugin/docs/rules/no-empty-function.md
index e06961d4259..2b37c4da396 100644
--- a/packages/eslint-plugin/docs/rules/no-empty-function.md
+++ b/packages/eslint-plugin/docs/rules/no-empty-function.md
@@ -44,4 +44,37 @@ See the [ESLint documentation](https://eslint.org/docs/rules/no-empty-function)
}
```
+## Options
+
+This rule has an object option:
+
+- `allow` (`string[]`)
+ - `"protected-constructors"` - Protected class constructors.
+ - `"private-constructors"` - Private class constructors.
+ - [See the other options allowed](https://github.com/eslint/eslint/blob/master/docs/rules/no-empty-function.md#options)
+
+#### allow: protected-constructors
+
+Examples of **correct** code for the `{ "allow": ["protected-constructors"] }` option:
+
+```ts
+/*eslint @typescript-eslint/no-empty-function: ["error", { "allow": ["protected-constructors"] }]*/
+
+class Foo {
+ protected constructor() {}
+}
+```
+
+#### allow: private-constructors
+
+Examples of **correct** code for the `{ "allow": ["private-constructors"] }` option:
+
+```ts
+/*eslint @typescript-eslint/no-empty-function: ["error", { "allow": ["private-constructors"] }]*/
+
+class Foo {
+ private constructor() {}
+}
+```
+
Taken with ❤️ [from ESLint core](https://github.com/eslint/eslint/blob/master/docs/rules/no-empty-function.md)
diff --git a/packages/eslint-plugin/src/rules/no-empty-function.ts b/packages/eslint-plugin/src/rules/no-empty-function.ts
index 31aa5d8bb63..b0c88342a49 100644
--- a/packages/eslint-plugin/src/rules/no-empty-function.ts
+++ b/packages/eslint-plugin/src/rules/no-empty-function.ts
@@ -8,6 +8,32 @@ import * as util from '../util';
type Options = util.InferOptionsTypeFromRule;
type MessageIds = util.InferMessageIdsTypeFromRule;
+const schema = util.deepMerge(
+ Array.isArray(baseRule.meta.schema)
+ ? baseRule.meta.schema[0]
+ : baseRule.meta.schema,
+ {
+ properties: {
+ allow: {
+ items: {
+ enum: [
+ 'functions',
+ 'arrowFunctions',
+ 'generatorFunctions',
+ 'methods',
+ 'generatorMethods',
+ 'getters',
+ 'setters',
+ 'constructors',
+ 'private-constructors',
+ 'protected-constructors',
+ ],
+ },
+ },
+ },
+ },
+);
+
export default util.createRule({
name: 'no-empty-function',
meta: {
@@ -17,7 +43,7 @@ export default util.createRule({
category: 'Best Practices',
recommended: 'error',
},
- schema: baseRule.meta.schema,
+ schema: [schema],
messages: baseRule.meta.messages,
},
defaultOptions: [
@@ -25,24 +51,13 @@ export default util.createRule({
allow: [],
},
],
- create(context) {
+ create(context, [{ allow = [] }]) {
const rules = baseRule.create(context);
- /**
- * Checks if the node is a constructor
- * @param node the node to ve validated
- * @returns true if the node is a constructor
- * @private
- */
- function isConstructor(
- node: TSESTree.FunctionDeclaration | TSESTree.FunctionExpression,
- ): boolean {
- return !!(
- node.parent &&
- node.parent.type === 'MethodDefinition' &&
- node.parent.kind === 'constructor'
- );
- }
+ const isAllowedProtectedConstructors = allow.includes(
+ 'protected-constructors',
+ );
+ const isAllowedPrivateConstructors = allow.includes('private-constructors');
/**
* Check if the method body is empty
@@ -74,30 +89,44 @@ export default util.createRule({
}
/**
- * Checks if the method is a concise constructor (no function body, but has parameter properties)
* @param node the node to be validated
- * @returns true if the method is a concise constructor
+ * @returns true if the constructor is allowed to be empty
* @private
*/
- function isConciseConstructor(
+ function isAllowedEmptyConstructor(
node: TSESTree.FunctionDeclaration | TSESTree.FunctionExpression,
): boolean {
- // Check TypeScript specific nodes
- return (
- isConstructor(node) && isBodyEmpty(node) && hasParameterProperties(node)
- );
+ const parent = node.parent;
+ if (
+ isBodyEmpty(node) &&
+ parent?.type === 'MethodDefinition' &&
+ parent.kind === 'constructor'
+ ) {
+ const { accessibility } = parent;
+
+ return (
+ // allow protected constructors
+ (accessibility === 'protected' && isAllowedProtectedConstructors) ||
+ // allow private constructors
+ (accessibility === 'private' && isAllowedPrivateConstructors) ||
+ // allow constructors which have parameter properties
+ hasParameterProperties(node)
+ );
+ }
+
+ return false;
}
return {
FunctionDeclaration(node): void {
- if (!isConciseConstructor(node)) {
- rules.FunctionDeclaration(node);
- }
+ rules.FunctionDeclaration(node);
},
FunctionExpression(node): void {
- if (!isConciseConstructor(node)) {
- rules.FunctionExpression(node);
+ if (isAllowedEmptyConstructor(node)) {
+ return;
}
+
+ rules.FunctionExpression(node);
},
};
},
diff --git a/packages/eslint-plugin/tests/rules/no-empty-function.test.ts b/packages/eslint-plugin/tests/rules/no-empty-function.test.ts
index 4b40c170867..9b5ad3507d3 100644
--- a/packages/eslint-plugin/tests/rules/no-empty-function.test.ts
+++ b/packages/eslint-plugin/tests/rules/no-empty-function.test.ts
@@ -32,6 +32,29 @@ ruleTester.run('no-empty-function', rule, {
}`,
options: [{ allow: ['methods'] }],
},
+ {
+ code: `
+class Foo {
+ private constructor() {}
+}
+ `,
+ options: [{ allow: ['private-constructors'] }],
+ },
+ {
+ code: `
+class Foo {
+ protected constructor() {}
+}
+ `,
+ options: [{ allow: ['protected-constructors'] }],
+ },
+ {
+ code: `
+function foo() {
+ const a = null;
+}
+ `,
+ },
],
invalid: [
@@ -65,5 +88,55 @@ ruleTester.run('no-empty-function', rule, {
},
],
},
+ {
+ code: `
+class Foo {
+ private constructor() {}
+}
+ `,
+ errors: [
+ {
+ messageId: 'unexpected',
+ data: {
+ name: 'constructor',
+ },
+ line: 3,
+ column: 25,
+ },
+ ],
+ },
+ {
+ code: `
+class Foo {
+ protected constructor() {}
+}
+ `,
+ errors: [
+ {
+ messageId: 'unexpected',
+ data: {
+ name: 'constructor',
+ },
+ line: 3,
+ column: 27,
+ },
+ ],
+ },
+ {
+ code: `
+function foo() {
+}
+ `,
+ errors: [
+ {
+ messageId: 'unexpected',
+ data: {
+ name: "function 'foo'",
+ },
+ line: 2,
+ column: 16,
+ },
+ ],
+ },
],
});