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