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

New boolean-prop-naming rule for enforcing the naming of booleans #1264

Merged
merged 1 commit into from Jun 27, 2017

Conversation

EvHaus
Copy link
Collaborator

@EvHaus EvHaus commented Jun 19, 2017

At my workplace, we prefer to name our boolean props as "isSomething" or "hasWhatever". This is an ESLint rule which allows devs to specify a Regex pattern for how they want their boolean props named.

Tested with createReactClass, ES6 components, stateless components and Flowtype.

I support, in the future, this rule can be expanded to support naming rules for ANY props, but that felt like overkill for the time being. Hopefully this rule will be useful as is.

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.

What about custom propTypes that enforce booleans but aren't PropTypes.bool? For example, mutuallyExclusiveTrueProps from https://www.npmjs.com/package/airbnb-prop-types.

Can there be a way to customize the name of custom propTypes that are booleans?

```jsx
var Hello = createReactClass({
propTypes: {
enabled: PropTypes.boolean
Copy link
Member

Choose a reason for hiding this comment

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

PropTypes.bool - .boolean isn't a thing (same throughout)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Oops. Good catch.


### `rule`

The Regex pattern to use when validating the name of the prop. Some common examples are:
Copy link
Member

Choose a reason for hiding this comment

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

regex or RegExp


## About component detection

For this rule to work we need to detect React components, this could be very hard since components could be declared in a lot of ways.
Copy link
Member

Choose a reason for hiding this comment

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

We should be using Components.detect in all our rules; is there a reason this paragraph needs to exist?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Was copy/pasted from another rule's document. Should I remove it?

Copy link
Member

Choose a reason for hiding this comment

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

Yeah, this seems more like a project-level thing, not a rule-level thing.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

WIll remove.

*/
'use strict';

var has = require('has');
Copy link
Member

Choose a reason for hiding this comment

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

const?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Thanks. Not all rules were using ES6, wasn't sure which version of Node we're supporting. Will update accordingly.

// createReactClass components with PropTypes
code: [
'var Hello = createReactClass({',
' propTypes: {isSomething: PropTypes.boolean},',
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 all the PropType test cases are invalid - s/boolean/bool/g.

@EvHaus
Copy link
Collaborator Author

EvHaus commented Jun 19, 2017

What about custom propTypes that enforce booleans but aren't PropTypes.bool? For example, mutuallyExclusiveTrueProps from https://www.npmjs.com/package/airbnb-prop-types.

Can there be a way to customize the name of custom propTypes that are booleans?

Good idea. I've added a new propTypeNames configuration option.


### `rule`

The RegExp pattern to use when validating the name of the prop. Some common examples are:
Copy link
Member

Choose a reason for hiding this comment

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

what's the default for this option?

Copy link
Collaborator Author

@EvHaus EvHaus Jun 20, 2017

Choose a reason for hiding this comment

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

null is the default at the moment (ie. the naming checks are disabled). It didn't seem right to enforce my own preferences on all users. Do you think is makes sense to set "(is|has)*" as the default?

Copy link
Member

Choose a reason for hiding this comment

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

Since if not, the rule does nothing by default, yes.

The ideal scenario is that nobody ever needs to write a regex, except for the minority edge cases.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Agreed. Will set a default.

create: Components.detect(function(context, components, utils) {
const sourceCode = context.getSourceCode();
const config = context.options[0] || {};
const rule = config.rule ? new RegExp(config.rule) : null;
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 really really not a fan of user-supplied regexes, and passing unsanitized input into new RegExp can definitely cause catastrophic backtracking and other malicious regexes to be invoked.

Can we simplify this somehow, perhaps requiring a list of prefixes?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I'm not opposed to replacing the RegExp entry to just prefixes, however, that makes the rule much less powerful and less customizable. Having user-supplied regexes in ESLint rules isn't uncommon (eg. http://eslint.org/docs/rules/id-match) when specifying naming conventions for things.

return false;
}

return Boolean(
Copy link
Member

Choose a reason for hiding this comment

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

usually we just do ‼x instead of Boolean(x), nbd tho

require('babel-eslint');

var parserOptions = {
ecmaVersion: 8,
Copy link
Member

Choose a reason for hiding this comment

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

this can probably be 6/2015, yes?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Will fix.

ecmaVersion: 8,
sourceType: 'module',
ecmaFeatures: {
experimentalObjectRestSpread: true,
Copy link
Member

Choose a reason for hiding this comment

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

i don't think we're using this feature in these tests

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Will fix.

@EvHaus EvHaus force-pushed the boolean-prop-naming branch 2 times, most recently from c9d8647 to d76c2fb Compare June 20, 2017 16:23
type: 'array'
},
rule: {
default: null,
Copy link
Member

Choose a reason for hiding this comment

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

Can we put the default here, in the schema, and use minLength: 1 to avoid the empty string?

const sourceCode = context.getSourceCode();
const config = context.options[0] || {};
const rule = new RegExp(config.rule || DEFAULT_RULE);
const propTypeNames = config.propTypeNames ? config.propTypeNames : ['bool'];
Copy link
Member

Choose a reason for hiding this comment

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

Similarly, can this default go in the schema instead?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

For some reason, default schema definition doesn't work for this option. Maybe because it's an array type? Specifying a default at the schema level here never returns anything from config object.

Copy link
Member

Choose a reason for hiding this comment

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

i guess you can't have a default array in jsonschema.

in that case, a ? a : b should always be a || b :-)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Updated. Thanks!

items: {
type: 'string'
},
type: 'array'
Copy link
Member

Choose a reason for hiding this comment

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

Let's add uniqueItems: true and minItems: 1

@EvHaus EvHaus force-pushed the boolean-prop-naming branch 3 times, most recently from fe71deb to 38e479a Compare June 20, 2017 20:32
}

const list = components.list();
for (let component in list) {
Copy link
Member

Choose a reason for hiding this comment

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

Let's avoid for..in; can we use Object.keys().forEach here instead?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Fixed

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.

Thanks!

Let's wait til we get at least one other collab review before merging.

Copy link
Collaborator

@lencioni lencioni left a comment

Choose a reason for hiding this comment

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

Thanks for the contribution!

(
prop.type === 'ObjectTypeProperty' &&
prop.value.type === 'BooleanTypeAnnotation' &&
getPropName(prop).search(rule) < 0
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think rule.test(getPropName(prop)) might be more appropriate here (and below)

Copy link
Collaborator Author

@EvHaus EvHaus Jun 26, 2017

Choose a reason for hiding this comment

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

Will change to use .exec() actually, since (after some research) that appears to be the most performant way to execute Regex rules. Hope that's ok.

Copy link
Member

Choose a reason for hiding this comment

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

exec mutates; test doesn't - test might be better.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Fixed.

const propName = getPropName(propNode);
context.report({
node: propNode,
message: `Prop name (${propName}) doesn't match specified rule (${config.rule})`,
Copy link
Collaborator

