Skip to content

Commit 63aceff

Browse files
golopotljharb
andcommitted
[Fix] destructuring-assignment: fix false negative when using typeof props.a
Fixes #3828 Co-authored-by: Chiawen Chen <golopot@gmail.com> Co-authored-by: Jordan Harband <ljharb@gmail.com>
1 parent 96d46d5 commit 63aceff

File tree

3 files changed

+106
-1
lines changed

3 files changed

+106
-1
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange
66

77
## Unreleased
88

9+
### Fixed
10+
* [`destructuring-assignment`]: fix false negative when using `typeof props.a` ([#3835][] @golopot)
11+
912
### Changed
1013
* [Refactor] [`destructuring-assignment`]: use `getParentStatelessComponent` ([#3835][] @golopot)
1114

lib/rules/destructuring-assignment.js

Lines changed: 39 additions & 0 deletions
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,
@@ -207,6 +226,25 @@ module.exports = {
207226
}
208227
},
209228

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+
210248
VariableDeclarator(node) {
211249
const classComponent = utils.getParentComponent(node);
212250
const SFCComponent = components.get(getScope(context, node).block);
@@ -252,6 +290,7 @@ module.exports = {
252290
if (!propsRefs) {
253291
return;
254292
}
293+
255294
// Skip if props is used elsewhere
256295
if (propsRefs.length > 1) {
257296
return;

tests/lib/rules/destructuring-assignment.js

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -872,6 +872,69 @@ ${' '}
872872
`,
873873
features: ['ts', 'no-babel'],
874874
},
875-
] : []
875+
] : [],
876+
{
877+
code: `
878+
type Props = { text: string };
879+
export const MyComponent: React.FC<Props> = (props) => {
880+
type MyType = typeof props.text;
881+
return <div>{props.text as MyType}</div>;
882+
};
883+
`,
884+
options: ['always', { destructureInSignature: 'always' }],
885+
features: ['types', 'no-babel'],
886+
errors: [
887+
{
888+
messageId: 'useDestructAssignment',
889+
type: 'TSQualifiedName',
890+
data: { type: 'props' },
891+
},
892+
{
893+
messageId: 'useDestructAssignment',
894+
type: 'MemberExpression',
895+
data: { type: 'props' },
896+
},
897+
],
898+
},
899+
{
900+
code: `
901+
type Props = { text: string };
902+
export const MyOtherComponent: React.FC<Props> = (props) => {
903+
const { text } = props;
904+
type MyType = typeof props.text;
905+
return <div>{text as MyType}</div>;
906+
};
907+
`,
908+
options: ['always', { destructureInSignature: 'always' }],
909+
features: ['types', 'no-babel'],
910+
errors: [
911+
{
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'],
928+
errors: [
929+
{
930+
messageId: 'useDestructAssignment',
931+
data: { type: 'props' },
932+
},
933+
{
934+
messageId: 'useDestructAssignment',
935+
data: { type: 'props' },
936+
},
937+
],
938+
}
876939
)),
877940
});

0 commit comments

Comments
 (0)