From 6c733ff2fcc3cdf86488d09b007bf6307a602861 Mon Sep 17 00:00:00 2001 From: Joachim Seminck Date: Mon, 4 Sep 2017 09:11:57 +0300 Subject: [PATCH 1/3] Add flow annotation type support to no-unused-prop-types --- lib/rules/no-unused-prop-types.js | 42 +++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/lib/rules/no-unused-prop-types.js b/lib/rules/no-unused-prop-types.js index d076368777..eaac1f24eb 100644 --- a/lib/rules/no-unused-prop-types.js +++ b/lib/rules/no-unused-prop-types.js @@ -133,6 +133,42 @@ module.exports = { return false; } + /** + * Resolve the type annotation for a given node. + * Flow annotations are sometimes wrapped in outer `TypeAnnotation` + * and `NullableTypeAnnotation` nodes which obscure the annotation we're + * interested in. + * This method also resolves type aliases where possible. + * + * @param {ASTNode} node The annotation or a node containing the type annotation. + * @returns {ASTNode} The resolved type annotation for the node. + */ + function resolveTypeAnnotation(node) { + let annotation = node.typeAnnotation || node; + while (annotation && (annotation.type === 'TypeAnnotation' || annotation.type === 'NullableTypeAnnotation')) { + annotation = annotation.typeAnnotation; + } + if (annotation.type === 'GenericTypeAnnotation' && typeScope(annotation.id.name)) { + return typeScope(annotation.id.name); + } + return annotation; + } + + /** + * Checks if we are declaring a props as a generic type in a flow-annotated class. + * + * @param {ASTNode} node the AST node being checked. + * @returns {Boolean} True if the node is a class with generic prop types, false if not. + */ + function isSuperTypeParameterPropsDeclaration(node) { + if (node && node.type === 'ClassDeclaration') { + if (node.superTypeParameters && node.superTypeParameters.params.length > 0) { + return true; + } + } + return false; + } + /** * Checks if we are declaring a prop * @param {ASTNode} node The AST node being checked. @@ -866,6 +902,12 @@ module.exports = { // -------------------------------------------------------------------------- return { + ClassDeclaration: function(node) { + if (isSuperTypeParameterPropsDeclaration(node)) { + markPropTypesAsDeclared(node, resolveSuperParameterPropsType(node)); + } + }, + ClassProperty: function(node) { if (isAnnotatedClassPropsDeclaration(node)) { markPropTypesAsDeclared(node, resolveTypeAnnotation(node)); From 144d92a566141009ad3778b0b05770289d42a3d0 Mon Sep 17 00:00:00 2001 From: Joachim Seminck Date: Sun, 27 Aug 2017 09:17:25 +0300 Subject: [PATCH 2/3] Add tests and fix the function that was copied over --- lib/rules/no-unused-prop-types.js | 21 ++++++++----- lib/rules/prop-types.js | 1 + tests/lib/rules/no-unused-prop-types.js | 40 +++++++++++++++++++++++++ 3 files changed, 55 insertions(+), 7 deletions(-) diff --git a/lib/rules/no-unused-prop-types.js b/lib/rules/no-unused-prop-types.js index eaac1f24eb..2640ae352d 100644 --- a/lib/rules/no-unused-prop-types.js +++ b/lib/rules/no-unused-prop-types.js @@ -11,6 +11,7 @@ const has = require('has'); const Components = require('../util/Components'); const variable = require('../util/variable'); const annotations = require('../util/annotations'); +const versionUtil = require('../util/version'); // ------------------------------------------------------------------------------ // Constants @@ -134,17 +135,23 @@ module.exports = { } /** - * Resolve the type annotation for a given node. - * Flow annotations are sometimes wrapped in outer `TypeAnnotation` - * and `NullableTypeAnnotation` nodes which obscure the annotation we're - * interested in. - * This method also resolves type aliases where possible. + * Resolve the type annotation for a given class declaration node with superTypeParameters. * * @param {ASTNode} node The annotation or a node containing the type annotation. * @returns {ASTNode} The resolved type annotation for the node. */ - function resolveTypeAnnotation(node) { - let annotation = node.typeAnnotation || node; + function resolveSuperParameterPropsType(node) { + let propsParameterPosition; + try { + // Flow <=0.52 had 3 required TypedParameters of which the second one is the Props. + // Flow >=0.53 has 2 optional TypedParameters of which the first one is the Props. + propsParameterPosition = versionUtil.testFlowVersion(context, '0.53.0') ? 0 : 1; + } catch (e) { + // In case there is no flow version defined, we can safely assume that when there are 3 Props we are dealing with version <= 0.52 + propsParameterPosition = node.superTypeParameters.params.length <= 2 ? 0 : 1; + } + + let annotation = node.superTypeParameters.params[propsParameterPosition]; while (annotation && (annotation.type === 'TypeAnnotation' || annotation.type === 'NullableTypeAnnotation')) { annotation = annotation.typeAnnotation; } diff --git a/lib/rules/prop-types.js b/lib/rules/prop-types.js index 4a9d1d6c6f..88e3121ee5 100644 --- a/lib/rules/prop-types.js +++ b/lib/rules/prop-types.js @@ -12,6 +12,7 @@ const Components = require('../util/Components'); const variable = require('../util/variable'); const annotations = require('../util/annotations'); const versionUtil = require('../util/version'); + // ------------------------------------------------------------------------------ // Constants // ------------------------------------------------------------------------------ diff --git a/tests/lib/rules/no-unused-prop-types.js b/tests/lib/rules/no-unused-prop-types.js index 3985e4cca1..1c1937eba0 100644 --- a/tests/lib/rules/no-unused-prop-types.js +++ b/tests/lib/rules/no-unused-prop-types.js @@ -2069,6 +2069,30 @@ ruleTester.run('no-unused-prop-types', rule, { }; `, parser: 'babel-eslint' + }, { + code: ` + type Person = { + firstname: string + } + class MyComponent extends React.Component { + render() { + return
Hello {this.props.firstname}
+ } + } + `, + parser: 'babel-eslint' + }, { + code: ` + type Person = { + firstname: string + } + class MyComponent extends React.Component { + render() { + return
Hello {this.props.firstname}
+ } + } + `, + parser: 'babel-eslint' } ], @@ -3469,6 +3493,22 @@ ruleTester.run('no-unused-prop-types', rule, { errors: [{ message: '\'aProp\' PropType is defined but prop is never used' }] + }, { + code: ` + type Props = { + firstname: string, + lastname: string, + } + class MyComponent extends React.Component { + render() { + return
Hello {this.props.firstname}
+ } + } + `, + parser: 'babel-eslint', + errors: [{ + message: '\'lastname\' PropType is defined but prop is never used' + }] } /* , { From 3f03ac6f30a437099ea084b673310b86b439454e Mon Sep 17 00:00:00 2001 From: Joachim Seminck Date: Sun, 27 Aug 2017 09:35:20 +0300 Subject: [PATCH 3/3] Add flow 53 props typed argument support to no-unused-prop-types --- tests/lib/rules/no-unused-prop-types.js | 76 +++++++++++++++++++++++++ 1 file changed, 76 insertions(+) diff --git a/tests/lib/rules/no-unused-prop-types.js b/tests/lib/rules/no-unused-prop-types.js index 1c1937eba0..2a1897426f 100644 --- a/tests/lib/rules/no-unused-prop-types.js +++ b/tests/lib/rules/no-unused-prop-types.js @@ -2081,6 +2081,31 @@ ruleTester.run('no-unused-prop-types', rule, { } `, parser: 'babel-eslint' + }, { + code: ` + type Person = { + firstname: string + } + class MyComponent extends React.Component { + render() { + return
Hello {this.props.firstname}
+ } + } + `, + settings: {react: {flowVersion: '0.52'}}, + parser: 'babel-eslint' + }, { + code: ` + type Person = { + firstname: string + } + class MyComponent extends React.Component { + render() { + return
Hello {this.props.firstname}
+ } + } + `, + parser: 'babel-eslint' }, { code: ` type Person = { @@ -2092,6 +2117,7 @@ ruleTester.run('no-unused-prop-types', rule, { } } `, + settings: {react: {flowVersion: '0.53'}}, parser: 'babel-eslint' } ], @@ -3509,6 +3535,56 @@ ruleTester.run('no-unused-prop-types', rule, { errors: [{ message: '\'lastname\' PropType is defined but prop is never used' }] + }, { + code: ` + type Props = { + firstname: string, + lastname: string, + } + class MyComponent extends React.Component { + render() { + return
Hello {this.props.firstname}
+ } + } + `, + settings: {react: {flowVersion: '0.52'}}, + parser: 'babel-eslint', + errors: [{ + message: '\'lastname\' PropType is defined but prop is never used' + }] + }, { + code: ` + type Props = { + firstname: string, + lastname: string, + } + class MyComponent extends React.Component { + render() { + return
Hello {this.props.firstname}
+ } + } + `, + parser: 'babel-eslint', + errors: [{ + message: '\'lastname\' PropType is defined but prop is never used' + }] + }, { + code: ` + type Props = { + firstname: string, + lastname: string, + } + class MyComponent extends React.Component { + render() { + return
Hello {this.props.firstname}
+ } + } + `, + settings: {react: {flowVersion: '0.53'}}, + parser: 'babel-eslint', + errors: [{ + message: '\'lastname\' PropType is defined but prop is never used' + }] } /* , {