-
-
Notifications
You must be signed in to change notification settings - Fork 2.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
no-unused-props: not ignoring components with no used props #1303
Conversation
@@ -784,54 +764,6 @@ ruleTester.run('no-unused-prop-types', rule, { | |||
].join('\n') | |||
}, { | |||
code: [ | |||
'const statelessComponent = (props) => {', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
First of all for some reason the same test were copy-pasted 4 times. I had to remove this test all together because it was never validated correctly. Current implementation is not able to detect correctly nested definition of components (subRender technically is a react component). I don't think this is a common/idiomatic react style so I assume we shouldn't support it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's not the same test; I see 4 different variations: arrow, non-arrow, and destructured, and non-destructured.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Got it. Makes sense. Is ti ok if I delete these tests though. Do you agree that this rule technically doesn't support this use case.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that it ideally should support this use case, but our mechanism of detecting components may make it difficult.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you ask me, the fact that certain cases are not supported could also be tested. Of course, with a test name which clearly states "testing unsupported case" and assertions that clearly check for false positives or other signs of "not supported"-ness.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sure, that seems reasonable too.
@@ -3215,6 +3148,23 @@ ruleTester.run('no-unused-prop-types', rule, { | |||
line: 11, | |||
column: 8 | |||
}] | |||
}, { // None of the props are used | |||
code: [ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is the test reported for the issue
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for working on this!
lib/util/Components.js
Outdated
if (usedPropTypes[id]) { | ||
list[j].usedPropTypes = (list[j].usedPropTypes || []).concat(usedPropTypes[id]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this change doesn't seem like an improvement; it increases the number of mutations.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I agree it is not exactly an improvement.
Reason I added it - so usedPropTypes it is an empty array when there is no usedPropTypes instead of null. Otherwise anywhere we loop through it we would need to check if it is an array or not.
I can remove it and add few checks in the rule file - I don't mind either way. Let me know what is preferable.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see what you mean. Could you point to the other lines you'd need an || []
on, if you reverted these lines?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just one place actually:
lib/rules/no-unused-prop-types.js:236
function isPropUsed(node, prop) {
for (let i = 0, l = node.usedPropTypes.length; i < l; i++) {
const usedProp = node.usedPropTypes[i];
if (
...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm ambivalent; my general preference is to avoid mutations, but I don't feel super strongly about it. I'll leave it up to you :-)
code: [ | ||
'var Hello = createReactClass({', | ||
' propTypes: {', | ||
' unused: PropTypes.string', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The test case specifically had React.PropTypes
, and React.createClass
- are these not necessary to trigger the bug?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This didn't matter. But I updated the test so it is identical to the one reported.
lib/util/Components.js
Outdated
if (usedPropTypes[id]) { | ||
list[j].usedPropTypes = (list[j].usedPropTypes || []).concat(usedPropTypes[id]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see what you mean. Could you point to the other lines you'd need an || []
on, if you reverted these lines?
@@ -1387,15 +1319,16 @@ ruleTester.run('no-unused-prop-types', rule, { | |||
code: [ | |||
'const PagingBlock = function(props) {', | |||
' return (', | |||
' <SomeChild {...props} />', | |||
' <span>{...props}</span>', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why this change?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
to detect usage of a spread operator (that is handled in JSXSpreadChild I added to the rule) SomeChild has to be a valid react component (this is a babel-eslint thing). Originally when component was invalid, babel didn't detect any props passed to it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's no need to use babel-eslint for spread props; so let's revert this change and keep it on the default parser.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
as far as I can tell spread works with es6 but jsx spread inside a component does not. Simply removing babel and adding parserOptions fails with : message: 'Parsing error: Unexpected token ...',
I am happy to try any suggestions you might have.
{
code: [
'const PagingBlock = function(props) {',
' return (',
' <span>{...props}</span>',
' );',
'};',
'PagingBlock.propTypes = {',
' nextPage: PropTypes.func.isRequired,',
' previousPage: PropTypes.func.isRequired,',
'};'
].join('\n'),
parserOptions: Object.assign({}, parserOptions, {ecmaVersion: 2017})
}
error:
AssertionError: Should have no errors but had 1: [ { ruleId: null,
fatal: true,
severity: 2,
source: ' <span>{...props}</span>',
message: 'Parsing error: Unexpected token ...',
line: 3,
column: 12 } ]
+ expected - actual
-1
+0
at testValidTemplate (node_modules/eslint/lib/testers/rule-tester.js:415:20)
at Context.RuleTester.it (node_modules/eslint/lib/testers/rule-tester.js:545:25)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm a bit confused… {...props}
in a child context isn't anything; that shouldn't work. Spreading is a JSX attribute thing, and a JS iterable thing, and a JS object thing - but not a child thing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess I have to add that the reason it worked before is that the component was invalid by itself so the parser wouldn't try to parse the props of SomeChild
. And that way the linter didn't know that props are actually used. That was ok before this change (where components with 0 used props were simply not validated). If we want to validate components with no props used, we need to make sure that where they are actually used, the code is syntactically valid.
In other words if I keep this test as it is it would fail with nextPage
and PreviousPage
are not used error.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm a bit confused… {...props} in a child context isn't anything; that shouldn't work. Spreading is a JSX attribute thing, and a JS iterable thing, and a JS object thing - but not a child thing.
I guess it is getting late... I am not sure I understand what you mean .. But I agree regrdless😃
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
<span>{...props}</span>
is invalid jsx - if babel-eslint parses it, it's a bug in babel-eslint.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh I see, good point. Let me update the test so SomeChild
is a thing then. I will look into creating the issue with babel-eslint.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The fix was easy. Changing type from JSXChildSpread to JSXAttribute spread...Thanks for catching this. I will go read about JSXChildSpread though... maybe it is some beta feature...
' );', | ||
'};', | ||
|
||
'PagingBlock.propTypes = {', | ||
' nextPage: PropTypes.func.isRequired,', | ||
' previousPage: PropTypes.func.isRequired,', | ||
'};' | ||
].join('\n') | ||
].join('\n'), | ||
parser: 'babel-eslint' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what in this test needs babel-eslint?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this component uses JSX spread few lines above.So it needs babel.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
jsx spread works in ES6 or above in the default parser with parserOptions: { jsx: true }
- there's definitely no need for babel-eslint
for it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
responded above
' }', | ||
'});' | ||
].join('\n'), | ||
parser: 'babel-eslint', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
similarly here, what in this test needs babel-eslint? this test seems like it'd work with ES6.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes, here es6 would do.
@ljharb, looks like this will take some more time. Neither babel nor es6 parser gives any indication that {...props} is being used. And it has nothing to do with component being a thing (thanks for catching that issue with invalid spread for babel ). So this return <SomeComponent {...props} />; is parsed into:
What dependency do you think this problem starts from? Would it be an eslint/espree issue? Update: Nevermind, it shouldn't have children it should have props. I will look into that and update PR. Thanks for your help!! |
@@ -1317,9 +1318,16 @@ ruleTester.run('no-unused-prop-types', rule, { | |||
}, { | |||
// `no-unused-prop-types` rest param props in jsx expressions - [Issue #885] | |||
code: [ | |||
'const SomeComponent = funciton(props) {', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
function
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
will be gone. Thanks.
lib/util/Components.js
Outdated
if (usedPropTypes[id]) { | ||
list[j].usedPropTypes = (list[j].usedPropTypes || []).concat(usedPropTypes[id]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm ambivalent; my general preference is to avoid mutations, but I don't feel super strongly about it. I'll leave it up to you :-)
@@ -3148,24 +3148,7 @@ ruleTester.run('no-unused-prop-types', rule, { | |||
line: 11, | |||
column: 8 | |||
}] | |||
}, { // None of the props are used | |||
code: [ | |||
'var Hello = createReactClass({', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In this case, the duplicated test isn't a duplicate; one imports React, and the other doesn't, and it's intentional to check both.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok I will bring this back.
@@ -784,54 +764,6 @@ ruleTester.run('no-unused-prop-types', rule, { | |||
].join('\n') | |||
}, { | |||
code: [ | |||
'const statelessComponent = (props) => {', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's not the same test; I see 4 different variations: arrow, non-arrow, and destructured, and non-destructured.
@@ -234,8 +233,9 @@ module.exports = { | |||
* @returns {Boolean} True if the prop is used, false if not. | |||
*/ | |||
function isPropUsed(node, prop) { | |||
for (let i = 0, l = node.usedPropTypes.length; i < l; i++) { | |||
const usedProp = node.usedPropTypes[i]; | |||
const usedPropTypes = node.usedPropTypes || []; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I reverted that change from Components file.
8cde4a1
to
b7befbc
Compare
b7befbc
to
9d0707e
Compare
@ljharb sorry for so many updates. I squashed my change into 1 commit and it is ready for re-review. Thanks! |
@@ -897,6 +897,13 @@ module.exports = { | |||
} | |||
}, | |||
|
|||
JSXSpreadAttribute: function(node) { | |||
const component = components.get(utils.getParentComponent()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems super weird to me that getParentComponent
infers the node
rather than taking it as an argument (not your code ofc, just thought I'd comment)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeap
' }', | ||
'}', | ||
'Comp2.propTypes = {', | ||
' prop2: PropTypes.arrayOf(Comp1.propTypes.prop1)', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This test implies that Comp1's prop1 prop should be marked as used, because Comp2 references its propType; but in fact I think it shouldn't - could you move this test to be invalid instead of just deleting it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
@@ -784,54 +764,6 @@ ruleTester.run('no-unused-prop-types', rule, { | |||
].join('\n') | |||
}, { | |||
code: [ | |||
'const statelessComponent = (props) => {', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that it ideally should support this use case, but our mechanism of detecting components may make it difficult.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, thanks for the iteration!
@lencioni, when would you be able to take a look at this PR? This is my first PR for the repo so I am not sure what is the normal turn around here. Thank you, |
I'll give it another day, then I'll merge it if there's no objections by then. |
Sorry for the inconvenience; I've just merged a refactor to start using arrow functions. Would you mind rebasing latest master? I'll merge ASAP after that's done. |
@ljharb, no problem. Should be all set. |
This is tackling one of the many issues with the rule (issue #1162).
The problem was that the rule simply ignored the components that had no used properties. Once this condition was removed I had to fix some tests and remove some other (that were never actually worked correctly but were passing because of the component had no used props and thus was excluded from validation).
This change might fix few more false positives that users reported. I am planning to go through the list of open issues for the rule and close/fix some more after I get some feedback on my change.