Merge pull request #10673 from Microsoft/tsserverVS-Types2.0
Updating TSServer to use @Types instead of TSD for d.ts auto acquisition
This commit is contained in:
commit
4fc38fb1bf
9 changed files with 311 additions and 133 deletions
|
@ -18627,6 +18627,9 @@ namespace ts {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const file = host.getSourceFile(resolvedDirective.resolvedFileName);
|
const file = host.getSourceFile(resolvedDirective.resolvedFileName);
|
||||||
|
if (!file) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
fileToDirective.set(file.path, key);
|
fileToDirective.set(file.path, key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,17 @@
|
||||||
/// <reference path="../../server/typingsInstaller/typingsInstaller.ts" />
|
/// <reference path="../../server/typingsInstaller/typingsInstaller.ts" />
|
||||||
|
|
||||||
namespace ts.projectSystem {
|
namespace ts.projectSystem {
|
||||||
|
const safeList = {
|
||||||
|
path: <Path>"/safeList.json",
|
||||||
|
content: JSON.stringify({
|
||||||
|
commander: "commander",
|
||||||
|
express: "express",
|
||||||
|
jquery: "jquery",
|
||||||
|
lodash: "lodash",
|
||||||
|
moment: "moment"
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
export function notImplemented(): any {
|
export function notImplemented(): any {
|
||||||
throw new Error("Not yet implemented");
|
throw new Error("Not yet implemented");
|
||||||
}
|
}
|
||||||
|
@ -31,11 +42,11 @@ namespace ts.projectSystem {
|
||||||
export class TestTypingsInstaller extends server.typingsInstaller.TypingsInstaller implements server.ITypingsInstaller {
|
export class TestTypingsInstaller extends server.typingsInstaller.TypingsInstaller implements server.ITypingsInstaller {
|
||||||
protected projectService: server.ProjectService;
|
protected projectService: server.ProjectService;
|
||||||
constructor(readonly globalTypingsCacheLocation: string, readonly installTypingHost: server.ServerHost) {
|
constructor(readonly globalTypingsCacheLocation: string, readonly installTypingHost: server.ServerHost) {
|
||||||
super(globalTypingsCacheLocation, <Path>"");
|
super(globalTypingsCacheLocation, safeList.path);
|
||||||
this.init();
|
this.init();
|
||||||
}
|
}
|
||||||
|
|
||||||
safeFileList = <Path>"";
|
safeFileList = safeList.path;
|
||||||
postInstallActions: ((map: (t: string[]) => string[]) => void)[] = [];
|
postInstallActions: ((map: (t: string[]) => string[]) => void)[] = [];
|
||||||
|
|
||||||
runPostInstallActions(map: (t: string[]) => string[]) {
|
runPostInstallActions(map: (t: string[]) => string[]) {
|
||||||
|
@ -56,15 +67,11 @@ namespace ts.projectSystem {
|
||||||
return this.installTypingHost;
|
return this.installTypingHost;
|
||||||
}
|
}
|
||||||
|
|
||||||
installPackage(packageName: string) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
isPackageInstalled(packageName: string) {
|
isPackageInstalled(packageName: string) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
runTsd(cachePath: string, typingsToInstall: string[], postInstallAction: (installedTypings: string[]) => void) {
|
runInstall(cachePath: string, typingsToInstall: string[], postInstallAction: (installedTypings: string[]) => void) {
|
||||||
this.postInstallActions.push(map => {
|
this.postInstallActions.push(map => {
|
||||||
postInstallAction(map(typingsToInstall));
|
postInstallAction(map(typingsToInstall));
|
||||||
});
|
});
|
||||||
|
@ -106,11 +113,13 @@ namespace ts.projectSystem {
|
||||||
if (!params) {
|
if (!params) {
|
||||||
params = {};
|
params = {};
|
||||||
}
|
}
|
||||||
return new TestServerHost(
|
const host = new TestServerHost(
|
||||||
params.useCaseSensitiveFileNames !== undefined ? params.useCaseSensitiveFileNames : false,
|
params.useCaseSensitiveFileNames !== undefined ? params.useCaseSensitiveFileNames : false,
|
||||||
params.executingFilePath || getExecutingFilePathFromLibFile(libFilePath),
|
params.executingFilePath || getExecutingFilePathFromLibFile(libFilePath),
|
||||||
params.currentDirectory || "/",
|
params.currentDirectory || "/",
|
||||||
fileOrFolderList);
|
fileOrFolderList);
|
||||||
|
host.createFileOrFolder(safeList, /*createParentDirectory*/ true);
|
||||||
|
return host;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createSession(host: server.ServerHost, typingsInstaller?: server.ITypingsInstaller) {
|
export function createSession(host: server.ServerHost, typingsInstaller?: server.ITypingsInstaller) {
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
namespace ts.projectSystem {
|
namespace ts.projectSystem {
|
||||||
describe("typings installer", () => {
|
describe("typings installer", () => {
|
||||||
it("configured projects (tsd installed) 1", () => {
|
it("configured projects (typings installed) 1", () => {
|
||||||
const file1 = {
|
const file1 = {
|
||||||
path: "/a/b/app.js",
|
path: "/a/b/app.js",
|
||||||
content: ""
|
content: ""
|
||||||
|
@ -31,7 +31,7 @@ namespace ts.projectSystem {
|
||||||
};
|
};
|
||||||
|
|
||||||
const jquery = {
|
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 }"
|
content: "declare const $: { x: number }"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -44,18 +44,18 @@ namespace ts.projectSystem {
|
||||||
const p = projectService.configuredProjects[0];
|
const p = projectService.configuredProjects[0];
|
||||||
checkProjectActualFiles(p, [file1.path]);
|
checkProjectActualFiles(p, [file1.path]);
|
||||||
|
|
||||||
assert(host.fileExists(combinePaths(installer.globalTypingsCacheLocation, "tsd.json")));
|
assert(host.fileExists(combinePaths(installer.globalTypingsCacheLocation, "package.json")));
|
||||||
|
|
||||||
installer.runPostInstallActions(t => {
|
installer.runPostInstallActions(t => {
|
||||||
assert.deepEqual(t, ["jquery"]);
|
assert.deepEqual(t, ["jquery"]);
|
||||||
host.createFileOrFolder(jquery, /*createParentDirectory*/ true);
|
host.createFileOrFolder(jquery, /*createParentDirectory*/ true);
|
||||||
return ["jquery/jquery.d.ts"];
|
return ["@types/jquery"];
|
||||||
});
|
});
|
||||||
checkNumberOfProjects(projectService, { configuredProjects: 1 });
|
checkNumberOfProjects(projectService, { configuredProjects: 1 });
|
||||||
checkProjectActualFiles(p, [file1.path, jquery.path]);
|
checkProjectActualFiles(p, [file1.path, jquery.path]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("inferred project (tsd installed)", () => {
|
it("inferred project (typings installed)", () => {
|
||||||
const file1 = {
|
const file1 = {
|
||||||
path: "/a/b/app.js",
|
path: "/a/b/app.js",
|
||||||
content: ""
|
content: ""
|
||||||
|
@ -71,7 +71,7 @@ namespace ts.projectSystem {
|
||||||
};
|
};
|
||||||
|
|
||||||
const jquery = {
|
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 }"
|
content: "declare const $: { x: number }"
|
||||||
};
|
};
|
||||||
const host = createServerHost([file1, packageJson]);
|
const host = createServerHost([file1, packageJson]);
|
||||||
|
@ -84,12 +84,12 @@ namespace ts.projectSystem {
|
||||||
const p = projectService.inferredProjects[0];
|
const p = projectService.inferredProjects[0];
|
||||||
checkProjectActualFiles(p, [file1.path]);
|
checkProjectActualFiles(p, [file1.path]);
|
||||||
|
|
||||||
assert(host.fileExists(combinePaths(installer.globalTypingsCacheLocation, "tsd.json")));
|
assert(host.fileExists(combinePaths(installer.globalTypingsCacheLocation, "package.json")));
|
||||||
|
|
||||||
installer.runPostInstallActions(t => {
|
installer.runPostInstallActions(t => {
|
||||||
assert.deepEqual(t, ["jquery"]);
|
assert.deepEqual(t, ["jquery"]);
|
||||||
host.createFileOrFolder(jquery, /*createParentDirectory*/ true);
|
host.createFileOrFolder(jquery, /*createParentDirectory*/ true);
|
||||||
return ["jquery/jquery.d.ts"];
|
return ["@types/jquery"];
|
||||||
});
|
});
|
||||||
checkNumberOfProjects(projectService, { inferredProjects: 1 });
|
checkNumberOfProjects(projectService, { inferredProjects: 1 });
|
||||||
checkProjectActualFiles(p, [file1.path, jquery.path]);
|
checkProjectActualFiles(p, [file1.path, jquery.path]);
|
||||||
|
@ -157,7 +157,7 @@ namespace ts.projectSystem {
|
||||||
};
|
};
|
||||||
const host = createServerHost([file1]);
|
const host = createServerHost([file1]);
|
||||||
let enqueueIsCalled = false;
|
let enqueueIsCalled = false;
|
||||||
let runTsdIsCalled = false;
|
let runInstallIsCalled = false;
|
||||||
const installer = new (class extends TestTypingsInstaller {
|
const installer = new (class extends TestTypingsInstaller {
|
||||||
constructor() {
|
constructor() {
|
||||||
super("", host);
|
super("", host);
|
||||||
|
@ -166,10 +166,10 @@ namespace ts.projectSystem {
|
||||||
enqueueIsCalled = true;
|
enqueueIsCalled = true;
|
||||||
super.enqueueInstallTypingsRequest(project, typingOptions);
|
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"]);
|
assert.deepEqual(typingsToInstall, ["node"]);
|
||||||
runTsdIsCalled = true;
|
runInstallIsCalled = true;
|
||||||
super.runTsd(cachePath, typingsToInstall, postInstallAction);
|
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
|
// autoDiscovery is set in typing options - use it even if project contains only .ts files
|
||||||
projectService.checkNumberOfProjects({ externalProjects: 1 });
|
projectService.checkNumberOfProjects({ externalProjects: 1 });
|
||||||
assert.isTrue(enqueueIsCalled, "expected 'enqueueIsCalled' to be true");
|
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]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
|
@ -312,6 +312,7 @@ namespace ts.server {
|
||||||
const info = this.getScriptInfoForNormalizedPath(fileName);
|
const info = this.getScriptInfoForNormalizedPath(fileName);
|
||||||
if (!info) {
|
if (!info) {
|
||||||
this.logger.info(`Error: got watch notification for unknown file: ${fileName}`);
|
this.logger.info(`Error: got watch notification for unknown file: ${fileName}`);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.host.fileExists(fileName)) {
|
if (!this.host.fileExists(fileName)) {
|
||||||
|
|
|
@ -301,7 +301,7 @@ namespace ts.server {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
let hasChanges = this.updateGraphWorker();
|
let hasChanges = this.updateGraphWorker();
|
||||||
const cachedTypings = this.projectService.typingsCache.getTypingsForProject(this);
|
const cachedTypings = this.projectService.typingsCache.getTypingsForProject(this, hasChanges);
|
||||||
if (this.setTypings(cachedTypings)) {
|
if (this.setTypings(cachedTypings)) {
|
||||||
hasChanges = this.updateGraphWorker() || hasChanges;
|
hasChanges = this.updateGraphWorker() || hasChanges;
|
||||||
}
|
}
|
||||||
|
@ -423,17 +423,15 @@ namespace ts.server {
|
||||||
const added: string[] = [];
|
const added: string[] = [];
|
||||||
const removed: string[] = [];
|
const removed: string[] = [];
|
||||||
for (const id in currentFiles) {
|
for (const id in currentFiles) {
|
||||||
if (hasProperty(currentFiles, id) && !hasProperty(lastReportedFileNames, id)) {
|
if (!hasProperty(lastReportedFileNames, id)) {
|
||||||
added.push(id);
|
added.push(id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (const id in lastReportedFileNames) {
|
for (const id in lastReportedFileNames) {
|
||||||
if (hasProperty(lastReportedFileNames, id) && !hasProperty(currentFiles, id)) {
|
if (!hasProperty(currentFiles, id)) {
|
||||||
removed.push(id);
|
removed.push(id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.lastReportedFileNames = currentFiles;
|
|
||||||
|
|
||||||
this.lastReportedFileNames = currentFiles;
|
this.lastReportedFileNames = currentFiles;
|
||||||
this.lastReportedVersion = this.projectStructureVersion;
|
this.lastReportedVersion = this.projectStructureVersion;
|
||||||
return { info, changes: { added, removed }, projectErrors: this.projectErrors };
|
return { info, changes: { added, removed }, projectErrors: this.projectErrors };
|
||||||
|
|
|
@ -168,7 +168,7 @@ namespace ts.server {
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
this.projectService = new ProjectService(host, logger, cancellationToken, useSingleInferredProject, typingsInstaller, eventHandler);
|
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) {
|
private handleEvent(event: ProjectServiceEvent) {
|
||||||
|
|
|
@ -76,7 +76,7 @@ namespace ts.server {
|
||||||
constructor(private readonly installer: ITypingsInstaller) {
|
constructor(private readonly installer: ITypingsInstaller) {
|
||||||
}
|
}
|
||||||
|
|
||||||
getTypingsForProject(project: Project): TypingsArray {
|
getTypingsForProject(project: Project, forceRefresh: boolean): TypingsArray {
|
||||||
const typingOptions = project.getTypingOptions();
|
const typingOptions = project.getTypingOptions();
|
||||||
|
|
||||||
if (!typingOptions || !typingOptions.enableAutoDiscovery) {
|
if (!typingOptions || !typingOptions.enableAutoDiscovery) {
|
||||||
|
@ -85,9 +85,7 @@ namespace ts.server {
|
||||||
|
|
||||||
const entry = this.perProjectCache[project.getProjectName()];
|
const entry = this.perProjectCache[project.getProjectName()];
|
||||||
const result: TypingsArray = entry ? entry.typings : <any>emptyArray;
|
const result: TypingsArray = entry ? entry.typings : <any>emptyArray;
|
||||||
if (!entry || typingOptionsChanged(typingOptions, entry.typingOptions) || compilerOptionsChanged(project.getCompilerOptions(), entry.compilerOptions)) {
|
if (forceRefresh || !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);
|
|
||||||
// Note: entry is now poisoned since it does not really contain typings for a given combination of compiler options\typings options.
|
// 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
|
// instead it acts as a placeholder to prevent issuing multiple requests
|
||||||
this.perProjectCache[project.getProjectName()] = {
|
this.perProjectCache[project.getProjectName()] = {
|
||||||
|
@ -96,6 +94,8 @@ namespace ts.server {
|
||||||
typings: result,
|
typings: result,
|
||||||
poisoned: true
|
poisoned: true
|
||||||
};
|
};
|
||||||
|
// something has been changed, issue a request to update typings
|
||||||
|
this.installer.enqueueInstallTypingsRequest(project, typingOptions);
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 exec: { (command: string, options: { cwd: string }, callback?: (error: Error, stdout: string, stderr: string) => void): any };
|
||||||
private npmBinPath: string;
|
private npmBinPath: string;
|
||||||
|
|
||||||
private tsdRunCount = 1;
|
private installRunCount = 1;
|
||||||
readonly installTypingHost: InstallTypingHost = sys;
|
readonly installTypingHost: InstallTypingHost = sys;
|
||||||
|
|
||||||
constructor(globalTypingsCacheLocation: string, log: Log) {
|
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) {
|
protected sendResponse(response: SetTypings | InvalidateCachedTypings) {
|
||||||
if (this.log.isEnabled()) {
|
if (this.log.isEnabled()) {
|
||||||
this.log.writeLine(`Sending response: ${JSON.stringify(response)}`);
|
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 {
|
protected runInstall(cachePath: string, typingsToInstall: string[], postInstallAction: (installedTypings: string[]) => void): void {
|
||||||
const id = this.tsdRunCount;
|
const id = this.installRunCount;
|
||||||
this.tsdRunCount++;
|
this.installRunCount++;
|
||||||
const tsdPath = combinePaths(this.npmBinPath, "tsd");
|
let execInstallCmdCount = 0;
|
||||||
const command = `${tsdPath} install ${typingsToInstall.join(" ")} -ros`;
|
const filteredTypings: string[] = [];
|
||||||
if (this.log.isEnabled()) {
|
for (const typing of typingsToInstall) {
|
||||||
this.log.writeLine(`Running tsd ${id}, command '${command}'. cache path '${cachePath}'`);
|
const command = `npm view @types/${typing} --silent name`;
|
||||||
|
this.execAsync("npm view", command, cachePath, id, (err, stdout, stderr) => {
|
||||||
|
if (stdout) {
|
||||||
|
filteredTypings.push(typing);
|
||||||
}
|
}
|
||||||
this.exec(command, { cwd: cachePath }, (err, stdout, stderr) => {
|
execInstallCmdCount++;
|
||||||
if (this.log.isEnabled()) {
|
if (execInstallCmdCount === typingsToInstall.length) {
|
||||||
this.log.writeLine(`TSD ${id} stdout: ${stdout}`);
|
installFilteredTypings(this, filteredTypings);
|
||||||
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;
|
function installFilteredTypings(self: NodeTypingsInstaller, filteredTypings: string[]) {
|
||||||
expr.lastIndex = i;
|
const command = `npm install ${filteredTypings.map(t => "@types/" + t).join(" ")} --save-dev`;
|
||||||
let match: RegExpExecArray;
|
self.execAsync("npm install", command, cachePath, id, (err, stdout, stderr) => {
|
||||||
while (match = expr.exec(stdout)) {
|
if (stdout) {
|
||||||
installedTypings.push(match[1]);
|
reportInstalledTypings(self);
|
||||||
}
|
}
|
||||||
postInstallAction(installedTypings);
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
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}`);
|
||||||
|
}
|
||||||
|
cb(err, stdout, stderr);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,16 +1,10 @@
|
||||||
|
/// <reference path="../../compiler/core.ts" />
|
||||||
/// <reference path="../../services/jsTyping.ts"/>
|
/// <reference path="../../services/jsTyping.ts"/>
|
||||||
/// <reference path="../types.d.ts"/>
|
/// <reference path="../types.d.ts"/>
|
||||||
|
|
||||||
namespace ts.server.typingsInstaller {
|
namespace ts.server.typingsInstaller {
|
||||||
const DefaultTsdSettings = JSON.stringify({
|
interface NpmConfig {
|
||||||
version: "v4",
|
devDependencies: MapLike<any>;
|
||||||
repo: "DefinitelyTyped/DefinitelyTyped",
|
|
||||||
ref: "master",
|
|
||||||
path: "typings"
|
|
||||||
}, /*replacer*/undefined, /*space*/4);
|
|
||||||
|
|
||||||
interface TsdConfig {
|
|
||||||
installed: MapLike<any>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Log {
|
export interface Log {
|
||||||
|
@ -23,22 +17,15 @@ namespace ts.server.typingsInstaller {
|
||||||
writeLine: () => {}
|
writeLine: () => {}
|
||||||
};
|
};
|
||||||
|
|
||||||
function tsdTypingToFileName(cachePath: string, tsdTypingFile: string) {
|
function typingToFileName(cachePath: string, packageName: string, installTypingHost: InstallTypingHost): string {
|
||||||
return combinePaths(cachePath, `typings/${tsdTypingFile}`);
|
const result = resolveModuleName(packageName, combinePaths(cachePath, "index.d.ts"), { moduleResolution: ModuleResolutionKind.NodeJs }, installTypingHost);
|
||||||
}
|
return result.resolvedModule && result.resolvedModule.resolvedFileName;
|
||||||
|
|
||||||
function getPackageName(tsdTypingFile: string) {
|
|
||||||
const idx = tsdTypingFile.indexOf("/");
|
|
||||||
return idx > 0 ? tsdTypingFile.substr(0, idx) : undefined;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export abstract class TypingsInstaller {
|
export abstract class TypingsInstaller {
|
||||||
private isTsdInstalled: boolean;
|
|
||||||
|
|
||||||
private packageNameToTypingLocation: Map<string> = createMap<string>();
|
private packageNameToTypingLocation: Map<string> = createMap<string>();
|
||||||
private missingTypingsSet: Map<true> = createMap<true>();
|
private missingTypingsSet: Map<true> = createMap<true>();
|
||||||
private knownCachesSet: Map<true> = createMap<true>();
|
private knownCachesSet: Map<true> = createMap<true>();
|
||||||
|
|
||||||
private projectWatchers: Map<FileWatcher[]> = createMap<FileWatcher[]>();
|
private projectWatchers: Map<FileWatcher[]> = createMap<FileWatcher[]>();
|
||||||
|
|
||||||
abstract readonly installTypingHost: InstallTypingHost;
|
abstract readonly installTypingHost: InstallTypingHost;
|
||||||
|
@ -50,20 +37,6 @@ namespace ts.server.typingsInstaller {
|
||||||
}
|
}
|
||||||
|
|
||||||
init() {
|
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);
|
this.processCacheLocation(this.globalCachePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -94,13 +67,6 @@ namespace ts.server.typingsInstaller {
|
||||||
}
|
}
|
||||||
|
|
||||||
install(req: DiscoverTypings) {
|
install(req: DiscoverTypings) {
|
||||||
if (!this.isTsdInstalled) {
|
|
||||||
if (this.log.isEnabled()) {
|
|
||||||
this.log.writeLine(`tsd is not installed, ignoring request...`);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.log.isEnabled()) {
|
if (this.log.isEnabled()) {
|
||||||
this.log.writeLine(`Got install request ${JSON.stringify(req)}`);
|
this.log.writeLine(`Got install request ${JSON.stringify(req)}`);
|
||||||
}
|
}
|
||||||
|
@ -153,23 +119,26 @@ namespace ts.server.typingsInstaller {
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const tsdJson = combinePaths(cacheLocation, "tsd.json");
|
const packageJson = combinePaths(cacheLocation, "package.json");
|
||||||
if (this.log.isEnabled()) {
|
if (this.log.isEnabled()) {
|
||||||
this.log.writeLine(`Trying to find '${tsdJson}'...`);
|
this.log.writeLine(`Trying to find '${packageJson}'...`);
|
||||||
}
|
}
|
||||||
if (this.installTypingHost.fileExists(tsdJson)) {
|
if (this.installTypingHost.fileExists(packageJson)) {
|
||||||
const tsdConfig = <TsdConfig>JSON.parse(this.installTypingHost.readFile(tsdJson));
|
const npmConfig = <NpmConfig>JSON.parse(this.installTypingHost.readFile(packageJson));
|
||||||
if (this.log.isEnabled()) {
|
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) {
|
if (npmConfig.devDependencies) {
|
||||||
for (const key in tsdConfig.installed) {
|
for (const key in npmConfig.devDependencies) {
|
||||||
// key is <package name>/<typing file>
|
// key is @types/<package name>
|
||||||
const packageName = getPackageName(key);
|
const packageName = getBaseFileName(key);
|
||||||
if (!packageName) {
|
if (!packageName) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const typingFile = tsdTypingToFileName(cacheLocation, key);
|
const typingFile = typingToFileName(cacheLocation, packageName, this.installTypingHost);
|
||||||
|
if (!typingFile) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
const existingTypingFile = this.packageNameToTypingLocation[packageName];
|
const existingTypingFile = this.packageNameToTypingLocation[packageName];
|
||||||
if (existingTypingFile === typingFile) {
|
if (existingTypingFile === typingFile) {
|
||||||
continue;
|
continue;
|
||||||
|
@ -204,20 +173,19 @@ namespace ts.server.typingsInstaller {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: install typings and send response when they are ready
|
const npmConfigPath = combinePaths(cachePath, "package.json");
|
||||||
const tsdPath = combinePaths(cachePath, "tsd.json");
|
|
||||||
if (this.log.isEnabled()) {
|
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()) {
|
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.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
|
// TODO: watch project directory
|
||||||
if (this.log.isEnabled()) {
|
if (this.log.isEnabled()) {
|
||||||
this.log.writeLine(`Requested to install typings ${JSON.stringify(typingsToInstall)}, installed typings ${JSON.stringify(installedTypings)}`);
|
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<true> = createMap<true>();
|
const installedPackages: Map<true> = createMap<true>();
|
||||||
const installedTypingFiles: string[] = [];
|
const installedTypingFiles: string[] = [];
|
||||||
for (const t of installedTypings) {
|
for (const t of installedTypings) {
|
||||||
const packageName = getPackageName(t);
|
const packageName = getBaseFileName(t);
|
||||||
if (!packageName) {
|
if (!packageName) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
installedPackages[packageName] = true;
|
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()) {
|
if (this.log.isEnabled()) {
|
||||||
this.log.writeLine(`Installed typing files ${JSON.stringify(installedTypingFiles)}`);
|
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 isPackageInstalled(packageName: string): boolean;
|
||||||
protected abstract installPackage(packageName: string): boolean;
|
|
||||||
protected abstract sendResponse(response: SetTypings | InvalidateCachedTypings): void;
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
Reference in a new issue