diff --git a/src/harness/unittests/tsserverProjectSystem.ts b/src/harness/unittests/tsserverProjectSystem.ts index 20fad9f9fa0..bb6ab7cc1a6 100644 --- a/src/harness/unittests/tsserverProjectSystem.ts +++ b/src/harness/unittests/tsserverProjectSystem.ts @@ -1,4 +1,5 @@ /// +/// namespace ts { function notImplemented(): any { @@ -26,6 +27,57 @@ namespace ts { content: libFileContent }; + abstract class TestTypingsInstaller extends server.typingsInstaller.TypingsInstaller implements server.ITypingsInstaller { + protected projectService: server.ProjectService; + constructor(private readonly host: server.ServerHost) { + super(); + } + + abstract cachePath: string; + safeFileList = ""; + packageNameToTypingLocation: Map = {}; + + postInstallActions: (( map: (t: string[]) => string[]) => void)[] = []; + + runPostInstallActions(map: (t: string[]) => string[]) { + for (const f of this.postInstallActions) { + f(map); + } + this.postInstallActions = []; + } + + attach(projectService: server.ProjectService) { + this.projectService = projectService; + } + + getInstallTypingHost() { + return this.host; + } + + installPackage(packageName: string) { + return true; + } + + isPackageInstalled(packageName: string) { + return true; + } + + runTsd(cachePath: string, typingsToInstall: string[], postInstallAction: (installedTypings: string[]) => void) { + this.postInstallActions.push(map => { + postInstallAction(map(typingsToInstall)); + }) + } + + sendResponse(response: server.InstallTypingsResponse) { + this.projectService.updateTypingsForProject(response); + } + + enqueueInstallTypingsRequest(project: server.Project, typingOptions: TypingOptions) { + const request = server.createInstallTypingsRequest(project, typingOptions, this.safeFileList, this.packageNameToTypingLocation, this.cachePath); + this.install(request) + } + } + function getExecutingFilePathFromLibFile(libFilePath: string): string { return combinePaths(getDirectoryPath(libFile.path), "tsc.js"); } @@ -1408,4 +1460,61 @@ namespace ts { checkNumberOfProjects(projectService, { configuredProjects: 0 }); }); }); + + describe("typings installer", () => { + it("configured projects (tsd installed) 1", () => { + const file1 = { + path: "/a/b/app.js", + content: "" + }; + const tsconfig = { + path: "/a/b/tsconfig.json", + content: JSON.stringify({ + compilerOptions: { + allowJs: true + }, + typingOptions: { + enableAutoDiscovery: true + } + }) + }; + const packageJson = { + path: "/a/b/package.json", + content: JSON.stringify({ + name: "test", + dependencies: { + jquery: "^3.1.0" + } + }) + }; + + const jquery = { + path: "/a/data/jquery/jquery.d.ts", + content: "declare const $: { x: number }" + }; + + const host = createServerHost([file1, tsconfig, packageJson]); + class TypingInstaller extends TestTypingsInstaller { + cachePath = "/a/data/"; + constructor(host: server.ServerHost) { + super(host) + } + }; + const installer = new TypingInstaller(host); + const projectService = new server.ProjectService(host, nullLogger, nullCancellationToken, /*useSingleInferredProject*/ true, installer); + projectService.openClientFile(file1.path); + + checkNumberOfProjects(projectService, { configuredProjects: 1 }) + const p = projectService.configuredProjects[0]; + checkProjectActualFiles(p, [ file1.path ]); + + installer.runPostInstallActions(t => { + assert.deepEqual(t, ["jquery"]); + host.reloadFS([file1, tsconfig, packageJson, jquery]); + return ["jquery/jquery.d.ts"]; + }); + checkNumberOfProjects(projectService, { configuredProjects: 1 }) + checkProjectActualFiles(p, [ file1.path, jquery.path ]); + }); + }); } \ No newline at end of file diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index aba0b3e6d8b..ae1c3c55bae 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -182,7 +182,12 @@ namespace ts.server { this.toCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames); this.directoryWatchers = new DirectoryWatchers(this); this.throttledOperations = new ThrottledOperations(host); - this.typingsCache = new TypingsCache(typingsInstaller || nullTypingsInstaller); + + const installer = typingsInstaller || nullTypingsInstaller; + installer.attach(this); + + this.typingsCache = new TypingsCache(installer); + // ts.disableIncrementalParsing = true; this.hostConfiguration = { @@ -201,6 +206,15 @@ namespace ts.server { this.ensureInferredProjectsUpToDate(); } + updateTypingsForProject(response: InstallTypingsResponse): void { + const project = this.findProject(response.projectName); + if (!project) { + return; + } + this.typingsCache.updateTypingsForProject(response.projectName, response.compilerOptions, response.typingOptions, response.typings); + project.updateGraph(); + } + setCompilerOptionsForInferredProjects(compilerOptions: CompilerOptions): void { this.compilerOptionsForInferredProjects = compilerOptions; for (const proj of this.inferredProjects) { diff --git a/src/server/server.ts b/src/server/server.ts index fa26de2e079..9927427a8d5 100644 --- a/src/server/server.ts +++ b/src/server/server.ts @@ -161,7 +161,7 @@ namespace ts.server { class NodeTypingsInstaller implements ITypingsInstaller { private installer: NodeChildProcess; - private session: Session; + private projectService: ProjectService; private cachePath: string; constructor(private readonly logger: server.Logger) { @@ -176,8 +176,8 @@ namespace ts.server { } } - bind(session: Session) { - this.session = session; + attach(projectService: ProjectService) { + this.projectService = projectService; if (this.logger.hasLevel(LogLevel.requestTime)) { this.logger.info("Binding...") } @@ -187,16 +187,13 @@ namespace ts.server { } enqueueInstallTypingsRequest(project: Project, typingOptions: TypingOptions): void { - const request: InstallTypingsRequest = { - projectName: project.getProjectName(), - fileNames: project.getFileNames(), - compilerOptions: project.getCompilerOptions(), + const request = createInstallTypingsRequest( + project, typingOptions, - projectRootPath: (project.projectKind === ProjectKind.Inferred ? "" : getDirectoryPath(project.getProjectName())), // TODO: fixme - safeListPath: (combinePaths(process.cwd(), "typingSafeList.json")), // TODO: fixme - packageNameToTypingLocation: {}, // TODO: fixme - cachePath: this.cachePath - }; + /*safeListPath*/ (combinePaths(process.cwd(), "typingSafeList.json")), // TODO: fixme + /*packageNameToTypingLocation*/ {}, // TODO: fixme + this.cachePath + ); if (this.logger.hasLevel(LogLevel.verbose)) { this.logger.info(`Sending request: ${JSON.stringify(request)}`); } @@ -207,14 +204,13 @@ namespace ts.server { if (this.logger.hasLevel(LogLevel.verbose)) { this.logger.info(`Received response: ${JSON.stringify(response)}`) } - this.session.onTypingsInstalled(response); + this.projectService.updateTypingsForProject(response); } } class IOSession extends Session { constructor(host: ServerHost, cancellationToken: HostCancellationToken, useSingleInferredProject: boolean, logger: server.Logger) { super(host, cancellationToken, useSingleInferredProject, new NodeTypingsInstaller(logger), Buffer.byteLength, maxUncompressedMessageSize, compress, process.hrtime, logger); - (this.typingsInstaller).bind(this); } exit() { diff --git a/src/server/session.ts b/src/server/session.ts index 6c4deef2e7d..6a1ebb52115 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -1452,16 +1452,6 @@ namespace ts.server { } } - public onTypingsInstalled(response: InstallTypingsResponse) { - const project = this.projectService.findProject(response.projectName); - if (!project) { - return; - } - this.projectService.typingsCache.updateTypingsForProject(response.projectName, response.compilerOptions, response.typingOptions, response.typings); - project.updateGraph(); - - } - public onMessage(message: string) { this.gcTimer.scheduleCollect(); let start: number[]; diff --git a/src/server/typingsCache.ts b/src/server/typingsCache.ts index 97c90647b34..971766739d7 100644 --- a/src/server/typingsCache.ts +++ b/src/server/typingsCache.ts @@ -3,10 +3,12 @@ namespace ts.server { export interface ITypingsInstaller { enqueueInstallTypingsRequest(p: Project, typingOptions: TypingOptions): void; + attach(projectService: ProjectService): void; } export const nullTypingsInstaller: ITypingsInstaller = { - enqueueInstallTypingsRequest: () => {} + enqueueInstallTypingsRequest: () => {}, + attach: (projectService: ProjectService) => {} }; class TypingsCacheEntry { diff --git a/src/server/utilities.ts b/src/server/utilities.ts index 4ad6730a1af..d167ac310fa 100644 --- a/src/server/utilities.ts +++ b/src/server/utilities.ts @@ -30,6 +30,19 @@ namespace ts.server { export type Types = Err | Info | Perf; } + export function createInstallTypingsRequest(project: Project, typingOptions: TypingOptions, safeListPath: Path, packageNameToTypingLocation: Map, cachePath: string): InstallTypingsRequest { + return { + projectName: project.getProjectName(), + fileNames: project.getFileNames(), + compilerOptions: project.getCompilerOptions(), + typingOptions, + projectRootPath: (project.projectKind === ProjectKind.Inferred ? "" : getDirectoryPath(project.getProjectName())), // TODO: fixme + safeListPath, + packageNameToTypingLocation, + cachePath + }; + } + export namespace Errors { export const NoProject = new Error("No Project."); export const ProjectLanguageServiceDisabled = new Error("The project's language service is disabled.");