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 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 not recognize anonymous function as component.
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 _slicedToArray(arr, i) {
4
- return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest();
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
- function _toConsumableArray(arr) {
8
- return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableSpread();
8
+ function _arrayWithHoles(r) {
9
+ if (Array.isArray(r)) return r;
9
10
  }
10
-
11
- function _arrayWithoutHoles(arr) {
12
- if (Array.isArray(arr)) return _arrayLikeToArray(arr);
11
+ function _arrayWithoutHoles(r) {
12
+ if (Array.isArray(r)) return _arrayLikeToArray(r);
13
13
  }
14
-
15
- function _arrayWithHoles(arr) {
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
- function _iterableToArray(iter) {
20
- if (typeof Symbol !== "undefined" && iter[Symbol.iterator] != null || iter["@@iterator"] != null) return Array.from(iter);
21
- }
22
-
23
- function _iterableToArrayLimit(arr, i) {
24
- var _i = arr == null ? null : typeof Symbol !== "undefined" && arr[Symbol.iterator] || arr["@@iterator"];
25
-
26
- if (_i == null) return;
27
- var _arr = [];
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 (!_n && _i["return"] != null) _i["return"]();
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
- if (_d) throw _e;
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
- function _unsupportedIterableToArray(o, minLen) {
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
- function _nonIterableRest() {
75
- throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
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
- isMobxDecorator$1 = utils.isMobxDecorator; // TODO support this.foo = 5; in constructor
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
- firstArg = _makeObservable$argum[0],
125
- secondArg = _makeObservable$argum[1];
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 === 'FunctionExpression' || node.type === 'FunctionDeclaration';
114
+ return node.type === "FunctionExpression" || node.type === "FunctionDeclaration";
135
115
  });
136
- if ((closestFunction === null || closestFunction === void 0 ? void 0 : (_closestFunction$pare = closestFunction.parent) === null || _closestFunction$pare === void 0 ? void 0 : _closestFunction$pare.kind) !== 'constructor') return;
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 === 'ObjectExpression') {
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; //if (member.type !== 'MethodDefinition' && member.type !== 'ClassProperty') 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
- context.report({
166
- node: makeObservable,
167
- message: 'When using decorators, second arg must be `null`, `undefined` or not provided.',
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
- context.report({
173
- node: makeObservable,
174
- message: 'Second argument must be object in form of `{ key: annotation }`.',
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, ": true");
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 !== 'ObjectExpression') {
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: 'missingAnnotation',
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: 'suggestion',
218
- fixable: 'code',
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: 'enforce all fields being listen in `makeObservable`',
201
+ description: "enforce all fields being listen in `makeObservable`",
221
202
  recommended: true,
222
203
  suggestion: false
223
204
  },
224
205
  messages: {
225
- 'missingAnnotation': 'Missing annotation for {{ keyList }}. To exclude a field, use `false` as annotation.'
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
- firstArg = _makeObservable$argum[0];
241
-
219
+ firstArg = _makeObservable$argum[0];
242
220
  if (!firstArg) return;
243
- if (firstArg.type !== 'ThisExpression') return; // MethodDefinition[key.name="constructor"][kind="constructor"]
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 ? void 0 : (_closestFunction$pare = closestFunction.parent) === null || _closestFunction$pare === void 0 ? void 0 : _closestFunction$pare.kind) !== 'constructor') return;
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
- isMobxDecorator = utils.isMobxDecorator;
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, _constructor$value$bo2;
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; // ClassDeclaration > ClassBody > []
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
- }); // MethodDefinition > FunctionExpression > BlockStatement > []
301
-
274
+ });
275
+ // MethodDefinition > FunctionExpression > BlockStatement > []
302
276
  var isMakeObservable = function isMakeObservable(node) {
303
- var _node$expression, _node$expression$call, _node$expression2, _node$expression2$arg;
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
- 'FunctionDeclaration,FunctionExpression,ArrowFunctionExpression,ClassDeclaration,ClassExpression': function FunctionDeclarationFunctionExpressionArrowFunctionExpressionClassDeclarationClassExpression(cmp) {
369
- var _cmp$id, _cmp$parent;
370
-
371
- // Already has observer
372
- if (cmp.parent && cmp.parent.type === 'CallExpression' && cmp.parent.callee.name === 'observer') return;
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 (cmp.type.startsWith('Class')) {
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) return;
355
+ if (!superClass) {
356
+ // not a component
357
+ return;
358
+ }
383
359
  var superClassText = sourceCode.getText(superClass);
384
- if (superClassText !== 'Component' && superClassText !== 'React.Component') return;
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]$/))) return;
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(cmp), (name && cmp.type.endsWith('Declaration') ? "const ".concat(name, " = ") : '') + 'observer('), fixer.insertTextAfter(sourceCode.getLastToken(cmp), ')')];
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: 'missingObserver',
377
+ messageId: "missingObserver",
399
378
  data: {
400
- name: name || '<anonymous>'
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: 'problem',
411
- fixable: 'code',
388
+ type: "problem",
389
+ fixable: "code",
412
390
  docs: {
413
- description: 'prevents missing `observer` on react component',
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.9",
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": "^4.29.3",
32
- "@typescript-eslint/parser": "^4.0.0",
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
- "prepublish": "yarn build"
50
+ "prepublishOnly": "yarn build"
51
51
  }
52
52
  }
