Skip to content

Commit 1396e4a

Browse files
author
Pascal Fong Kye
authored
Fixes eslint warning when node type is ChainExpression (#19680)
* Add babel parser which supports ChainExpression * Add and fix tests for new babel eslint parser * extract function to mark node * refactor for compatibility with eslint v7.7.0+ * Update eslint to v7.7.0 Update hook test since eslint now supports nullish coalescing
1 parent a8500be commit 1396e4a

File tree

6 files changed

+249
-275
lines changed

6 files changed

+249
-275
lines changed

package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
"@babel/cli": "^7.10.5",
88
"@babel/code-frame": "^7.10.4",
99
"@babel/core": "^7.11.1",
10+
"@babel/eslint-parser": "^7.11.4",
1011
"@babel/helper-module-imports": "^7.10.4",
1112
"@babel/parser": "^7.11.3",
1213
"@babel/plugin-external-helpers": "^7.10.4",
@@ -46,7 +47,7 @@
4647
"create-react-class": "^15.6.3",
4748
"danger": "^9.2.10",
4849
"error-stack-parser": "^2.0.6",
49-
"eslint": "^7.0.0",
50+
"eslint": "^7.7.0",
5051
"eslint-config-fbjs": "^1.1.1",
5152
"eslint-config-prettier": "^6.9.0",
5253
"eslint-plugin-babel": "^5.3.0",

packages/eslint-plugin-react-hooks/__tests__/ESLintRuleExhaustiveDeps-test.js

+5
Original file line numberDiff line numberDiff line change
@@ -7972,6 +7972,11 @@ new ESLintTester({
79727972
parserOptions,
79737973
}).run('react-hooks', ReactHooksESLintRule, tests);
79747974

7975+
new ESLintTester({
7976+
parser: require.resolve('@babel/eslint-parser'),
7977+
parserOptions,
7978+
}).run('react-hooks', ReactHooksESLintRule, tests);
7979+
79757980
new ESLintTester({
79767981
parser: require.resolve('@typescript-eslint/parser'),
79777982
parserOptions,

packages/eslint-plugin-react-hooks/__tests__/ESLintRulesOfHooks-test.js

+1-3
Original file line numberDiff line numberDiff line change
@@ -752,9 +752,7 @@ const tests = {
752752
errors: [
753753
conditionalError('useState'),
754754
conditionalError('useState'),
755-
// TODO: ideally this *should* warn, but ESLint
756-
// doesn't plan full support for ?? until it advances.
757-
// conditionalError('useState'),
755+
conditionalError('useState'),
758756
],
759757
},
760758
{

packages/eslint-plugin-react-hooks/src/ExhaustiveDeps.js

+33-21
Original file line numberDiff line numberDiff line change
@@ -814,9 +814,10 @@ export default {
814814
let maybeID = declaredDependencyNode;
815815
while (
816816
maybeID.type === 'MemberExpression' ||
817-
maybeID.type === 'OptionalMemberExpression'
817+
maybeID.type === 'OptionalMemberExpression' ||
818+
maybeID.type === 'ChainExpression'
818819
) {
819-
maybeID = maybeID.object;
820+
maybeID = maybeID.object || maybeID.expression.object;
820821
}
821822
const isDeclaredInComponent = !componentScope.through.some(
822823
ref => ref.identifier === maybeID,
@@ -1590,6 +1591,27 @@ function getDependency(node) {
15901591
}
15911592
}
15921593

1594+
/**
1595+
* Mark a node as either optional or required.
1596+
* Note: If the node argument is an OptionalMemberExpression, it doesn't necessarily mean it is optional.
1597+
* It just means there is an optional member somewhere inside.
1598+
* This particular node might still represent a required member, so check .optional field.
1599+
*/
1600+
function markNode(node, optionalChains, result) {
1601+
if (optionalChains) {
1602+
if (node.optional) {
1603+
// We only want to consider it optional if *all* usages were optional.
1604+
if (!optionalChains.has(result)) {
1605+
// Mark as (maybe) optional. If there's a required usage, this will be overridden.
1606+
optionalChains.set(result, true);
1607+
}
1608+
} else {
1609+
// Mark as required.
1610+
optionalChains.set(result, false);
1611+
}
1612+
}
1613+
}
1614+
15931615
/**
15941616
* Assuming () means the passed node.
15951617
* (foo) -> 'foo'
@@ -1609,30 +1631,20 @@ function analyzePropertyChain(node, optionalChains) {
16091631
const object = analyzePropertyChain(node.object, optionalChains);
16101632
const property = analyzePropertyChain(node.property, null);
16111633
const result = `${object}.${property}`;
1612-
if (optionalChains) {
1613-
// Mark as required.
1614-
optionalChains.set(result, false);
1615-
}
1634+
markNode(node, optionalChains, result);
16161635
return result;
16171636
} else if (node.type === 'OptionalMemberExpression' && !node.computed) {
16181637
const object = analyzePropertyChain(node.object, optionalChains);
16191638
const property = analyzePropertyChain(node.property, null);
16201639
const result = `${object}.${property}`;
1621-
if (optionalChains) {
1622-
// Note: OptionalMemberExpression doesn't necessarily mean this node is optional.
1623-
// It just means there is an optional member somewhere inside.
1624-
// This particular node might still represent a required member, so check .optional field.
1625-
if (node.optional) {
1626-
// We only want to consider it optional if *all* usages were optional.
1627-
if (!optionalChains.has(result)) {
1628-
// Mark as (maybe) optional. If there's a required usage, this will be overridden.
1629-
optionalChains.set(result, true);
1630-
}
1631-
} else {
1632-
// Mark as required.
1633-
optionalChains.set(result, false);
1634-
}
1635-
}
1640+
markNode(node, optionalChains, result);
1641+
return result;
1642+
} else if (node.type === 'ChainExpression' && !node.computed) {
1643+
const expression = node.expression;
1644+
const object = analyzePropertyChain(expression.object, optionalChains);
1645+
const property = analyzePropertyChain(expression.property, null);
1646+
const result = `${object}.${property}`;
1647+
markNode(expression, optionalChains, result);
16361648
return result;
16371649
} else {
16381650
throw new Error(`Unsupported node type: ${node.type}`);

scripts/eslint-rules/no-production-logging.js

+65-63
Original file line numberDiff line numberDiff line change
@@ -9,75 +9,77 @@
99

1010
'use strict';
1111

12-
module.exports = function(context) {
13-
function isInDEVBlock(node) {
14-
let done = false;
15-
while (!done) {
16-
let parent = node.parent;
17-
if (!parent) {
18-
return false;
19-
}
20-
if (
21-
parent.type === 'IfStatement' &&
22-
node === parent.consequent &&
23-
parent.test.type === 'Identifier' &&
24-
// This is intentionally strict so we can
25-
// see blocks of DEV-only code at once.
26-
parent.test.name === '__DEV__'
27-
) {
28-
return true;
12+
module.exports = {
13+
meta: {
14+
fixable: 'code',
15+
},
16+
create: function(context) {
17+
function isInDEVBlock(node) {
18+
let done = false;
19+
while (!done) {
20+
let parent = node.parent;
21+
if (!parent) {
22+
return false;
23+
}
24+
if (
25+
parent.type === 'IfStatement' &&
26+
node === parent.consequent &&
27+
parent.test.type === 'Identifier' &&
28+
// This is intentionally strict so we can
29+
// see blocks of DEV-only code at once.
30+
parent.test.name === '__DEV__'
31+
) {
32+
return true;
33+
}
34+
node = parent;
2935
}
30-
node = parent;
3136
}
32-
}
3337

34-
function reportWrapInDEV(node) {
35-
context.report({
36-
node: node,
37-
message: `Wrap console.{{identifier}}() in an "if (__DEV__) {}" check`,
38-
data: {
39-
identifier: node.property.name,
40-
},
41-
fix: function(fixer) {
42-
return [
43-
fixer.insertTextBefore(node.parent, `if (__DEV__) {`),
44-
fixer.insertTextAfter(node.parent, '}'),
45-
];
46-
},
47-
});
48-
}
38+
function reportWrapInDEV(node) {
39+
context.report({
40+
node: node,
41+
message: `Wrap console.{{identifier}}() in an "if (__DEV__) {}" check`,
42+
data: {
43+
identifier: node.property.name,
44+
},
45+
fix: function(fixer) {
46+
return [
47+
fixer.insertTextBefore(node.parent, `if (__DEV__) {`),
48+
fixer.insertTextAfter(node.parent, '}'),
49+
];
50+
},
51+
});
52+
}
4953

50-
function reportUnexpectedConsole(node) {
51-
context.report({
52-
node: node,
53-
message: `Unexpected use of console`,
54-
});
55-
}
54+
function reportUnexpectedConsole(node) {
55+
context.report({
56+
node: node,
57+
message: `Unexpected use of console`,
58+
});
59+
}
5660

57-
return {
58-
meta: {
59-
fixable: 'code',
60-
},
61-
MemberExpression: function(node) {
62-
if (
63-
node.object.type === 'Identifier' &&
64-
node.object.name === 'console' &&
65-
node.property.type === 'Identifier'
66-
) {
67-
switch (node.property.name) {
68-
case 'error':
69-
case 'warn': {
70-
if (!isInDEVBlock(node)) {
71-
reportWrapInDEV(node);
61+
return {
62+
MemberExpression: function(node) {
63+
if (
64+
node.object.type === 'Identifier' &&
65+
node.object.name === 'console' &&
66+
node.property.type === 'Identifier'
67+
) {
68+
switch (node.property.name) {
69+
case 'error':
70+
case 'warn': {
71+
if (!isInDEVBlock(node)) {
72+
reportWrapInDEV(node);
73+
}
74+
break;
75+
}
76+
default: {
77+
reportUnexpectedConsole(node);
78+
break;
7279
}
73-
break;
74-
}
75-
default: {
76-
reportUnexpectedConsole(node);
77-
break;
7880
}
7981
}
80-
}
81-
},
82-
};
82+
},
83+
};
84+
},
8385
};

0 commit comments

Comments
 (0)