Skip to content

Commit 3625285

Browse files
committed
Optimize
1 parent 8677737 commit 3625285

3 files changed

Lines changed: 77 additions & 71 deletions

File tree

src/analyzer.ts

Lines changed: 32 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -91,14 +91,15 @@ export class ComponentLensAnalyzer {
9191
}
9292

9393
const componentKinds = new Map<string, ComponentKind>()
94-
await Promise.all(
95-
[...uniqueFilePaths].map(async (resolvedPath) => {
96-
componentKinds.set(
97-
resolvedPath,
98-
await this.getFileComponentKind(resolvedPath),
99-
)
100-
}),
101-
)
94+
const kindPromises: Promise<void>[] = []
95+
for (const resolvedPath of uniqueFilePaths) {
96+
kindPromises.push(
97+
this.getFileComponentKind(resolvedPath).then((kind) => {
98+
componentKinds.set(resolvedPath, kind)
99+
}),
100+
)
101+
}
102+
await Promise.all(kindPromises)
102103

103104
const usages: ComponentUsage[] = []
104105

@@ -183,19 +184,35 @@ function parseFileAnalysis(filePath: string, sourceText: string): FileAnalysis {
183184

184185
const imports = new Map<string, ImportBinding>()
185186
const localComponentNames = new Set<string>()
187+
let ownComponentKind: Exclude<ComponentKind, 'unknown'> = 'server'
188+
let statementIndex = 0
189+
190+
for (; statementIndex < sourceFile.statements.length; statementIndex++) {
191+
const statement = sourceFile.statements[statementIndex]!
192+
if (
193+
!ts.isExpressionStatement(statement) ||
194+
!ts.isStringLiteral(statement.expression)
195+
) {
196+
break
197+
}
198+
if (statement.expression.text === 'use client') {
199+
ownComponentKind = 'client'
200+
statementIndex++
201+
break
202+
}
203+
}
186204

187-
for (const statement of sourceFile.statements) {
205+
for (; statementIndex < sourceFile.statements.length; statementIndex++) {
206+
const statement = sourceFile.statements[statementIndex]!
188207
collectImportBindings(statement, imports)
189208
collectLocalComponentName(statement, localComponentNames)
190209
}
191210

192-
const jsxTags = collectJsxTags(sourceFile)
193-
194211
return {
195212
imports,
196-
jsxTags,
213+
jsxTags: collectJsxTags(sourceFile),
197214
localComponentNames,
198-
ownComponentKind: hasUseClientDirective(sourceFile) ? 'client' : 'server',
215+
ownComponentKind,
199216
}
200217
}
201218

@@ -278,24 +295,6 @@ function collectLocalComponentName(
278295
}
279296
}
280297

