@@ -304,6 +304,7 @@ function parseFileAnalysis(filePath: string, sourceText: string): FileAnalysis {
304304 getScriptKind ( filePath ) ,
305305 )
306306
307+ const asyncComponents = new Set < string > ( )
307308 const componentRanges : { end : number ; name : string ; start : number } [ ] = [ ]
308309 const exportReferences : NamedRange [ ] = [ ]
309310 const importReferences : NamedRange [ ] = [ ]
@@ -326,29 +327,20 @@ function parseFileAnalysis(filePath: string, sourceText: string): FileAnalysis {
326327 name : string ,
327328 nameNode : ts . Node ,
328329 scopeNode : ts . Node ,
329- kind : Exclude < ComponentKind , 'unknown' > ,
330330 ) : void => {
331331 localComponentDeclarations . push ( { name, range : nodeRange ( nameNode ) } )
332- localComponentKinds . set ( name , kind )
332+ localComponentKinds . set ( name , ownComponentKind )
333333 componentRanges . push ( {
334334 end : scopeNode . getEnd ( ) ,
335335 name,
336336 start : scopeNode . getStart ( sourceFile ) ,
337337 } )
338338 }
339339
340- const inferKind = (
341- isAsync : boolean ,
342- node : ts . Node ,
343- ) : Exclude < ComponentKind , 'unknown' > => {
344- if ( ownComponentKind === 'client' ) {
345- return 'client'
346- }
347- if ( isAsync ) {
348- return 'server'
349- }
350- return hasNonServerFunctionProps ( node ) ? 'client' : 'server'
351- }
340+ const hasAsyncModifier = (
341+ modifiers : ts . NodeArray < ts . ModifierLike > | undefined ,
342+ ) : boolean =>
343+ modifiers ?. some ( ( m ) => m . kind === ts . SyntaxKind . AsyncKeyword ) ?? false
352344
353345 for ( ; statementIndex < sourceFile . statements . length ; statementIndex ++ ) {
354346 const statement = sourceFile . statements [ statementIndex ] !
@@ -407,16 +399,10 @@ function parseFileAnalysis(filePath: string, sourceText: string): FileAnalysis {
407399 statement . name &&
408400 isComponentIdentifier ( statement . name . text )
409401 ) {
410- const isAsync =
411- statement . modifiers ?. some (
412- ( m ) => m . kind === ts . SyntaxKind . AsyncKeyword ,
413- ) ?? false
414- registerComponent (
415- statement . name . text ,
416- statement . name ,
417- statement ,
418- inferKind ( isAsync , statement ) ,
419- )
402+ registerComponent ( statement . name . text , statement . name , statement )
403+ if ( hasAsyncModifier ( statement . modifiers ) ) {
404+ asyncComponents . add ( statement . name . text )
405+ }
420406 continue
421407 }
422408
@@ -425,12 +411,7 @@ function parseFileAnalysis(filePath: string, sourceText: string): FileAnalysis {
425411 statement . name &&
426412 isComponentIdentifier ( statement . name . text )
427413 ) {
428- registerComponent (
429- statement . name . text ,
430- statement . name ,
431- statement ,
432- inferKind ( false , statement ) ,
433- )
414+ registerComponent ( statement . name . text , statement . name , statement )
434415 continue
435416 }
436417
@@ -477,20 +458,32 @@ function parseFileAnalysis(filePath: string, sourceText: string): FileAnalysis {
477458 if ( ts . isVariableStatement ( statement ) ) {
478459 for ( const declaration of statement . declarationList . declarations ) {
479460 if (
480- ts . isIdentifier ( declaration . name ) &&
481- isComponentIdentifier ( declaration . name . text ) &&
482- isComponentInitializer ( declaration . initializer )
461+ ! ts . isIdentifier ( declaration . name ) ||
462+ ! isComponentIdentifier ( declaration . name . text ) ||
463+ ! declaration . initializer
483464 ) {
484- const fn = getComponentFunction ( declaration . initializer )
485- const isAsync =
486- fn ?. modifiers ?. some ( ( m ) => m . kind === ts . SyntaxKind . AsyncKeyword ) ??
487- false
465+ continue
466+ }
467+
468+ if ( ts . isClassExpression ( declaration . initializer ) ) {
488469 registerComponent (
489470 declaration . name . text ,
490471 declaration . name ,
491472 declaration ,
492- inferKind ( isAsync , declaration ) ,
493473 )
474+ continue
475+ }
476+
477+ const fn = getComponentFunction ( declaration . initializer )
478+ if ( fn ) {
479+ registerComponent (
480+ declaration . name . text ,
481+ declaration . name ,
482+ declaration ,
483+ )
484+ if ( hasAsyncModifier ( fn . modifiers ) ) {
485+ asyncComponents . add ( declaration . name . text )
486+ }
494487 }
495488 }
496489 }
@@ -500,6 +493,9 @@ function parseFileAnalysis(filePath: string, sourceText: string): FileAnalysis {
500493 sourceFile ,
501494 componentRanges ,
502495 typeIdentifiers ,
496+ localComponentKinds ,
497+ asyncComponents ,
498+ ownComponentKind === 'server' ,
503499 )
504500
505501 return {
@@ -527,12 +523,8 @@ function isComponentIdentifier(name: string): boolean {
527523}
528524
529525function getComponentFunction (
530- initializer : ts . Expression | undefined ,
526+ initializer : ts . Expression ,
531527) : ts . ArrowFunction | ts . FunctionExpression | undefined {
532- if ( ! initializer ) {
533- return undefined
534- }
535-
536528 if ( ts . isArrowFunction ( initializer ) || ts . isFunctionExpression ( initializer ) ) {
537529 return initializer
538530 }
@@ -550,72 +542,6 @@ function getComponentFunction(
550542 return undefined
551543}
552544
553- function isComponentInitializer (
554- initializer : ts . Expression | undefined ,
555- ) : boolean {
556- if ( ! initializer ) {
557- return false
558- }
559-
560- return (
561- ts . isClassExpression ( initializer ) ||
562- getComponentFunction ( initializer ) !== undefined
563- )
564- }
565-
566- function hasNonServerFunctionProps ( node : ts . Node ) : boolean {
567- const localFunctions = new Map < string , boolean > ( )
568- const identifierRefs : string [ ] = [ ]
569- let hasInlineFn = false
570-
571- const visit = ( n : ts . Node ) : void => {
572- if ( hasInlineFn ) {
573- return
574- }
575-
576- if ( ts . isFunctionDeclaration ( n ) && n . name ) {
577- localFunctions . set ( n . name . text , hasUseServerDirective ( n ) )
578- } else if (
579- ts . isVariableDeclaration ( n ) &&
580- ts . isIdentifier ( n . name ) &&
581- n . initializer &&
582- ( ts . isArrowFunction ( n . initializer ) ||
583- ts . isFunctionExpression ( n . initializer ) )
584- ) {
585- localFunctions . set ( n . name . text , hasUseServerDirective ( n . initializer ) )
586- } else if (
587- ts . isJsxAttribute ( n ) &&
588- n . initializer &&
589- ts . isJsxExpression ( n . initializer ) &&
590- n . initializer . expression
591- ) {
592- const expr = n . initializer . expression
593-
594- if (
595- ( ts . isArrowFunction ( expr ) || ts . isFunctionExpression ( expr ) ) &&
596- ! hasUseServerDirective ( expr )
597- ) {
598- hasInlineFn = true
599- return
600- }
601-
602- if ( ts . isIdentifier ( expr ) ) {
603- identifierRefs . push ( expr . text )
604- }
605- }
606-
607- ts . forEachChild ( n , visit )
608- }
609- ts . forEachChild ( node , visit )
610-
611- return (
612- hasInlineFn ||
613- identifierRefs . some (
614- ( ref ) => localFunctions . has ( ref ) && ! localFunctions . get ( ref ) ,
615- )
616- )
617- }
618-
619545function hasUseServerDirective (
620546 fn : ts . ArrowFunction | ts . FunctionDeclaration | ts . FunctionExpression ,
621547) : boolean {
@@ -658,9 +584,43 @@ function collectSourceElements(
658584 sourceFile : ts . SourceFile ,
659585 componentRanges : { end : number ; name : string ; start : number } [ ] ,
660586 typeIdentifiers : TypeIdentifier [ ] ,
587+ localComponentKinds : Map < string , Exclude < ComponentKind , 'unknown' > > ,
588+ asyncComponents : Set < string > ,
589+ inferClientKind : boolean ,
661590) : JsxTagReference [ ] {
662591 const jsxTags : JsxTagReference [ ] = [ ]
592+
593+ const componentByStart = new Map < number , { end : number ; name : string } > ( )
594+ for ( const range of componentRanges ) {
595+ componentByStart . set ( range . start , range )
596+ }
597+
598+ const perComponentFuncs = new Map < string , Map < string , boolean > > ( )
599+ const perComponentRefs = new Map < string , string [ ] > ( )
600+ const componentsWithInlineFn = new Set < string > ( )
601+
602+ if ( inferClientKind ) {
603+ for ( const range of componentRanges ) {
604+ if ( ! asyncComponents . has ( range . name ) ) {
605+ perComponentFuncs . set ( range . name , new Map ( ) )
606+ perComponentRefs . set ( range . name , [ ] )
607+ }
608+ }
609+ }
610+
611+ let currentComponent : string | undefined
612+
663613 const visit = ( node : ts . Node ) : void => {
614+ const start = node . getStart ( sourceFile )
615+ const entry = componentByStart . get ( start )
616+ const entered = entry !== undefined && entry . end === node . getEnd ( )
617+
618+ let savedComponent : string | undefined
619+ if ( entered ) {
620+ savedComponent = currentComponent
621+ currentComponent = entry . name
622+ }
623+
664624 if (
665625 ts . isJsxOpeningElement ( node ) ||
666626 ts . isJsxSelfClosingElement ( node ) ||
@@ -675,19 +635,73 @@ function collectSourceElements(
675635 ts . isIdentifier ( node . typeName ) &&
676636 isComponentIdentifier ( node . typeName . text )
677637 ) {
678- const start = node . typeName . getStart ( sourceFile )
679- const end = node . typeName . getEnd ( )
680638 typeIdentifiers . push ( {
681- enclosingComponent : componentRanges . find (
682- ( r ) => start >= r . start && end <= r . end ,
683- ) ?. name ,
639+ enclosingComponent : currentComponent ,
684640 name : node . typeName . text ,
685- range : { end, start } ,
641+ range : {
642+ end : node . typeName . getEnd ( ) ,
643+ start : node . typeName . getStart ( sourceFile ) ,
644+ } ,
686645 } )
687646 }
647+
648+ if (
649+ currentComponent &&
650+ perComponentFuncs . has ( currentComponent ) &&
651+ ! componentsWithInlineFn . has ( currentComponent )
652+ ) {
653+ if ( ts . isFunctionDeclaration ( node ) && node . name ) {
654+ perComponentFuncs
655+ . get ( currentComponent ) !
656+ . set ( node . name . text , hasUseServerDirective ( node ) )
657+ } else if (
658+ ts . isVariableDeclaration ( node ) &&
659+ ts . isIdentifier ( node . name ) &&
660+ node . initializer &&
661+ ( ts . isArrowFunction ( node . initializer ) ||
662+ ts . isFunctionExpression ( node . initializer ) )
663+ ) {
664+ perComponentFuncs
665+ . get ( currentComponent ) !
666+ . set ( node . name . text , hasUseServerDirective ( node . initializer ) )
667+ } else if (
668+ ts . isJsxAttribute ( node ) &&
669+ node . initializer &&
670+ ts . isJsxExpression ( node . initializer ) &&
671+ node . initializer . expression
672+ ) {
673+ const expr = node . initializer . expression
674+
675+ if (
676+ ( ts . isArrowFunction ( expr ) || ts . isFunctionExpression ( expr ) ) &&
677+ ! hasUseServerDirective ( expr )
678+ ) {
679+ componentsWithInlineFn . add ( currentComponent )
680+ } else if ( ts . isIdentifier ( expr ) ) {
681+ perComponentRefs . get ( currentComponent ) ! . push ( expr . text )
682+ }
683+ }
684+ }
685+
688686 ts . forEachChild ( node , visit )
687+
688+ if ( entered ) {
689+ currentComponent = savedComponent
690+ }
689691 }
690692 ts . forEachChild ( sourceFile , visit )
693+
694+ for ( const [ name , funcs ] of perComponentFuncs ) {
695+ if ( componentsWithInlineFn . has ( name ) ) {
696+ localComponentKinds . set ( name , 'client' )
697+ continue
698+ }
699+ const refs = perComponentRefs . get ( name ) !
700+ if ( refs . some ( ( ref ) => funcs . has ( ref ) && ! funcs . get ( ref ) ) ) {
701+ localComponentKinds . set ( name , 'client' )
702+ }
703+ }
704+
691705 return jsxTags
692706}
693707
0 commit comments