Skip to content
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

Merged
merged 1 commit into from
Feb 15, 2017
Merged

Conversation

kentor
Copy link
Contributor

@kentor kentor commented Oct 6, 2016

May specify a list of forbidden elements and their desired replacement
elements.

addresses #887

@kentor kentor changed the title Add new rule: forbid-elements Add forbid-elements rule Oct 6, 2016

## 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.
Copy link
Member

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds reasonable.

Copy link
Contributor Author

@kentor kentor Oct 6, 2016

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

Copy link
Member

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.

Copy link
Contributor Author

@kentor kentor Oct 7, 2016

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

Copy link
Member

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.

Copy link
Contributor Author

@kentor kentor Oct 7, 2016

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:

  1. 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 use eslint-ignore).

  2. In general:

  • if 'button' is forbidden, then the rule should check for React.createElement('button') and ignore React.createElement(button)
  • if 'Modal' is forbidden, then the rule should check for React.createElement(Modal) and ignore React.createElement('Modal').
  • if 'a.b' is forbidden, then the rule should check for React.createElement(a.b) and ignore React.createElement('a.b').
  • if '_a' is forbidden, then the rule should check for React.createElement(_a) and ignore React.createElement('_a')

because this aligns with how React translates JSX element names into createElement calls

babel example: https://goo.gl/ALVt2s

Copy link
Member

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']}]
Copy link
Member

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.

Copy link
Contributor Author

@kentor kentor Oct 6, 2016

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()

Copy link
Member

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']]
Copy link
Member

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?

Copy link
Contributor Author

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.

// [1, {forbid: ['button']}]
<Button />

// [1, {forbid: [{button: 'Button']}]
Copy link
Collaborator

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?

Copy link
Member

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;
Copy link
Collaborator

@lencioni lencioni Oct 6, 2016

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+++

@kentor kentor force-pushed the forbid-elements branch 2 times, most recently from 5efb73e to 3fd0df0 Compare October 7, 2016 05:37
@kentor
Copy link
Contributor Author

kentor commented Oct 7, 2016

@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.

@kentor
Copy link
Contributor Author

kentor commented Jan 30, 2017

I'm no longer interested in having this merged. If no one else wants this rule it's fine to close it imo.

@ljharb
Copy link
Member

ljharb commented Jan 30, 2017

@kentor sorry this fell through the cracks - I still think it's a great rule to add.

Copy link
Member

@ljharb ljharb left a 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

@TheXardas
Copy link

Will it work for named imports like:

import MyBlock from './Block.jsx';

class MyComponent extends React.Component {
    render() {
        return <MyBlock />;
    }
}

with forbidden Block element?

@ljharb
Copy link
Member

ljharb commented Jan 30, 2017

@TheXardas no, there is no block element in that file.

@TheXardas
Copy link

But there is!
It is aliased as MyBlock, but it's still a Block component.

@ljharb
Copy link
Member

ljharb commented Jan 30, 2017

@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)

May specify a list of forbidden elements and a custom reporting message
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Development

Successfully merging this pull request may close these issues.

4 participants