11'use strict' ; 
22
33const  assert  =  require ( 'assert' ) ; 
4+ const  entries  =  require ( 'object.entries' ) ; 
45const  eslint  =  require ( 'eslint' ) ; 
6+ const  fromEntries  =  require ( 'object.fromentries' ) ; 
57const  values  =  require ( 'object.values' ) ; 
68
79const  Components  =  require ( '../../lib/util/Components' ) ; 
@@ -19,12 +21,32 @@ const ruleTester = new eslint.RuleTester({
1921
2022describe ( 'Components' ,  ( )  =>  { 
2123 describe ( 'static detect' ,  ( )  =>  { 
22-  function  testComponentsDetect ( test ,  done )  { 
23-  const  rule  =  Components . detect ( ( context ,  components ,  util )  =>  ( { 
24-  'Program:exit' ( )  { 
25-  done ( context ,  components ,  util ) ; 
26-  } , 
27-  } ) ) ; 
24+  function  testComponentsDetect ( test ,  instructionsOrDone ,  orDone )  { 
25+  const  done  =  orDone  ||  instructionsOrDone ; 
26+  const  instructions  =  orDone  ? instructionsOrDone  : instructionsOrDone ; 
27+ 
28+  const  rule  =  Components . detect ( ( _context ,  components ,  util )  =>  { 
29+  const  instructionResults  =  [ ] ; 
30+ 
31+  const  augmentedInstructions  =  fromEntries ( 
32+  entries ( instructions  ||  { } ) . map ( ( nodeTypeAndHandler )  =>  { 
33+  const  nodeType  =  nodeTypeAndHandler [ 0 ] ; 
34+  const  handler  =  nodeTypeAndHandler [ 1 ] ; 
35+  return  [ nodeType ,  ( node )  =>  { 
36+  instructionResults . push ( {  type : nodeType ,  result : handler ( node ,  context ,  components ,  util )  } ) ; 
37+  } ] ; 
38+  } ) 
39+  ) ; 
40+ 
41+  return  Object . assign ( { } ,  augmentedInstructions ,  { 
42+  'Program:exit' ( node )  { 
43+  if  ( augmentedInstructions [ 'Program:exit' ] )  { 
44+  augmentedInstructions [ 'Program:exit' ] ( node ,  context ,  components ,  util ) ; 
45+  } 
46+  done ( components ,  instructionResults ) ; 
47+  } , 
48+  } ) ; 
49+  } ) ; 
2850
2951 const  tests  =  { 
3052 valid : parsers . all ( [ Object . assign ( { } ,  test ,  { 
@@ -36,6 +58,7 @@ describe('Components', () => {
3658 } ) ] ) , 
3759 invalid : [ ] , 
3860 } ; 
61+ 
3962 ruleTester . run ( test . code ,  rule ,  tests ) ; 
4063 } 
4164
@@ -45,7 +68,7 @@ describe('Components', () => {
4568 function MyStatelessComponent() { 
4669 return <React.Fragment />; 
4770 }` , 
48-  } ,  ( _context ,   components )  =>  { 
71+  } ,  ( components )  =>  { 
4972 assert . equal ( components . length ( ) ,  1 ,  'MyStatelessComponent should be detected component' ) ; 
5073 values ( components . list ( ) ) . forEach ( ( component )  =>  { 
5174 assert . equal ( 
@@ -65,7 +88,7 @@ describe('Components', () => {
6588 return <React.Fragment />; 
6689 } 
6790 }` , 
68-  } ,  ( _context ,   components )  =>  { 
91+  } ,  ( components )  =>  { 
6992 assert ( components . length ( )  ===  1 ,  'MyClassComponent should be detected component' ) ; 
7093 values ( components . list ( ) ) . forEach ( ( component )  =>  { 
7194 assert . equal ( 
@@ -80,7 +103,7 @@ describe('Components', () => {
80103 it ( 'should detect React Imports' ,  ( )  =>  { 
81104 testComponentsDetect ( { 
82105 code : 'import React, { useCallback, useState } from \'react\'' , 
83-  } ,  ( _context ,   components )  =>  { 
106+  } ,  ( components )  =>  { 
84107 assert . deepEqual ( 
85108 components . getDefaultReactImports ( ) . map ( ( specifier )  =>  specifier . local . name ) , 
86109 [ 'React' ] , 
@@ -94,5 +117,186 @@ describe('Components', () => {
94117 ) ; 
95118 } ) ; 
96119 } ) ; 
120+ 
121+  describe ( 'utils' ,  ( )  =>  { 
122+  describe ( 'isReactHookCall' ,  ( )  =>  { 
123+  it ( 'should not identify hook-like call' ,  ( )  =>  { 
124+  testComponentsDetect ( { 
125+  code : `import { useRef } from 'react' 
126+  function useColor() { 
127+  return useState() 
128+  }` , 
129+  } ,  { 
130+  CallExpression : ( node ,  _context ,  _components ,  util )  =>  util . isReactHookCall ( node ) , 
131+  } ,  ( _components ,  instructionResults )  =>  { 
132+  assert . deepEqual ( instructionResults ,  [ {  type : 'CallExpression' ,  result : false  } ] ) ; 
133+  } ) ; 
134+  } ) ; 
135+ 
136+  it ( 'should identify hook call' ,  ( )  =>  { 
137+  testComponentsDetect ( { 
138+  code : `import { useState } from 'react' 
139+  function useColor() { 
140+  return useState() 
141+  }` , 
142+  } ,  { 
143+  CallExpression : ( node ,  _context ,  _components ,  util )  =>  util . isReactHookCall ( node ) , 
144+  } ,  ( _components ,  instructionResults )  =>  { 
145+  assert . deepEqual ( instructionResults ,  [ {  type : 'CallExpression' ,  result : true  } ] ) ; 
146+  } ) ; 
147+  } ) ; 
148+ 
149+  it ( 'should identify aliased hook call' ,  ( )  =>  { 
150+  testComponentsDetect ( { 
151+  code : `import { useState as useStateAlternative } from 'react' 
152+  function useColor() { 
153+  return useStateAlternative() 
154+  }` , 
155+  } ,  { 
156+  CallExpression : ( node ,  _context ,  _components ,  util )  =>  util . isReactHookCall ( node ) , 
157+  } ,  ( _components ,  instructionResults )  =>  { 
158+  assert . deepEqual ( instructionResults ,  [ {  type : 'CallExpression' ,  result : true  } ] ) ; 
159+  } ) ; 
160+  } ) ; 
161+ 
162+  it ( 'should identify aliased present named hook call' ,  ( )  =>  { 
163+  testComponentsDetect ( { 
164+  code : `import { useState as useStateAlternative } from 'react' 
165+  function useColor() { 
166+  return useStateAlternative() 
167+  }` , 
168+  } ,  { 
169+  CallExpression : ( node ,  _context ,  _components ,  util )  =>  util . isReactHookCall ( node ,  [ 'useState' ] ) , 
170+  } ,  ( _components ,  instructionResults )  =>  { 
171+  assert . deepEqual ( instructionResults ,  [ {  type : 'CallExpression' ,  result : true  } ] ) ; 
172+  } ) ; 
173+  } ) ; 
174+ 
175+  it ( 'should not identify shadowed hook call' ,  ( )  =>  { 
176+  testComponentsDetect ( { 
177+  code : `import { useState } from 'react' 
178+  function useColor() { 
179+  function useState() { 
180+  return null 
181+  } 
182+  return useState() 
183+  }` , 
184+  } ,  { 
185+  CallExpression : ( node ,  _context ,  _components ,  util )  =>  util . isReactHookCall ( node ) , 
186+  } ,  ( _components ,  instructionResults )  =>  { 
187+  assert . deepEqual ( instructionResults ,  [ {  type : 'CallExpression' ,  result : false  } ] ) ; 
188+  } ) ; 
189+  } ) ; 
190+ 
191+  it ( 'should not identify shadowed aliased present named hook call' ,  ( )  =>  { 
192+  testComponentsDetect ( { 
193+  code : `import { useState as useStateAlternative } from 'react' 
194+  function useColor() { 
195+  function useStateAlternative() { 
196+  return null 
197+  } 
198+  return useStateAlternative() 
199+  }` , 
200+  } ,  { 
201+  CallExpression : ( node ,  _context ,  _components ,  util )  =>  util . isReactHookCall ( node ,  [ 'useState' ] ) , 
202+  } ,  ( _components ,  instructionResults )  =>  { 
203+  assert . deepEqual ( instructionResults ,  [ {  type : 'CallExpression' ,  result : false  } ] ) ; 
204+  } ) ; 
205+  } ) ; 
206+ 
207+  it ( 'should identify React hook call' ,  ( )  =>  { 
208+  testComponentsDetect ( { 
209+  code : `import React from 'react' 
210+  function useColor() { 
211+  return React.useState() 
212+  }` , 
213+  } ,  { 
214+  CallExpression : ( node ,  _context ,  _components ,  util )  =>  util . isReactHookCall ( node ) , 
215+  } ,  ( _components ,  instructionResults )  =>  { 
216+  assert . deepEqual ( instructionResults ,  [ {  type : 'CallExpression' ,  result : true  } ] ) ; 
217+  } ) ; 
218+  } ) ; 
219+ 
220+  it ( 'should identify aliased React hook call' ,  ( )  =>  { 
221+  testComponentsDetect ( { 
222+  code : `import ReactAlternative from 'react' 
223+  function useColor() { 
224+  return ReactAlternative.useState() 
225+  }` , 
226+  } ,  { 
227+  CallExpression : ( node ,  _context ,  _components ,  util )  =>  util . isReactHookCall ( node ) , 
228+  } ,  ( _components ,  instructionResults )  =>  { 
229+  assert . deepEqual ( instructionResults ,  [ {  type : 'CallExpression' ,  result : true  } ] ) ; 
230+  } ) ; 
231+  } ) ; 
232+ 
233+  it ( 'should not identify shadowed React hook call' ,  ( )  =>  { 
234+  testComponentsDetect ( { 
235+  code : `import React from 'react' 
236+  function useColor() { 
237+  const React = { 
238+  useState: () => null 
239+  } 
240+  return React.useState() 
241+  }` , 
242+  } ,  { 
243+  CallExpression : ( node ,  _context ,  _components ,  util )  =>  util . isReactHookCall ( node ) , 
244+  } ,  ( _components ,  instructionResults )  =>  { 
245+  assert . deepEqual ( instructionResults ,  [ {  type : 'CallExpression' ,  result : false  } ] ) ; 
246+  } ) ; 
247+  } ) ; 
248+ 
249+  it ( 'should identify present named hook call' ,  ( )  =>  { 
250+  testComponentsDetect ( { 
251+  code : `import { useState } from 'react' 
252+  function useColor() { 
253+  return useState() 
254+  }` , 
255+  } ,  { 
256+  CallExpression : ( node ,  _context ,  _components ,  util )  =>  util . isReactHookCall ( node ,  [ 'useState' ] ) , 
257+  } ,  ( _components ,  instructionResults )  =>  { 
258+  assert . deepEqual ( instructionResults ,  [ {  type : 'CallExpression' ,  result : true  } ] ) ; 
259+  } ) ; 
260+  } ) ; 
261+ 
262+  it ( 'should identify present named React hook call' ,  ( )  =>  { 
263+  testComponentsDetect ( { 
264+  code : `import React from 'react' 
265+  function useColor() { 
266+  return React.useState() 
267+  }` , 
268+  } ,  { 
269+  CallExpression : ( node ,  _context ,  _components ,  util )  =>  util . isReactHookCall ( node ,  [ 'useState' ] ) , 
270+  } ,  ( _components ,  instructionResults )  =>  { 
271+  assert . deepEqual ( instructionResults ,  [ {  type : 'CallExpression' ,  result : true  } ] ) ; 
272+  } ) ; 
273+  } ) ; 
274+ 
275+  it ( 'should not identify missing named hook call' ,  ( )  =>  { 
276+  testComponentsDetect ( { 
277+  code : `import { useState } from 'react' 
278+  function useColor() { 
279+  return useState() 
280+  }` , 
281+  } ,  { 
282+  CallExpression : ( node ,  _context ,  _components ,  util )  =>  util . isReactHookCall ( node ,  [ 'useRef' ] ) , 
283+  } ,  ( _components ,  instructionResults )  =>  { 
284+  assert . deepEqual ( instructionResults ,  [ {  type : 'CallExpression' ,  result : false  } ] ) ; 
285+  } ) ; 
286+  } ) ; 
287+  } ) ; 
288+  } ) ; 
289+ 
290+  describe ( 'testComponentsDetect' ,  ( )  =>  { 
291+  it ( 'should log Program:exit instruction' ,  ( )  =>  { 
292+  testComponentsDetect ( { 
293+  code : '' , 
294+  } ,  { 
295+  'Program:exit' : ( )  =>  true , 
296+  } ,  ( _components ,  instructionResults )  =>  { 
297+  assert . deepEqual ( instructionResults ,  [ {  type : 'Program:exit' ,  result : true  } ] ) ; 
298+  } ) ; 
299+  } ) ; 
300+  } ) ; 
97301 } ) ; 
98302} ) ; 
0 commit comments