11/**
22 * @fileoverview Enforce stateless components to be written as a pure function
33 * @author Yannick Croissant
4+ * @author Alberto Rodríguez
5+ * @copyright 2015 Alberto Rodríguez. All rights reserved.
46 */
57'use strict' ;
68
@@ -14,19 +16,6 @@ module.exports = Components.detect(function(context, components, utils) {
1416
1517 var sourceCode = context . getSourceCode ( ) ;
1618
17- var lifecycleMethods = [
18- 'state' ,
19- 'getInitialState' ,
20- 'getChildContext' ,
21- 'componentWillMount' ,
22- 'componentDidMount' ,
23- 'componentWillReceiveProps' ,
24- 'shouldComponentUpdate' ,
25- 'componentWillUpdate' ,
26- 'componentDidUpdate' ,
27- 'componentWillUnmount'
28- ] ;
29-
3019 // --------------------------------------------------------------------------
3120 // Public
3221 // --------------------------------------------------------------------------
@@ -64,24 +53,87 @@ module.exports = Components.detect(function(context, components, utils) {
6453 }
6554
6655 /**
67- * Check if a given AST node have any lifecycle method
56+ * Checks whether the constructor body is a redundant super call.
57+ * @see ESLint no-useless-constructor rule
58+ * @param {Array } body - constructor body content.
59+ * @param {Array } ctorParams - The params to check against super call.
60+ * @returns {boolean } true if the construtor body is redundant
61+ */
62+ function isRedundantSuperCall ( body , ctorParams ) {
63+ if (
64+ body . length !== 1 ||
65+ body [ 0 ] . type !== 'ExpressionStatement' ||
66+ body [ 0 ] . expression . callee . type !== 'Super'
67+ ) {
68+ return false ;
69+ }
70+
71+ var superArgs = body [ 0 ] . expression . arguments ;
72+ var firstSuperArg = superArgs [ 0 ] ;
73+ var lastSuperArgIndex = superArgs . length - 1 ;
74+ var lastSuperArg = superArgs [ lastSuperArgIndex ] ;
75+ var isSimpleParameterList = ctorParams . every ( function ( param ) {
76+ return param . type === 'Identifier' || param . type === 'RestElement' ;
77+ } ) ;
78+
79+ /**
80+ * Checks if a super argument is the same with constructor argument
81+ * @param {ASTNode } arg argument node
82+ * @param {number } index argument index
83+ * @returns {boolean } true if the arguments are same, false otherwise
84+ */
85+ function isSameIdentifier ( arg , index ) {
86+ return (
87+ arg . type === 'Identifier' &&
88+ arg . name === ctorParams [ index ] . name
89+ ) ;
90+ }
91+
92+ var spreadsArguments =
93+ superArgs . length === 1 &&
94+ firstSuperArg . type === 'SpreadElement' &&
95+ firstSuperArg . argument . name === 'arguments' ;
96+
97+ var passesParamsAsArgs =
98+ superArgs . length === ctorParams . length &&
99+ superArgs . every ( isSameIdentifier ) ||
100+ superArgs . length <= ctorParams . length &&
101+ superArgs . slice ( 0 , - 1 ) . every ( isSameIdentifier ) &&
102+ lastSuperArg . type === 'SpreadElement' &&
103+ ctorParams [ lastSuperArgIndex ] . type === 'RestElement' &&
104+ lastSuperArg . argument . name === ctorParams [ lastSuperArgIndex ] . argument . name ;
105+
106+ return isSimpleParameterList && ( spreadsArguments || passesParamsAsArgs ) ;
107+ }
108+
109+ /**
110+ * Check if a given AST node have any other properties the ones available in stateless components
68111 * @param {ASTNode } node The AST node being checked.
69- * @returns {Boolean } True if the node has at least one lifecycle method , false if not.
112+ * @returns {Boolean } True if the node has at least one other property , false if not.
70113 */
71- function hasLifecycleMethod ( node ) {
114+ function hasOtherProperties ( node ) {
72115 var properties = getComponentProperties ( node ) ;
73116 return properties . some ( function ( property ) {
74- return lifecycleMethods . indexOf ( getPropertyName ( property ) ) !== - 1 ;
117+ var name = getPropertyName ( property ) ;
118+ var isDisplayName = name === 'displayName' ;
119+ var isPropTypes = name === 'propTypes' || name === 'props' && property . typeAnnotation ;
120+ var contextTypes = name === 'contextTypes' ;
121+ var isUselessConstructor =
122+ property . kind === 'constructor' &&
123+ isRedundantSuperCall ( property . value . body . body , property . value . params )
124+ ;
125+ var isRender = name === 'render' ;
126+ return ! isDisplayName && ! isPropTypes && ! contextTypes && ! isUselessConstructor && ! isRender ;
75127 } ) ;
76128 }
77129
78130 /**
79131 * Mark a setState as used
80132 * @param {ASTNode } node The AST node being checked.
81133 */
82- function markSetStateAsUsed ( node ) {
134+ function markThisAsUsed ( node ) {
83135 components . set ( node , {
84- useSetState : true
136+ useThis : true
85137 } ) ;
86138 }
87139
@@ -95,18 +147,48 @@ module.exports = Components.detect(function(context, components, utils) {
95147 } ) ;
96148 }
97149
150+ /**
151+ * Mark return as invalid
152+ * @param {ASTNode } node The AST node being checked.
153+ */
154+ function markReturnAsInvalid ( node ) {
155+ components . set ( node , {
156+ invalidReturn : true
157+ } ) ;
158+ }
159+
98160 return {
99- CallExpression : function ( node ) {
100- var callee = node . callee ;
101- if ( callee . type !== 'MemberExpression' ) {
161+ // Mark `this` destructuring as a usage of `this`
162+ VariableDeclarator : function ( node ) {
163+ // Ignore destructuring on other than `this`
164+ if ( ! node . id || node . id . type !== 'ObjectPattern' || ! node . init || node . init . type !== 'ThisExpression' ) {
102165 return ;
103166 }
104- if ( callee . object . type !== 'ThisExpression' || callee . property . name !== 'setState' ) {
167+ // Ignore `props` and `context`
168+ var useThis = node . id . properties . some ( function ( property ) {
169+ var name = getPropertyName ( property ) ;
170+ return name !== 'props' && name !== 'context' ;
171+ } ) ;
172+ if ( ! useThis ) {
173+ return ;
174+ }
175+ markThisAsUsed ( node ) ;
176+ } ,
177+
178+ // Mark `this` usage
179+ MemberExpression : function ( node ) {
180+ // Ignore calls to `this.props` and `this.context`
181+ if (
182+ node . object . type !== 'ThisExpression' ||
183+ ( node . property . name || node . property . value ) === 'props' ||
184+ ( node . property . name || node . property . value ) === 'context'
185+ ) {
105186 return ;
106187 }
107- markSetStateAsUsed ( node ) ;
188+ markThisAsUsed ( node ) ;
108189 } ,
109190
191+ // Mark `ref` usage
110192 JSXAttribute : function ( node ) {
111193 var name = sourceCode . getText ( node . name ) ;
112194 if ( name !== 'ref' ) {
@@ -115,14 +197,32 @@ module.exports = Components.detect(function(context, components, utils) {
115197 markRefAsUsed ( node ) ;
116198 } ,
117199
200+ // Mark `render` that do not return some JSX
201+ ReturnStatement : function ( node ) {
202+ var blockNode ;
203+ var scope = context . getScope ( ) ;
204+ while ( scope ) {
205+ blockNode = scope . block && scope . block . parent ;
206+ if ( blockNode && ( blockNode . type === 'MethodDefinition' || blockNode . type === 'Property' ) ) {
207+ break ;
208+ }
209+ scope = scope . upper ;
210+ }
211+ if ( ! blockNode || ! blockNode . key || blockNode . key . name !== 'render' || utils . isReturningJSX ( node ) ) {
212+ return ;
213+ }
214+ markReturnAsInvalid ( node ) ;
215+ } ,
216+
118217 'Program:exit' : function ( ) {
119218 var list = components . list ( ) ;
120219 for ( var component in list ) {
121220 if (
122221 ! list . hasOwnProperty ( component ) ||
123- hasLifecycleMethod ( list [ component ] . node ) ||
124- list [ component ] . useSetState ||
222+ hasOtherProperties ( list [ component ] . node ) ||
223+ list [ component ] . useThis ||
125224 list [ component ] . useRef ||
225+ list [ component ] . invalidReturn ||
126226 ( ! utils . isES5Component ( list [ component ] . node ) && ! utils . isES6Component ( list [ component ] . node ) )
127227 ) {
128228 continue ;
0 commit comments