Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Consider open unattached scriptInfo for external files #303

Merged
merged 2 commits into from
Oct 18, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"typecheck",
"ucfirst",
"Uncapitalize",
"unconfigured",
"vetur",
"vuedx"
],
Expand Down
83 changes: 66 additions & 17 deletions packages/typescript-plugin-vue/src/managers/PluginManager.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { first, setDebugging } from '@vuedx/shared'
import { createHash } from 'crypto'
import { Container } from 'inversify'
import { TS_LANGUAGE_SERVICE } from '../constants'
import type {
Expand Down Expand Up @@ -28,24 +27,33 @@ export class PluginManager {
readonly #containers = new Map<string, Container>()
private readonly logger = LoggerService.getLogger(PluginManager.name)

private _activeContainerId: string | undefined
public create(options: Options): TSLanguageService {
this.#setupLogger(options)

if (TS_LANGUAGE_SERVICE in options.languageService) {
return options.languageService
}

this.#patchTypescript(options.typescript)

const containerKey = options.project.getProjectName()

const container =
this.#containers.get(containerKey) ?? this.#createContainer(options)

if (this._activeContainerId === containerKey) {
return this.#createLanguageService(
options.languageService,
container.get(TypescriptPluginService),
)
}

this.logger.debug(
'Creating language service for project:',
options.project.getProjectName(),
)

this.#patchTypescript(options.typescript)

const container =
this.#containers.get(options.project.getProjectName()) ??
this.#createContainer(options)

container.get(TypescriptContextService).updateOptions(options)

this.#patchProject(container, options.project)
Expand All @@ -54,8 +62,13 @@ export class PluginManager {

try {
const plugin = container.get(TypescriptPluginService)
plugin.onDispose(() => {
this.#containers.delete(containerKey)
container.unbindAll()
})
return this.#createLanguageService(options.languageService, plugin)
} finally {
this._activeContainerId = containerKey
const current = (
(options.project.projectService as any)
.hostConfiguration as TypeScript.server.HostConfiguration
Expand All @@ -72,6 +85,7 @@ export class PluginManager {
extraFileExtensions: [],
})
}
this._activeContainerId = undefined
}
}

Expand Down Expand Up @@ -120,6 +134,7 @@ export class PluginManager {

#patchProject(container: Container, project: TSProject): void {
const ts = container.get(TypescriptContextService)
const fs = container.get(FilesystemService)
const logger = LoggerService.getLogger('Project')

overrideMethod(
Expand Down Expand Up @@ -179,6 +194,35 @@ export class PluginManager {
}
},
)

overrideMethod(
project as unknown as {
detachScriptInfoFromProject(
uncheckedFileName: string,
noRemoveResolution?: boolean,
): void
},
'detachScriptInfoFromProject',
(detachScriptInfoFromProject) =>
(uncheckedFileName, noRemoveResolution) => {
if (fs.isVueFile(uncheckedFileName)) return
if (fs.isGeneratedVueFile(uncheckedFileName)) {
const fileName = fs.getRealFileNameIfAny(uncheckedFileName)
console.debug(`@@@ Detaching ${fileName}`)
return detachScriptInfoFromProject.call(
project,
fileName,
noRemoveResolution,
)
}

return detachScriptInfoFromProject.call(
project,
uncheckedFileName,
noRemoveResolution,
)
},
)
}

