@@ -120,42 +120,30 @@ export default {
120120 let lastEffect = null ;
121121 const codePathReactHooksMapStack = [ ] ;
122122 const codePathSegmentStack = [ ] ;
123- const useEventViolations = new Set ( ) ;
124-
125- // For a given AST node, iterate through the top level statements and add all useEvent
126- // definitions. We can do this in non-Program nodes because we can rely on the assumption that
127- // useEvent functions can only be declared within a component or hook at its top level.
128- function addAllUseEventViolations ( node ) {
129- if ( node . body . type !== 'BlockStatement' ) return ;
130- for ( const statement of node . body . body ) {
131- if ( statement . type !== 'VariableDeclaration' ) continue ;
132- for ( const declaration of statement . declarations ) {
133- if (
134- declaration . type === 'VariableDeclarator' &&
135- declaration . init &&
136- declaration . init . type === 'CallExpression' &&
137- declaration . init . callee &&
138- isUseEventIdentifier ( declaration . init . callee )
139- ) {
140- useEventViolations . add ( declaration . id ) ;
123+ const useEventFunctions = new WeakSet ( ) ;
124+
125+ // For a given scope, iterate through the references and add all useEvent definitions. We can
126+ // do this in non-Program nodes because we can rely on the assumption that useEvent functions
127+ // can only be declared within a component or hook at its top level.
128+ function recordAllUseEventFunctions ( scope ) {
129+ for ( const reference of scope . references ) {
130+ const parent = reference . identifier . parent ;
131+ if (
132+ parent . type === 'VariableDeclarator' &&
133+ parent . init &&
134+ parent . init . type === 'CallExpression' &&
135+ parent . init . callee &&
136+ isUseEventIdentifier ( parent . init . callee )
137+ ) {
138+ for ( const ref of reference . resolved . references ) {
139+ if ( ref !== reference ) {
140+ useEventFunctions . add ( ref . identifier ) ;
141+ }
141142 }
142143 }
143144 }
144145 }
145146
146- // Resolve a useEvent violation, ie the useEvent created function was called.
147- function resolveUseEventViolation ( scope , ident ) {
148- if ( scope . references == null || useEventViolations . size === 0 ) return ;
149- for ( const ref of scope . references ) {
150- if ( ref . resolved == null ) continue ;
151- const [ useEventFunctionIdentifier ] = ref . resolved . identifiers ;
152- if ( ident . name === useEventFunctionIdentifier . name ) {
153- useEventViolations . delete ( useEventFunctionIdentifier ) ;
154- break ;
155- }
156- }
157- }
158-
159147 return {
160148 // Maintain code segment path stack as we traverse.
161149 onCodePathSegmentStart : segment => codePathSegmentStack . push ( segment ) ,
@@ -567,11 +555,6 @@ export default {
567555 reactHooks . push ( node . callee ) ;
568556 }
569557
570- const scope = context . getScope ( ) ;
571- // useEvent: Resolve a function created with useEvent that is invoked locally at least once.
572- // OK - onClick();
573- resolveUseEventViolation ( scope , node . callee ) ;
574-
575558 // useEvent: useEvent functions can be passed by reference within useEffect as well as in
576559 // another useEvent
577560 if (
@@ -587,9 +570,21 @@ export default {
587570 } ,
588571
589572 Identifier ( node ) {
590- // OK - useEffect(() => { setInterval(onClick, ...) }, []);
591- if ( lastEffect != null && node . parent . type === 'CallExpression' ) {
592- resolveUseEventViolation ( context . getScope ( ) , node ) ;
573+ // This identifier resolves to a useEvent function, but isn't being referenced in an
574+ // effect or another event function. It isn't being called either.
575+ if (
576+ lastEffect == null &&
577+ useEventFunctions . has ( node ) &&
578+ node . parent . type !== 'CallExpression'
579+ ) {
580+ context . report ( {
581+ node,
582+ message :
583+ `\`${ context . getSource (
584+ node ,
585+ ) } \` is a function created with React Hook "useEvent", and can only be called from ` +
586+ 'the same component. They cannot be assigned to variables or passed down.' ,
587+ } ) ;
593588 }
594589 } ,
595590
@@ -602,27 +597,14 @@ export default {
602597 FunctionDeclaration ( node ) {
603598 // function MyComponent() { const onClick = useEvent(...) }
604599 if ( isInsideComponentOrHook ( node ) ) {
605- addAllUseEventViolations ( node ) ;
600+ recordAllUseEventFunctions ( context . getScope ( ) ) ;
606601 }
607602 } ,
608603
609604 ArrowFunctionExpression ( node ) {
610605 // const MyComponent = () => { const onClick = useEvent(...) }
611606 if ( isInsideComponentOrHook ( node ) ) {
612- addAllUseEventViolations ( node ) ;
613- }
614- } ,
615-
616- 'Program:exit' ( _node ) {
617- for ( const node of useEventViolations . values ( ) ) {
618- context . report ( {
619- node,
620- message :
621- `\`${ context . getSource (
622- node ,
623- ) } \` is a function created with React Hook "useEvent", and can only be called from ` +
624- 'the same component. They cannot be assigned to variables or passed down.' ,
625- } ) ;
607+ recordAllUseEventFunctions ( context . getScope ( ) ) ;
626608 }
627609 } ,
628610 } ;
0 commit comments