Choose a reason for hiding this comment

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

"specified" seems redundant in this message. I think you can remove it without losing any meaning. What do you think?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Agreed. Will remove.

const propTypeNames = config.propTypeNames || ['bool'];

// Remembers all Flowtype object definitions
var ObjectTypeAnnonations = {};
Copy link
Collaborator

Choose a reason for hiding this comment

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

typo? ObjectTypeAnnotations?

Also, since this isn't instantiated, it should probably start with a lowercase letter.

And it might be a bit nicer to use a Map instead of a plain object.

Copy link
Member

Choose a reason for hiding this comment

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

+1 on all 3 points, nice catch

@EvHaus EvHaus force-pushed the boolean-prop-naming branch 2 times, most recently from d27f42e to f9c5fae Compare June 26, 2017 22:17
@wmertens
Copy link

How should you name a prop that enables some feature? Normally I'd write e.g. <Nav showHeaders />. Should this instead be <Nav hasHeaders />, <Nav isHeadered />, …?

@EvHaus
Copy link
Collaborator Author

EvHaus commented Jul 15, 2019

@wmertens I would call it hasHeader. Keep in mind, this rule is optional and configurable. If you prefer to use showHeaders then you can add the "show" prefix to the rule's options.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Development

Successfully merging this pull request may close these issues.

None yet

4 participants