diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts
index d38ce359e8..a209db4e73 100644
--- a/src/compiler/checker.ts
+++ b/src/compiler/checker.ts
@@ -18627,6 +18627,9 @@ namespace ts {
continue;
}
const file = host.getSourceFile(resolvedDirective.resolvedFileName);
+ if (!file) {
+ continue;
+ }
fileToDirective.set(file.path, key);
}
}
diff --git a/src/harness/unittests/tsserverProjectSystem.ts b/src/harness/unittests/tsserverProjectSystem.ts
index 8cbc3848cf..e22f1752ce 100644
--- a/src/harness/unittests/tsserverProjectSystem.ts
+++ b/src/harness/unittests/tsserverProjectSystem.ts
@@ -2,6 +2,17 @@
///
namespace ts.projectSystem {
+ const safeList = {
+ path: "/safeList.json",
+ content: JSON.stringify({
+ commander: "commander",
+ express: "express",
+ jquery: "jquery",
+ lodash: "lodash",
+ moment: "moment"
+ })
+ };
+
export function notImplemented(): any {
throw new Error("Not yet implemented");
}
@@ -31,11 +42,11 @@ namespace ts.projectSystem {
export class TestTypingsInstaller extends server.typingsInstaller.TypingsInstaller implements server.ITypingsInstaller {
protected projectService: server.ProjectService;
constructor(readonly globalTypingsCacheLocation: string, readonly installTypingHost: server.ServerHost) {
- super(globalTypingsCacheLocation, "");
+ super(globalTypingsCacheLocation, safeList.path);
this.init();
}
- safeFileList = "";
+ safeFileList = safeList.path;
postInstallActions: ((map: (t: string[]) => string[]) => void)[] = [];
runPostInstallActions(map: (t: string[]) => string[]) {
@@ -56,15 +67,11 @@ namespace ts.projectSystem {
return this.installTypingHost;
}
- installPackage(packageName: string) {
- return true;
- }
-
isPackageInstalled(packageName: string) {
return true;
}
- runTsd(cachePath: string, typingsToInstall: string[], postInstallAction: (installedTypings: string[]) => void) {
+ runInstall(cachePath: string, typingsToInstall: string[], postInstallAction: (installedTypings: string[]) => void) {
this.postInstallActions.push(map => {
postInstallAction(map(typingsToInstall));
});
@@ -106,11 +113,13 @@ namespace ts.projectSystem {
if (!params) {
params = {};
}
- return new TestServerHost(
+ const host = new TestServerHost(
params.useCaseSensitiveFileNames !== undefined ? params.useCaseSensitiveFileNames : false,
params.executingFilePath || getExecutingFilePathFromLibFile(libFilePath),
params.currentDirectory || "/",
fileOrFolderList);
+ host.createFileOrFolder(safeList, /*createParentDirectory*/ true);
+ return host;
}
export function createSession(host: server.ServerHost, typingsInstaller?: server.ITypingsInstaller) {
diff --git a/src/harness/unittests/typingsInstaller.ts b/src/harness/unittests/typingsInstaller.ts
index 18c1e89ec7..47373bfd26 100644
--- a/src/harness/unittests/typingsInstaller.ts
+++ b/src/harness/unittests/typingsInstaller.ts
@@ -4,7 +4,7 @@
namespace ts.projectSystem {
describe("typings installer", () => {
- it("configured projects (tsd installed) 1", () => {
+ it("configured projects (typings installed) 1", () => {
const file1 = {
path: "/a/b/app.js",
content: ""
@@ -31,7 +31,7 @@ namespace ts.projectSystem {
};
const jquery = {
- path: "/a/data/typings/jquery/jquery.d.ts",
+ path: "/a/data/node_modules/@types/jquery/index.d.ts",
content: "declare const $: { x: number }"
};
@@ -44,18 +44,18 @@ namespace ts.projectSystem {
const p = projectService.configuredProjects[0];
checkProjectActualFiles(p, [file1.path]);
- assert(host.fileExists(combinePaths(installer.globalTypingsCacheLocation, "tsd.json")));
+ assert(host.fileExists(combinePaths(installer.globalTypingsCacheLocation, "package.json")));
installer.runPostInstallActions(t => {
assert.deepEqual(t, ["jquery"]);
host.createFileOrFolder(jquery, /*createParentDirectory*/ true);
- return ["jquery/jquery.d.ts"];
+ return ["@types/jquery"];
});
checkNumberOfProjects(projectService, { configuredProjects: 1 });
checkProjectActualFiles(p, [file1.path, jquery.path]);
});
- it("inferred project (tsd installed)", () => {
+ it("inferred project (typings installed)", () => {
const file1 = {
path: "/a/b/app.js",
content: ""
@@ -71,7 +71,7 @@ namespace ts.projectSystem {
};
const jquery = {
- path: "/a/data/typings/jquery/jquery.d.ts",
+ path: "/a/data/node_modules/@types/jquery/index.d.ts",
content: "declare const $: { x: number }"
};
const host = createServerHost([file1, packageJson]);
@@ -84,12 +84,12 @@ namespace ts.projectSystem {
const p = projectService.inferredProjects[0];
checkProjectActualFiles(p, [file1.path]);
- assert(host.fileExists(combinePaths(installer.globalTypingsCacheLocation, "tsd.json")));
+ assert(host.fileExists(combinePaths(installer.globalTypingsCacheLocation, "package.json")));
installer.runPostInstallActions(t => {
assert.deepEqual(t, ["jquery"]);
host.createFileOrFolder(jquery, /*createParentDirectory*/ true);
- return ["jquery/jquery.d.ts"];
+ return ["@types/jquery"];
});
checkNumberOfProjects(projectService, { inferredProjects: 1 });
checkProjectActualFiles(p, [file1.path, jquery.path]);
@@ -157,7 +157,7 @@ namespace ts.projectSystem {
};
const host = createServerHost([file1]);
let enqueueIsCalled = false;
- let runTsdIsCalled = false;
+ let runInstallIsCalled = false;
const installer = new (class extends TestTypingsInstaller {
constructor() {
super("", host);
@@ -166,10 +166,10 @@ namespace ts.projectSystem {
enqueueIsCalled = true;
super.enqueueInstallTypingsRequest(project, typingOptions);
}
- runTsd(cachePath: string, typingsToInstall: string[], postInstallAction: (installedTypings: string[]) => void): void {
+ runInstall(cachePath: string, typingsToInstall: string[], postInstallAction: (installedTypings: string[]) => void): void {
assert.deepEqual(typingsToInstall, ["node"]);
- runTsdIsCalled = true;
- super.runTsd(cachePath, typingsToInstall, postInstallAction);
+ runInstallIsCalled = true;
+ super.runInstall(cachePath, typingsToInstall, postInstallAction);
}
})();
@@ -184,7 +184,188 @@ namespace ts.projectSystem {
// autoDiscovery is set in typing options - use it even if project contains only .ts files
projectService.checkNumberOfProjects({ externalProjects: 1 });
assert.isTrue(enqueueIsCalled, "expected 'enqueueIsCalled' to be true");
- assert.isTrue(runTsdIsCalled, "expected 'runTsdIsCalled' to be true");
+ assert.isTrue(runInstallIsCalled, "expected 'runInstallIsCalled' to be true");
+ });
+
+ it("external project - no typing options, with only js, jsx, d.ts files", () => {
+ // Tests:
+ // 1. react typings are installed for .jsx
+ // 2. loose files names are matched against safe list for typings if
+ // this is a JS project (only js, jsx, d.ts files are present)
+ const file1 = {
+ path: "/a/b/lodash.js",
+ content: ""
+ };
+ const file2 = {
+ path: "/a/b/file2.jsx",
+ content: ""
+ };
+ const file3 = {
+ path: "/a/b/file3.d.ts",
+ content: ""
+ };
+ const react = {
+ path: "/a/data/node_modules/@types/react/index.d.ts",
+ content: "declare const react: { x: number }"
+ };
+ const lodash = {
+ path: "/a/data/node_modules/@types/lodash/index.d.ts",
+ content: "declare const lodash: { x: number }"
+ };
+
+ const host = createServerHost([file1, file2, file3]);
+ const installer = new TestTypingsInstaller("/a/data/", host);
+
+ const projectFileName = "/a/app/test.csproj";
+ const projectService = createProjectService(host, { typingsInstaller: installer });
+ projectService.openExternalProject({
+ projectFileName,
+ options: { allowJS: true, moduleResolution: ModuleResolutionKind.NodeJs },
+ rootFiles: [toExternalFile(file1.path), toExternalFile(file2.path), toExternalFile(file3.path)],
+ typingOptions: {}
+ });
+
+ const p = projectService.externalProjects[0];
+ projectService.checkNumberOfProjects({ externalProjects: 1 });
+ checkProjectActualFiles(p, [file1.path, file2.path, file3.path]);
+
+ installer.runPostInstallActions(t => {
+ assert.deepEqual(t, ["lodash", "react"]);
+ host.createFileOrFolder(lodash, /*createParentDirectory*/ true);
+ host.createFileOrFolder(react, /*createParentDirectory*/ true);
+ return ["@types/lodash", "@types/react"];
+ });
+
+ checkNumberOfProjects(projectService, { externalProjects: 1 });
+ checkProjectActualFiles(p, [file1.path, file2.path, file3.path, lodash.path, react.path]);
+ });
+
+ it("external project - no typing options, with js & ts files", () => {
+ // Tests:
+ // 1. No typings are included for JS projects when the project contains ts files
+ const file1 = {
+ path: "/a/b/jquery.js",
+ content: ""
+ };
+ const file2 = {
+ path: "/a/b/file2.ts",
+ content: ""
+ };
+
+ const host = createServerHost([file1, file2]);
+ let enqueueIsCalled = false;
+ let runInstallIsCalled = false;
+ let runPostInstallIsCalled = false;
+ const installer = new (class extends TestTypingsInstaller {
+ constructor() {
+ super("/a/data/", host);
+ }
+ enqueueInstallTypingsRequest(project: server.Project, typingOptions: TypingOptions) {
+ enqueueIsCalled = true;
+ super.enqueueInstallTypingsRequest(project, typingOptions);
+ }
+ runInstall(cachePath: string, typingsToInstall: string[], postInstallAction: (installedTypings: string[]) => void): void {
+ runInstallIsCalled = true;
+ super.runInstall(cachePath, typingsToInstall, postInstallAction);
+ }
+ })();
+
+ const projectFileName = "/a/app/test.csproj";
+ const projectService = createProjectService(host, { typingsInstaller: installer });
+ projectService.openExternalProject({
+ projectFileName,
+ options: { allowJS: true, moduleResolution: ModuleResolutionKind.NodeJs },
+ rootFiles: [toExternalFile(file1.path), toExternalFile(file2.path)],
+ typingOptions: {}
+ });
+
+ const p = projectService.externalProjects[0];
+ projectService.checkNumberOfProjects({ externalProjects: 1 });
+ checkProjectActualFiles(p, [file1.path, file2.path]);
+
+ installer.runPostInstallActions(t => {
+ runPostInstallIsCalled = true;
+ return [];
+ });
+
+ checkNumberOfProjects(projectService, { externalProjects: 1 });
+ checkProjectActualFiles(p, [file1.path, file2.path]);
+ assert.isFalse(enqueueIsCalled, "expected 'enqueueIsCalled' to be false");
+ assert.isFalse(runInstallIsCalled, "expected 'runInstallIsCalled' to be false");
+ assert.isFalse(runPostInstallIsCalled, "expected 'runPostInstallIsCalled' to be false");
+ });
+
+ it("external project - with typing options, with only js, d.ts files", () => {
+ // Tests:
+ // 1. Safelist matching, typing options includes/excludes and package.json typings are all acquired
+ // 2. Types for safelist matches are not included when they also appear in the typing option exclude list
+ // 3. Multiple includes and excludes are respected in typing options
+ const file1 = {
+ path: "/a/b/lodash.js",
+ content: ""
+ };
+ const file2 = {
+ path: "/a/b/commander.js",
+ content: ""
+ };
+ const file3 = {
+ path: "/a/b/file3.d.ts",
+ content: ""
+ };
+ const packageJson = {
+ path: "/a/b/package.json",
+ content: JSON.stringify({
+ name: "test",
+ dependencies: {
+ express: "^3.1.0"
+ }
+ })
+ };
+
+ const commander = {
+ path: "/a/data/node_modules/@types/commander/index.d.ts",
+ content: "declare const commander: { x: number }"
+ };
+ const express = {
+ path: "/a/data/node_modules/@types/express/index.d.ts",
+ content: "declare const express: { x: number }"
+ };
+ const jquery = {
+ path: "/a/data/node_modules/@types/jquery/index.d.ts",
+ content: "declare const jquery: { x: number }"
+ };
+ const moment = {
+ path: "/a/data/node_modules/@types/moment/index.d.ts",
+ content: "declare const moment: { x: number }"
+ };
+
+ const host = createServerHost([file1, file2, file3, packageJson]);
+ const installer = new TestTypingsInstaller("/a/data/", host);
+
+ const projectFileName = "/a/app/test.csproj";
+ const projectService = createProjectService(host, { typingsInstaller: installer });
+ projectService.openExternalProject({
+ projectFileName,
+ options: { allowJS: true, moduleResolution: ModuleResolutionKind.NodeJs },
+ rootFiles: [toExternalFile(file1.path), toExternalFile(file2.path), toExternalFile(file3.path)],
+ typingOptions: { include: ["jquery", "moment"], exclude: ["lodash"] }
+ });
+
+ const p = projectService.externalProjects[0];
+ projectService.checkNumberOfProjects({ externalProjects: 1 });
+ checkProjectActualFiles(p, [file1.path, file2.path, file3.path]);
+
+ installer.runPostInstallActions(t => {
+ assert.deepEqual(t, ["jquery", "moment", "express", "commander" ]);
+ host.createFileOrFolder(commander, /*createParentDirectory*/ true);
+ host.createFileOrFolder(express, /*createParentDirectory*/ true);
+ host.createFileOrFolder(jquery, /*createParentDirectory*/ true);
+ host.createFileOrFolder(moment, /*createParentDirectory*/ true);
+ return ["@types/commander", "@types/express", "@types/jquery", "@types/moment"];
+ });
+
+ checkNumberOfProjects(projectService, { externalProjects: 1 });
+ checkProjectActualFiles(p, [file1.path, file2.path, file3.path, commander.path, express.path, jquery.path, moment.path]);
});
});
}
\ No newline at end of file
diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts
index bd46f26923..a840a73b73 100644
--- a/src/server/editorServices.ts
+++ b/src/server/editorServices.ts
@@ -312,6 +312,7 @@ namespace ts.server {
const info = this.getScriptInfoForNormalizedPath(fileName);
if (!info) {
this.logger.info(`Error: got watch notification for unknown file: ${fileName}`);
+ return;
}
if (!this.host.fileExists(fileName)) {
diff --git a/src/server/project.ts b/src/server/project.ts
index bb4808a767..7708cf797f 100644
--- a/src/server/project.ts
+++ b/src/server/project.ts
@@ -301,7 +301,7 @@ namespace ts.server {
return true;
}
let hasChanges = this.updateGraphWorker();
- const cachedTypings = this.projectService.typingsCache.getTypingsForProject(this);
+ const cachedTypings = this.projectService.typingsCache.getTypingsForProject(this, hasChanges);
if (this.setTypings(cachedTypings)) {
hasChanges = this.updateGraphWorker() || hasChanges;
}
@@ -423,17 +423,15 @@ namespace ts.server {
const added: string[] = [];
const removed: string[] = [];
for (const id in currentFiles) {
- if (hasProperty(currentFiles, id) && !hasProperty(lastReportedFileNames, id)) {
+ if (!hasProperty(lastReportedFileNames, id)) {
added.push(id);
}
}
for (const id in lastReportedFileNames) {
- if (hasProperty(lastReportedFileNames, id) && !hasProperty(currentFiles, id)) {
+ if (!hasProperty(currentFiles, id)) {
removed.push(id);
}
}
- this.lastReportedFileNames = currentFiles;
-
this.lastReportedFileNames = currentFiles;
this.lastReportedVersion = this.projectStructureVersion;
return { info, changes: { added, removed }, projectErrors: this.projectErrors };
diff --git a/src/server/session.ts b/src/server/session.ts
index 1f55673aef..fc6b4669b1 100644
--- a/src/server/session.ts
+++ b/src/server/session.ts
@@ -168,7 +168,7 @@ namespace ts.server {
: undefined;
this.projectService = new ProjectService(host, logger, cancellationToken, useSingleInferredProject, typingsInstaller, eventHandler);
- this.gcTimer = new GcTimer(host, /*delay*/ 15000, logger);
+ this.gcTimer = new GcTimer(host, /*delay*/ 7000, logger);
}
private handleEvent(event: ProjectServiceEvent) {
diff --git a/src/server/typingsCache.ts b/src/server/typingsCache.ts
index 25efa02c34..889cfe1fb8 100644
--- a/src/server/typingsCache.ts
+++ b/src/server/typingsCache.ts
@@ -76,7 +76,7 @@ namespace ts.server {
constructor(private readonly installer: ITypingsInstaller) {
}
- getTypingsForProject(project: Project): TypingsArray {
+ getTypingsForProject(project: Project, forceRefresh: boolean): TypingsArray {
const typingOptions = project.getTypingOptions();
if (!typingOptions || !typingOptions.enableAutoDiscovery) {
@@ -85,9 +85,7 @@ namespace ts.server {
const entry = this.perProjectCache[project.getProjectName()];
const result: TypingsArray = entry ? entry.typings : emptyArray;
- if (!entry || typingOptionsChanged(typingOptions, entry.typingOptions) || compilerOptionsChanged(project.getCompilerOptions(), entry.compilerOptions)) {
- // something has been changed, issue a request to update typings
- this.installer.enqueueInstallTypingsRequest(project, typingOptions);
+ if (forceRefresh || !entry || typingOptionsChanged(typingOptions, entry.typingOptions) || compilerOptionsChanged(project.getCompilerOptions(), entry.compilerOptions)) {
// Note: entry is now poisoned since it does not really contain typings for a given combination of compiler options\typings options.
// instead it acts as a placeholder to prevent issuing multiple requests
this.perProjectCache[project.getProjectName()] = {
@@ -96,6 +94,8 @@ namespace ts.server {
typings: result,
poisoned: true
};
+ // something has been changed, issue a request to update typings
+ this.installer.enqueueInstallTypingsRequest(project, typingOptions);
}
return result;
}
diff --git a/src/server/typingsInstaller/nodeTypingsInstaller.ts b/src/server/typingsInstaller/nodeTypingsInstaller.ts
index 97228e7375..0d22b1b364 100644
--- a/src/server/typingsInstaller/nodeTypingsInstaller.ts
+++ b/src/server/typingsInstaller/nodeTypingsInstaller.ts
@@ -24,7 +24,7 @@ namespace ts.server.typingsInstaller {
private exec: { (command: string, options: { cwd: string }, callback?: (error: Error, stdout: string, stderr: string) => void): any };
private npmBinPath: string;
- private tsdRunCount = 1;
+ private installRunCount = 1;
readonly installTypingHost: InstallTypingHost = sys;
constructor(globalTypingsCacheLocation: string, log: Log) {
@@ -79,23 +79,6 @@ namespace ts.server.typingsInstaller {
}
}
- protected installPackage(packageName: string) {
- try {
- const output = this.execSync(`npm install --silent --global ${packageName}`, { stdio: "pipe" }).toString();
- if (this.log.isEnabled()) {
- this.log.writeLine(`installPackage::stdout '${output}'`);
- }
- return true;
- }
- catch (e) {
- if (this.log.isEnabled()) {
- this.log.writeLine(`installPackage::err::stdout '${e.stdout && e.stdout.toString()}'`);
- this.log.writeLine(`installPackage::err::stderr '${e.stdout && e.stderr.toString()}'`);
- }
- return false;
- }
- }
-
protected sendResponse(response: SetTypings | InvalidateCachedTypings) {
if (this.log.isEnabled()) {
this.log.writeLine(`Sending response: ${JSON.stringify(response)}`);
@@ -106,32 +89,61 @@ namespace ts.server.typingsInstaller {
}
}
- protected runTsd(cachePath: string, typingsToInstall: string[], postInstallAction: (installedTypings: string[]) => void): void {
- const id = this.tsdRunCount;
- this.tsdRunCount++;
- const tsdPath = combinePaths(this.npmBinPath, "tsd");
- const command = `${tsdPath} install ${typingsToInstall.join(" ")} -ros`;
- if (this.log.isEnabled()) {
- this.log.writeLine(`Running tsd ${id}, command '${command}'. cache path '${cachePath}'`);
+ protected runInstall(cachePath: string, typingsToInstall: string[], postInstallAction: (installedTypings: string[]) => void): void {
+ const id = this.installRunCount;
+ this.installRunCount++;
+ let execInstallCmdCount = 0;
+ const filteredTypings: string[] = [];
+ for (const typing of typingsToInstall) {
+ const command = `npm view @types/${typing} --silent name`;
+ this.execAsync("npm view", command, cachePath, id, (err, stdout, stderr) => {
+ if (stdout) {
+ filteredTypings.push(typing);
+ }
+ execInstallCmdCount++;
+ if (execInstallCmdCount === typingsToInstall.length) {
+ installFilteredTypings(this, filteredTypings);
+ }
+ });
}
- this.exec(command, { cwd: cachePath }, (err, stdout, stderr) => {
- if (this.log.isEnabled()) {
- this.log.writeLine(`TSD ${id} stdout: ${stdout}`);
- this.log.writeLine(`TSD ${id} stderr: ${stderr}`);
- }
- const i = stdout.indexOf("running install");
- if (i < 0) {
- return;
- }
- const installedTypings: string[] = [];
- const expr = /^\s*-\s*(\S+)\s*$/gm;
- expr.lastIndex = i;
- let match: RegExpExecArray;
- while (match = expr.exec(stdout)) {
- installedTypings.push(match[1]);
+ function installFilteredTypings(self: NodeTypingsInstaller, filteredTypings: string[]) {
+ const command = `npm install ${filteredTypings.map(t => "@types/" + t).join(" ")} --save-dev`;
+ self.execAsync("npm install", command, cachePath, id, (err, stdout, stderr) => {
+ if (stdout) {
+ reportInstalledTypings(self);
+ }
+ });
+ }
+
+ function reportInstalledTypings(self: NodeTypingsInstaller) {
+ const command = "npm ls -json";
+ self.execAsync("npm ls", command, cachePath, id, (err, stdout, stderr) => {
+ let installedTypings: string[];
+ try {
+ const response = JSON.parse(stdout);
+ if (response.dependencies) {
+ installedTypings = getOwnKeys(response.dependencies);
+ }
+ }
+ catch (e) {
+ self.log.writeLine(`Error parsing installed @types dependencies. Error details: ${e.message}`);
+ }
+ postInstallAction(installedTypings || []);
+ });
+ }
+ }
+
+ private execAsync(prefix: string, command: string, cwd: string, requestId: number, cb: (err: Error, stdout: string, stderr: string) => void) {
+ if (this.log.isEnabled()) {
+ this.log.writeLine(`#${requestId} running command '${command}'.`);
+ }
+ this.exec(command, { cwd }, (err, stdout, stderr) => {
+ if (this.log.isEnabled()) {
+ this.log.writeLine(`${prefix} #${requestId} stdout: ${stdout}`);
+ this.log.writeLine(`${prefix} #${requestId} stderr: ${stderr}`);
}
- postInstallAction(installedTypings);
+ cb(err, stdout, stderr);
});
}
}
diff --git a/src/server/typingsInstaller/typingsInstaller.ts b/src/server/typingsInstaller/typingsInstaller.ts
index f4a899068f..c96fe3c6c4 100644
--- a/src/server/typingsInstaller/typingsInstaller.ts
+++ b/src/server/typingsInstaller/typingsInstaller.ts
@@ -1,16 +1,10 @@
+///
///
///
namespace ts.server.typingsInstaller {
- const DefaultTsdSettings = JSON.stringify({
- version: "v4",
- repo: "DefinitelyTyped/DefinitelyTyped",
- ref: "master",
- path: "typings"
- }, /*replacer*/undefined, /*space*/4);
-
- interface TsdConfig {
- installed: MapLike;
+ interface NpmConfig {
+ devDependencies: MapLike;
}
export interface Log {
@@ -23,22 +17,15 @@ namespace ts.server.typingsInstaller {
writeLine: () => {}
};
- function tsdTypingToFileName(cachePath: string, tsdTypingFile: string) {
- return combinePaths(cachePath, `typings/${tsdTypingFile}`);
- }
-
- function getPackageName(tsdTypingFile: string) {
- const idx = tsdTypingFile.indexOf("/");
- return idx > 0 ? tsdTypingFile.substr(0, idx) : undefined;
+ function typingToFileName(cachePath: string, packageName: string, installTypingHost: InstallTypingHost): string {
+ const result = resolveModuleName(packageName, combinePaths(cachePath, "index.d.ts"), { moduleResolution: ModuleResolutionKind.NodeJs }, installTypingHost);
+ return result.resolvedModule && result.resolvedModule.resolvedFileName;
}
export abstract class TypingsInstaller {
- private isTsdInstalled: boolean;
-
private packageNameToTypingLocation: Map = createMap();
private missingTypingsSet: Map = createMap();
private knownCachesSet: Map = createMap();
-
private projectWatchers: Map = createMap();
abstract readonly installTypingHost: InstallTypingHost;
@@ -50,20 +37,6 @@ namespace ts.server.typingsInstaller {
}
init() {
- this.isTsdInstalled = this.isPackageInstalled("tsd");
- if (this.log.isEnabled()) {
- this.log.writeLine(`isTsdInstalled: ${this.isTsdInstalled}`);
- }
-
- if (!this.isTsdInstalled) {
- if (this.log.isEnabled()) {
- this.log.writeLine(`tsd is not installed, installing tsd...`);
- }
- this.isTsdInstalled = this.installPackage("tsd");
- if (this.log.isEnabled()) {
- this.log.writeLine(`isTsdInstalled: ${this.isTsdInstalled}`);
- }
- }
this.processCacheLocation(this.globalCachePath);
}
@@ -94,13 +67,6 @@ namespace ts.server.typingsInstaller {
}
install(req: DiscoverTypings) {
- if (!this.isTsdInstalled) {
- if (this.log.isEnabled()) {
- this.log.writeLine(`tsd is not installed, ignoring request...`);
- }
- return;
- }
-
if (this.log.isEnabled()) {
this.log.writeLine(`Got install request ${JSON.stringify(req)}`);
}
@@ -153,23 +119,26 @@ namespace ts.server.typingsInstaller {
}
return;
}
- const tsdJson = combinePaths(cacheLocation, "tsd.json");
+ const packageJson = combinePaths(cacheLocation, "package.json");
if (this.log.isEnabled()) {
- this.log.writeLine(`Trying to find '${tsdJson}'...`);
+ this.log.writeLine(`Trying to find '${packageJson}'...`);
}
- if (this.installTypingHost.fileExists(tsdJson)) {
- const tsdConfig = JSON.parse(this.installTypingHost.readFile(tsdJson));
+ if (this.installTypingHost.fileExists(packageJson)) {
+ const npmConfig = JSON.parse(this.installTypingHost.readFile(packageJson));
if (this.log.isEnabled()) {
- this.log.writeLine(`Loaded content of '${tsdJson}': ${JSON.stringify(tsdConfig)}`);
+ this.log.writeLine(`Loaded content of '${npmConfig}': ${JSON.stringify(npmConfig)}`);
}
- if (tsdConfig.installed) {
- for (const key in tsdConfig.installed) {
- // key is /
- const packageName = getPackageName(key);
+ if (npmConfig.devDependencies) {
+ for (const key in npmConfig.devDependencies) {
+ // key is @types/
+ const packageName = getBaseFileName(key);
if (!packageName) {
continue;
}
- const typingFile = tsdTypingToFileName(cacheLocation, key);
+ const typingFile = typingToFileName(cacheLocation, packageName, this.installTypingHost);
+ if (!typingFile) {
+ continue;
+ }
const existingTypingFile = this.packageNameToTypingLocation[packageName];
if (existingTypingFile === typingFile) {
continue;
@@ -204,20 +173,19 @@ namespace ts.server.typingsInstaller {
return;
}
- // TODO: install typings and send response when they are ready
- const tsdPath = combinePaths(cachePath, "tsd.json");
+ const npmConfigPath = combinePaths(cachePath, "package.json");
if (this.log.isEnabled()) {
- this.log.writeLine(`Tsd config file: ${tsdPath}`);
+ this.log.writeLine(`Npm config file: ${npmConfigPath}`);
}
- if (!this.installTypingHost.fileExists(tsdPath)) {
+ if (!this.installTypingHost.fileExists(npmConfigPath)) {
if (this.log.isEnabled()) {
- this.log.writeLine(`Tsd config file '${tsdPath}' is missing, creating new one...`);
+ this.log.writeLine(`Npm config file: '${npmConfigPath}' is missing, creating new one...`);
}
this.ensureDirectoryExists(cachePath, this.installTypingHost);
- this.installTypingHost.writeFile(tsdPath, DefaultTsdSettings);
+ this.installTypingHost.writeFile(npmConfigPath, "{}");
}
- this.runTsd(cachePath, typingsToInstall, installedTypings => {
+ this.runInstall(cachePath, typingsToInstall, installedTypings => {
// TODO: watch project directory
if (this.log.isEnabled()) {
this.log.writeLine(`Requested to install typings ${JSON.stringify(typingsToInstall)}, installed typings ${JSON.stringify(installedTypings)}`);
@@ -225,12 +193,19 @@ namespace ts.server.typingsInstaller {
const installedPackages: Map = createMap();
const installedTypingFiles: string[] = [];
for (const t of installedTypings) {
- const packageName = getPackageName(t);
+ const packageName = getBaseFileName(t);
if (!packageName) {
continue;
}
installedPackages[packageName] = true;
- installedTypingFiles.push(tsdTypingToFileName(cachePath, t));
+ const typingFile = typingToFileName(cachePath, packageName, this.installTypingHost);
+ if (!typingFile) {
+ continue;
+ }
+ if (!this.packageNameToTypingLocation[packageName]) {
+ this.packageNameToTypingLocation[packageName] = typingFile;
+ }
+ installedTypingFiles.push(typingFile);
}
if (this.log.isEnabled()) {
this.log.writeLine(`Installed typing files ${JSON.stringify(installedTypingFiles)}`);
@@ -292,8 +267,7 @@ namespace ts.server.typingsInstaller {
}
protected abstract isPackageInstalled(packageName: string): boolean;
- protected abstract installPackage(packageName: string): boolean;
protected abstract sendResponse(response: SetTypings | InvalidateCachedTypings): void;
- protected abstract runTsd(cachePath: string, typingsToInstall: string[], postInstallAction: (installedTypings: string[]) => void): void;
+ protected abstract runInstall(cachePath: string, typingsToInstall: string[], postInstallAction: (installedTypings: string[]) => void): void;
}
}
\ No newline at end of file