281-
function hasUseClientDirective(sourceFile: ts.SourceFile): boolean {
282-
for (const statement of sourceFile.statements) {
283-
if (
284-
!ts.isExpressionStatement(statement) ||
285-
!ts.isStringLiteral(statement.expression)
286-
) {
287-
return false
288-
}
289-
290-
if (statement.expression.text === 'use client') {
291-
return true
292-
}
293-
}
294-
295-
return false
296-
}
297-
298-
const COMPONENT_NAME_RE = /^[A-Z]/u
299298
const COMPONENT_WRAPPER_NAMES = new Set([
300299
'forwardRef',
301300
'memo',
@@ -304,7 +303,8 @@ const COMPONENT_WRAPPER_NAMES = new Set([
304303
])
305304

306305
function isComponentIdentifier(name: string): boolean {
307-
return COMPONENT_NAME_RE.test(name)
306+
const code = name.charCodeAt(0)
307+
return code >= 65 && code <= 90
308308
}
309309

310310
function isComponentInitializer(

src/decorations.ts

Lines changed: 29 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -28,23 +28,38 @@ export class LensDecorations implements vscode.Disposable {
2828
public apply(editor: vscode.TextEditor, usages: ComponentUsage[]): void {
2929
const clientDecorations: vscode.DecorationOptions[] = []
3030
const serverDecorations: vscode.DecorationOptions[] = []
31+
const editorDir = path.dirname(editor.document.uri.fsPath)
32+
const displayPathCache = new Map<string, string>()
3133

3234
for (const usage of usages) {
33-
for (const range of usage.ranges) {
34-
const decoration: vscode.DecorationOptions = {
35-
hoverMessage: new vscode.MarkdownString(
36-
`${usage.kind === 'client' ? 'Client' : 'Server'} component from \`${toDisplayPath(editor.document.uri, usage.sourceFilePath)}\``,
37-
),
38-
range: new vscode.Range(
39-
editor.document.positionAt(range.start),
40-
editor.document.positionAt(range.end),
41-
),
42-
}
35+
let displayPath = displayPathCache.get(usage.sourceFilePath)
36+
if (displayPath === undefined) {
37+
displayPath = toDisplayPath(editorDir, usage.sourceFilePath)
38+
displayPathCache.set(usage.sourceFilePath, displayPath)
39+
}
4340

41+
const label = usage.kind === 'client' ? 'Client' : 'Server'
42+
const hoverMessage = new vscode.MarkdownString(
43+
`${label} component from \`${displayPath}\``,
44+
)
45+
46+
for (const range of usage.ranges) {
4447
if (usage.kind === 'client') {
45-
clientDecorations.push(decoration)
48+
clientDecorations.push({
49+
hoverMessage,
50+
range: new vscode.Range(
51+
editor.document.positionAt(range.start),
52+
editor.document.positionAt(range.end),
53+
),
54+
})
4655
} else {
47-
serverDecorations.push(decoration)
56+
serverDecorations.push({
57+
hoverMessage,
58+
range: new vscode.Range(
59+
editor.document.positionAt(range.start),
60+
editor.document.positionAt(range.end),
61+
),
62+
})
4863
}
4964
}
5065
}
@@ -64,14 +79,8 @@ export class LensDecorations implements vscode.Disposable {
6479
}
6580
}
6681

67-
function toDisplayPath(
68-
documentUri: vscode.Uri,
69-
sourceFilePath: string,
70-
): string {
71-
const relativePath = path.relative(
72-
path.dirname(documentUri.fsPath),
73-
sourceFilePath,
74-
)
82+
function toDisplayPath(editorDir: string, sourceFilePath: string): string {
83+
const relativePath = path.relative(editorDir, sourceFilePath)
7584
return relativePath.length > 0 ? relativePath : path.basename(sourceFilePath)
7685
}
7786

src/resolver.ts

Lines changed: 16 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -33,17 +33,22 @@ export class ImportResolver {
3333
>()
3434
private readonly configPathCache = new Map<string, string | undefined>()
3535
private readonly resolutionCache = new Map<string, string | undefined>()
36-
private readonly hostDirectoryExists: (directoryPath: string) => boolean
37-
private readonly hostFileExists: (filePath: string) => boolean
38-
private readonly hostReadFile: (filePath: string) => string | undefined
36+
private readonly resolutionHost: ts.ModuleResolutionHost
37+
private currentDirectory = ''
3938

4039
public constructor(private readonly host: SourceHost) {
41-
this.hostDirectoryExists = (directoryPath) =>
42-
host.fileExists(directoryPath) || ts.sys.directoryExists(directoryPath)
43-
this.hostFileExists = (filePath) =>
44-
host.fileExists(filePath) || ts.sys.fileExists(filePath)
45-
this.hostReadFile = (filePath) =>
46-
host.readFile(filePath) ?? ts.sys.readFile(filePath)
40+
this.resolutionHost = {
41+
directoryExists: (directoryPath) =>
42+
host.fileExists(directoryPath) || ts.sys.directoryExists(directoryPath),
43+
fileExists: (filePath) =>
44+
host.fileExists(filePath) || ts.sys.fileExists(filePath),
45+
getCurrentDirectory: () => this.currentDirectory,
46+
getDirectories: ts.sys.getDirectories,
47+
readFile: (filePath) =>
48+
host.readFile(filePath) ?? ts.sys.readFile(filePath),
49+
realpath: ts.sys.realpath,
50+
useCaseSensitiveFileNames: ts.sys.useCaseSensitiveFileNames,
51+
}
4752
}
4853

4954
public clear(): void {
@@ -64,21 +69,13 @@ export class ImportResolver {
6469
}
6570

6671
const compilerOptions = this.getCompilerOptions(normalizedFromFilePath)
67-
const resolutionHost: ts.ModuleResolutionHost = {
68-
directoryExists: this.hostDirectoryExists,
69-
fileExists: this.hostFileExists,
70-
getCurrentDirectory: () => path.dirname(normalizedFromFilePath),
71-
getDirectories: ts.sys.getDirectories,
72-
readFile: this.hostReadFile,
73-
realpath: ts.sys.realpath,
74-
useCaseSensitiveFileNames: ts.sys.useCaseSensitiveFileNames,
75-
}
72+
this.currentDirectory = path.dirname(normalizedFromFilePath)
7673

7774
const result = ts.resolveModuleName(
7875
specifier,
7976
normalizedFromFilePath,
8077
compilerOptions,
81-
resolutionHost,
78+
this.resolutionHost,
8279
).resolvedModule
8380

8481
if (!result || !isSupportedSourceFile(result.resolvedFileName)) {

0 commit comments

Comments
 (0)