Skip to content

Commit

Permalink
[Fix] destructuring-assignment: fix false negative when using `type…
Browse files Browse the repository at this point in the history
…of props.a`

Fixes #3828

Co-authored-by: Chiawen Chen <golopot@gmail.com>
Co-authored-by: Jordan Harband <ljharb@gmail.com>
  • Loading branch information
golopot and ljharb committed Sep 29, 2024
1 parent 96d46d5 commit 63aceff
Show file tree
Hide file tree
Showing 3 changed files with 106 additions and 1 deletion.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange

## Unreleased

### Fixed
* [`destructuring-assignment`]: fix false negative when using `typeof props.a` ([#3835][] @golopot)

### Changed
* [Refactor] [`destructuring-assignment`]: use `getParentStatelessComponent` ([#3835][] @golopot)

Expand Down
39 changes: 39 additions & 0 deletions lib/rules/destructuring-assignment.js
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,25 @@ module.exports = {
}
}

// valid-jsdoc cannot read function types
// eslint-disable-next-line valid-jsdoc
/**
* Find a parent that satisfy the given predicate
* @param {ASTNode} node
* @param {(node: ASTNode) => boolean} predicate
* @returns {ASTNode | undefined}
*/
function findParent(node, predicate) {
let n = node;
while (n) {
if (predicate(n)) {
return n;
}
n = n.parent;
}
return undefined;
}

return {

FunctionDeclaration: handleStatelessComponent,
Expand All @@ -207,6 +226,25 @@ module.exports = {
}
},

TSQualifiedName(node) {
if (configuration !== 'always') {
return;
}
// handle `typeof props.a.b`
if (node.left.type === 'Identifier'
&& node.left.name === sfcParams.propsName()
&& findParent(node, (n) => n.type === 'TSTypeQuery')
&& utils.getParentStatelessComponent(node)
) {
report(context, messages.useDestructAssignment, 'useDestructAssignment', {
node,
data: {
type: 'props',
},
});
}
},

VariableDeclarator(node) {
const classComponent = utils.getParentComponent(node);
const SFCComponent = components.get(getScope(context, node).block);
Expand Down Expand Up @@ -252,6 +290,7 @@ module.exports = {
if (!propsRefs) {
return;
}

// Skip if props is used elsewhere
if (propsRefs.length > 1) {
return;
Expand Down
65 changes: 64 additions & 1 deletion tests/lib/rules/destructuring-assignment.js
Original file line number Diff line number Diff line change
Expand Up @@ -872,6 +872,69 @@ ${' '}
`,
features: ['ts', 'no-babel'],
},
] : []
] : [],
{
code: `
type Props = { text: string };
export const MyComponent: React.FC<Props> = (props) => {
type MyType = typeof props.text;
return <div>{props.text as MyType}</div>;
};
`,
options: ['always', { destructureInSignature: 'always' }],
features: ['types', 'no-babel'],
errors: [
{
messageId: 'useDestructAssignment',
type: 'TSQualifiedName',
data: { type: 'props' },
},
{
messageId: 'useDestructAssignment',
type: 'MemberExpression',
data: { type: 'props' },
},
],
},
{
code: `
type Props = { text: string };
export const MyOtherComponent: React.FC<Props> = (props) => {
const { text } = props;
type MyType = typeof props.text;
return <div>{text as MyType}</div>;
};
`,
options: ['always', { destructureInSignature: 'always' }],
features: ['types', 'no-babel'],
errors: [
{
messageId: 'useDestructAssignment',
type: 'TSQualifiedName',
data: { type: 'props' },
},
],
},
{
code: `
function C(props: Props) {
void props.a
typeof props.b
return <div />
}
`,
options: ['always'],
features: ['types'],
errors: [
{
messageId: 'useDestructAssignment',
data: { type: 'props' },
},
{
messageId: 'useDestructAssignment',
data: { type: 'props' },
},
],
}
)),
});

0 comments on commit 63aceff

Please sign in to comment.