Skip to content

Commit

Permalink
cache export source getType (#202)
Browse files Browse the repository at this point in the history
  • Loading branch information
souporserious authored Oct 12, 2024
1 parent d6c374b commit 555815e
Show file tree
Hide file tree
Showing 5 changed files with 280 additions and 70 deletions.
5 changes: 5 additions & 0 deletions .changeset/new-shoes-swim.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'renoun': minor
---

Adds a cache to the `<ExportSource>.getType` method to prevent unnecessary processing of types since this is an expensive operation. Types will now only be resolved the first time they are requested and then cached for subsequent requests unless one of the file dependencies has changed.
15 changes: 9 additions & 6 deletions packages/renoun/src/project/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,10 +87,10 @@ export async function resolveType({
filter?: SymbolFilter
useClient?: boolean
}) {
if (useClient && client) {
const filePath = declaration.getSourceFile().getFilePath()
const position = declaration.getPos()
const filePath = declaration.getSourceFile().getFilePath()
const position = declaration.getPos()

if (useClient && client) {
return client.callMethod('resolveType', {
filePath,
position,
Expand All @@ -99,7 +99,10 @@ export async function resolveType({
})
}

return import('../utils/resolve-type.js').then(({ resolveType }) => {
return resolveType(declaration.getType(), declaration, filter)
})
return import('../utils/resolve-type-at-location.js').then(
async ({ resolveTypeAtLocation }) => {
const project = await getProject(projectOptions)
return resolveTypeAtLocation(project, filePath, position, filter)
}
)
}
23 changes: 6 additions & 17 deletions packages/renoun/src/project/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,8 @@ import {
type Highlighter,
} from '../utils/create-highlighter.js'
import { getRootDirectory } from '../utils/get-root-directory.js'
import {
resolveType as baseResolveType,
type SymbolFilter,
} from '../utils/resolve-type.js'
import type { SymbolFilter } from '../utils/resolve-type.js'
import { resolveTypeAtLocation } from '../utils/resolve-type-at-location.js'
import { WebSocketServer } from './rpc/server.js'
import { getProject } from './get-project.js'
import { ProjectOptions } from './types.js'
Expand Down Expand Up @@ -95,16 +93,6 @@ export function createServer() {
projectOptions?: ProjectOptions
}) {
const project = await getProject(projectOptions)
const sourceFile = project.addSourceFileAtPath(options.filePath)
const declaration = sourceFile.getDescendantAtPos(options.position)

if (!declaration) {
throw new Error(
`[renoun] Could not find declaration at position ${options.position}`
)
}

const exportDeclaration = declaration.getParentOrThrow()
const filterFn = filter
? (new Function(
'symbolMetadata',
Expand All @@ -123,9 +111,10 @@ export function createServer() {
) as SymbolFilter)
: undefined

return baseResolveType(
exportDeclaration.getType(),
exportDeclaration,
return resolveTypeAtLocation(
project,
options.filePath,
options.position,
filterFn
)
}
Expand Down
89 changes: 89 additions & 0 deletions packages/renoun/src/utils/resolve-type-at-location.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import type { Project } from 'ts-morph'
import { statSync } from 'node:fs'

import {
resolveType,
type ResolvedType,
type SymbolFilter,
} from './resolve-type.js'

const resolvedTypeCache = new Map<
string,
{
resolvedType?: ResolvedType
dependencies: Map<string, number>
}
>()

/** Process all properties of a given type including their default values. */
export function resolveTypeAtLocation(
project: Project,
filePath: string,
position: number,
filter?: SymbolFilter
) {
const typeId = `${filePath}:${position}`
const sourceFile = project.addSourceFileAtPath(filePath)
const declaration = sourceFile.getDescendantAtPos(position)

if (!declaration) {
throw new Error(
`[renoun] Could not resolve type at position: ${position}. Try restarting the server or file an issue if you continue to encounter this error.`
)
}

const exportDeclaration = declaration.getParentOrThrow()
const exportDeclarationType = exportDeclaration.getType()
const cacheEntry = resolvedTypeCache.get(typeId)

if (cacheEntry) {
let dependenciesChanged = false

for (const [
depFilePath,
cachedDepLastModified,
] of cacheEntry.dependencies) {
let depLastModified: number
try {
depLastModified = statSync(depFilePath).mtimeMs
} catch {
// File might have been deleted; invalidate the cache
dependenciesChanged = true
break
}
if (depLastModified !== cachedDepLastModified) {
dependenciesChanged = true
break
}
}

if (!dependenciesChanged) {
return cacheEntry.resolvedType
}
}

const dependencies = new Set<string>([filePath])
const resolvedType = resolveType(
exportDeclarationType,
exportDeclaration,
filter,
true,
undefined,
false,
dependencies
)

resolvedTypeCache.set(typeId, {
resolvedType,
dependencies: new Map(
Array.from(dependencies).map((filePath) => [
filePath,
statSync(filePath).mtimeMs,
])
),
})

dependencies.clear()

return resolvedType
}
Loading

0 comments on commit 555815e

Please sign in to comment.