Skip to content

Commit 587850a

Browse files
committed
Optimize
1 parent 7e3be44 commit 587850a

File tree

3 files changed

+135
-96
lines changed

3 files changed

+135
-96
lines changed

src/analyzer.ts

Lines changed: 129 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -36,17 +36,17 @@ interface CachedDirective {
3636

3737
interface NamedRange {
3838
name: string
39-
range: DecorationSegment
39+
ranges: DecorationSegment[]
4040
}
4141

4242
interface LocalComponent {
4343
kind: Exclude<ComponentKind, 'unknown'>
44-
range: DecorationSegment
44+
ranges: DecorationSegment[]
4545
}
4646

4747
interface 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 {
6262
interface TypeIdentifier {
6363
enclosingComponent: string | undefined
6464
name: string
65-
range: DecorationSegment
65+
ranges: DecorationSegment[]
6666
}
6767

6868
export 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
518524
const SK_ImportDecl = ts.SyntaxKind.ImportDeclaration
519525
const SK_EnumDecl = ts.SyntaxKind.EnumDeclaration
520526
const 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

529539
function isComponentIdentifier(name: string): boolean {
530540
const code = name.charCodeAt(0)
@@ -534,18 +544,22 @@ function isComponentIdentifier(name: string): boolean {
534544
function 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

592606
function 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
}

src/decorations.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,7 @@ export class LensDecorations implements vscode.Disposable {
4646
hoverMap.set(usage.sourceFilePath, hoverMessage)
4747
}
4848

49-
const target =
50-
usage.kind === 'client' ? clientDecorations : serverDecorations
49+
const target = isClient ? clientDecorations : serverDecorations
5150

5251
for (const range of usage.ranges) {
5352
target.push({

0 commit comments

Comments
 (0)