Skip to content

Commit af0b037

Browse files
committed
Optimize
1 parent c48a6e8 commit af0b037

1 file changed

Lines changed: 129 additions & 115 deletions

File tree

src/analyzer.ts

Lines changed: 129 additions & 115 deletions
Original file line numberDiff line numberDiff line change
@@ -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

529525
function 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-
619545
function 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

Comments
 (0)