Skip to content

Commit 0f5a94a

Browse files
kirkwaiblingernzakasJoshuaKGoldberg
authored
docs: [class-methods-use-this] explain purpose of rule (#20008)
* docs: [class-methods-use-this] explain purpose of rule * more * tweaks * Update docs/src/rules/class-methods-use-this.md Co-authored-by: Nicholas C. Zakas <[email protected]> * remove excess words * Apply suggestion from @JoshuaKGoldberg Co-authored-by: Josh Goldberg ✨ <[email protected]> * Apply suggestion from @JoshuaKGoldberg Co-authored-by: Josh Goldberg ✨ <[email protected]> * Apply suggestion from @JoshuaKGoldberg Co-authored-by: Josh Goldberg ✨ <[email protected]> * cleanup from merge problems and feedback --------- Co-authored-by: Nicholas C. Zakas <[email protected]> Co-authored-by: Josh Goldberg ✨ <[email protected]>
1 parent d6e7bf3 commit 0f5a94a

1 file changed

Lines changed: 75 additions & 33 deletions

File tree

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

Lines changed: 75 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -6,55 +6,91 @@ further_reading:
66
- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/static
77
---
88

9+
[Classes](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes) are often used to encapsulate reusable logic, especially stateful logic, into an object where each instance's state is accessed via `this`. When an API is written with an instance method, it signals to consumers:
910

10-
If a class method does not use `this`, it can *sometimes* be made into a static function. If you do convert the method into a static function, instances of the class that call that particular method have to be converted to a static call as well (`MyClass.callStaticMethod()`).
11+
- The method's outcome is related to the object on which it's invoked, including possibly its state.
1112

12-
It's possible to have a class method which doesn't use `this`, such as:
13+
```js
14+
const array1 = [1, 2, 3];
15+
const array2 = [4, 5, 6];
1316

14-
```js
15-
class A {
16-
constructor() {
17-
this.a = "hi";
18-
}
17+
// Using the `includes()` method on different objects gives different results:
18+
array1.includes(1); // true
19+
array2.includes(1); // false
1920

20-
print() {
21-
console.log(this.a);
22-
}
21+
// Modifying the state of an object may change the outcome of its instance methods:
22+
array2.push(1);
23+
array2.includes(1); // true
24+
```
25+
26+
- The method doesn't make sense to be used without an associated object. (For example, it doesn't make sense to call `Array#includes()` without an array to operate on.)
2327

28+
It's possible to have a class method which doesn't use `this`, such as:
29+
30+
```js
31+
class Person {
2432
sayHi() {
25-
console.log("hi");
33+
console.log("Hi!");
2634
}
2735
}
2836

29-
let a = new A();
30-
a.sayHi(); // => "hi"
37+
const person = new Person();
38+
person.sayHi(); // => "Hi!"
3139
```
3240

33-
In the example above, the `sayHi` method doesn't use `this`, so we can make it a static method:
41+
If a class instance method does not use `this`, that normally means that it does not access any instance state and therefore doesn't need to be a method.
42+
Therefore, it can *sometimes* be refactored into an [ordinary function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions) or a [static method](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/static), which may better communicate intent to users of the API.
43+
44+
In the example above, the `sayHi` method doesn't use `this`, so we can make it an ordinary function:
3445

3546
```js
36-
class A {
37-
constructor() {
38-
this.a = "hi";
39-
}
47+
// Ordinary function
48+
function sayHi() {
49+
console.log("Hi!");
50+
}
4051

41-
print() {
42-
console.log(this.a);
43-
}
52+
// No need for `Person` class or any instance thereof
53+
sayHi(); // => "Hi!"
4454

55+
// Alternately, a static method may be used if it offers a more natural API
56+
class Person {
4557
static sayHi() {
46-
console.log("hi");
58+
console.log("Hi!");
4759
}
4860
}
4961

50-
A.sayHi(); // => "hi"
62+
Person.sayHi(); // => "Hi!"
63+
64+
// Keep in mind that, either way, the following now throws an error,
65+
// since sayHi() is no longer an instance method!
66+
//
67+
// const person = new Person();
68+
// person.sayHi();
5169
```
5270

53-
Also note in the above examples that if you switch a method to a static method, *instances* of the class that call the static method (`let a = new A(); a.sayHi();`) have to be updated to being a static call (`A.sayHi();`) instead of having the instance of the *class* call the method.
71+
It's also possible the author forgot to use some piece of instance data that they intended to include.
72+
73+
```js
74+
class Person {
75+
constructor(name) {
76+
this.name = name;
77+
}
78+
79+
sayHi() {
80+
console.log(`Hi from ${this.name}!`);
81+
}
82+
}
83+
84+
const alice = new Person('Alice');
85+
alice.sayHi(); // => 'Hi from Alice!'
86+
87+
const bob = new Person('Bob');
88+
bob.sayHi(); // => 'Hi from Bob!'
89+
```
5490

5591
## Rule Details
5692

57-
This rule is aimed to flag class methods that do not use `this`.
93+
This rule flags class instance methods that do not use `this`.
5894

5995
Examples of **incorrect** code for this rule:
6096

@@ -65,7 +101,7 @@ Examples of **incorrect** code for this rule:
65101

66102
class A {
67103
foo() {
68-
console.log("Hello World"); /*error Expected 'this' to be used by class method 'foo'.*/
104+
console.log("Hello World"); /* error Expected 'this' to be used by class method 'foo'. */
69105
}
70106
}
71107
```
@@ -81,7 +117,7 @@ Examples of **correct** code for this rule:
81117

82118
class A {
83119
foo() {
84-
this.bar = "Hello World"; // OK, this is used
120+
this.bar = "Hello World"; // OK, `this` is used
85121
}
86122
}
87123

@@ -108,10 +144,10 @@ class C {
108144

109145
This rule has four options:
110146

111-
* `"exceptMethods"` allows specified method names to be ignored with this rule.
112-
* `"enforceForClassFields"` enforces that arrow functions and function expressions used as instance field initializers utilize `this`. This also applies to auto-accessor fields (fields declared with the `accessor` keyword) which are part of the [stage 3 decorators proposal](https://github.com/tc39/proposal-decorators). (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)
147+
- `"exceptMethods"` allows specified method names to be ignored with this rule.
148+
- `"enforceForClassFields"` enforces that arrow functions and function expressions used as instance field initializers utilize `this`. This also applies to auto-accessor fields (fields declared with the `accessor` keyword) which are part of the [stage 3 decorators proposal](https://github.com/tc39/proposal-decorators). (default: `true`)
149+
- `"ignoreOverrideMethods"` ignores members that are marked with the [`override` modifier](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-3.html#override-and-the---noimplicitoverride-flag). (TypeScript only, default: `false`)
150+
- `"ignoreClassesWithImplements"` ignores class members that are defined within a class that [`implements`](https://www.typescriptlang.org/docs/handbook/2/classes.html#implements-clauses) an interface. (TypeScript only)
115151

116152
### exceptMethods
117153

@@ -328,8 +364,8 @@ class Derived extends Base {
328364

329365
The `ignoreClassesWithImplements` ignores class members that are defined within a class that `implements` an interface. The option accepts two possible values:
330366

331-
* `"all"` - Ignores all classes that implement interfaces
332-
* `"public-fields"` - Only ignores public fields in classes that implement interfaces
367+
- `"all"` - Ignores all classes that implement interfaces
368+
- `"public-fields"` - Only ignores public fields in classes that implement interfaces
333369

334370
Examples of **incorrect** TypeScript code for this rule with the `{ "ignoreClassesWithImplements": "all" }`:
335371

@@ -408,3 +444,9 @@ class Derived implements Base {
408444
```
409445

410446
:::
447+
448+
449+
## When Not To Use It
450+
451+
Fixing violations of this rule almost always is a breaking change, since it requires a change at every usage of the affected method.
452+
Therefore, if your project has downstream consumers you cannot break, or you do not wish to make invasive changes to every call site of a method, it likely does not make sense to address violations of this rule.

0 commit comments

Comments
 (0)