Skip to content

Commit

Permalink
[Fix] boolean-prop-naming: add check for typescript "boolean" type
Browse files Browse the repository at this point in the history
Fixes #2892.
  • Loading branch information
vedadeepta authored and ljharb committed Feb 21, 2021
1 parent e54118b commit ae5ace5
Show file tree
Hide file tree
Showing 3 changed files with 76 additions and 10 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Expand Up @@ -18,12 +18,14 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel
* [`jsx-curly-brace-presence`]: ignore containers with comments ([#2900][] @golopot)
* [`destructuring-assignment`]: fix a false positive for local prop named `context` in SFC ([#2929][] @SyMind)
* [`jsx-no-target-blank`]: Allow rel="noreferrer" when `allowReferrer` is true ([#2925][] @edemaine)
* [`boolean-prop-naming`]: add check for typescript "boolean" type ([#2930][] @vedadeepta)

### Changed
* [Docs] [`jsx-no-constructed-context-values`][]: fix invalid example syntax ([#2910][] @kud)
* [readme] Replace lists of rules with tables in readme ([#2908][] @motato1)
* [Docs] added missing curly braces ([#2923][] @Muditxofficial)

[#2930]: https://github.com/yannickcr/eslint-plugin-react/pull/2930
[#2929]: https://github.com/yannickcr/eslint-plugin-react/pull/2929
[#2925]: https://github.com/yannickcr/eslint-plugin-react/pull/2925
[#2923]: https://github.com/yannickcr/eslint-plugin-react/pull/2923
Expand Down
36 changes: 30 additions & 6 deletions lib/rules/boolean-prop-naming.js
Expand Up @@ -86,7 +86,7 @@ module.exports = {
if (node.type === 'ExperimentalSpreadProperty' || node.type === 'SpreadElement') {
return null;
}
if (node.value.property) {
if (node.value && node.value.property) {
const name = node.value.property.name;
if (name === 'isRequired') {
if (node.value.object && node.value.object.property) {
Expand All @@ -96,7 +96,7 @@ module.exports = {
}
return name;
}
if (node.value.type === 'Identifier') {
if (node.value && node.value.type === 'Identifier') {
return node.value.name;
}
return null;
Expand Down Expand Up @@ -145,6 +145,16 @@ module.exports = {
);
}

function tsCheck(prop) {
if (prop.type !== 'TSPropertySignature') return false;
const typeAnnotation = (prop.typeAnnotation || {}).typeAnnotation;
return (
typeAnnotation
&& typeAnnotation.type === 'TSBooleanKeyword'
&& rule.test(getPropName(prop)) === false
);
}

/**
* Checks if prop is nested
* @param {Object} prop Property object, single prop type declaration
Expand All @@ -170,7 +180,7 @@ module.exports = {
runCheck(prop.value.arguments[0].properties, addInvalidProp);
return;
}
if (flowCheck(prop) || regularCheck(prop)) {
if (flowCheck(prop) || regularCheck(prop) || tsCheck(prop)) {
addInvalidProp(prop);
}
});
Expand Down Expand Up @@ -289,6 +299,12 @@ module.exports = {
}
},

TSTypeAliasDeclaration(node) {
if (node.typeAnnotation.type === 'TSTypeLiteral') {
objectTypeAnnotations.set(node.id.name, node.typeAnnotation);
}
},

// eslint-disable-next-line object-shorthand
'Program:exit'() {
if (!rule) {
Expand All @@ -299,22 +315,30 @@ module.exports = {
Object.keys(list).forEach((component) => {
// If this is a functional component that uses a global type, check it
if (
list[component].node.type === 'FunctionDeclaration'
(
list[component].node.type === 'FunctionDeclaration'
|| list[component].node.type === 'ArrowFunctionExpression'
)
&& list[component].node.params
&& list[component].node.params.length
&& list[component].node.params[0].typeAnnotation
) {
const typeNode = list[component].node.params[0].typeAnnotation;
const annotation = typeNode.typeAnnotation;

let propType;
if (annotation.type === 'GenericTypeAnnotation') {
propType = objectTypeAnnotations.get(annotation.id.name);
} else if (annotation.type === 'ObjectTypeAnnotation') {
propType = annotation;
} else if (annotation.type === 'TSTypeReference') {
propType = objectTypeAnnotations.get(annotation.typeName.name);
}

if (propType) {
validatePropNaming(list[component].node, propType.properties);
validatePropNaming(
list[component].node,
propType.properties || propType.members
);
}
}

Expand Down
48 changes: 44 additions & 4 deletions tests/lib/rules/boolean-prop-naming.js
Expand Up @@ -29,7 +29,7 @@ const parserOptions = {
const ruleTester = new RuleTester({parserOptions});
ruleTester.run('boolean-prop-naming', rule, {

valid: [{
valid: [].concat({
// Should support both `is` and `has` prefixes by default
code: `
var Hello = createReactClass({
Expand Down Expand Up @@ -416,9 +416,21 @@ ruleTester.run('boolean-prop-naming', rule, {
rule: '^is[A-Z]([A-Za-z0-9]?)+',
validateNested: true
}]
}],
}, parsers.TS({
code: `
type TestFNType = {
isEnabled: boolean
}
const HelloNew = (props: TestFNType) => { return <div /> };
`,
options: [{
rule: '^is[A-Z]([A-Za-z0-9]?)+'
}],
parser: parsers['@TYPESCRIPT_ESLINT'],
errors: []
})),

invalid: [{
invalid: [].concat({
// createReactClass components with PropTypes
code: `
var Hello = createReactClass({
Expand Down Expand Up @@ -944,5 +956,33 @@ ruleTester.run('boolean-prop-naming', rule, {
messageId: 'patternMismatch',
data: {propName: 'something', pattern: '^is[A-Z]([A-Za-z0-9]?)+'}
}]
}]
}, parsers.TS({
code: `
type TestConstType = {
enabled: boolean
}
const HelloNew = (props: TestConstType) => { return <div /> };
`,
options: [{
rule: '^is[A-Z]([A-Za-z0-9]?)+'
}],
parser: parsers['@TYPESCRIPT_ESLINT'],
errors: [{
message: 'Prop name (enabled) doesn\'t match rule (^is[A-Z]([A-Za-z0-9]?)+)'
}]
}, {
code: `
type TestFNType = {
enabled: boolean
}
const HelloNew = (props: TestFNType) => { return <div /> };
`,
options: [{
rule: '^is[A-Z]([A-Za-z0-9]?)+'
}],
parser: parsers['@TYPESCRIPT_ESLINT'],
errors: [{
message: 'Prop name (enabled) doesn\'t match rule (^is[A-Z]([A-Za-z0-9]?)+)'
}]
}))
});

0 comments on commit ae5ace5

Please sign in to comment.