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+
newESLintTester({
7976+
parser: require.resolve('@babel/eslint-parser'),
7977+
parserOptions,
7978+
}).run('react-hooks',ReactHooksESLintRule,tests);
7979+
79757980
newESLintTester({
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
letmaybeID=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
constisDeclaredInComponent=!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+
functionmarkNode(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
constobject=analyzePropertyChain(node.object,optionalChains);
16101632
constproperty=analyzePropertyChain(node.property,null);
16111633
constresult=`${object}.${property}`;
1612-
if(optionalChains){
1613-
// Mark as required.
1614-
optionalChains.set(result,false);
1615-
}
1634+
markNode(node,optionalChains,result);
16161635
returnresult;
16171636
}elseif(node.type==='OptionalMemberExpression'&&!node.computed){
16181637
constobject=analyzePropertyChain(node.object,optionalChains);
16191638
constproperty=analyzePropertyChain(node.property,null);
16201639
constresult=`${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+
returnresult;
1642+
}elseif(node.type==='ChainExpression'&&!node.computed){
1643+
constexpression=node.expression;
1644+
constobject=analyzePropertyChain(expression.object,optionalChains);
1645+
constproperty=analyzePropertyChain(expression.property,null);
1646+
constresult=`${object}.${property}`;
1647+
markNode(expression,optionalChains,result);
16361648
returnresult;
16371649
}else{
16381650
thrownewError(`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-
functionisInDEVBlock(node){
14-
letdone=false;
15-
while(!done){
16-
letparent=node.parent;
17-
if(!parent){
18-
returnfalse;
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-
returntrue;
12+
module.exports={
13+
meta: {
14+
fixable: 'code',
15+
},
16+
create: function(context){
17+
functionisInDEVBlock(node){
18+
letdone=false;
19+
while(!done){
20+
letparent=node.parent;
21+
if(!parent){
22+
returnfalse;
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+
returntrue;
33+
}
34+
node=parent;
2935
}
30-
node=parent;
3136
}
32-
}
3337

34-
functionreportWrapInDEV(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+
functionreportWrapInDEV(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-
functionreportUnexpectedConsole(node){
51-
context.report({
52-
node: node,
53-
message: `Unexpected use of console`,
54-
});
55-
}
54+
functionreportUnexpectedConsole(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)
close