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.");