@@ -1,55 +1,62 @@
1
- 'use strict';
1
+ "use strict"
2
2
 
3
- const { findAncestor, isMobxDecorator } = require('./utils.js');
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
- const sourceCode = context.getSourceCode();
8
+ const sourceCode = context.getSourceCode()
9
+ const autofixAnnotation = context.options[0]?.autofixAnnotation ?? true
9
10
 
10
- function fieldToKey(field) {
11
- // TODO cache on field?
12
- const key = sourceCode.getText(field.key);
13
- return field.computed ? `[${key}]` : key;
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
- return {
17
- 'CallExpression[callee.name="makeObservable"]': makeObservable => {
18
- // Only interested about makeObservable(this, ...) in constructor or makeObservable({}, ...)
19
- // ClassDeclaration
20
- // ClassBody
21
- // MethodDefinition[kind="constructor"]
22
- // FunctionExpression
23
- // BlockStatement
24
- // ExpressionStatement
25
- // CallExpression[callee.name="makeObservable"]
26
- const [firstArg, secondArg] = makeObservable.arguments;
27
- if (!firstArg) return;
28
- let members;
29
- if (firstArg.type === 'ThisExpression') {
30
- const closestFunction = findAncestor(makeObservable, node => node.type === 'FunctionExpression' || node.type === 'FunctionDeclaration')
31
- if (closestFunction?.parent?.kind !== 'constructor') return;
32
- members = closestFunction.parent.parent.parent.body.body;
33
- } else if (firstArg.type === 'ObjectExpression') {
34
- members = firstArg.properties;
35
- } else {
36
- return;
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
- const annotationProps = secondArg?.properties || [];
40
- const nonAnnotatedMembers = [];
41
- let hasAnyDecorator = false;
44
+ const annotationProps = secondArg?.properties || []
45
+ const nonAnnotatedMembers = []
46
+ let hasAnyDecorator = false
42
47
 
43
- members.forEach(member => {
44
- if (member.static) return;
45
- if (member.kind === "constructor") return;
46
- //if (member.type !== 'MethodDefinition' && member.type !== 'ClassProperty') return;
47
- hasAnyDecorator = hasAnyDecorator || member.decorators?.some(isMobxDecorator) || false;
48
- if (!annotationProps.some(prop => fieldToKey(prop) === fieldToKey(member))) { // TODO optimize?
49
- nonAnnotatedMembers.push(member);
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
- if (!hasAnyDecorator && nonAnnotatedMembers.length) {
70
- // Set avoids reporting twice for setter+getter pair or actual duplicates
71
- const keys = [...new Set(nonAnnotatedMembers.map(fieldToKey))];
72
- const keyList = keys.map(key => `\`${key}\``).join(', ');
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
- const fix = fixer => {
75
- const annotationList = keys.map(key => `${key}: true`).join(', ') + ',';
76
- if (!secondArg) {
77
- return fixer.insertTextAfter(firstArg, `, { ${annotationList} }`);
78
- } else if (secondArg.type !== 'ObjectExpression') {
79
- return fixer.replaceText(secondArg, `{ ${annotationList} }`);
80
- } else {
81
- const openingBracket = sourceCode.getFirstToken(secondArg)
82
- return fixer.insertTextAfter(openingBracket, ` ${annotationList} `);
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
- context.report({
87
- node: makeObservable,
88
- messageId: 'missingAnnotation',
89
- data: { keyList },
90
- fix,
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
- meta: {
99
- type: 'suggestion',
100
- fixable: 'code',
101
- docs: {
102
- description: 'enforce all fields being listen in `makeObservable`',
103
- recommended: true,
104
- suggestion: false,
105
- },
106
- messages: {
107
- 'missingAnnotation': 'Missing annotation for {{ keyList }}. To exclude a field, use `false` as annotation.',
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
- create,
111
- };
130
+ create
131
+ }
@@ -1,63 +1,94 @@
1
- 'use strict';
1
+ "use strict"
2
2
 
3
3
  function create(context) {
4
- const sourceCode = context.getSourceCode();
4
+ const sourceCode = context.getSourceCode()
5
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
- }
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
- 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
- };
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
- 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`.",
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
- create,
63
- };
93
+ create
94
+ }