#patchServerHost(container: Container, serverHost: TSServerHost): void {
Expand Down Expand Up @@ -311,7 +355,8 @@ export class PluginManager {
containingFile,
reusedNames,
redirectedReference,
_options,
options,
containingSourceFile,
) => {
if (fs.isVueRuntimeFile(containingFile)) {
const anyProjectFile = first(ts.project.getRootFiles())
Expand All @@ -320,15 +365,15 @@ export class PluginManager {
const core = ts.lib.resolveModuleName(
'@vue/runtime-core',
anyProjectFile,
_options,
options,
ts.serverHost,
undefined,
redirectedReference,
)
const vue = ts.lib.resolveModuleName(
'vue',
anyProjectFile,
_options,
options,
ts.serverHost,
undefined,
redirectedReference,
Expand All @@ -352,13 +397,15 @@ export class PluginManager {
containingFile,
reusedNames,
redirectedReference,
_options,
options,
)
: ts.project.resolveModuleNames(
moduleNames,
containingFile,
reusedNames,
redirectedReference,
options,
containingSourceFile,
)

const known = {
Expand All @@ -373,7 +420,8 @@ export class PluginManager {
}
moduleNames.forEach((name, index) => {
const handler = known[name as keyof typeof known]
if (handler != null && result[index] == null) {
const resolved = result[index]
if (handler != null && resolved == null) {
result[index] = handler()
}
})
Expand Down Expand Up @@ -409,11 +457,13 @@ export class PluginManager {
return container
}

readonly #loggerIds = new Map<string, string>()
#setupLogger(options: Options): void {
const id = createHash('md5')
.update(options.project.getProjectName())
.digest('hex')
.slice(0, 6)
const id =
this.#loggerIds.get(options.project.getProjectName()) ??
`${this.#loggerIds.size}`

this.#loggerIds.set(options.project.getProjectName(), id)

if (LoggerService.currentId === id) return
const logger = options.project.projectService.logger
Expand Down Expand Up @@ -464,7 +514,6 @@ export class PluginManager {
has: (target, prop) => {
return prop === TS_LANGUAGE_SERVICE || prop in target
},
// TODO: Implement set?
})
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,19 +63,7 @@ export class TypescriptPluginService
private readonly ipc: IPCService,
) {
if (Math.random() > 1) {
console.log([
this.classifications,
this.codeFix,
this.completions,
this.definitions,
this.folding,
this.implementation,
this.quickInfo,
this.refactor,
this.references,
this.rename,
this.signature,
])
console.log([this.classifications, this.folding, this.implementation])
}
}
//#endregion
Expand Down Expand Up @@ -139,6 +127,20 @@ export class TypescriptPluginService
const vue = new Set<string>()
const virtual = new Set<string>()

// This is not needed for any functionality, but it's needed to prevent unnecessary creation of inferred project.
this.ts.projectService.openFiles.forEach((_, file) => {
const scriptInfo = this.ts.projectService.getScriptInfoForPath(
file as TypeScript.Path,
)
if (scriptInfo == null) return
if (
scriptInfo.containingProjects.length === 0 || // creating new project, so this is likely to be part of current project. TODO: verify hypothesis.
scriptInfo.containingProjects.includes(this.ts.project)
) {
all.push(scriptInfo.fileName)
}
})

if (this.ts.isConfiguredProject(this.ts.project)) {
const options = this.ts.project.getParsedCommandLine?.(
this.ts.project.getConfigFilePath(),
Expand All @@ -161,23 +163,15 @@ export class TypescriptPluginService

if (vue.size === 0) {
this.#isVueProject = false
this.logger.debug('Not a Vue project')
this.logger.debug('Not a Vue project:', this.ts.project.getProjectName())
return [] // do not retain any files if no .vue files
}

this.#isVueProject = true
const fileNames = [...this.getScriptFileNames([...vue]), ...virtual]

this.logger.debug(`Project: ${this.ts.project.getProjectName()}`)
this.logger.debug(`Project:`, this.ts.project.getProjectName())
this.logger.debug(`External files:`, fileNames)
this.logger.debug(
'Open external files:',
fileNames.filter((fileName) =>
this.ts.projectService.openFiles.has(
this.ts.toNormalizedPath(fileName),
),
),
)

return fileNames
}
Expand Down Expand Up @@ -958,8 +952,15 @@ export class TypescriptPluginService
return this.ts.service.getTodoComments(fileName, descriptors)
}

readonly #disposables: Array<() => void> = []

public onDispose(callback: () => void): void {
this.#disposables.push(callback)
}

public dispose(): void {
this.ipc.dispose()
this.#disposables.forEach((dispose) => dispose())
this.ts.service.dispose()
}

Expand Down
Empty file.
Empty file.
Loading