eslint-plugin-mobx 0.0.4 → 0.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +7 -0
- package/README.md +45 -5
- package/dist/index.js +128 -7
- package/package.json +1 -1
- package/src/index.js +6 -0
- package/src/missing-observer.js +63 -0
- package/src/no-anonymous-observer.js +57 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# eslint-plugin-mobx
|
|
2
2
|
|
|
3
|
+
## 0.0.5
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [`021f34ec`](https://github.com/mobxjs/mobx/commit/021f34ec81daed9e5b5ed8425b2f3e0fa85dfe5b) [#3219](https://github.com/mobxjs/mobx/pull/3219) Thanks [@urugator](https://github.com/urugator)! - Add [`mobx/missing-observer`](https://github.com/mobxjs/mobx/tree/main/packages/eslint-plugin-mobx#mobxmissing-observer),
|
|
8
|
+
[`mobx/no-anonymous-observer`](https://github.com/mobxjs/mobx/tree/main/packages/eslint-plugin-mobx#mobxno-anonymous-observer) rules,
|
|
9
|
+
|
|
3
10
|
## 0.0.4
|
|
4
11
|
|
|
5
12
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -21,9 +21,11 @@ module.exports = {
|
|
|
21
21
|
// ...or specify and customize individual rules:
|
|
22
22
|
rules: {
|
|
23
23
|
// these values are the same as recommended
|
|
24
|
-
'mobx/exhaustive-make-observable': 'warn',
|
|
24
|
+
'mobx/exhaustive-make-observable': 'warn',
|
|
25
|
+
'mobx/unconditional-make-observable': 'error',
|
|
25
26
|
'mobx/missing-make-observable': 'error',
|
|
26
|
-
'mobx/
|
|
27
|
+
'mobx/no-missing-observer': 'warn',
|
|
28
|
+
'mobx/no-anonymous-observer': 'warn',
|
|
27
29
|
},
|
|
28
30
|
};
|
|
29
31
|
```
|
|
@@ -33,7 +35,7 @@ module.exports = {
|
|
|
33
35
|
### mobx/exhaustive-make-observable
|
|
34
36
|
|
|
35
37
|
Makes sure that `makeObservable` annotates all fields defined on class or object literal.<br>
|
|
36
|
-
Autofix adds `field: true` for each missing field.<br>
|
|
38
|
+
**Autofix** adds `field: true` for each missing field.<br>
|
|
37
39
|
To exclude a field, annotate it using `field: false`.<br>
|
|
38
40
|
Does not support fields introduced by constructor (`this.foo = 5`).<br>
|
|
39
41
|
Does not warn about annotated non-existing fields (there is a runtime check, but the autofix removing the field could be handy...).
|
|
@@ -41,8 +43,46 @@ Does not warn about annotated non-existing fields (there is a runtime check, but
|
|
|
41
43
|
### mobx/missing-make-observable
|
|
42
44
|
|
|
43
45
|
*When using decorators (eg `@observable foo = 5`)*, makes sure that `makeObservable(this)` is called in a constructor.<br>
|
|
44
|
-
Autofix creates a constructor if necessary and adds `makeObservable(this)` at it's end.
|
|
46
|
+
**Autofix** creates a constructor if necessary and adds `makeObservable(this)` at it's end.
|
|
45
47
|
|
|
46
48
|
### mobx/unconditional-make-observable
|
|
47
49
|
|
|
48
|
-
Makes sure the `make(Auto)Observable(this)` is called unconditionally inside a constructor.
|
|
50
|
+
Makes sure the `make(Auto)Observable(this)` is called unconditionally inside a constructor.
|
|
51
|
+
|
|
52
|
+
### mobx/missing-observer
|
|
53
|
+
|
|
54
|
+
Makes sure every React component is wrapped with `observer`. A React component is considered to be any *class* extending from `Component` or `React.Component` and any *function* which name has the first letter capitalized (for anonymous functions the name is inferred from variable). These are all considered components:
|
|
55
|
+
```javascript
|
|
56
|
+
class Cmp extends React.Component { }
|
|
57
|
+
class Cmp extends Component { }
|
|
58
|
+
const Cmp = class extends React.Component { }
|
|
59
|
+
const Cmp = class extends Component { }
|
|
60
|
+
class extends Component { }
|
|
61
|
+
class extends React.Component { }
|
|
62
|
+
|
|
63
|
+
function Named() { }
|
|
64
|
+
const foo = function Named() { }
|
|
65
|
+
const Anonym = function () { };
|
|
66
|
+
const Arrow = () => { };
|
|
67
|
+
```
|
|
68
|
+
**Autofix** wraps the component with `observer` and if necessary declares a constant of the same name: `const Name = observer(function Name() {})`.
|
|
69
|
+
It's a bit opinionated and can lead to a lot of false positives depending on your conventions. You will probably want to combine this rule with `overrides` option, eg:
|
|
70
|
+
```javascript
|
|
71
|
+
// .eslintrc.js
|
|
72
|
+
"overrides": [
|
|
73
|
+
{
|
|
74
|
+
"files": ["*.jsx"],
|
|
75
|
+
"rules": {
|
|
76
|
+
"mobx/missing-observer": "error"
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
]
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### mobx/no-anonymous-observer
|
|
83
|
+
|
|
84
|
+
Forbids anonymous functions or classes as `observer` components.
|
|
85
|
+
Improves debugging experience and [avoids problem with inability to customize `displayName`](https://github.com/mobxjs/mobx/issues/2721).
|
|
86
|
+
Plays nice with `eslint-plugin-react-hooks` and `mobx/missing-observer` as both of these don't not recognize anonymous function as component.
|
|
87
|
+
**Autofix** infers the name from variable if possible.
|
|
88
|
+
|
package/dist/index.js
CHANGED
|
@@ -101,7 +101,7 @@ var findAncestor$2 = utils.findAncestor,
|
|
|
101
101
|
isMobxDecorator$1 = utils.isMobxDecorator; // TODO support this.foo = 5; in constructor
|
|
102
102
|
// TODO? report on field as well
|
|
103
103
|
|
|
104
|
-
function create$
|
|
104
|
+
function create$4(context) {
|
|
105
105
|
var sourceCode = context.getSourceCode();
|
|
106
106
|
|
|
107
107
|
function fieldToKey(field) {
|
|
@@ -225,12 +225,12 @@ var exhaustiveMakeObservable$1 = {
|
|
|
225
225
|
'missingAnnotation': 'Missing annotation for {{ keyList }}. To exclude a field, use `false` as annotation.'
|
|
226
226
|
}
|
|
227
227
|
},
|
|
228
|
-
create: create$
|
|
228
|
+
create: create$4
|
|
229
229
|
};
|
|
230
230
|
|
|
231
231
|
var findAncestor$1 = utils.findAncestor;
|
|
232
232
|
|
|
233
|
-
function create$
|
|
233
|
+
function create$3(context) {
|
|
234
234
|
return {
|
|
235
235
|
'CallExpression[callee.name=/(makeObservable|makeAutoObservable)/]': function CallExpressionCalleeNameMakeObservableMakeAutoObservable(makeObservable) {
|
|
236
236
|
var _closestFunction$pare;
|
|
@@ -275,13 +275,13 @@ var unconditionalMakeObservable$1 = {
|
|
|
275
275
|
mustCallUnconditionally: '`{{ name }}` must be called unconditionally inside constructor.'
|
|
276
276
|
}
|
|
277
277
|
},
|
|
278
|
-
create: create$
|
|
278
|
+
create: create$3
|
|
279
279
|
};
|
|
280
280
|
|
|
281
281
|
var findAncestor = utils.findAncestor,
|
|
282
282
|
isMobxDecorator = utils.isMobxDecorator;
|
|
283
283
|
|
|
284
|
-
function create(context) {
|
|
284
|
+
function create$2(context) {
|
|
285
285
|
var sourceCode = context.getSourceCode();
|
|
286
286
|
return {
|
|
287
287
|
'Decorator': function Decorator(decorator) {
|
|
@@ -357,12 +357,129 @@ var missingMakeObservable$1 = {
|
|
|
357
357
|
secondArgMustBeNullish: "`makeObservable`'s second argument must be nullish or not provided when using decorators."
|
|
358
358
|
}
|
|
359
359
|
},
|
|
360
|
+
create: create$2
|
|
361
|
+
};
|
|
362
|
+
|
|
363
|
+
function create$1(context) {
|
|
364
|
+
var sourceCode = context.getSourceCode();
|
|
365
|
+
return {
|
|
366
|
+
'FunctionDeclaration,FunctionExpression,ArrowFunctionExpression,ClassDeclaration,ClassExpression': function FunctionDeclarationFunctionExpressionArrowFunctionExpressionClassDeclarationClassExpression(cmp) {
|
|
367
|
+
var _cmp$id, _cmp$parent;
|
|
368
|
+
|
|
369
|
+
// Already has observer
|
|
370
|
+
if (cmp.parent && cmp.parent.type === 'CallExpression' && cmp.parent.callee.name === 'observer') return;
|
|
371
|
+
var name = (_cmp$id = cmp.id) === null || _cmp$id === void 0 ? void 0 : _cmp$id.name; // If anonymous try to infer name from variable declaration
|
|
372
|
+
|
|
373
|
+
if (!name && ((_cmp$parent = cmp.parent) === null || _cmp$parent === void 0 ? void 0 : _cmp$parent.type) === 'VariableDeclarator') {
|
|
374
|
+
name = cmp.parent.id.name;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
if (cmp.type.startsWith('Class')) {
|
|
378
|
+
// Must extend Component or React.Component
|
|
379
|
+
var superClass = cmp.superClass;
|
|
380
|
+
if (!superClass) return;
|
|
381
|
+
var superClassText = sourceCode.getText(superClass);
|
|
382
|
+
if (superClassText !== 'Component' && superClassText !== 'React.Component') return;
|
|
383
|
+
} else {
|
|
384
|
+
var _name;
|
|
385
|
+
|
|
386
|
+
// Name must start with uppercase letter
|
|
387
|
+
if (!((_name = name) !== null && _name !== void 0 && _name.charAt(0).match(/^[A-Z]$/))) return;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
var fix = function fix(fixer) {
|
|
391
|
+
return [fixer.insertTextBefore(sourceCode.getFirstToken(cmp), (name && cmp.type.endsWith('Declaration') ? "const ".concat(name, " = ") : '') + 'observer('), fixer.insertTextAfter(sourceCode.getLastToken(cmp), ')')];
|
|
392
|
+
};
|
|
393
|
+
|
|
394
|
+
context.report({
|
|
395
|
+
node: cmp,
|
|
396
|
+
messageId: 'missingObserver',
|
|
397
|
+
data: {
|
|
398
|
+
name: name || '<anonymous>'
|
|
399
|
+
},
|
|
400
|
+
fix: fix
|
|
401
|
+
});
|
|
402
|
+
}
|
|
403
|
+
};
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
var missingObserver$1 = {
|
|
407
|
+
meta: {
|
|
408
|
+
type: 'problem',
|
|
409
|
+
fixable: 'code',
|
|
410
|
+
docs: {
|
|
411
|
+
description: 'prevents missing `observer` on react component',
|
|
412
|
+
recommended: true
|
|
413
|
+
},
|
|
414
|
+
messages: {
|
|
415
|
+
missingObserver: "Component `{{ name }}` is missing `observer`."
|
|
416
|
+
}
|
|
417
|
+
},
|
|
418
|
+
create: create$1
|
|
419
|
+
};
|
|
420
|
+
|
|
421
|
+
function create(context) {
|
|
422
|
+
var sourceCode = context.getSourceCode();
|
|
423
|
+
return {
|
|
424
|
+
'CallExpression[callee.name="observer"]': function CallExpressionCalleeNameObserver(observer) {
|
|
425
|
+
var _cmp$id;
|
|
426
|
+
|
|
427
|
+
var cmp = observer.arguments[0];
|
|
428
|
+
if (!cmp) return;
|
|
429
|
+
if (cmp !== null && cmp !== void 0 && (_cmp$id = cmp.id) !== null && _cmp$id !== void 0 && _cmp$id.name) return;
|
|
430
|
+
|
|
431
|
+
var fix = function fix(fixer) {
|
|
432
|
+
var _observer$parent;
|
|
433
|
+
|
|
434
|
+
// Use name from variable for autofix
|
|
435
|
+
var name = ((_observer$parent = observer.parent) === null || _observer$parent === void 0 ? void 0 : _observer$parent.type) === 'VariableDeclarator' ? observer.parent.id.name : undefined;
|
|
436
|
+
if (!name) return;
|
|
437
|
+
|
|
438
|
+
if (cmp.type === 'ArrowFunctionExpression') {
|
|
439
|
+
var arrowToken = sourceCode.getTokenBefore(cmp.body);
|
|
440
|
+
return [fixer.replaceText(arrowToken, ''), fixer.insertTextBefore(cmp, "function ".concat(name))];
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
if (cmp.type === 'FunctionExpression') {
|
|
444
|
+
var functionToken = sourceCode.getFirstToken(cmp);
|
|
445
|
+
return fixer.replaceText(functionToken, "function ".concat(name));
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
if (cmp.type === 'ClassExpression') {
|
|
449
|
+
var classToken = sourceCode.getFirstToken(cmp);
|
|
450
|
+
return fixer.replaceText(classToken, "class ".concat(name));
|
|
451
|
+
}
|
|
452
|
+
};
|
|
453
|
+
|
|
454
|
+
context.report({
|
|
455
|
+
node: cmp,
|
|
456
|
+
messageId: 'observerComponentMustHaveName',
|
|
457
|
+
fix: fix
|
|
458
|
+
});
|
|
459
|
+
}
|
|
460
|
+
};
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
var noAnonymousObserver$1 = {
|
|
464
|
+
meta: {
|
|
465
|
+
type: 'problem',
|
|
466
|
+
fixable: 'code',
|
|
467
|
+
docs: {
|
|
468
|
+
description: 'forbids anonymous functions or classes as `observer` components',
|
|
469
|
+
recommended: true
|
|
470
|
+
},
|
|
471
|
+
messages: {
|
|
472
|
+
observerComponentMustHaveName: "`observer` component must have a name."
|
|
473
|
+
}
|
|
474
|
+
},
|
|
360
475
|
create: create
|
|
361
476
|
};
|
|
362
477
|
|
|
363
478
|
var exhaustiveMakeObservable = exhaustiveMakeObservable$1;
|
|
364
479
|
var unconditionalMakeObservable = unconditionalMakeObservable$1;
|
|
365
480
|
var missingMakeObservable = missingMakeObservable$1;
|
|
481
|
+
var missingObserver = missingObserver$1;
|
|
482
|
+
var noAnonymousObserver = noAnonymousObserver$1;
|
|
366
483
|
var src = {
|
|
367
484
|
configs: {
|
|
368
485
|
recommended: {
|
|
@@ -370,14 +487,18 @@ var src = {
|
|
|
370
487
|
rules: {
|
|
371
488
|
'mobx/exhaustive-make-observable': 'warn',
|
|
372
489
|
'mobx/unconditional-make-observable': 'error',
|
|
373
|
-
'mobx/missing-make-observable': 'error'
|
|
490
|
+
'mobx/missing-make-observable': 'error',
|
|
491
|
+
'mobx/no-missing-observer': 'warn',
|
|
492
|
+
'mobx/no-anonymous-observer': 'warn'
|
|
374
493
|
}
|
|
375
494
|
}
|
|
376
495
|
},
|
|
377
496
|
rules: {
|
|
378
497
|
'exhaustive-make-observable': exhaustiveMakeObservable,
|
|
379
498
|
'unconditional-make-observable': unconditionalMakeObservable,
|
|
380
|
-
'missing-make-observable': missingMakeObservable
|
|
499
|
+
'missing-make-observable': missingMakeObservable,
|
|
500
|
+
'missing-observer': missingObserver,
|
|
501
|
+
'no-anonymous-observer': noAnonymousObserver
|
|
381
502
|
}
|
|
382
503
|
};
|
|
383
504
|
|
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
const exhaustiveMakeObservable = require('./exhaustive-make-observable.js');
|
|
4
4
|
const unconditionalMakeObservable = require('./unconditional-make-observable.js');
|
|
5
5
|
const missingMakeObservable = require('./missing-make-observable.js');
|
|
6
|
+
const missingObserver = require('./missing-observer');
|
|
7
|
+
const noAnonymousObserver = require('./no-anonymous-observer.js');
|
|
6
8
|
|
|
7
9
|
module.exports = {
|
|
8
10
|
configs: {
|
|
@@ -12,6 +14,8 @@ module.exports = {
|
|
|
12
14
|
'mobx/exhaustive-make-observable': 'warn',
|
|
13
15
|
'mobx/unconditional-make-observable': 'error',
|
|
14
16
|
'mobx/missing-make-observable': 'error',
|
|
17
|
+
'mobx/no-missing-observer': 'warn',
|
|
18
|
+
'mobx/no-anonymous-observer': 'warn',
|
|
15
19
|
},
|
|
16
20
|
},
|
|
17
21
|
},
|
|
@@ -19,5 +23,7 @@ module.exports = {
|
|
|
19
23
|
'exhaustive-make-observable': exhaustiveMakeObservable,
|
|
20
24
|
'unconditional-make-observable': unconditionalMakeObservable,
|
|
21
25
|
'missing-make-observable': missingMakeObservable,
|
|
26
|
+
'missing-observer': missingObserver,
|
|
27
|
+
'no-anonymous-observer': noAnonymousObserver,
|
|
22
28
|
}
|
|
23
29
|
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
function create(context) {
|
|
4
|
+
const sourceCode = context.getSourceCode();
|
|
5
|
+
|
|
6
|
+
return {
|
|
7
|
+
'FunctionDeclaration,FunctionExpression,ArrowFunctionExpression,ClassDeclaration,ClassExpression': cmp => {
|
|
8
|
+
// Already has observer
|
|
9
|
+
if (cmp.parent && cmp.parent.type === 'CallExpression' && cmp.parent.callee.name === 'observer') return;
|
|
10
|
+
let name = cmp.id?.name;
|
|
11
|
+
// If anonymous try to infer name from variable declaration
|
|
12
|
+
if (!name && cmp.parent?.type === 'VariableDeclarator') {
|
|
13
|
+
name = cmp.parent.id.name;
|
|
14
|
+
}
|
|
15
|
+
if (cmp.type.startsWith('Class')) {
|
|
16
|
+
// Must extend Component or React.Component
|
|
17
|
+
const { superClass } = cmp;
|
|
18
|
+
if (!superClass) return;
|
|
19
|
+
const superClassText = sourceCode.getText(superClass);
|
|
20
|
+
if (superClassText !== 'Component' && superClassText !== 'React.Component') return;
|
|
21
|
+
} else {
|
|
22
|
+
// Name must start with uppercase letter
|
|
23
|
+
if (!name?.charAt(0).match(/^[A-Z]$/)) return;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const fix = fixer => {
|
|
27
|
+
return [
|
|
28
|
+
fixer.insertTextBefore(
|
|
29
|
+
sourceCode.getFirstToken(cmp),
|
|
30
|
+
(name && cmp.type.endsWith('Declaration') ? `const ${name} = ` : '') + 'observer(',
|
|
31
|
+
),
|
|
32
|
+
fixer.insertTextAfter(
|
|
33
|
+
sourceCode.getLastToken(cmp),
|
|
34
|
+
')',
|
|
35
|
+
),
|
|
36
|
+
]
|
|
37
|
+
}
|
|
38
|
+
context.report({
|
|
39
|
+
node: cmp,
|
|
40
|
+
messageId: 'missingObserver',
|
|
41
|
+
data: {
|
|
42
|
+
name: name || '<anonymous>',
|
|
43
|
+
},
|
|
44
|
+
fix,
|
|
45
|
+
})
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
module.exports = {
|
|
51
|
+
meta: {
|
|
52
|
+
type: 'problem',
|
|
53
|
+
fixable: 'code',
|
|
54
|
+
docs: {
|
|
55
|
+
description: 'prevents missing `observer` on react component',
|
|
56
|
+
recommended: true,
|
|
57
|
+
},
|
|
58
|
+
messages: {
|
|
59
|
+
missingObserver: "Component `{{ name }}` is missing `observer`.",
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
create,
|
|
63
|
+
};
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
function create(context) {
|
|
4
|
+
const sourceCode = context.getSourceCode();
|
|
5
|
+
|
|
6
|
+
return {
|
|
7
|
+
'CallExpression[callee.name="observer"]': observer => {
|
|
8
|
+
const cmp = observer.arguments[0];
|
|
9
|
+
if (!cmp) return;
|
|
10
|
+
if (cmp?.id?.name) return;
|
|
11
|
+
|
|
12
|
+
const fix = fixer => {
|
|
13
|
+
// Use name from variable for autofix
|
|
14
|
+
const name = observer.parent?.type === 'VariableDeclarator'
|
|
15
|
+
? observer.parent.id.name
|
|
16
|
+
: undefined;
|
|
17
|
+
|
|
18
|
+
if (!name) return;
|
|
19
|
+
if (cmp.type === 'ArrowFunctionExpression') {
|
|
20
|
+
const arrowToken = sourceCode.getTokenBefore(cmp.body);
|
|
21
|
+
return [
|
|
22
|
+
fixer.replaceText(arrowToken, ''),
|
|
23
|
+
fixer.insertTextBefore(cmp, `function ${name}`),
|
|
24
|
+
]
|
|
25
|
+
}
|
|
26
|
+
if (cmp.type === 'FunctionExpression') {
|
|
27
|
+
const functionToken = sourceCode.getFirstToken(cmp);
|
|
28
|
+
return fixer.replaceText(functionToken, `function ${name}`);
|
|
29
|
+
}
|
|
30
|
+
if (cmp.type === 'ClassExpression') {
|
|
31
|
+
const classToken = sourceCode.getFirstToken(cmp);
|
|
32
|
+
return fixer.replaceText(classToken, `class ${name}`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
context.report({
|
|
36
|
+
node: cmp,
|
|
37
|
+
messageId: 'observerComponentMustHaveName',
|
|
38
|
+
fix,
|
|
39
|
+
})
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
module.exports = {
|
|
45
|
+
meta: {
|
|
46
|
+
type: 'problem',
|
|
47
|
+
fixable: 'code',
|
|
48
|
+
docs: {
|
|
49
|
+
description: 'forbids anonymous functions or classes as `observer` components',
|
|
50
|
+
recommended: true,
|
|
51
|
+
},
|
|
52
|
+
messages: {
|
|
53
|
+
observerComponentMustHaveName: "`observer` component must have a name.",
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
create,
|
|
57
|
+
};
|