From ae5ace56a605c8aab108662bb673bda1061be39c Mon Sep 17 00:00:00 2001 From: Veda Date: Sun, 21 Feb 2021 23:46:50 +0530 Subject: [PATCH] [Fix] `boolean-prop-naming`: add check for typescript "boolean" type Fixes #2892. --- CHANGELOG.md | 2 ++ lib/rules/boolean-prop-naming.js | 36 +++++++++++++++---- tests/lib/rules/boolean-prop-naming.js | 48 +++++++++++++++++++++++--- 3 files changed, 76 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 85db74365e..fe2ef055e0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/lib/rules/boolean-prop-naming.js b/lib/rules/boolean-prop-naming.js index fe429093f1..2f7171820b 100644 --- a/lib/rules/boolean-prop-naming.js +++ b/lib/rules/boolean-prop-naming.js @@ -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) { @@ -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; @@ -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 @@ -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); } }); @@ -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) { @@ -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 + ); } } diff --git a/tests/lib/rules/boolean-prop-naming.js b/tests/lib/rules/boolean-prop-naming.js index fddb423064..d31902f760 100644 --- a/tests/lib/rules/boolean-prop-naming.js +++ b/tests/lib/rules/boolean-prop-naming.js @@ -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({ @@ -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
}; + `, + 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({ @@ -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
}; + `, + 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
}; + `, + 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]?)+)' + }] + })) });