@@ -31,32 +31,36 @@ const messages = {
3131module . exports = {
3232 meta : {
3333 docs : {
34- description : 'Disallow missing displayName in a React component definition' ,
34+ description :
35+ 'Disallow missing displayName in a React component definition' ,
3536 category : 'Best Practices' ,
3637 recommended : true ,
3738 url : docsUrl ( 'display-name' ) ,
3839 } ,
3940
4041 messages,
4142
42- schema : [ {
43- type : 'object' ,
44- properties : {
45- ignoreTranspilerName : {
46- type : 'boolean' ,
47- } ,
48- checkContextObjects : {
49- type : 'boolean' ,
43+ schema : [
44+ {
45+ type : 'object' ,
46+ properties : {
47+ ignoreTranspilerName : {
48+ type : 'boolean' ,
49+ } ,
50+ checkContextObjects : {
51+ type : 'boolean' ,
52+ } ,
5053 } ,
54+ additionalProperties : false ,
5155 } ,
52- additionalProperties : false ,
53- } ] ,
56+ ] ,
5457 } ,
5558
5659 create : Components . detect ( ( context , components , utils ) => {
5760 const config = context . options [ 0 ] || { } ;
5861 const ignoreTranspilerName = config . ignoreTranspilerName || false ;
59- const checkContextObjects = ( config . checkContextObjects || false ) && testReactVersion ( context , '>= 16.3.0' ) ;
62+ const checkContextObjects = ( config . checkContextObjects || false )
63+ && testReactVersion ( context , '>= 16.3.0' ) ;
6064
6165 const contextObjects = new Map ( ) ;
6266
@@ -76,10 +80,12 @@ module.exports = {
7680 * @returns {boolean } True if React.forwardRef is nested inside React.memo, false if not.
7781 */
7882 function isNestedMemo ( node ) {
79- return astUtil . isCallExpression ( node )
83+ return (
84+ astUtil . isCallExpression ( node )
8085 && node . arguments
8186 && astUtil . isCallExpression ( node . arguments [ 0 ] )
82- && utils . isPragmaComponentWrapper ( node ) ;
87+ && utils . isPragmaComponentWrapper ( node )
88+ ) ;
8389 }
8490
8591 /**
@@ -115,60 +121,142 @@ module.exports = {
115121 * @returns {boolean } True if component has a name, false if not.
116122 */
117123 function hasTranspilerName ( node ) {
118- const namedObjectAssignment = (
119- node . type === 'ObjectExpression'
124+ const namedObjectAssignment = node . type === 'ObjectExpression'
120125 && node . parent
121126 && node . parent . parent
122127 && node . parent . parent . type === 'AssignmentExpression'
123- && (
124- ! node . parent . parent . left . object
128+ && ( ! node . parent . parent . left . object
125129 || node . parent . parent . left . object . name !== 'module'
126- || node . parent . parent . left . property . name !== 'exports'
127- )
128- ) ;
129- const namedObjectDeclaration = (
130- node . type === 'ObjectExpression'
130+ || node . parent . parent . left . property . name !== 'exports' ) ;
131+ const namedObjectDeclaration = node . type === 'ObjectExpression'
131132 && node . parent
132133 && node . parent . parent
133- && node . parent . parent . type === 'VariableDeclarator'
134- ) ;
135- const namedClass = (
136- ( node . type === 'ClassDeclaration' || node . type === 'ClassExpression' )
134+ && node . parent . parent . type === 'VariableDeclarator' ;
135+ const namedClass = ( node . type === 'ClassDeclaration' || node . type === 'ClassExpression' )
137136 && node . id
138- && ! ! node . id . name
139- ) ;
137+ && ! ! node . id . name ;
140138
141- const namedFunctionDeclaration = (
142- ( node . type === 'FunctionDeclaration' || node . type === 'FunctionExpression' )
139+ const namedFunctionDeclaration = ( node . type === 'FunctionDeclaration'
140+ || node . type === 'FunctionExpression' )
143141 && node . id
144- && ! ! node . id . name
145- ) ;
142+ && ! ! node . id . name ;
146143
147- const namedFunctionExpression = (
148- astUtil . isFunctionLikeExpression ( node )
144+ const namedFunctionExpression = astUtil . isFunctionLikeExpression ( node )
149145 && node . parent
150- && ( node . parent . type === 'VariableDeclarator' || node . parent . type === 'Property' || node . parent . method === true )
151- && ( ! node . parent . parent || ! componentUtil . isES5Component ( node . parent . parent , context ) )
152- ) ;
146+ && ( node . parent . type === 'VariableDeclarator'
147+ || node . parent . type === 'Property'
148+ || node . parent . method === true )
149+ && ( ! node . parent . parent
150+ || ! componentUtil . isES5Component ( node . parent . parent , context ) ) ;
153151
154152 if (
155- namedObjectAssignment || namedObjectDeclaration
153+ namedObjectAssignment
154+ || namedObjectDeclaration
156155 || namedClass
157- || namedFunctionDeclaration || namedFunctionExpression
156+ || namedFunctionDeclaration
157+ || namedFunctionExpression
158158 ) {
159159 return true ;
160160 }
161161 return false ;
162162 }
163163
164+ function hasVariableDeclaration ( node , name ) {
165+ if ( ! node ) return false ;
166+
167+ if ( node . type === 'VariableDeclaration' ) {
168+ return node . declarations . some ( ( decl ) => {
169+ if ( ! decl . id ) return false ;
170+
171+ // const name = ...
172+ if ( decl . id . type === 'Identifier' && decl . id . name === name ) {
173+ return true ;
174+ }
175+
176+ // const [name] = ...
177+ if ( decl . id . type === 'ArrayPattern' ) {
178+ return decl . id . elements . some (
179+ ( el ) => el && el . type === 'Identifier' && el . name === name
180+ ) ;
181+ }
182+
183+ // const { name } = ...
184+ if ( decl . id . type === 'ObjectPattern' ) {
185+ return decl . id . properties . some (
186+ ( prop ) => prop . type === 'Property' && prop . key && prop . key . name === name
187+ ) ;
188+ }
189+
190+ return false ;
191+ } ) ;
192+ }
193+
194+ if ( node . type === 'BlockStatement' && node . body ) {
195+ return node . body . some ( ( stmt ) => hasVariableDeclaration ( stmt , name ) ) ;
196+ }
197+
198+ return false ;
199+ }
200+
201+ function isIdentifierShadowed ( node , identifierName ) {
202+ while ( node && node . parent ) {
203+ node = node . parent ;
204+ if (
205+ node . type === 'FunctionDeclaration'
206+ || node . type === 'FunctionExpression'
207+ || node . type === 'ArrowFunctionExpression'
208+ ) {
209+ break ;
210+ }
211+ }
212+
213+ if ( ! node || ! node . body ) {
214+ return false ;
215+ }
216+
217+ return hasVariableDeclaration ( node . body , identifierName ) ;
218+ }
219+
220+ /**
221+ *
222+ * Check is current component shadowed
223+ * @param {ASTNode } node The AST node being checked.
224+ * @returns {boolean } True if component has a name, false if not.
225+ */
226+
227+ function isShadowedComponent ( node ) {
228+ if ( ! node || node . type !== 'CallExpression' ) {
229+ return false ;
230+ }
231+
232+ if (
233+ node . callee . type === 'MemberExpression'
234+ && node . callee . object . name === 'React'
235+ ) {
236+ return isIdentifierShadowed ( node , 'React' ) ;
237+ }
238+
239+ if ( node . callee . type === 'Identifier' ) {
240+ const name = node . callee . name ;
241+ if ( name === 'memo' || name === 'forwardRef' ) {
242+ return isIdentifierShadowed ( node , name ) ;
243+ }
244+ }
245+
246+ return false ;
247+ }
248+
164249 // --------------------------------------------------------------------------
165250 // Public
166251 // --------------------------------------------------------------------------
167252
168253 return {
169254 ExpressionStatement ( node ) {
170255 if ( checkContextObjects && isCreateContext ( node ) ) {
171- contextObjects . set ( node . expression . left . name , { node, hasDisplayName : false } ) ;
256+ contextObjects . set ( node . expression . left . name , {
257+ node,
258+ hasDisplayName : false ,
259+ } ) ;
172260 }
173261 } ,
174262 VariableDeclarator ( node ) {
@@ -232,7 +320,10 @@ module.exports = {
232320 if ( ignoreTranspilerName || ! hasTranspilerName ( node ) ) {
233321 // Search for the displayName declaration
234322 node . properties . forEach ( ( property ) => {
235- if ( ! property . key || ! propsUtil . isDisplayNameDeclaration ( property . key ) ) {
323+ if (
324+ ! property . key
325+ || ! propsUtil . isDisplayNameDeclaration ( property . key )
326+ ) {
236327 return ;
237328 }
238329 markDisplayNameAsDeclared ( node ) ;
@@ -247,7 +338,10 @@ module.exports = {
247338 return ;
248339 }
249340
250- if ( node . arguments . length > 0 && astUtil . isFunctionLikeExpression ( node . arguments [ 0 ] ) ) {
341+ if (
342+ node . arguments . length > 0
343+ && astUtil . isFunctionLikeExpression ( node . arguments [ 0 ] )
344+ ) {
251345 // Skip over React.forwardRef declarations that are embedded within
252346 // a React.memo i.e. React.memo(React.forwardRef(/* ... */))
253347 // This means that we raise a single error for the call to React.memo
@@ -269,9 +363,16 @@ module.exports = {
269363 'Program:exit' ( ) {
270364 const list = components . list ( ) ;
271365 // Report missing display name for all components
272- values ( list ) . filter ( ( component ) => ! component . hasDisplayName ) . forEach ( ( component ) => {
273- reportMissingDisplayName ( component ) ;
274- } ) ;
366+ values ( list )
367+ . filter ( ( component ) => {
368+ if ( isShadowedComponent ( component . node ) ) {
369+ return false ;
370+ }
371+ return ! component . hasDisplayName ;
372+ } )
373+ . forEach ( ( component ) => {
374+ reportMissingDisplayName ( component ) ;
375+ } ) ;
275376 if ( checkContextObjects ) {
276377 // Report missing display name for all context objects
277378 forEach (
0 commit comments