@@ -36,17 +36,17 @@ interface CachedDirective {
3636
3737interface NamedRange {
3838 name : string
39- range : DecorationSegment
39+ ranges : DecorationSegment [ ]
4040}
4141
4242interface LocalComponent {
4343 kind : Exclude < ComponentKind , 'unknown' >
44- range : DecorationSegment
44+ ranges : DecorationSegment [ ]
4545}
4646
4747interface FileAnalysis {
4848 exportReferences : NamedRange [ ]
49- imports : Map < string , { range : DecorationSegment ; source : string } >
49+ imports : Map < string , { ranges : DecorationSegment [ ] ; source : string } >
5050 jsxTags : JsxTagReference [ ]
5151 localComponents : Map < string , LocalComponent >
5252 ownComponentKind : Exclude < ComponentKind , 'unknown' >
@@ -62,7 +62,7 @@ interface JsxTagReference {
6262interface TypeIdentifier {
6363 enclosingComponent : string | undefined
6464 name : string
65- range : DecorationSegment
65+ ranges : DecorationSegment [ ]
6666}
6767
6868export class ComponentLensAnalyzer {
@@ -182,7 +182,7 @@ export class ComponentLensAnalyzer {
182182
183183 usages . push ( {
184184 kind : componentKind ,
185- ranges : [ entry . range ] ,
185+ ranges : entry . ranges ,
186186 sourceFilePath : resolvedFilePath ,
187187 tagName : name ,
188188 } )
@@ -193,7 +193,7 @@ export class ComponentLensAnalyzer {
193193 for ( const [ name , component ] of analysis . localComponents ) {
194194 usages . push ( {
195195 kind : component . kind ,
196- ranges : [ component . range ] ,
196+ ranges : component . ranges ,
197197 sourceFilePath : filePath ,
198198 tagName : name ,
199199 } )
@@ -217,7 +217,7 @@ export class ComponentLensAnalyzer {
217217 }
218218 usages . push ( {
219219 kind,
220- ranges : [ typeId . range ] ,
220+ ranges : typeId . ranges ,
221221 sourceFilePath : filePath ,
222222 tagName : typeId . name ,
223223 } )
@@ -229,7 +229,7 @@ export class ComponentLensAnalyzer {
229229 for ( const typeId of deferredDeclarations ) {
230230 usages . push ( {
231231 kind : typeUsageKinds . get ( typeId . name ) ?? analysis . ownComponentKind ,
232- ranges : [ typeId . range ] ,
232+ ranges : typeId . ranges ,
233233 sourceFilePath : filePath ,
234234 tagName : typeId . name ,
235235 } )
@@ -240,7 +240,7 @@ export class ComponentLensAnalyzer {
240240 for ( const exportRef of analysis . exportReferences ) {
241241 usages . push ( {
242242 kind : analysis . ownComponentKind ,
243- ranges : [ exportRef . range ] ,
243+ ranges : exportRef . ranges ,
244244 sourceFilePath : filePath ,
245245 tagName : exportRef . name ,
246246 } )
@@ -311,7 +311,7 @@ function parseFileAnalysis(filePath: string, sourceText: string): FileAnalysis {
311311 const exportReferences : NamedRange [ ] = [ ]
312312 const imports = new Map <
313313 string ,
314- { range : DecorationSegment ; source : string }
314+ { ranges : DecorationSegment [ ] ; source : string }
315315 > ( )
316316 const localComponents = new Map < string , LocalComponent > ( )
317317 const typeIdentifiers : TypeIdentifier [ ] = [ ]
@@ -330,7 +330,7 @@ function parseFileAnalysis(filePath: string, sourceText: string): FileAnalysis {
330330 ) : void => {
331331 localComponents . set ( name , {
332332 kind : ownComponentKind ,
333- range : nodeRange ( nameNode ) ,
333+ ranges : [ nodeRange ( nameNode ) ] ,
334334 } )
335335 componentRanges . push ( {
336336 end : scopeNode . end ,
@@ -341,12 +341,18 @@ function parseFileAnalysis(filePath: string, sourceText: string): FileAnalysis {
341341
342342 const hasAsyncModifier = (
343343 modifiers : ts . NodeArray < ts . ModifierLike > | undefined ,
344- ) : boolean => modifiers ?. some ( ( m ) => m . kind === ASYNC_KEYWORD ) ?? false
344+ ) : boolean => {
345+ if ( ! modifiers ) return false
346+ for ( let i = 0 ; i < modifiers . length ; i ++ ) {
347+ if ( modifiers [ i ] ! . kind === ASYNC_KEYWORD ) return true
348+ }
349+ return false
350+ }
345351
346352 const addImport = ( identifier : ts . Identifier , source : string ) : void => {
347353 if ( isComponentIdentifier ( identifier . text ) ) {
348354 imports . set ( identifier . text , {
349- range : nodeRange ( identifier ) ,
355+ ranges : [ nodeRange ( identifier ) ] ,
350356 source,
351357 } )
352358 }
@@ -423,7 +429,7 @@ function parseFileAnalysis(filePath: string, sourceText: string): FileAnalysis {
423429 typeIdentifiers . push ( {
424430 enclosingComponent : undefined ,
425431 name : statement . name . text ,
426- range : nodeRange ( statement . name ) ,
432+ ranges : [ nodeRange ( statement . name ) ] ,
427433 } )
428434 continue
429435 }
@@ -434,7 +440,7 @@ function parseFileAnalysis(filePath: string, sourceText: string): FileAnalysis {
434440 if ( isComponentIdentifier ( element . name . text ) ) {
435441 exportReferences . push ( {
436442 name : element . name . text ,
437- range : nodeRange ( element . name ) ,
443+ ranges : [ nodeRange ( element . name ) ] ,
438444 } )
439445 }
440446 }
@@ -450,7 +456,7 @@ function parseFileAnalysis(filePath: string, sourceText: string): FileAnalysis {
450456 ) {
451457 exportReferences . push ( {
452458 name : statement . expression . text ,
453- range : nodeRange ( statement . expression ) ,
459+ ranges : [ nodeRange ( statement . expression ) ] ,
454460 } )
455461 continue
456462 }
@@ -465,7 +471,7 @@ function parseFileAnalysis(filePath: string, sourceText: string): FileAnalysis {
465471 continue
466472 }
467473
468- if ( ts . isClassExpression ( declaration . initializer ) ) {
474+ if ( declaration . initializer . kind === SK_ClassExpr ) {
469475 registerComponent (
470476 declaration . name . text ,
471477 declaration . name ,
@@ -518,13 +524,17 @@ const SK_TypeReference = ts.SyntaxKind.TypeReference
518524const SK_ImportDecl = ts . SyntaxKind . ImportDeclaration
519525const SK_EnumDecl = ts . SyntaxKind . EnumDeclaration
520526const SK_ExportDecl = ts . SyntaxKind . ExportDeclaration
521-
522- const COMPONENT_WRAPPER_NAMES = new Set ( [
523- 'forwardRef' ,
524- 'memo' ,
525- 'React.forwardRef' ,
526- 'React.memo' ,
527- ] )
527+ const SK_FunctionDecl = ts . SyntaxKind . FunctionDeclaration
528+ const SK_VariableDecl = ts . SyntaxKind . VariableDeclaration
529+ const SK_JsxAttribute = ts . SyntaxKind . JsxAttribute
530+ const SK_JsxExpression = ts . SyntaxKind . JsxExpression
531+ const SK_ArrowFunction = ts . SyntaxKind . ArrowFunction
532+ const SK_FunctionExpr = ts . SyntaxKind . FunctionExpression
533+ const SK_CallExpression = ts . SyntaxKind . CallExpression
534+ const SK_ClassExpr = ts . SyntaxKind . ClassExpression
535+ const SK_Block = ts . SyntaxKind . Block
536+ const SK_ExprStmt = ts . SyntaxKind . ExpressionStatement
537+ const SK_StringLiteral = ts . SyntaxKind . StringLiteral
528538
529539function isComponentIdentifier ( name : string ) : boolean {
530540 const code = name . charCodeAt ( 0 )
@@ -534,18 +544,22 @@ function isComponentIdentifier(name: string): boolean {
534544function getComponentFunction (
535545 initializer : ts . Expression ,
536546) : ts . ArrowFunction | ts . FunctionExpression | undefined {
537- if ( ts . isArrowFunction ( initializer ) || ts . isFunctionExpression ( initializer ) ) {
538- return initializer
539- }
540-
541- if (
542- ts . isCallExpression ( initializer ) &&
543- COMPONENT_WRAPPER_NAMES . has ( getCalleeText ( initializer . expression ) )
544- ) {
545- return initializer . arguments . find (
546- ( arg ) : arg is ts . ArrowFunction | ts . FunctionExpression =>
547- ts . isArrowFunction ( arg ) || ts . isFunctionExpression ( arg ) ,
548- )
547+ const kind = initializer . kind
548+ if ( kind === SK_ArrowFunction || kind === SK_FunctionExpr ) {
549+ return initializer as ts . ArrowFunction | ts . FunctionExpression
550+ }
551+
552+ if ( kind === SK_CallExpression ) {
553+ const call = initializer as ts . CallExpression
554+ if ( isComponentWrapper ( call . expression ) ) {
555+ const args = call . arguments
556+ for ( let i = 0 ; i < args . length ; i ++ ) {
557+ const argKind = args [ i ] ! . kind
558+ if ( argKind === SK_ArrowFunction || argKind === SK_FunctionExpr ) {
559+ return args [ i ] as ts . ArrowFunction | ts . FunctionExpression
560+ }
561+ }
562+ }
549563 }
550564
551565 return undefined
@@ -555,38 +569,38 @@ function hasUseServerDirective(
555569 fn : ts . ArrowFunction | ts . FunctionDeclaration | ts . FunctionExpression ,
556570) : boolean {
557571 const body = fn . body
558- if ( ! body || ! ts . isBlock ( body ) ) {
572+ if ( ! body || body . kind !== SK_Block ) {
559573 return false
560574 }
561575
562- for ( const stmt of body . statements ) {
563- if (
564- ! ts . isExpressionStatement ( stmt ) ||
565- ! ts . isStringLiteral ( stmt . expression )
566- ) {
567- break
568- }
569- if ( stmt . expression . text === 'use server' ) {
576+ const statements = ( body as ts . Block ) . statements
577+ for ( let i = 0 ; i < statements . length ; i ++ ) {
578+ const stmt = statements [ i ] !
579+ if ( stmt . kind !== SK_ExprStmt ) break
580+ const expr = ( stmt as ts . ExpressionStatement ) . expression
581+ if ( expr . kind !== SK_StringLiteral ) break
582+ if ( ( expr as ts . StringLiteral ) . text === 'use server' ) {
570583 return true
571584 }
572585 }
573586
574587 return false
575588}
576589
577- function getCalleeText ( expression : ts . Expression ) : string {
578- if ( ts . isIdentifier ( expression ) ) {
579- return expression . text
580- }
581-
582- if (
583- ts . isPropertyAccessExpression ( expression ) &&
584- ts . isIdentifier ( expression . expression )
585- ) {
586- return `${ expression . expression . text } .${ expression . name . text } `
590+ function isComponentWrapper ( expr : ts . Expression ) : boolean {
591+ if ( expr . kind === SK_Identifier ) {
592+ const text = ( expr as ts . Identifier ) . text
593+ return text === 'forwardRef' || text === 'memo'
594+ }
595+ if ( expr . kind === SK_PropertyAccess ) {
596+ const pa = expr as ts . PropertyAccessExpression
597+ return (
598+ pa . expression . kind === SK_Identifier &&
599+ ( pa . expression as ts . Identifier ) . text === 'React' &&
600+ ( pa . name . text === 'forwardRef' || pa . name . text === 'memo' )
601+ )
587602 }
588-
589- return ''
603+ return false
590604}
591605
592606function collectSourceElements (
@@ -664,14 +678,15 @@ function collectSourceElements(
664678 }
665679 } else if ( nodeKind === SK_TypeReference ) {
666680 const typeName = ( node as ts . TypeReferenceNode ) . typeName
667- if ( ts . isIdentifier ( typeName ) && isComponentIdentifier ( typeName . text ) ) {
681+ if (
682+ typeName . kind === SK_Identifier &&
683+ isComponentIdentifier ( ( typeName as ts . Identifier ) . text )
684+ ) {
685+ const id = typeName as ts . Identifier
668686 typeIdentifiers . push ( {
669687 enclosingComponent : currentComponent ,
670- name : typeName . text ,
671- range : {
672- end : typeName . end ,
673- start : typeName . getStart ( sourceFile ) ,
674- } ,
688+ name : id . text ,
689+ ranges : [ { end : id . end , start : id . getStart ( sourceFile ) } ] ,
675690 } )
676691 }
677692 }
@@ -680,35 +695,49 @@ function collectSourceElements(
680695 currentComponentTracked &&
681696 ! componentsWithInlineFn ! . has ( currentComponent ! )
682697 ) {
683- if ( ts . isFunctionDeclaration ( node ) && node . name ) {
684- perComponentFuncs !
685- . get ( currentComponent ! ) !
686- . set ( node . name . text , hasUseServerDirective ( node ) )
687- } else if (
688- ts . isVariableDeclaration ( node ) &&
689- ts . isIdentifier ( node . name ) &&
690- node . initializer &&
691- ( ts . isArrowFunction ( node . initializer ) ||
692- ts . isFunctionExpression ( node . initializer ) )
693- ) {
694- perComponentFuncs !
695- . get ( currentComponent ! ) !
696- . set ( node . name . text , hasUseServerDirective ( node . initializer ) )
697- } else if (
698- ts . isJsxAttribute ( node ) &&
699- node . initializer &&
700- ts . isJsxExpression ( node . initializer ) &&
701- node . initializer . expression
702- ) {
703- const expr = node . initializer . expression
704-
698+ if ( nodeKind === SK_FunctionDecl ) {
699+ const fn = node as ts . FunctionDeclaration
700+ if ( fn . name ) {
701+ perComponentFuncs !
702+ . get ( currentComponent ! ) !
703+ . set ( fn . name . text , hasUseServerDirective ( fn ) )
704+ }
705+ } else if ( nodeKind === SK_VariableDecl ) {
706+ const decl = node as ts . VariableDeclaration
705707 if (
706- ( ts . isArrowFunction ( expr ) || ts . isFunctionExpression ( expr ) ) &&
707- ! hasUseServerDirective ( expr )
708+ decl . name . kind === SK_Identifier &&
709+ decl . initializer &&
710+ ( decl . initializer . kind === SK_ArrowFunction ||
711+ decl . initializer . kind === SK_FunctionExpr )
708712 ) {
709- componentsWithInlineFn ! . add ( currentComponent ! )
710- } else if ( ts . isIdentifier ( expr ) ) {
711- perComponentRefs ! . get ( currentComponent ! ) ! . push ( expr . text )
713+ perComponentFuncs !
714+ . get ( currentComponent ! ) !
715+ . set (
716+ ( decl . name as ts . Identifier ) . text ,
717+ hasUseServerDirective (
718+ decl . initializer as ts . ArrowFunction | ts . FunctionExpression ,
719+ ) ,
720+ )
721+ }
722+ } else if ( nodeKind === SK_JsxAttribute ) {
723+ const attr = node as ts . JsxAttribute
724+ if ( attr . initializer && attr . initializer . kind === SK_JsxExpression ) {
725+ const expr = ( attr . initializer as ts . JsxExpression ) . expression
726+ if ( expr ) {
727+ const exprKind = expr . kind
728+ if (
729+ ( exprKind === SK_ArrowFunction || exprKind === SK_FunctionExpr ) &&
730+ ! hasUseServerDirective (
731+ expr as ts . ArrowFunction | ts . FunctionExpression ,
732+ )
733+ ) {
734+ componentsWithInlineFn ! . add ( currentComponent ! )
735+ } else if ( exprKind === SK_Identifier ) {
736+ perComponentRefs !
737+ . get ( currentComponent ! ) !
738+ . push ( ( expr as ts . Identifier ) . text )
739+ }
740+ }
712741 }
713742 }
714743 }
@@ -884,7 +913,16 @@ function hasUseClientDirective(sourceText: string): boolean {
884913 if (
885914 i + 11 < len &&
886915 sourceText . charCodeAt ( i + 11 ) === ch &&
887- sourceText . startsWith ( 'use client' , i + 1 )
916+ sourceText . charCodeAt ( i + 1 ) === 117 &&
917+ sourceText . charCodeAt ( i + 2 ) === 115 &&
918+ sourceText . charCodeAt ( i + 3 ) === 101 &&
919+ sourceText . charCodeAt ( i + 4 ) === 32 &&
920+ sourceText . charCodeAt ( i + 5 ) === 99 &&
921+ sourceText . charCodeAt ( i + 6 ) === 108 &&
922+ sourceText . charCodeAt ( i + 7 ) === 105 &&
923+ sourceText . charCodeAt ( i + 8 ) === 101 &&
924+ sourceText . charCodeAt ( i + 9 ) === 110 &&
925+ sourceText . charCodeAt ( i + 10 ) === 116
888926 ) {
889927 return true
890928 }
0 commit comments