Skip to content

Commit aea54ad

Browse files
committed
refactor: class WorkspaceContext
1 parent 8e3e540 commit aea54ad

6 files changed

Lines changed: 107 additions & 102 deletions

File tree

src/composables/workspace-context.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,20 @@ import type { Uri } from 'vscode'
22
import { SUPPORTED_DOCUMENT_PATTERN } from '#constants'
33
import { isSupportedDependencyDocument } from '#extractors'
44
import { logger } from '#state'
5-
import { getWorkspaceContextState } from '#utils/workspace'
5+
import { getWorkspaceContext } from '#utils/workspace'
66
import { useActiveTextEditor, useDocumentText, useFileSystemWatcher, watch } from 'reactive-vscode'
77
import { workspace } from 'vscode'
88

99
export function useWorkspaceContext() {
1010
workspace.onDidChangeWorkspaceFolders(({ removed }) => {
1111
removed.forEach((folder) => {
12-
getWorkspaceContextState.delete(folder.uri)
12+
getWorkspaceContext.delete(folder.uri)
1313
logger.info(`[workspace-context] delete workspace folder cache: ${folder.uri.path}`)
1414
})
1515
})
1616

1717
async function deleteCacheByUri(uri: Uri) {
18-
const ctx = await getWorkspaceContextState(uri)
18+
const ctx = await getWorkspaceContext(uri)
1919
if (!ctx)
2020
return
2121

src/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,10 @@ export const { activate, deactivate } = defineExtension(() => {
1515

1616
useWorkspaceContext()
1717

18-
useDiagnostics()
19-
useCodeActions()
2018
useHover()
2119
useCompletionItem()
20+
useDiagnostics()
21+
useCodeActions()
2222
useDocumentLink()
2323

2424
useCommands({

src/providers/diagnostics/rules/engine-mismatch.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import type { Engines } from 'fast-npm-meta'
22
import type { DiagnosticRule } from '..'
33
import { npmxPackageUrl } from '#utils/links'
44
import { formatPackageId } from '#utils/package'
5-
import { getWorkspaceContextState, isPackageManifestPath } from '#utils/workspace'
5+
import { getWorkspaceContext, isPackageManifestPath } from '#utils/workspace'
66
import Range from 'semver/classes/range'
77
import intersects from 'semver/ranges/intersects'
88
import subset from 'semver/ranges/subset'
@@ -55,7 +55,7 @@ export const checkEngineMismatch: DiagnosticRule = async ({ uri, dep, pkg }) =>
5555
if (!exactVersion)
5656
return
5757

58-
const state = await getWorkspaceContextState(uri)
58+
const state = await getWorkspaceContext(uri)
5959
const engines = (await state?.loadPackageManifestInfo(uri))?.engines
6060

6161
if (!engines)

src/types/context.ts

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,6 @@ export type DependencyProtocol
1515

1616
export type CatalogsInfo = Record<string, Record<string, string>>
1717

18-
export interface WorkspaceContext {
19-
packageManager: PackageManager
20-
catalogs?: CatalogsInfo
21-
}
22-
2318
export interface ResolvedDependencyInfo extends DependencyInfo {
2419
protocol: DependencyProtocol
2520
resolvedName: string

src/utils/shared.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
export function lazyInit<T>(factory: () => T): () => T {
2+
let cached: { value: T } | undefined
3+
return () => {
4+
if (!cached)
5+
cached = { value: factory() }
6+
return cached.value
7+
}
8+
}

src/utils/workspace.ts

Lines changed: 92 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import type { CatalogsInfo, ResolvedDependencyInfo, WorkspaceContext } from '#types/context'
1+
import type { CatalogsInfo, PackageManager, ResolvedDependencyInfo } from '#types/context'
22
import type { DependencyInfo, PackageManifestInfo, WorkspaceCatalogInfo } from '#types/extractor'
3-
import type { MemoizedFunction } from '#utils/memoize'
3+
import type { MemoizeOptions } from '#utils/memoize'
44
import type { WorkspaceFolder } from 'vscode'
55
import { packageManifestExtractorEntry, workspaceCatalogExtractorEntries } from '#extractors'
66
import { logger } from '#state'
@@ -12,69 +12,99 @@ import { resolveExactVersion } from '#utils/package'
1212
import { detectPackageManager } from '#utils/package-manager'
1313
import { Uri, workspace } from 'vscode'
1414
import { getDocumentText } from './document'
15+
import { lazyInit } from './shared'
1516

1617
type WithResolvedDependencyInfo<T> = Omit<T, 'dependencies'> & {
1718
dependencies: ResolvedDependencyInfo[]
1819
}
1920

20-
interface WorkspaceContextState {
21-
folder: WorkspaceFolder
22-
workspaceContext: WorkspaceContext
23-
loadPackageManifestInfo: MemoizedFunction<Uri, Promise<WithResolvedDependencyInfo<PackageManifestInfo> | undefined>>
24-
loadWorkspaceCatalogInfo: MemoizedFunction<Uri, Promise<WithResolvedDependencyInfo<WorkspaceCatalogInfo> | undefined>>
25-
}
26-
2721
export function isPackageManifestPath(path: string) {
2822
return path.endsWith(`/${packageManifestExtractorEntry.basename}`)
2923
}
3024

31-
function lazyInit<T>(factory: () => T): () => T {
32-
let cached: { value: T } | undefined
33-
return () => {
34-
if (!cached)
35-
cached = { value: factory() }
36-
return cached.value
25+
class WorkspaceContext {
26+
folder: WorkspaceFolder
27+
packageManager: PackageManager = 'npm'
28+
catalogs?: CatalogsInfo
29+
30+
constructor(folder: WorkspaceFolder) {
31+
this.folder = folder
32+
this.#init()
3733
}
38-
}
3934

40-
function createResolvedDependencyInfo(
41-
dependency: DependencyInfo,
42-
catalogs?: CatalogsInfo,
43-
): ResolvedDependencyInfo {
44-
const resolution = resolveDependencySpec(dependency.rawName, dependency.rawSpec, catalogs)
45-
46-
const packageInfo = lazyInit(
47-
async () => resolution.resolvedProtocol === 'npm'
48-
? await getPackageInfo(resolution.resolvedName) ?? null
49-
: null,
50-
)
51-
52-
return {
53-
...dependency,
54-
...resolution,
55-
categoryName: dependency.categoryName ?? resolution.categoryName,
56-
packageInfo,
57-
resolvedVersion: lazyInit(async () => {
58-
if (resolution.resolvedProtocol !== 'npm')
59-
return null
60-
61-
const pkg = await packageInfo()
62-
if (!pkg)
63-
return null
64-
65-
return resolveExactVersion(pkg, resolution.resolvedSpec)
66-
}),
35+
async #init() {
36+
this.packageManager = await detectPackageManager(this.folder)
37+
38+
if (this.packageManager !== 'npm') {
39+
const workspaceFilename = workspaceCatalogExtractorEntries.find(
40+
(entry) => this.packageManager === entry.packageManager,
41+
)!.basename
42+
const workspaceFile = Uri.joinPath(
43+
this.folder.uri,
44+
workspaceFilename,
45+
)
46+
this.catalogs = (await this.loadWorkspaceCatalogInfo(workspaceFile))?.catalogs
47+
}
6748
}
68-
}
6949

70-
export const getWorkspaceContextState = memoize<Uri, Promise<WorkspaceContextState | undefined>>(async (uri) => {
71-
const folder = workspace.getWorkspaceFolder(uri)
72-
if (!folder)
73-
return
50+
#memoizeOptions: MemoizeOptions<Uri> = {
51+
getKey: (uri) => uri.path,
52+
ttl: false,
53+
maxSize: Number.POSITIVE_INFINITY,
54+
fallbackToCachedOnError: false,
55+
}
56+
57+
#createResolvedDependencyInfo(dependency: DependencyInfo): ResolvedDependencyInfo {
58+
const resolution = resolveDependencySpec(dependency.rawName, dependency.rawSpec, this.catalogs)
59+
60+
const packageInfo = lazyInit(
61+
async () => resolution.resolvedProtocol === 'npm'
62+
? await getPackageInfo(resolution.resolvedName) ?? null
63+
: null,
64+
)
65+
66+
return {
67+
...dependency,
68+
...resolution,
69+
categoryName: dependency.categoryName ?? resolution.categoryName,
70+
packageInfo,
71+
resolvedVersion: lazyInit(async () => {
72+
if (resolution.resolvedProtocol !== 'npm')
73+
return null
74+
75+
const pkg = await packageInfo()
76+
if (!pkg)
77+
return null
78+
79+
return resolveExactVersion(pkg, resolution.resolvedSpec)
80+
}),
81+
}
82+
}
83+
84+
loadPackageManifestInfo = memoize<
85+
Uri,
86+
Promise<WithResolvedDependencyInfo<PackageManifestInfo> | undefined>
87+
>(async (uri) => {
88+
if (!isPackageManifestPath(uri.path))
89+
return
7490

75-
const packageManager = await detectPackageManager(folder)
91+
logger.info(`[workspace-context] load package manifest info: ${uri.path}`)
92+
const text = await getDocumentText(uri)
7693

77-
const loadWorkspaceCatalogInfo = memoize(async (uri: Uri): Promise<WithResolvedDependencyInfo<WorkspaceCatalogInfo> | undefined> => {
94+
const info = packageManifestExtractorEntry.extractor.getPackageManifestInfo(text)
95+
if (!info)
96+
return
97+
98+
return {
99+
...info,
100+
dependencies: info.dependencies.map(this.#createResolvedDependencyInfo),
101+
}
102+
}, this.#memoizeOptions)
103+
104+
loadWorkspaceCatalogInfo = memoize<
105+
Uri,
106+
Promise<WithResolvedDependencyInfo<WorkspaceCatalogInfo> | undefined>
107+
>(async (uri) => {
78108
const path = uri.path
79109
logger.info(`[workspace-context] load workspace catalog info: ${path}`)
80110

@@ -90,62 +120,34 @@ export const getWorkspaceContextState = memoize<Uri, Promise<WorkspaceContextSta
90120

91121
return {
92122
...info,
93-
dependencies: info.dependencies.map((dependency) => createResolvedDependencyInfo(dependency)),
123+
dependencies: info.dependencies.map(this.#createResolvedDependencyInfo),
94124
}
95125
}
96-
}, { getKey: (uri) => uri.path, ttl: false, maxSize: Number.POSITIVE_INFINITY, fallbackToCachedOnError: false })
97-
98-
let catalogs: CatalogsInfo | undefined
126+
}, this.#memoizeOptions)
127+
}
99128

100-
if (packageManager !== 'npm') {
101-
const workspaceFile = Uri.joinPath(
102-
folder.uri,
103-
workspaceCatalogExtractorEntries.find((entry) => packageManager === entry.packageManager)!.basename,
104-
)
105-
catalogs = (await loadWorkspaceCatalogInfo(workspaceFile))?.catalogs
106-
}
129+
export const getWorkspaceContext = memoize<Uri, Promise<WorkspaceContext | undefined>>(async (uri) => {
130+
const folder = workspace.getWorkspaceFolder(uri)
131+
if (!folder)
132+
return
107133

108134
logger.info(`[workspace-context] built ${folder.uri.path}`)
109-
110-
return {
111-
folder,
112-
workspaceContext: {
113-
packageManager,
114-
catalogs,
115-
},
116-
loadPackageManifestInfo: memoize(async (uri: Uri) => {
117-
if (!isPackageManifestPath(uri.path))
118-
return
119-
120-
logger.info(`[workspace-context] load package manifest info: ${uri.path}`)
121-
const text = await getDocumentText(uri)
122-
123-
const info = packageManifestExtractorEntry.extractor.getPackageManifestInfo(text)
124-
if (!info)
125-
return
126-
127-
return {
128-
...info,
129-
dependencies: info.dependencies.map((dependency) => createResolvedDependencyInfo(dependency, catalogs)),
130-
}
131-
}, { getKey: (uri) => uri.path, ttl: false, maxSize: Number.POSITIVE_INFINITY, fallbackToCachedOnError: false }),
132-
loadWorkspaceCatalogInfo,
133-
}
135+
return new WorkspaceContext(folder)
134136
}, {
135137
getKey: (uri: Uri) => workspace.getWorkspaceFolder(uri)!.uri.path,
136138
ttl: false,
137139
fallbackToCachedOnError: false,
138140
})
139141

140142
export async function getResolvedDependencies(uri: Uri): Promise<ResolvedDependencyInfo[] | undefined> {
141-
const state = await getWorkspaceContextState(uri)
142-
if (!state)
143+
const ctx = await getWorkspaceContext(uri)
144+
if (!ctx)
143145
return []
144146

145147
return (
146148
isPackageManifestPath(uri.path)
147-
? await state.loadPackageManifestInfo(uri)
148-
: await state.loadWorkspaceCatalogInfo(uri)
149+
? await ctx.loadPackageManifestInfo(uri)
150+
: await ctx.loadWorkspaceCatalogInfo(uri)
149151
)?.dependencies
150152
}
151153

0 commit comments

Comments
 (0)