Skip to content

Commit 3cf37dc

Browse files
committed
[Fix] destructuring-assignment: fix false negative when using typeof props.a
Fixes #3828
1 parent 4abcf14 commit 3cf37dc

File tree

2 files changed

+66
-9
lines changed

2 files changed

+66
-9
lines changed

lib/rules/destructuring-assignment.js

+39-6
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,25 @@ module.exports = {
181181
}
182182
}
183183

184+
// valid-jsdoc cannot read function types
185+
// eslint-disable-next-line valid-jsdoc
186+
/**
187+
* Find a parent that satisfy the given predicate
188+
* @param {ASTNode} node
189+
* @param {(node: ASTNode) => boolean} predicate
190+
* @returns {ASTNode | undefined}
191+
*/
192+
function findParent(node, predicate) {
193+
let n = node;
194+
while (n) {
195+
if (predicate(n)) {
196+
return n;
197+
}
198+
n = n.parent;
199+
}
200+
return undefined;
201+
}
202+
184203
return {
185204

186205
FunctionDeclaration: handleStatelessComponent,
@@ -196,12 +215,7 @@ module.exports = {
196215
'FunctionExpression:exit': handleStatelessComponentExit,
197216

198217
MemberExpression(node) {
199-
let scope = getScope(context, node);
200-
let SFCComponent = components.get(scope.block);
201-
while (!SFCComponent && scope.upper && scope.upper !== scope) {
202-
SFCComponent = components.get(scope.upper.block);
203-
scope = scope.upper;
204-
}
218+
const SFCComponent = utils.getParentStatelessComponent(node);
205219
if (SFCComponent) {
206220
handleSFCUsage(node);
207221
}
@@ -212,6 +226,25 @@ module.exports = {
212226
}
213227
},
214228

229+
TSQualifiedName(node) {
230+
if (configuration !== 'always') {
231+
return;
232+
}
233+
// handle `typeof props.a.b`
234+
if (node.left.type === 'Identifier'
235+
&& node.left.name === sfcParams.propsName()
236+
&& findParent(node, (n) => n.type === 'TSTypeQuery')
237+
&& utils.getParentStatelessComponent(node)
238+
) {
239+
report(context, messages.useDestructAssignment, 'useDestructAssignment', {
240+
node,
241+
data: {
242+
type: 'props',
243+
},
244+
});
245+
}
246+
},
247+
215248
VariableDeclarator(node) {
216249
const classComponent = utils.getParentComponent(node);
217250
const SFCComponent = components.get(getScope(context, node).block);

tests/lib/rules/destructuring-assignment.js

+27-3
Original file line numberDiff line numberDiff line change
@@ -882,10 +882,16 @@ ${' '}
882882
};
883883
`,
884884
options: ['always', { destructureInSignature: 'always' }],
885-
features: ['types'],
885+
features: ['types', 'no-babel'],
886886
errors: [
887887
{
888888
messageId: 'useDestructAssignment',
889+
type: 'TSQualifiedName',
890+
data: { type: 'props' },
891+
},
892+
{
893+
messageId: 'useDestructAssignment',
894+
type: 'MemberExpression',
889895
data: { type: 'props' },
890896
},
891897
],
@@ -900,10 +906,28 @@ ${' '}
900906
};
901907
`,
902908
options: ['always', { destructureInSignature: 'always' }],
903-
features: ['types'],
909+
features: ['types', 'no-babel'],
904910
errors: [
905911
{
906-
messageId: 'destructureInSignature',
912+
messageId: 'useDestructAssignment',
913+
type: 'TSQualifiedName',
914+
data: { type: 'props' },
915+
},
916+
],
917+
},
918+
{
919+
code: `
920+
function C(props: Props) {
921+
void props.a
922+
typeof props.b
923+
return <div />
924+
}
925+
`,
926+
options: ['always'],
927+
features: ['types', 'no-babel'],
928+
errors: [
929+
{
930+
messageId: 'useDestructAssignment',
907931
data: { type: 'props' },
908932
},
909933
],

0 commit comments

Comments
 (0)