[ESLint] Extend isHook to recognize those under PascalCase's namespace (#18722)
* Extend Namespace to PascalCase * Add valid case for jest.useFakeTimer * format * format :( * fix nits
Chang Yan committed
Apr 23, 2020 at 18:50 UTC
4b02b6611141255a8e8b6d8a997e87c865f06626
2 files changed
+58
-47
packages/eslint-plugin-react-hooks/__tests__/ESLintRulesOfHooks-test.js
+56
-44
@@ -106,6 +106,7 @@ const tests = {
106
({useHook() { useState(); }});
107
const {useHook3 = () => { useState(); }} = {};
108
({useHook = () => { useState(); }} = {});
109
+ Namespace.useHook = () => { useState(); };
110
`,
111
`
112
// Valid because hooks can call hooks.
@@ -191,29 +192,6 @@ const tests = {
192
}
193
}
194
`,
194
- `
195
- // Currently valid.
196
- // We *could* make this invalid if we want, but it creates false positives
197
- // (see the FooStore case).
198
- class C {
199
- m() {
200
- This.useHook();
201
- Super.useHook();
202
- }
203
- }
204
- `,
205
- `
206
- // Valid although we *could* consider these invalid.
207
- // But it doesn't bring much benefit since it's an immediate runtime error anyway.
208
- // So might as well allow it.
209
- Hook.use();
210
- Hook._use();
211
- Hook.useState();
212
- Hook._useState();
213
- Hook.use42();
214
- Hook.useHook();
215
- Hook.use_hook();
216
- `,
195
`
196
// Valid -- this is a regression test.
197
jest.useFakeTimers();
@@ -221,17 +199,6 @@ const tests = {
199
jest.useRealTimers();
200
})
201
`,
224
- `
225
- // Valid because that's a false positive we've seen quite a bit.
226
- // This is a regression test.
227
- class Foo extends Component {
228
- render() {
229
- if (cond) {
230
- FooStore.useFeatureFlag();
231
- }
232
- }
233
- }
234
- `,
202
`
203
// Valid because they're not matching use[A-Z].
204
fooState();
@@ -239,16 +206,8 @@ const tests = {
206
_use();
207
_useState();
208
use_hook();
242
- `,
243
- `
244
- // This is grey area.
245
- // Currently it's valid (although React.useCallback would fail here).
246
- // We could also get stricter and disallow it, just like we did
247
- // with non-namespace use*() top-level calls.
248
- const History = require('history-2.1.2');
249
- const browserHistory = History.useBasename(History.createHistory)({
250
- basename: '/',
251
- });
209
+ // also valid because it's not matching the PascalCase namespace
210
+ jest.useFakeTimer()
211
`,
212
`
213
// Regression test for some internal code.
@@ -392,6 +351,59 @@ const tests = {
351
`,
352
errors: [conditionalError('useConditionalHook')],
353
},
354
+ {
355
+ code: `
356
+ Hook.use();
357
+ Hook._use();
358
+ Hook.useState();
359
+ Hook._useState();
360
+ Hook.use42();
361
+ Hook.useHook();
362
+ Hook.use_hook();
363
+ `,
364
+ errors: [
365
+ topLevelError('Hook.useState'),
366
+ topLevelError('Hook.use42'),
367
+ topLevelError('Hook.useHook'),
368
+ ],
369
+ },
370
+ {
371
+ code: `
372
+ class C {
373
+ m() {
374
+ This.useHook();
375
+ Super.useHook();
376
+ }
377
+ }
378
+ `,
379
+ errors: [classError('This.useHook'), classError('Super.useHook')],
380
+ },
381
+ {
382
+ code: `
383
+ // This is a false positive (it's valid) that unfortunately
384
+ // we cannot avoid. Prefer to rename it to not start with "use"
385
+ class Foo extends Component {
386
+ render() {
387
+ if (cond) {
388
+ FooStore.useFeatureFlag();
389
+ }
390
+ }
391
+ }
392
+ `,
393
+ errors: [classError('FooStore.useFeatureFlag')],
394
+ },
395
+ {
396
+ code: `
397
+ // Invalid because it's dangerous and might not warn otherwise.
398
+ // This *must* be invalid.
399
+ function ComponentWithConditionalHook() {
400
+ if (cond) {
401
+ Namespace.useConditionalHook();
402
+ }
403
+ }
404
+ `,
405
+ errors: [conditionalError('Namespace.useConditionalHook')],
406
+ },
407
{
408
code: `
409
// Invalid because it's dangerous and might not warn otherwise.
packages/eslint-plugin-react-hooks/src/RulesOfHooks.js
+2
-3
@@ -31,10 +31,9 @@ function isHook(node) {
31
!node.computed &&
32
isHook(node.property)
33
) {
34
- // Only consider React.useFoo() to be namespace hooks for now to avoid false positives.
35
- // We can expand this check later.
34
const obj = node.object;
37
- return obj.type === 'Identifier' && obj.name === 'React';
35
+ const isPascalCaseNameSpace = /^[A-Z].*/;
36
+ return obj.type === 'Identifier' && isPascalCaseNameSpace.test(obj.name);
37
} else {
38
return false;
39
}