Skip to content

Commit 30ae4ed

Browse files
sethamusnzakas
andauthored
feat: add new options to class-methods-use-this (#19527)
* feat: add ignoreOverrideMethods option to class-methods-use-this * feat: add ignoreClassesWithImplements option to class-methods-use-this * fix example * update docs Co-authored-by: Nicholas C. Zakas <[email protected]> * shorten hasImplements() Co-authored-by: Nicholas C. Zakas <[email protected]> * tweak docs and add tests for PrivateIdentifier * format * tweak examples * use ts --------- Co-authored-by: Nicholas C. Zakas <[email protected]>
1 parent b79ade6 commit 30ae4ed

File tree

3 files changed

+795
-4
lines changed

3 files changed

+795
-4
lines changed

docs/src/rules/class-methods-use-this.md

+166-3
Original file line numberDiff line numberDiff line change
@@ -106,14 +106,16 @@ class C {
106106

107107
## Options
108108

109-
This rule has two options:
109+
This rule has four options:
110110

111111
* `"exceptMethods"` allows specified method names to be ignored with this rule.
112112
* `"enforceForClassFields"` enforces that functions used as instance field initializers utilize `this`. (default: `true`)
113+
* `"ignoreOverrideMethods"` ignores members that are marked with the `override` modifier. (TypeScript only, default: `false`)
114+
* `"ignoreClassesWithImplements"` ignores class members that are defined within a class that `implements` an interface. (TypeScript only)
113115

114116
### exceptMethods
115117

116-
```js
118+
```ts
117119
"class-methods-use-this": [<enabled>, { "exceptMethods": [<...exceptions>] }]
118120
```
119121

@@ -153,7 +155,7 @@ class A {
153155

154156
### enforceForClassFields
155157

156-
```js
158+
```ts
157159
"class-methods-use-this": [<enabled>, { "enforceForClassFields": true | false }]
158160
```
159161

@@ -200,3 +202,164 @@ class A {
200202
```
201203

202204
:::
205+
206+
### ignoreOverrideMethods
207+
208+
```ts
209+
"class-methods-use-this": [<enabled>, { "ignoreOverrideMethods": true | false }]
210+
```
211+
212+
The `ignoreOverrideMethods` option ignores members that are marked with the `override` modifier. (default: `false`)
213+
214+
Examples of **incorrect** TypeScript code for this rule with the `{ "ignoreOverrideMethods": false }` option (default):
215+
216+
::: incorrect
217+
218+
```ts
219+
/*eslint class-methods-use-this: ["error", { "ignoreOverrideMethods": false }] */
220+
221+
abstract class Base {
222+
abstract method(): void;
223+
abstract property: () => void;
224+
}
225+
226+
class Derived extends Base {
227+
override method() {}
228+
override property = () => {};
229+
}
230+
```
231+
232+
:::
233+
234+
Examples of **correct** TypeScript code for this rule with the `{ "ignoreOverrideMethods": false }` option (default):
235+
236+
::: correct
237+
238+
```ts
239+
/*eslint class-methods-use-this: ["error", { "ignoreOverrideMethods": false }] */
240+
241+
abstract class Base {
242+
abstract method(): void;
243+
abstract property: () => void;
244+
}
245+
246+
class Derived extends Base {
247+
override method() {
248+
this.foo = "Hello World";
249+
};
250+
override property = () => {
251+
this;
252+
};
253+
}
254+
```
255+
256+
:::
257+
258+
Examples of **correct** TypeScript code for this rule with the `{ "ignoreOverrideMethods": true }` option:
259+
260+
::: correct
261+
262+
```ts
263+
/*eslint class-methods-use-this: ["error", { "ignoreOverrideMethods": true }] */
264+
265+
abstract class Base {
266+
abstract method(): void;
267+
abstract property: () => void;
268+
}
269+
270+
class Derived extends Base {
271+
override method() {}
272+
override property = () => {};
273+
}
274+
```
275+
276+
:::
277+
278+
### ignoreClassesWithImplements
279+
280+
```ts
281+
"class-methods-use-this": [<enabled>, { "ignoreClassesWithImplements": "all" | "public-fields" }]
282+
```
283+
284+
The `ignoreClassesWithImplements` ignores class members that are defined within a class that `implements` an interface. The option accepts two possible values:
285+
286+
* `"all"` - Ignores all classes that implement interfaces
287+
* `"public-fields"` - Only ignores public fields in classes that implement interfaces
288+
289+
Examples of **incorrect** TypeScript code for this rule with the `{ "ignoreClassesWithImplements": "all" }`:
290+
291+
::: incorrect
292+
293+
```ts
294+
/*eslint class-methods-use-this: ["error", { "ignoreClassesWithImplements": "all" }] */
295+
296+
class Standalone {
297+
method() {}
298+
property = () => {};
299+
}
300+
```
301+
302+
:::
303+
304+
Examples of **correct** TypeScript code for this rule with the `{ "ignoreClassesWithImplements": "all" }` option:
305+
306+
::: correct
307+
308+
```ts
309+
/*eslint class-methods-use-this: ["error", { "ignoreClassesWithImplements": "all" }] */
310+
311+
interface Base {
312+
method(): void;
313+
}
314+
315+
class Derived implements Base {
316+
method() {}
317+
property = () => {};
318+
}
319+
```
320+
321+
:::
322+
323+
Examples of **incorrect** TypeScript code for this rule with the `{ "ignoreClassesWithImplements": "public-fields" }` option:
324+
325+
::: incorrect
326+
327+
```ts
328+
/*eslint class-methods-use-this: ["error", { "ignoreClassesWithImplements": "public-fields" }] */
329+
330+
interface Base {
331+
method(): void;
332+
}
333+
334+
class Derived implements Base {
335+
method() {}
336+
property = () => {};
337+
338+
private privateMethod() {}
339+
private privateProperty = () => {};
340+
341+
protected protectedMethod() {}
342+
protected protectedProperty = () => {};
343+
}
344+
```
345+
346+
:::
347+
348+
Examples of **correct** TypeScript code for this rule with the `{ "ignoreClassesWithImplements": "public-fields" }` option:
349+
350+
::: correct
351+
352+
```ts
353+
/*eslint class-methods-use-this: ["error", { "ignoreClassesWithImplements": "public-fields" }] */
354+
355+
interface Base {
356+
method(): void;
357+
}
358+
359+
class Derived implements Base {
360+
method() {}
361+
property = () => {};
362+
}
363+
```
364+
365+
:::

lib/rules/class-methods-use-this.js

+45-1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ module.exports = {
2626
{
2727
enforceForClassFields: true,
2828
exceptMethods: [],
29+
ignoreOverrideMethods: false,
2930
},
3031
],
3132

@@ -48,6 +49,12 @@ module.exports = {
4849
enforceForClassFields: {
4950
type: "boolean",
5051
},
52+
ignoreOverrideMethods: {
53+
type: "boolean",
54+
},
55+
ignoreClassesWithImplements: {
56+
enum: ["all", "public-fields"],
57+
},
5158
},
5259
additionalProperties: false,
5360
},
@@ -59,7 +66,11 @@ module.exports = {
5966
},
6067
create(context) {
6168
const [options] = context.options;
62-
const { enforceForClassFields } = options;
69+
const {
70+
enforceForClassFields,
71+
ignoreOverrideMethods,
72+
ignoreClassesWithImplements,
73+
} = options;
6374
const exceptMethods = new Set(options.exceptMethods);
6475

6576
const stack = [];
@@ -107,6 +118,20 @@ module.exports = {
107118
}
108119
}
109120

121+
/**
122+
* Check if the node's parent class implements any interfaces
123+
* @param {ASTNode} node node to check
124+
* @returns {boolean} True if parent class implements interfaces
125+
* @private
126+
*/
127+
function hasImplements(node) {
128+
const classNode = node.parent.parent;
129+
return (
130+
classNode?.type === "ClassDeclaration" &&
131+
classNode.implements?.length > 0
132+
);
133+
}
134+
110135
/**
111136
* Check if the node is an instance method not excluded by config
112137
* @param {ASTNode} node node to check
@@ -119,6 +144,25 @@ module.exports = {
119144
return true;
120145
}
121146

147+
if (ignoreOverrideMethods && node.override) {
148+
return false;
149+
}
150+
151+
if (ignoreClassesWithImplements) {
152+
const implementsInterfaces = hasImplements(node);
153+
if (implementsInterfaces) {
154+
if (
155+
ignoreClassesWithImplements === "all" ||
156+
(ignoreClassesWithImplements === "public-fields" &&
157+
node.key.type !== "PrivateIdentifier" &&
158+
(!node.accessibility ||
159+
node.accessibility === "public"))
160+
) {
161+
return false;
162+
}
163+
}
164+
}
165+
122166
const hashIfNeeded =
123167
node.key.type === "PrivateIdentifier" ? "#" : "";
124168
const name =

0 commit comments

Comments
 (0)