eslint-plugin-mobx 0.0.9 → 0.0.11
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 +14 -0
- package/README.md +17 -3
- package/dist/index.js +135 -167
- package/package.json +4 -4
- package/src/exhaustive-make-observable.js +100 -80
- package/src/missing-observer.js +86 -55
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# eslint-plugin-mobx
|
|
2
2
|
|
|
3
|
+
## 0.0.11
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [`638533e592f4fda7663fa351447380f9d0917d14`](https://github.com/mobxjs/mobx/commit/638533e592f4fda7663fa351447380f9d0917d14) [#3909](https://github.com/mobxjs/mobx/pull/3909) Thanks [@urugator](https://github.com/urugator)! - mobx/missing-observer rule false positive with forwardRef #3908
|
|
8
|
+
|
|
9
|
+
## 0.0.10
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- [`44a5fe07`](https://github.com/mobxjs/mobx/commit/44a5fe07fb95c2ba24d8df19f18b57ee92abb1a9) [#3881](https://github.com/mobxjs/mobx/pull/3881) Thanks [@kade-robertson](https://github.com/kade-robertson)! - Adds an option for the `mobx/exhaustive-make-observable` eslint rule to configure whether fields are annotated with `true` or `false` with the autofixer.
|
|
14
|
+
|
|
15
|
+
This option defaults to `true` if not present or an invalid value is received to maintain existing behavior.
|
|
16
|
+
|
|
3
17
|
## 0.0.9
|
|
4
18
|
|
|
5
19
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -35,10 +35,22 @@ module.exports = {
|
|
|
35
35
|
### mobx/exhaustive-make-observable
|
|
36
36
|
|
|
37
37
|
Makes sure that `makeObservable` annotates all fields defined on class or object literal.<br>
|
|
38
|
-
**Autofix** adds `field: true` for each missing field.<br>
|
|
39
38
|
To exclude a field, annotate it using `field: false`.<br>
|
|
40
39
|
Does not support fields introduced by constructor (`this.foo = 5`).<br>
|
|
41
|
-
Does not warn about annotated non-existing fields (there is a runtime check, but the autofix removing the field could be handy...)
|
|
40
|
+
Does not warn about annotated non-existing fields (there is a runtime check, but the autofix removing the field could be handy...).<br>
|
|
41
|
+
**Autofix** adds `field: true` for each missing field by default. You can change this behaviour by specifying options in your eslint config:
|
|
42
|
+
|
|
43
|
+
```json
|
|
44
|
+
{
|
|
45
|
+
"rules": {
|
|
46
|
+
"mobx/exhaustive-make-observable": ["error", { "autofixAnnotation": false }]
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
This is a boolean value that controls if the field is annotated with `true` or `false`.
|
|
52
|
+
If you are migrating an existing project using `makeObservable` and do not want this rule to override
|
|
53
|
+
your current usage (even if it may be wrong), you should run the autofix with the annotation set to `false` to maintain existing behaviour: `eslint --no-eslintrc --fix --rule='mobx/exhaustive-make-observable: [2, { "autofixAnnotation": false }]' .`
|
|
42
54
|
|
|
43
55
|
### mobx/missing-make-observable
|
|
44
56
|
|
|
@@ -85,6 +97,7 @@ It's a bit opinionated and can lead to a lot of false positives depending on you
|
|
|
85
97
|
### mobx/no-anonymous-observer (deprecated)
|
|
86
98
|
|
|
87
99
|
_Deprecated in favor of [react/display-name](https://github.com/jsx-eslint/eslint-plugin-react/blob/master/docs/rules/display-name.md) + [componentWrapperFunctions](https://github.com/jsx-eslint/eslint-plugin-react). Example of **.eslintrc**:_
|
|
100
|
+
|
|
88
101
|
```
|
|
89
102
|
{
|
|
90
103
|
"rules": {
|
|
@@ -97,9 +110,10 @@ _Deprecated in favor of [react/display-name](https://github.com/jsx-eslint/eslin
|
|
|
97
110
|
}
|
|
98
111
|
}
|
|
99
112
|
```
|
|
113
|
+
|
|
100
114
|
---
|
|
101
115
|
|
|
102
116
|
Forbids anonymous functions or classes as `observer` components.
|
|
103
117
|
Improves debugging experience and [avoids problem with inability to customize `displayName`](https://github.com/mobxjs/mobx/issues/2721).
|
|
104
|
-
Plays nice with `eslint-plugin-react-hooks` and `mobx/missing-observer` as both of these don't
|
|
118
|
+
Plays nice with `eslint-plugin-react-hooks` and `mobx/missing-observer` as both of these don't recognize anonymous function as component.
|
|
105
119
|
**Autofix** infers the name from variable if possible.
|
package/dist/index.js
CHANGED
|
@@ -1,157 +1,134 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
function
|
|
4
|
-
|
|
3
|
+
function _arrayLikeToArray(r, a) {
|
|
4
|
+
(null == a || a > r.length) && (a = r.length);
|
|
5
|
+
for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e];
|
|
6
|
+
return n;
|
|
5
7
|
}
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableSpread();
|
|
8
|
+
function _arrayWithHoles(r) {
|
|
9
|
+
if (Array.isArray(r)) return r;
|
|
9
10
|
}
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
if (Array.isArray(arr)) return _arrayLikeToArray(arr);
|
|
11
|
+
function _arrayWithoutHoles(r) {
|
|
12
|
+
if (Array.isArray(r)) return _arrayLikeToArray(r);
|
|
13
13
|
}
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
if (Array.isArray(arr)) return arr;
|
|
14
|
+
function _iterableToArray(r) {
|
|
15
|
+
if ("undefined" != typeof Symbol && null != r[Symbol.iterator] || null != r["@@iterator"]) return Array.from(r);
|
|
17
16
|
}
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
if (
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
var _n = true;
|
|
29
|
-
var _d = false;
|
|
30
|
-
|
|
31
|
-
var _s, _e;
|
|
32
|
-
|
|
33
|
-
try {
|
|
34
|
-
for (_i = _i.call(arr); !(_n = (_s = _i.next()).done); _n = true) {
|
|
35
|
-
_arr.push(_s.value);
|
|
36
|
-
|
|
37
|
-
if (i && _arr.length === i) break;
|
|
38
|
-
}
|
|
39
|
-
} catch (err) {
|
|
40
|
-
_d = true;
|
|
41
|
-
_e = err;
|
|
42
|
-
} finally {
|
|
17
|
+
function _iterableToArrayLimit(r, l) {
|
|
18
|
+
var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"];
|
|
19
|
+
if (null != t) {
|
|
20
|
+
var e,
|
|
21
|
+
n,
|
|
22
|
+
i,
|
|
23
|
+
u,
|
|
24
|
+
a = [],
|
|
25
|
+
f = !0,
|
|
26
|
+
o = !1;
|
|
43
27
|
try {
|
|
44
|
-
if (
|
|
28
|
+
if (i = (t = t.call(r)).next, 0 === l) {
|
|
29
|
+
if (Object(t) !== t) return;
|
|
30
|
+
f = !1;
|
|
31
|
+
} else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0);
|
|
32
|
+
} catch (r) {
|
|
33
|
+
o = !0, n = r;
|
|
45
34
|
} finally {
|
|
46
|
-
|
|
35
|
+
try {
|
|
36
|
+
if (!f && null != t.return && (u = t.return(), Object(u) !== u)) return;
|
|
37
|
+
} finally {
|
|
38
|
+
if (o) throw n;
|
|
39
|
+
}
|
|
47
40
|
}
|
|
41
|
+
return a;
|
|
48
42
|
}
|
|
49
|
-
|
|
50
|
-
return _arr;
|
|
51
43
|
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
if (!o) return;
|
|
55
|
-
if (typeof o === "string") return _arrayLikeToArray(o, minLen);
|
|
56
|
-
var n = Object.prototype.toString.call(o).slice(8, -1);
|
|
57
|
-
if (n === "Object" && o.constructor) n = o.constructor.name;
|
|
58
|
-
if (n === "Map" || n === "Set") return Array.from(o);
|
|
59
|
-
if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
function _arrayLikeToArray(arr, len) {
|
|
63
|
-
if (len == null || len > arr.length) len = arr.length;
|
|
64
|
-
|
|
65
|
-
for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i];
|
|
66
|
-
|
|
67
|
-
return arr2;
|
|
44
|
+
function _nonIterableRest() {
|
|
45
|
+
throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
|
|
68
46
|
}
|
|
69
|
-
|
|
70
47
|
function _nonIterableSpread() {
|
|
71
48
|
throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
|
|
72
49
|
}
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
50
|
+
function _slicedToArray(r, e) {
|
|
51
|
+
return _arrayWithHoles(r) || _iterableToArrayLimit(r, e) || _unsupportedIterableToArray(r, e) || _nonIterableRest();
|
|
52
|
+
}
|
|
53
|
+
function _toConsumableArray(r) {
|
|
54
|
+
return _arrayWithoutHoles(r) || _iterableToArray(r) || _unsupportedIterableToArray(r) || _nonIterableSpread();
|
|
55
|
+
}
|
|
56
|
+
function _unsupportedIterableToArray(r, a) {
|
|
57
|
+
if (r) {
|
|
58
|
+
if ("string" == typeof r) return _arrayLikeToArray(r, a);
|
|
59
|
+
var t = {}.toString.call(r).slice(8, -1);
|
|
60
|
+
return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0;
|
|
61
|
+
}
|
|
76
62
|
}
|
|
77
63
|
|
|
78
64
|
var mobxDecorators = new Set(['observable', 'computed', 'action', 'flow', 'override']);
|
|
79
|
-
|
|
80
65
|
function isMobxDecorator$2(decorator) {
|
|
81
66
|
var _decorator$expression, _decorator$expression2;
|
|
82
|
-
|
|
83
67
|
return mobxDecorators.has(decorator.expression.name) // @foo
|
|
84
68
|
|| mobxDecorators.has((_decorator$expression = decorator.expression.callee) === null || _decorator$expression === void 0 ? void 0 : _decorator$expression.name) // @foo()
|
|
85
69
|
|| mobxDecorators.has((_decorator$expression2 = decorator.expression.object) === null || _decorator$expression2 === void 0 ? void 0 : _decorator$expression2.name); // @foo.bar
|
|
86
70
|
}
|
|
87
|
-
|
|
88
71
|
function findAncestor$3(node, match) {
|
|
89
72
|
var parent = node.parent;
|
|
90
73
|
if (!parent) return;
|
|
91
74
|
if (match(parent)) return parent;
|
|
92
75
|
return findAncestor$3(parent, match);
|
|
93
76
|
}
|
|
94
|
-
|
|
95
77
|
var utils = {
|
|
96
78
|
findAncestor: findAncestor$3,
|
|
97
79
|
isMobxDecorator: isMobxDecorator$2
|
|
98
80
|
};
|
|
99
81
|
|
|
100
82
|
var findAncestor$2 = utils.findAncestor,
|
|
101
|
-
|
|
102
|
-
// TODO? report on field as well
|
|
83
|
+
isMobxDecorator$1 = utils.isMobxDecorator;
|
|
103
84
|
|
|
85
|
+
// TODO support this.foo = 5; in constructor
|
|
86
|
+
// TODO? report on field as well
|
|
104
87
|
function create$4(context) {
|
|
88
|
+
var _context$options$0$au, _context$options$;
|
|
105
89
|
var sourceCode = context.getSourceCode();
|
|
106
|
-
|
|
90
|
+
var autofixAnnotation = (_context$options$0$au = (_context$options$ = context.options[0]) === null || _context$options$ === void 0 ? void 0 : _context$options$.autofixAnnotation) !== null && _context$options$0$au !== void 0 ? _context$options$0$au : true;
|
|
107
91
|
function fieldToKey(field) {
|
|
108
92
|
// TODO cache on field?
|
|
109
93
|
var key = sourceCode.getText(field.key);
|
|
110
94
|
return field.computed ? "[".concat(key, "]") : key;
|
|
111
95
|
}
|
|
112
|
-
|
|
113
96
|
return {
|
|
114
97
|
'CallExpression[callee.name="makeObservable"]': function CallExpressionCalleeNameMakeObservable(makeObservable) {
|
|
115
98
|
// Only interested about makeObservable(this, ...) in constructor or makeObservable({}, ...)
|
|
116
|
-
// ClassDeclaration
|
|
99
|
+
// ClassDeclaration
|
|
117
100
|
// ClassBody
|
|
118
101
|
// MethodDefinition[kind="constructor"]
|
|
119
102
|
// FunctionExpression
|
|
120
103
|
// BlockStatement
|
|
121
104
|
// ExpressionStatement
|
|
122
|
-
// CallExpression[callee.name="makeObservable"]
|
|
105
|
+
// CallExpression[callee.name="makeObservable"]
|
|
123
106
|
var _makeObservable$argum = _slicedToArray(makeObservable.arguments, 2),
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
107
|
+
firstArg = _makeObservable$argum[0],
|
|
108
|
+
secondArg = _makeObservable$argum[1];
|
|
127
109
|
if (!firstArg) return;
|
|
128
110
|
var members;
|
|
129
|
-
|
|
130
|
-
if (firstArg.type === 'ThisExpression') {
|
|
111
|
+
if (firstArg.type === "ThisExpression") {
|
|
131
112
|
var _closestFunction$pare;
|
|
132
|
-
|
|
133
113
|
var closestFunction = findAncestor$2(makeObservable, function (node) {
|
|
134
|
-
return node.type ===
|
|
114
|
+
return node.type === "FunctionExpression" || node.type === "FunctionDeclaration";
|
|
135
115
|
});
|
|
136
|
-
if ((closestFunction === null || closestFunction === void 0
|
|
116
|
+
if ((closestFunction === null || closestFunction === void 0 || (_closestFunction$pare = closestFunction.parent) === null || _closestFunction$pare === void 0 ? void 0 : _closestFunction$pare.kind) !== "constructor") return;
|
|
137
117
|
members = closestFunction.parent.parent.parent.body.body;
|
|
138
|
-
} else if (firstArg.type ===
|
|
118
|
+
} else if (firstArg.type === "ObjectExpression") {
|
|
139
119
|
members = firstArg.properties;
|
|
140
120
|
} else {
|
|
141
121
|
return;
|
|
142
122
|
}
|
|
143
|
-
|
|
144
123
|
var annotationProps = (secondArg === null || secondArg === void 0 ? void 0 : secondArg.properties) || [];
|
|
145
124
|
var nonAnnotatedMembers = [];
|
|
146
125
|
var hasAnyDecorator = false;
|
|
147
126
|
members.forEach(function (member) {
|
|
148
127
|
var _member$decorators;
|
|
149
|
-
|
|
150
128
|
if (member["static"]) return;
|
|
151
|
-
if (member.kind === "constructor") return;
|
|
152
|
-
|
|
129
|
+
if (member.kind === "constructor") return;
|
|
130
|
+
//if (member.type !== 'MethodDefinition' && member.type !== 'ClassProperty') return;
|
|
153
131
|
hasAnyDecorator = hasAnyDecorator || ((_member$decorators = member.decorators) === null || _member$decorators === void 0 ? void 0 : _member$decorators.some(isMobxDecorator$1)) || false;
|
|
154
|
-
|
|
155
132
|
if (!annotationProps.some(function (prop) {
|
|
156
133
|
return fieldToKey(prop) === fieldToKey(member);
|
|
157
134
|
})) {
|
|
@@ -162,46 +139,42 @@ function create$4(context) {
|
|
|
162
139
|
/*
|
|
163
140
|
// With decorators, second arg must be null/undefined or not provided
|
|
164
141
|
if (hasAnyDecorator && secondArg && secondArg.name !== "undefined" && secondArg.value !== null) {
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
142
|
+
context.report({
|
|
143
|
+
node: makeObservable,
|
|
144
|
+
message: 'When using decorators, second arg must be `null`, `undefined` or not provided.',
|
|
145
|
+
})
|
|
169
146
|
}
|
|
170
147
|
// Without decorators, in constructor, second arg must be object literal
|
|
171
148
|
if (!hasAnyDecorator && firstArg.type === 'ThisExpression' && (!secondArg || secondArg.type !== 'ObjectExpression')) {
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
149
|
+
context.report({
|
|
150
|
+
node: makeObservable,
|
|
151
|
+
message: 'Second argument must be object in form of `{ key: annotation }`.',
|
|
152
|
+
})
|
|
176
153
|
}
|
|
177
154
|
*/
|
|
178
155
|
|
|
179
156
|
if (!hasAnyDecorator && nonAnnotatedMembers.length) {
|
|
180
157
|
// Set avoids reporting twice for setter+getter pair or actual duplicates
|
|
181
158
|
var keys = _toConsumableArray(new Set(nonAnnotatedMembers.map(fieldToKey)));
|
|
182
|
-
|
|
183
159
|
var keyList = keys.map(function (key) {
|
|
184
160
|
return "`".concat(key, "`");
|
|
185
|
-
}).join(
|
|
186
|
-
|
|
161
|
+
}).join(", ");
|
|
187
162
|
var fix = function fix(fixer) {
|
|
188
163
|
var annotationList = keys.map(function (key) {
|
|
189
|
-
return "".concat(key, ":
|
|
190
|
-
}).join(
|
|
191
|
-
|
|
164
|
+
return "".concat(key, ": ").concat(autofixAnnotation);
|
|
165
|
+
}).join(", ") + ",";
|
|
192
166
|
if (!secondArg) {
|
|
193
167
|
return fixer.insertTextAfter(firstArg, ", { ".concat(annotationList, " }"));
|
|
194
|
-
} else if (secondArg.type !==
|
|
168
|
+
} else if (secondArg.type !== "ObjectExpression") {
|
|
195
169
|
return fixer.replaceText(secondArg, "{ ".concat(annotationList, " }"));
|
|
196
170
|
} else {
|
|
197
171
|
var openingBracket = sourceCode.getFirstToken(secondArg);
|
|
198
172
|
return fixer.insertTextAfter(openingBracket, " ".concat(annotationList, " "));
|
|
199
173
|
}
|
|
200
174
|
};
|
|
201
|
-
|
|
202
175
|
context.report({
|
|
203
176
|
node: makeObservable,
|
|
204
|
-
messageId:
|
|
177
|
+
messageId: "missingAnnotation",
|
|
205
178
|
data: {
|
|
206
179
|
keyList: keyList
|
|
207
180
|
},
|
|
@@ -211,46 +184,50 @@ function create$4(context) {
|
|
|
211
184
|
}
|
|
212
185
|
};
|
|
213
186
|
}
|
|
214
|
-
|
|
215
187
|
var exhaustiveMakeObservable$1 = {
|
|
216
188
|
meta: {
|
|
217
|
-
type:
|
|
218
|
-
fixable:
|
|
189
|
+
type: "suggestion",
|
|
190
|
+
fixable: "code",
|
|
191
|
+
schema: [{
|
|
192
|
+
type: "object",
|
|
193
|
+
properties: {
|
|
194
|
+
autofixAnnotation: {
|
|
195
|
+
type: "boolean"
|
|
196
|
+
}
|
|
197
|
+
},
|
|
198
|
+
additionalProperties: false
|
|
199
|
+
}],
|
|
219
200
|
docs: {
|
|
220
|
-
description:
|
|
201
|
+
description: "enforce all fields being listen in `makeObservable`",
|
|
221
202
|
recommended: true,
|
|
222
203
|
suggestion: false
|
|
223
204
|
},
|
|
224
205
|
messages: {
|
|
225
|
-
|
|
206
|
+
missingAnnotation: "Missing annotation for {{ keyList }}. To exclude a field, use `false` as annotation."
|
|
226
207
|
}
|
|
227
208
|
},
|
|
228
209
|
create: create$4
|
|
229
210
|
};
|
|
230
211
|
|
|
231
212
|
var findAncestor$1 = utils.findAncestor;
|
|
232
|
-
|
|
233
213
|
function create$3(context) {
|
|
234
214
|
return {
|
|
235
215
|
'CallExpression[callee.name=/(makeObservable|makeAutoObservable)/]': function CallExpressionCalleeNameMakeObservableMakeAutoObservable(makeObservable) {
|
|
236
216
|
var _closestFunction$pare;
|
|
237
|
-
|
|
238
217
|
// Only iterested about makeObservable(this, ...) inside constructor and not inside nested bindable function
|
|
239
218
|
var _makeObservable$argum = _slicedToArray(makeObservable.arguments, 1),
|
|
240
|
-
|
|
241
|
-
|
|
219
|
+
firstArg = _makeObservable$argum[0];
|
|
242
220
|
if (!firstArg) return;
|
|
243
|
-
if (firstArg.type !== 'ThisExpression') return;
|
|
221
|
+
if (firstArg.type !== 'ThisExpression') return;
|
|
222
|
+
// MethodDefinition[key.name="constructor"][kind="constructor"]
|
|
244
223
|
// FunctionExpression
|
|
245
224
|
// BlockStatement
|
|
246
225
|
// ExpressionStatement
|
|
247
226
|
// CallExpression[callee.name="makeObservable"]
|
|
248
|
-
|
|
249
227
|
var closestFunction = findAncestor$1(makeObservable, function (node) {
|
|
250
228
|
return node.type === 'FunctionExpression' || node.type === 'FunctionDeclaration';
|
|
251
229
|
});
|
|
252
|
-
if ((closestFunction === null || closestFunction === void 0
|
|
253
|
-
|
|
230
|
+
if ((closestFunction === null || closestFunction === void 0 || (_closestFunction$pare = closestFunction.parent) === null || _closestFunction$pare === void 0 ? void 0 : _closestFunction$pare.kind) !== 'constructor') return;
|
|
254
231
|
if (makeObservable.parent.parent.parent !== closestFunction) {
|
|
255
232
|
context.report({
|
|
256
233
|
node: makeObservable,
|
|
@@ -263,7 +240,6 @@ function create$3(context) {
|
|
|
263
240
|
}
|
|
264
241
|
};
|
|
265
242
|
}
|
|
266
|
-
|
|
267
243
|
var unconditionalMakeObservable$1 = {
|
|
268
244
|
meta: {
|
|
269
245
|
type: 'problem',
|
|
@@ -279,38 +255,32 @@ var unconditionalMakeObservable$1 = {
|
|
|
279
255
|
};
|
|
280
256
|
|
|
281
257
|
var findAncestor = utils.findAncestor,
|
|
282
|
-
|
|
283
|
-
|
|
258
|
+
isMobxDecorator = utils.isMobxDecorator;
|
|
284
259
|
function create$2(context) {
|
|
285
260
|
var sourceCode = context.getSourceCode();
|
|
286
261
|
return {
|
|
287
262
|
'Decorator': function Decorator(decorator) {
|
|
288
|
-
var _clazz$body$body$find, _constructor$value$bo
|
|
289
|
-
|
|
263
|
+
var _clazz$body$body$find, _constructor$value$bo;
|
|
290
264
|
if (!isMobxDecorator(decorator)) return;
|
|
291
265
|
var clazz = findAncestor(decorator, function (node) {
|
|
292
266
|
return node.type === 'ClassDeclaration' || node.type === 'ClassExpression';
|
|
293
267
|
});
|
|
294
|
-
if (!clazz) return;
|
|
295
|
-
|
|
268
|
+
if (!clazz) return;
|
|
269
|
+
// ClassDeclaration > ClassBody > []
|
|
296
270
|
var constructor = (_clazz$body$body$find = clazz.body.body.find(function (node) {
|
|
297
271
|
return node.kind === 'constructor' && node.value.type === 'FunctionExpression';
|
|
298
272
|
})) !== null && _clazz$body$body$find !== void 0 ? _clazz$body$body$find : clazz.body.body.find(function (node) {
|
|
299
273
|
return node.kind === 'constructor';
|
|
300
|
-
});
|
|
301
|
-
|
|
274
|
+
});
|
|
275
|
+
// MethodDefinition > FunctionExpression > BlockStatement > []
|
|
302
276
|
var isMakeObservable = function isMakeObservable(node) {
|
|
303
|
-
var _node$expression, _node$
|
|
304
|
-
|
|
305
|
-
return ((_node$expression = node.expression) === null || _node$expression === void 0 ? void 0 : (_node$expression$call = _node$expression.callee) === null || _node$expression$call === void 0 ? void 0 : _node$expression$call.name) === 'makeObservable' && ((_node$expression2 = node.expression) === null || _node$expression2 === void 0 ? void 0 : (_node$expression2$arg = _node$expression2.arguments[0]) === null || _node$expression2$arg === void 0 ? void 0 : _node$expression2$arg.type) === 'ThisExpression';
|
|
277
|
+
var _node$expression, _node$expression2;
|
|
278
|
+
return ((_node$expression = node.expression) === null || _node$expression === void 0 || (_node$expression = _node$expression.callee) === null || _node$expression === void 0 ? void 0 : _node$expression.name) === 'makeObservable' && ((_node$expression2 = node.expression) === null || _node$expression2 === void 0 || (_node$expression2 = _node$expression2.arguments[0]) === null || _node$expression2 === void 0 ? void 0 : _node$expression2.type) === 'ThisExpression';
|
|
306
279
|
};
|
|
307
|
-
|
|
308
|
-
var makeObservable = constructor === null || constructor === void 0 ? void 0 : (_constructor$value$bo = constructor.value.body) === null || _constructor$value$bo === void 0 ? void 0 : (_constructor$value$bo2 = _constructor$value$bo.body.find(isMakeObservable)) === null || _constructor$value$bo2 === void 0 ? void 0 : _constructor$value$bo2.expression;
|
|
309
|
-
|
|
280
|
+
var makeObservable = constructor === null || constructor === void 0 || (_constructor$value$bo = constructor.value.body) === null || _constructor$value$bo === void 0 || (_constructor$value$bo = _constructor$value$bo.body.find(isMakeObservable)) === null || _constructor$value$bo === void 0 ? void 0 : _constructor$value$bo.expression;
|
|
310
281
|
if (makeObservable) {
|
|
311
282
|
// make sure second arg is nullish
|
|
312
283
|
var secondArg = makeObservable.arguments[1];
|
|
313
|
-
|
|
314
284
|
if (secondArg && secondArg.value !== null && secondArg.name !== 'undefined') {
|
|
315
285
|
context.report({
|
|
316
286
|
node: makeObservable,
|
|
@@ -326,7 +296,6 @@ function create$2(context) {
|
|
|
326
296
|
} else if (constructor) {
|
|
327
297
|
// constructor() {}
|
|
328
298
|
var _closingBracket = sourceCode.getLastToken(constructor.value.body);
|
|
329
|
-
|
|
330
299
|
return fixer.insertTextBefore(_closingBracket, ';makeObservable(this);');
|
|
331
300
|
} else {
|
|
332
301
|
// class C {}
|
|
@@ -334,7 +303,6 @@ function create$2(context) {
|
|
|
334
303
|
return fixer.insertTextAfter(openingBracket, '\nconstructor() { makeObservable(this); }');
|
|
335
304
|
}
|
|
336
305
|
};
|
|
337
|
-
|
|
338
306
|
context.report({
|
|
339
307
|
node: clazz,
|
|
340
308
|
messageId: 'missingMakeObservable',
|
|
@@ -344,7 +312,6 @@ function create$2(context) {
|
|
|
344
312
|
}
|
|
345
313
|
};
|
|
346
314
|
}
|
|
347
|
-
|
|
348
315
|
var missingMakeObservable$1 = {
|
|
349
316
|
meta: {
|
|
350
317
|
type: 'problem',
|
|
@@ -365,52 +332,63 @@ var missingMakeObservable$1 = {
|
|
|
365
332
|
function create$1(context) {
|
|
366
333
|
var sourceCode = context.getSourceCode();
|
|
367
334
|
return {
|
|
368
|
-
|
|
369
|
-
var _cmp$id,
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
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
|
|
374
|
-
|
|
375
|
-
if (!name && ((_cmp$parent = cmp.parent) === null || _cmp$parent === void 0 ? void 0 : _cmp$parent.type) === 'VariableDeclarator') {
|
|
376
|
-
name = cmp.parent.id.name;
|
|
335
|
+
"FunctionDeclaration,FunctionExpression,ArrowFunctionExpression,ClassDeclaration,ClassExpression": function FunctionDeclarationFunctionExpressionArrowFunctionExpressionClassDeclarationClassExpression(cmp) {
|
|
336
|
+
var _cmp$id, _cmpOrForwardRef$pare;
|
|
337
|
+
if (cmp.parent && cmp.parent.type === "CallExpression" && cmp.parent.callee.name === "observer") {
|
|
338
|
+
// observer(...)
|
|
339
|
+
return;
|
|
377
340
|
}
|
|
378
|
-
|
|
379
|
-
if (
|
|
341
|
+
var forwardRef = cmp.parent && cmp.parent.type === "CallExpression" && cmp.parent.callee.name === "forwardRef" ? cmp.parent : undefined;
|
|
342
|
+
if (forwardRef && forwardRef.parent && forwardRef.parent.type === "CallExpression" && forwardRef.parent.callee.name === "observer") {
|
|
343
|
+
// forwardRef(observer(...))
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
var cmpOrForwardRef = forwardRef || cmp;
|
|
347
|
+
var name = (_cmp$id = cmp.id) === null || _cmp$id === void 0 ? void 0 : _cmp$id.name;
|
|
348
|
+
// If anonymous try to infer name from variable declaration
|
|
349
|
+
if (!name && ((_cmpOrForwardRef$pare = cmpOrForwardRef.parent) === null || _cmpOrForwardRef$pare === void 0 ? void 0 : _cmpOrForwardRef$pare.type) === "VariableDeclarator") {
|
|
350
|
+
name = cmpOrForwardRef.parent.id.name;
|
|
351
|
+
}
|
|
352
|
+
if (cmp.type.startsWith("Class")) {
|
|
380
353
|
// Must extend Component or React.Component
|
|
381
354
|
var superClass = cmp.superClass;
|
|
382
|
-
if (!superClass)
|
|
355
|
+
if (!superClass) {
|
|
356
|
+
// not a component
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
383
359
|
var superClassText = sourceCode.getText(superClass);
|
|
384
|
-
if (superClassText !==
|
|
360
|
+
if (superClassText !== "Component" && superClassText !== "React.Component") {
|
|
361
|
+
// not a component
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
385
364
|
} else {
|
|
386
365
|
var _name;
|
|
387
|
-
|
|
388
366
|
// Name must start with uppercase letter
|
|
389
|
-
if (!((_name = name) !== null && _name !== void 0 && _name.charAt(0).match(/^[A-Z]$/)))
|
|
367
|
+
if (!((_name = name) !== null && _name !== void 0 && _name.charAt(0).match(/^[A-Z]$/))) {
|
|
368
|
+
// not a component
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
390
371
|
}
|
|
391
|
-
|
|
392
372
|
var fix = function fix(fixer) {
|
|
393
|
-
return [fixer.insertTextBefore(sourceCode.getFirstToken(
|
|
373
|
+
return [fixer.insertTextBefore(sourceCode.getFirstToken(cmpOrForwardRef), (name && cmp.type.endsWith("Declaration") ? "const ".concat(name, " = ") : "") + "observer("), fixer.insertTextAfter(sourceCode.getLastToken(cmpOrForwardRef), ")")];
|
|
394
374
|
};
|
|
395
|
-
|
|
396
375
|
context.report({
|
|
397
376
|
node: cmp,
|
|
398
|
-
messageId:
|
|
377
|
+
messageId: "missingObserver",
|
|
399
378
|
data: {
|
|
400
|
-
name: name ||
|
|
379
|
+
name: name || "<anonymous>"
|
|
401
380
|
},
|
|
402
381
|
fix: fix
|
|
403
382
|
});
|
|
404
383
|
}
|
|
405
384
|
};
|
|
406
385
|
}
|
|
407
|
-
|
|
408
386
|
var missingObserver$1 = {
|
|
409
387
|
meta: {
|
|
410
|
-
type:
|
|
411
|
-
fixable:
|
|
388
|
+
type: "problem",
|
|
389
|
+
fixable: "code",
|
|
412
390
|
docs: {
|
|
413
|
-
description:
|
|
391
|
+
description: "prevents missing `observer` on react component",
|
|
414
392
|
recommended: true
|
|
415
393
|
},
|
|
416
394
|
messages: {
|
|
@@ -425,40 +403,31 @@ function create(context) {
|
|
|
425
403
|
return {
|
|
426
404
|
'CallExpression[callee.name="observer"]': function CallExpressionCalleeNameObserver(observer) {
|
|
427
405
|
var _cmp$id;
|
|
428
|
-
|
|
429
406
|
var cmp = observer.arguments[0];
|
|
430
407
|
if (!cmp) return;
|
|
431
408
|
if (cmp !== null && cmp !== void 0 && (_cmp$id = cmp.id) !== null && _cmp$id !== void 0 && _cmp$id.name) return;
|
|
432
|
-
|
|
433
409
|
var fix = function fix(fixer) {
|
|
434
410
|
var _observer$parent;
|
|
435
|
-
|
|
436
411
|
// Use name from variable for autofix
|
|
437
412
|
var name = ((_observer$parent = observer.parent) === null || _observer$parent === void 0 ? void 0 : _observer$parent.type) === "VariableDeclarator" ? observer.parent.id.name : undefined;
|
|
438
413
|
if (!name) return;
|
|
439
|
-
|
|
440
414
|
if (cmp.type === "ArrowFunctionExpression") {
|
|
441
415
|
var arrowToken = sourceCode.getTokenBefore(cmp.body);
|
|
442
416
|
var fixes = [fixer.replaceText(arrowToken, ""), fixer.insertTextBefore(cmp, "function ".concat(name))];
|
|
443
|
-
|
|
444
417
|
if (cmp.body.type !== "BlockStatement") {
|
|
445
418
|
fixes.push(fixer.insertTextBefore(cmp.body, "{ return "), fixer.insertTextAfter(cmp.body, " }"));
|
|
446
419
|
}
|
|
447
|
-
|
|
448
420
|
return fixes;
|
|
449
421
|
}
|
|
450
|
-
|
|
451
422
|
if (cmp.type === "FunctionExpression") {
|
|
452
423
|
var functionToken = sourceCode.getFirstToken(cmp);
|
|
453
424
|
return fixer.replaceText(functionToken, "function ".concat(name));
|
|
454
425
|
}
|
|
455
|
-
|
|
456
426
|
if (cmp.type === "ClassExpression") {
|
|
457
427
|
var classToken = sourceCode.getFirstToken(cmp);
|
|
458
428
|
return fixer.replaceText(classToken, "class ".concat(name));
|
|
459
429
|
}
|
|
460
430
|
};
|
|
461
|
-
|
|
462
431
|
context.report({
|
|
463
432
|
node: cmp,
|
|
464
433
|
messageId: "observerComponentMustHaveName",
|
|
@@ -467,7 +436,6 @@ function create(context) {
|
|
|
467
436
|
}
|
|
468
437
|
};
|
|
469
438
|
}
|
|
470
|
-
|
|
471
439
|
var noAnonymousObserver$1 = {
|
|
472
440
|
meta: {
|
|
473
441
|
type: "problem",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eslint-plugin-mobx",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.11",
|
|
4
4
|
"description": "ESLint rules for MobX",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"repository": {
|
|
@@ -28,8 +28,8 @@
|
|
|
28
28
|
"eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0"
|
|
29
29
|
},
|
|
30
30
|
"devDependencies": {
|
|
31
|
-
"@typescript-eslint/eslint-plugin": "^
|
|
32
|
-
"@typescript-eslint/parser": "^
|
|
31
|
+
"@typescript-eslint/eslint-plugin": "^5.0.0",
|
|
32
|
+
"@typescript-eslint/parser": "^5.0.0",
|
|
33
33
|
"eslint": "^7.0.0",
|
|
34
34
|
"@babel/core": "^7.16.0",
|
|
35
35
|
"@babel/preset-env": "^7.16.4",
|
|
@@ -47,6 +47,6 @@
|
|
|
47
47
|
"scripts": {
|
|
48
48
|
"test": "jest",
|
|
49
49
|
"build": "yarn rollup --config",
|
|
50
|
-
"
|
|
50
|
+
"prepublishOnly": "yarn build"
|
|
51
51
|
}
|
|
52
52
|
}
|
|
@@ -1,55 +1,62 @@
|
|
|
1
|
-
|
|
1
|
+
"use strict"
|
|
2
2
|
|
|
3
|
-
const { findAncestor, isMobxDecorator } = require(
|
|
3
|
+
const { findAncestor, isMobxDecorator } = require("./utils.js")
|
|
4
4
|
|
|
5
5
|
// TODO support this.foo = 5; in constructor
|
|
6
6
|
// TODO? report on field as well
|
|
7
7
|
function create(context) {
|
|
8
|
-
|
|
8
|
+
const sourceCode = context.getSourceCode()
|
|
9
|
+
const autofixAnnotation = context.options[0]?.autofixAnnotation ?? true
|
|
9
10
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
11
|
+
function fieldToKey(field) {
|
|
12
|
+
// TODO cache on field?
|
|
13
|
+
const key = sourceCode.getText(field.key)
|
|
14
|
+
return field.computed ? `[${key}]` : key
|
|
15
|
+
}
|
|
15
16
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
17
|
+
return {
|
|
18
|
+
'CallExpression[callee.name="makeObservable"]': makeObservable => {
|
|
19
|
+
// Only interested about makeObservable(this, ...) in constructor or makeObservable({}, ...)
|
|
20
|
+
// ClassDeclaration
|
|
21
|
+
// ClassBody
|
|
22
|
+
// MethodDefinition[kind="constructor"]
|
|
23
|
+
// FunctionExpression
|
|
24
|
+
// BlockStatement
|
|
25
|
+
// ExpressionStatement
|
|
26
|
+
// CallExpression[callee.name="makeObservable"]
|
|
27
|
+
const [firstArg, secondArg] = makeObservable.arguments
|
|
28
|
+
if (!firstArg) return
|
|
29
|
+
let members
|
|
30
|
+
if (firstArg.type === "ThisExpression") {
|
|
31
|
+
const closestFunction = findAncestor(
|
|
32
|
+
makeObservable,
|
|
33
|
+
node =>
|
|
34
|
+
node.type === "FunctionExpression" || node.type === "FunctionDeclaration"
|
|
35
|
+
)
|
|
36
|
+
if (closestFunction?.parent?.kind !== "constructor") return
|
|
37
|
+
members = closestFunction.parent.parent.parent.body.body
|
|
38
|
+
} else if (firstArg.type === "ObjectExpression") {
|
|
39
|
+
members = firstArg.properties
|
|
40
|
+
} else {
|
|
41
|
+
return
|
|
42
|
+
}
|
|
38
43
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
44
|
+
const annotationProps = secondArg?.properties || []
|
|
45
|
+
const nonAnnotatedMembers = []
|
|
46
|
+
let hasAnyDecorator = false
|
|
42
47
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
48
|
+
members.forEach(member => {
|
|
49
|
+
if (member.static) return
|
|
50
|
+
if (member.kind === "constructor") return
|
|
51
|
+
//if (member.type !== 'MethodDefinition' && member.type !== 'ClassProperty') return;
|
|
52
|
+
hasAnyDecorator =
|
|
53
|
+
hasAnyDecorator || member.decorators?.some(isMobxDecorator) || false
|
|
54
|
+
if (!annotationProps.some(prop => fieldToKey(prop) === fieldToKey(member))) {
|
|
55
|
+
// TODO optimize?
|
|
56
|
+
nonAnnotatedMembers.push(member)
|
|
57
|
+
}
|
|
58
|
+
})
|
|
59
|
+
/*
|
|
53
60
|
// With decorators, second arg must be null/undefined or not provided
|
|
54
61
|
if (hasAnyDecorator && secondArg && secondArg.name !== "undefined" && secondArg.value !== null) {
|
|
55
62
|
context.report({
|
|
@@ -66,46 +73,59 @@ function create(context) {
|
|
|
66
73
|
}
|
|
67
74
|
*/
|
|
68
75
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
76
|
+
if (!hasAnyDecorator && nonAnnotatedMembers.length) {
|
|
77
|
+
// Set avoids reporting twice for setter+getter pair or actual duplicates
|
|
78
|
+
const keys = [...new Set(nonAnnotatedMembers.map(fieldToKey))]
|
|
79
|
+
const keyList = keys.map(key => `\`${key}\``).join(", ")
|
|
73
80
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
81
|
+
const fix = fixer => {
|
|
82
|
+
const annotationList =
|
|
83
|
+
keys.map(key => `${key}: ${autofixAnnotation}`).join(", ") + ","
|
|
84
|
+
if (!secondArg) {
|
|
85
|
+
return fixer.insertTextAfter(firstArg, `, { ${annotationList} }`)
|
|
86
|
+
} else if (secondArg.type !== "ObjectExpression") {
|
|
87
|
+
return fixer.replaceText(secondArg, `{ ${annotationList} }`)
|
|
88
|
+
} else {
|
|
89
|
+
const openingBracket = sourceCode.getFirstToken(secondArg)
|
|
90
|
+
return fixer.insertTextAfter(openingBracket, ` ${annotationList} `)
|
|
91
|
+
}
|
|
92
|
+
}
|
|
85
93
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
94
|
+
context.report({
|
|
95
|
+
node: makeObservable,
|
|
96
|
+
messageId: "missingAnnotation",
|
|
97
|
+
data: { keyList },
|
|
98
|
+
fix
|
|
99
|
+
})
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
95
103
|
}
|
|
96
104
|
|
|
97
105
|
module.exports = {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
106
|
+
meta: {
|
|
107
|
+
type: "suggestion",
|
|
108
|
+
fixable: "code",
|
|
109
|
+
schema: [
|
|
110
|
+
{
|
|
111
|
+
type: "object",
|
|
112
|
+
properties: {
|
|
113
|
+
autofixAnnotation: {
|
|
114
|
+
type: "boolean"
|
|
115
|
+
}
|
|
116
|
+
},
|
|
117
|
+
additionalProperties: false
|
|
118
|
+
}
|
|
119
|
+
],
|
|
120
|
+
docs: {
|
|
121
|
+
description: "enforce all fields being listen in `makeObservable`",
|
|
122
|
+
recommended: true,
|
|
123
|
+
suggestion: false
|
|
124
|
+
},
|
|
125
|
+
messages: {
|
|
126
|
+
missingAnnotation:
|
|
127
|
+
"Missing annotation for {{ keyList }}. To exclude a field, use `false` as annotation."
|
|
128
|
+
}
|
|
108
129
|
},
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
};
|
|
130
|
+
create
|
|
131
|
+
}
|
package/src/missing-observer.js
CHANGED
|
@@ -1,63 +1,94 @@
|
|
|
1
|
-
|
|
1
|
+
"use strict"
|
|
2
2
|
|
|
3
3
|
function create(context) {
|
|
4
|
-
|
|
4
|
+
const sourceCode = context.getSourceCode()
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
6
|
+
return {
|
|
7
|
+
"FunctionDeclaration,FunctionExpression,ArrowFunctionExpression,ClassDeclaration,ClassExpression":
|
|
8
|
+
cmp => {
|
|
9
|
+
if (
|
|
10
|
+
cmp.parent &&
|
|
11
|
+
cmp.parent.type === "CallExpression" &&
|
|
12
|
+
cmp.parent.callee.name === "observer"
|
|
13
|
+
) {
|
|
14
|
+
// observer(...)
|
|
15
|
+
return
|
|
16
|
+
}
|
|
17
|
+
let forwardRef =
|
|
18
|
+
cmp.parent &&
|
|
19
|
+
cmp.parent.type === "CallExpression" &&
|
|
20
|
+
cmp.parent.callee.name === "forwardRef"
|
|
21
|
+
? cmp.parent
|
|
22
|
+
: undefined
|
|
23
|
+
if (
|
|
24
|
+
forwardRef &&
|
|
25
|
+
forwardRef.parent &&
|
|
26
|
+
forwardRef.parent.type === "CallExpression" &&
|
|
27
|
+
forwardRef.parent.callee.name === "observer"
|
|
28
|
+
) {
|
|
29
|
+
// forwardRef(observer(...))
|
|
30
|
+
return
|
|
31
|
+
}
|
|
25
32
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
33
|
+
const cmpOrForwardRef = forwardRef || cmp
|
|
34
|
+
let name = cmp.id?.name
|
|
35
|
+
// If anonymous try to infer name from variable declaration
|
|
36
|
+
if (!name && cmpOrForwardRef.parent?.type === "VariableDeclarator") {
|
|
37
|
+
name = cmpOrForwardRef.parent.id.name
|
|
38
|
+
}
|
|
39
|
+
if (cmp.type.startsWith("Class")) {
|
|
40
|
+
// Must extend Component or React.Component
|
|
41
|
+
const { superClass } = cmp
|
|
42
|
+
if (!superClass) {
|
|
43
|
+
// not a component
|
|
44
|
+
return
|
|
45
|
+
}
|
|
46
|
+
const superClassText = sourceCode.getText(superClass)
|
|
47
|
+
if (superClassText !== "Component" && superClassText !== "React.Component") {
|
|
48
|
+
// not a component
|
|
49
|
+
return
|
|
50
|
+
}
|
|
51
|
+
} else {
|
|
52
|
+
// Name must start with uppercase letter
|
|
53
|
+
if (!name?.charAt(0).match(/^[A-Z]$/)) {
|
|
54
|
+
// not a component
|
|
55
|
+
return
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const fix = fixer => {
|
|
60
|
+
return [
|
|
61
|
+
fixer.insertTextBefore(
|
|
62
|
+
sourceCode.getFirstToken(cmpOrForwardRef),
|
|
63
|
+
(name && cmp.type.endsWith("Declaration") ? `const ${name} = ` : "") +
|
|
64
|
+
"observer("
|
|
65
|
+
),
|
|
66
|
+
fixer.insertTextAfter(sourceCode.getLastToken(cmpOrForwardRef), ")")
|
|
67
|
+
]
|
|
68
|
+
}
|
|
69
|
+
context.report({
|
|
70
|
+
node: cmp,
|
|
71
|
+
messageId: "missingObserver",
|
|
72
|
+
data: {
|
|
73
|
+
name: name || "<anonymous>"
|
|
74
|
+
},
|
|
75
|
+
fix
|
|
76
|
+
})
|
|
77
|
+
}
|
|
78
|
+
}
|
|
48
79
|
}
|
|
49
80
|
|
|
50
81
|
module.exports = {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
82
|
+
meta: {
|
|
83
|
+
type: "problem",
|
|
84
|
+
fixable: "code",
|
|
85
|
+
docs: {
|
|
86
|
+
description: "prevents missing `observer` on react component",
|
|
87
|
+
recommended: true
|
|
88
|
+
},
|
|
89
|
+
messages: {
|
|
90
|
+
missingObserver: "Component `{{ name }}` is missing `observer`."
|
|
91
|
+
}
|
|
60
92
|
},
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
};
|
|
93
|
+
create
|
|
94
|
+
}
|