Skip to content

Commit dcd95aa

Browse files
authored
feat: support TypeScript syntax in no-empty-function rule (#19551)
1 parent 77d6d5b commit dcd95aa

3 files changed

Lines changed: 747 additions & 16 deletions

File tree

docs/src/rules/no-empty-function.md

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,10 @@ This rule has an option to allow specific kinds of functions to be empty.
191191
* `"constructors"` - Class constructors.
192192
* `"asyncFunctions"` - Async functions.
193193
* `"asyncMethods"` - Async class methods and method shorthands of object literals.
194+
* `"privateConstructors"` - Private class constructors. (TypeScript only)
195+
* `"protectedConstructors"` - Protected class constructors. (TypeScript only)
196+
* `"decoratedFunctions"` - Class methods with decorators. (TypeScript only)
197+
* `"overrideMethods"` - Methods that use the override keyword. (TypeScript only)
194198

195199
### allow: functions
196200

@@ -381,6 +385,75 @@ class A {
381385

382386
:::
383387

388+
### allow: privateConstructors
389+
390+
Examples of **correct** TypeScript code for the `{ "allow": ["privateConstructors"] }` option:
391+
392+
::: correct
393+
394+
```ts
395+
/*eslint no-empty-function: ["error", { "allow": ["privateConstructors"] }]*/
396+
397+
class A {
398+
private constructor() {}
399+
}
400+
```
401+
402+
:::
403+
404+
### allow: protectedConstructors
405+
406+
Examples of **correct** TypeScript code for the `{ "allow": ["protectedConstructors"] }` option:
407+
408+
::: correct
409+
410+
```ts
411+
/*eslint no-empty-function: ["error", { "allow": ["protectedConstructors"] }]*/
412+
413+
class A {
414+
protected constructor() {}
415+
}
416+
```
417+
418+
:::
419+
420+
### allow: decoratedFunctions
421+
422+
Examples of **correct** TypeScript code for the `{ "allow": ["decoratedFunctions"] }` option:
423+
424+
::: correct
425+
426+
```ts
427+
/*eslint no-empty-function: ["error", { "allow": ["decoratedFunctions"] }]*/
428+
429+
class A {
430+
@decorator
431+
foo() {}
432+
}
433+
```
434+
435+
:::
436+
437+
### allow: overrideMethods
438+
439+
Examples of **correct** TypeScript code for the `{ "allow": ["overrideMethods"] }` option:
440+
441+
::: correct
442+
443+
```ts
444+
/*eslint no-empty-function: ["error", { "allow": ["overrideMethods"] }]*/
445+
446+
abstract class Base {
447+
abstract method(): void;
448+
}
449+
450+
class Derived extends Base {
451+
override method() {}
452+
}
453+
```
454+
455+
:::
456+
384457
## When Not To Use It
385458

386459
If you don't want to be notified about empty functions, then it's safe to disable this rule.

lib/rules/no-empty-function.js

Lines changed: 53 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@ const ALLOW_OPTIONS = Object.freeze([
2626
"constructors",
2727
"asyncFunctions",
2828
"asyncMethods",
29+
"privateConstructors",
30+
"protectedConstructors",
31+
"decoratedFunctions",
32+
"overrideMethods",
2933
]);
3034

3135
/**
@@ -83,13 +87,24 @@ function getKind(node) {
8387
return prefix + kind[0].toUpperCase() + kind.slice(1);
8488
}
8589

90+
/**
91+
* Checks if a constructor function has parameter properties.
92+
* @param {ASTNode} node The function node to examine.
93+
* @returns {boolean} True if the constructor has parameter properties, false otherwise.
94+
*/
95+
function isParameterPropertiesConstructor(node) {
96+
return node.params.some(param => param.type === "TSParameterProperty");
97+
}
98+
8699
//------------------------------------------------------------------------------
87100
// Rule Definition
88101
//------------------------------------------------------------------------------
89102

90103
/** @type {import('../types').Rule.RuleModule} */
91104
module.exports = {
92105
meta: {
106+
dialects: ["javascript", "typescript"],
107+
language: "javascript",
93108
type: "suggestion",
94109

95110
defaultOptions: [{ allow: [] }],
@@ -123,6 +138,43 @@ module.exports = {
123138
const [{ allow }] = context.options;
124139
const sourceCode = context.sourceCode;
125140

141+
/**
142+
* Checks if the given function node is allowed to be empty.
143+
* @param {ASTNode} node The function node to check.
144+
* @returns {boolean} True if the function is allowed to be empty, false otherwise.
145+
*/
146+
function isAllowedEmptyFunction(node) {
147+
const kind = getKind(node);
148+
149+
if (allow.includes(kind)) {
150+
return true;
151+
}
152+
153+
if (kind === "constructors") {
154+
if (
155+
(node.parent.accessibility === "private" &&
156+
allow.includes("privateConstructors")) ||
157+
(node.parent.accessibility === "protected" &&
158+
allow.includes("protectedConstructors")) ||
159+
isParameterPropertiesConstructor(node)
160+
) {
161+
return true;
162+
}
163+
}
164+
165+
if (/(g|s)etters|methods$/iu.test(kind)) {
166+
if (
167+
(node.parent.decorators?.length &&
168+
allow.includes("decoratedFunctions")) ||
169+
(node.parent.override && allow.includes("overrideMethods"))
170+
) {
171+
return true;
172+
}
173+
}
174+
175+
return false;
176+
}
177+
126178
/**
127179
* Reports a given function node if the node matches the following patterns.
128180
*
@@ -135,15 +187,14 @@ module.exports = {
135187
* @returns {void}
136188
*/
137189
function reportIfEmpty(node) {
138-
const kind = getKind(node);
139190
const name = astUtils.getFunctionNameWithKind(node);
140191
const innerComments = sourceCode.getTokens(node.body, {
141192
includeComments: true,
142193
filter: astUtils.isCommentToken,
143194
});
144195

145196
if (
146-
!allow.includes(kind) &&
197+
!isAllowedEmptyFunction(node) &&
147198
node.body.type === "BlockStatement" &&
148199
node.body.body.length === 0 &&
149200
innerComments.length === 0

0 commit comments

Comments
 (0)