-
-
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
Add forbid-elements rule #890
Conversation
|
||
## Rule Details | ||
|
||
This rule checks all JSX components and verifies that no forbidden elements are used. When it detects a forbidden element, the error message will contain the specified elements that you should use instead. This rule is off by default. If on, no elements are forbidden by default. |
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 rule should also check React.createElement
usages as well.
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.
Sounds reasonable.
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 we specify forbid: ['Modal']
, then for which of these do we warn?
React.createElement('Modal');
React.createElement(Modal);
or with forbid: ['button']
, which one?
React.createElement(button);
React.createElement('button');
i think we should follow the same rules as JSX when translating names into either strings or identifiers
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.
<Modal />
is the same as React.createElement(Modal)
, and I'd expect the same setting to forbid both.
I'm not sure about the latter example.
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 we consider React.createElement
calls that can be mapped back to JSX as "valid", then React.createElement('Modal')
, and React.createElement(button)
would not be valid.
<Modal />
translates to React.createElement(Modal)
, and there's no way that I know that we can get it to translate to React.createElement('Modal')
.
That's why I think this rule should not bother to make checks that cannot be mapped back to JSX, and can be left to a different rule that checks for createElement
validity
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 not sure we're on the same page here - <Modal />
is identical to React.createElement(Modal)
. React.createElement('Modal')
is the same as <{'Modal'} />
which is simply invalid react/jsx.
In other words, if Button
is forbidden, i could still do const NotButton = Button; <NotButton />
and work around this rule.
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.
Sorry, made some typos earlier. I guess I am arguing for two things:
-
If 'button' is forbidden, I don't think this rule should stop someone from doing
const button = 'div'; React.createElement(button)
because that looks like that's done for a reason other than to circumvent the rule (otherwise they would just useeslint-ignore
). -
In general:
- if
'button'
is forbidden, then the rule should check forReact.createElement('button')
and ignoreReact.createElement(button)
- if
'Modal'
is forbidden, then the rule should check forReact.createElement(Modal)
and ignoreReact.createElement('Modal')
. - if
'a.b'
is forbidden, then the rule should check forReact.createElement(a.b)
and ignoreReact.createElement('a.b')
. - if
'_a'
is forbidden, then the rule should check forReact.createElement(_a)
and ignoreReact.createElement('_a')
because this aligns with how React translates JSX element names into createElement calls
babel example: https://goo.gl/ALVt2s
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 agree with your summary (although I'm pretty sure React.createElement
won't work at all with strings that aren't DOM components)
// [1, {forbid: ['button', 'input']}] | ||
<div><button /><input /></div> | ||
|
||
// [1, {forbid: [{button: 'Button']}] |
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 maybe a better object schema would be:
{ component: 'button', replacement: 'Button', message: 'hey yo buttons suck, use Button' }
- where "replacement" and "message" would both be optional.
That way you can use the object form for everything, and the string form is just a convenience.
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 like this suggestion. The only thing is I don't want to use the word component
because to me that means a react component which excludes simple DOM nodes. I like to use element
because the it aligns with React.createElement()
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 sure, "element" makes more sense
// [1, {forbid: [{button: 'Button']}] | ||
<button /> | ||
|
||
// [1, {forbid: [{div: ['Box', 'View']}, 'button']] |
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 actually kind of confused what the point is of replacement elements is, unless it's to autofix or populate the message - but "message" would handle the latter, and an autofixer would need only a single replacement to be able to work. So, why multiple?
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 don't personally have a use case of multiple replacements, but yeah your custom message suggestion would make specifying multiple replacements obsolete.
8af0ff1
to
2978484
Compare
// [1, {forbid: ['button']}] | ||
<Button /> | ||
|
||
// [1, {forbid: [{button: 'Button']}] |
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.
Codebases may have multiple components with the same name. Would it be more useful to be able to specify a path to a component instead of or in addition to just a component name?
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 using a path would require using require.resolve
combined with the file's dirname/process.cwd()
, on both the provided path as well as the import/require path in the file, which might be dynamic.
I think that would be more robust, but I think there's already a lot of other rules that solely use the component name, so I think it'd be OK to keep with that pattern for now.
} | ||
|
||
var last = replacements.pop(); | ||
return replacements.join(', ') + ', or ' + last; |
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 including the Oxford comma! A+++
5efb73e
to
3fd0df0
Compare
@ljharb I've made the suggested schema changes. I've left out the auto fixing capability for now and I could add it in later. |
I'm no longer interested in having this merged. If no one else wants this rule it's fine to close it imo. |
@kentor sorry this fell through the cracks - I still think it's a great rule to add. |
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 pending a fresh rebase
Will it work for named imports like: import MyBlock from './Block.jsx';
class MyComponent extends React.Component {
render() {
return <MyBlock />;
}
} with forbidden |
@TheXardas no, there is no block element in that file. |
But there is! |
@TheXardas right but there's no way static analysis can determine that within that file. The only way a linter rule can determine what kind of component a thing is is its name in the file. If someone intentionally names it MyBlock to get around the prohibition, there's nothing a linter rule could do about it (just like if they used an eslint override comment) |
3fd0df0
to
bbcbaee
Compare
May specify a list of forbidden elements and a custom reporting message
bbcbaee
to
d884a98
Compare
May specify a list of forbidden elements and their desired replacement
elements.
addresses #887