add tsserver specific resolution pass that will load typings from cache locations if auto discovery is enabled

This commit is contained in:
Vladimir Matveev
2016-08-29 22:21:58 -07:00
parent ce02f8319e
commit 38ce6279cd
10 changed files with 223 additions and 62 deletions

View File

@@ -1571,7 +1571,8 @@ namespace ts {
return true;
}
function createResolvedModule(resolvedFileName: string, isExternalLibraryImport: boolean, failedLookupLocations: string[]): ResolvedModuleWithFailedLookupLocations {
/* @internal */
export function createResolvedModule(resolvedFileName: string, isExternalLibraryImport: boolean, failedLookupLocations: string[]): ResolvedModuleWithFailedLookupLocations {
return { resolvedModule: resolvedFileName ? { resolvedFileName, isExternalLibraryImport } : undefined, failedLookupLocations };
}
@@ -1989,7 +1990,7 @@ namespace ts {
if (traceEnabled) {
trace(host, Diagnostics.Loading_module_0_from_node_modules_folder, moduleName);
}
resolvedFileName = loadModuleFromNodeModules(moduleName, containingDirectory, failedLookupLocations, state);
resolvedFileName = loadModuleFromNodeModules(moduleName, containingDirectory, failedLookupLocations, state, /*checkOneLevel*/ false);
isExternalLibraryImport = resolvedFileName !== undefined;
}
else {
@@ -2138,7 +2139,7 @@ namespace ts {
}
/* @internal */
export function loadModuleFromNodeModules(moduleName: string, directory: string, failedLookupLocations: string[], state: ModuleResolutionState): string {
export function loadModuleFromNodeModules(moduleName: string, directory: string, failedLookupLocations: string[], state: ModuleResolutionState, checkOneLevel: boolean): string {
directory = normalizeSlashes(directory);
while (true) {
const baseName = getBaseFileName(directory);
@@ -2159,7 +2160,7 @@ namespace ts {
}
const parentPath = getDirectoryPath(directory);
if (parentPath === directory) {
if (parentPath === directory || checkOneLevel) {
break;
}

View File

@@ -2840,6 +2840,10 @@
"category": "Error",
"code": 6138
},
"Auto discovery for typings is enabled in project '{0}'. Running extra resolution pass for module '{1}' using cache location '{2}'.": {
"category": "Error",
"code": 6139
},
"Variable '{0}' implicitly has an '{1}' type.": {
"category": "Error",
"code": 7005

View File

@@ -174,7 +174,7 @@ namespace ts {
if (traceEnabled) {
trace(host, Diagnostics.Looking_up_in_node_modules_folder_initial_location_0, initialLocationForSecondaryLookup);
}
resolvedFile = loadModuleFromNodeModules(typeReferenceDirectiveName, initialLocationForSecondaryLookup, failedLookupLocations, moduleResolutionState);
resolvedFile = loadModuleFromNodeModules(typeReferenceDirectiveName, initialLocationForSecondaryLookup, failedLookupLocations, moduleResolutionState, /*checkOneLevel*/ false);
if (traceEnabled) {
if (resolvedFile) {
trace(host, Diagnostics.Type_reference_directive_0_was_successfully_resolved_to_1_primary_Colon_2, typeReferenceDirectiveName, resolvedFile, false);

View File

@@ -30,8 +30,8 @@ namespace ts {
class TestTypingsInstaller extends server.typingsInstaller.TypingsInstaller implements server.ITypingsInstaller {
protected projectService: server.ProjectService;
constructor(readonly cachePath: string, readonly installTypingHost: server.ServerHost) {
super(cachePath, <Path>"");
constructor(readonly globalTypingsCacheLocation: string, readonly installTypingHost: server.ServerHost) {
super(globalTypingsCacheLocation, <Path>"");
this.init();
}
@@ -75,7 +75,7 @@ namespace ts {
}
enqueueInstallTypingsRequest(project: server.Project, typingOptions: TypingOptions) {
const request = server.createInstallTypingsRequest(project, typingOptions, this.cachePath);
const request = server.createInstallTypingsRequest(project, typingOptions, this.globalTypingsCacheLocation);
this.install(request);
}
}
@@ -2030,7 +2030,7 @@ namespace ts {
const p = projectService.configuredProjects[0];
checkProjectActualFiles(p, [file1.path]);
assert(host.fileExists(combinePaths(installer.cachePath, "tsd.json")));
assert(host.fileExists(combinePaths(installer.globalTypingsCacheLocation, "tsd.json")));
installer.runPostInstallActions(t => {
assert.deepEqual(t, ["jquery"]);
@@ -2070,7 +2070,7 @@ namespace ts {
const p = projectService.inferredProjects[0];
checkProjectActualFiles(p, [file1.path]);
assert(host.fileExists(combinePaths(installer.cachePath, "tsd.json")));
assert(host.fileExists(combinePaths(installer.globalTypingsCacheLocation, "tsd.json")));
installer.runPostInstallActions(t => {
assert.deepEqual(t, ["jquery"]);
@@ -2398,4 +2398,121 @@ namespace ts {
assert.isTrue(typingOptions.enableAutoDiscovery, "Typing autodiscovery should be enabled");
});
});
describe("extra resolution pass in lshost", () => {
it("can load typings that are proper modules", () => {
const file1 = {
path: "/a/b/app.js",
content: `var x = require("lib")`
};
const lib = {
path: "/a/cache/node_modules/@types/lib/index.d.ts",
content: "export let x = 1"
};
const host: TestServerHost & ModuleResolutionHost = createServerHost([file1, lib]);
const resolutionTrace: string[] = [];
host.trace = resolutionTrace.push.bind(resolutionTrace);
const projectService = createProjectService(host, { typingsInstaller: new TestTypingsInstaller("/a/cache", host) });
projectService.setCompilerOptionsForInferredProjects({ traceResolution: true, allowJs: true });
projectService.openClientFile(file1.path);
projectService.checkNumberOfProjects({ inferredProjects: 1 });
const proj = projectService.inferredProjects[0];
assert.deepEqual(resolutionTrace, [
"======== Resolving module 'lib' from '/a/b/app.js'. ========",
"Module resolution kind is not specified, using 'NodeJs'.",
"Loading module 'lib' from 'node_modules' folder.",
"File '/a/b/node_modules/lib.ts' does not exist.",
"File '/a/b/node_modules/lib.tsx' does not exist.",
"File '/a/b/node_modules/lib.d.ts' does not exist.",
"File '/a/b/node_modules/lib.js' does not exist.",
"File '/a/b/node_modules/lib.jsx' does not exist.",
"File '/a/b/node_modules/lib/package.json' does not exist.",
"File '/a/b/node_modules/lib/index.ts' does not exist.",
"File '/a/b/node_modules/lib/index.tsx' does not exist.",
"File '/a/b/node_modules/lib/index.d.ts' does not exist.",
"File '/a/b/node_modules/lib/index.js' does not exist.",
"File '/a/b/node_modules/lib/index.jsx' does not exist.",
"File '/a/b/node_modules/@types/lib.ts' does not exist.",
"File '/a/b/node_modules/@types/lib.tsx' does not exist.",
"File '/a/b/node_modules/@types/lib.d.ts' does not exist.",
"File '/a/b/node_modules/@types/lib.js' does not exist.",
"File '/a/b/node_modules/@types/lib.jsx' does not exist.",
"File '/a/b/node_modules/@types/lib/package.json' does not exist.",
"File '/a/b/node_modules/@types/lib/index.ts' does not exist.",
"File '/a/b/node_modules/@types/lib/index.tsx' does not exist.",
"File '/a/b/node_modules/@types/lib/index.d.ts' does not exist.",
"File '/a/b/node_modules/@types/lib/index.js' does not exist.",
"File '/a/b/node_modules/@types/lib/index.jsx' does not exist.",
"File '/a/node_modules/lib.ts' does not exist.",
"File '/a/node_modules/lib.tsx' does not exist.",
"File '/a/node_modules/lib.d.ts' does not exist.",
"File '/a/node_modules/lib.js' does not exist.",
"File '/a/node_modules/lib.jsx' does not exist.",
"File '/a/node_modules/lib/package.json' does not exist.",
"File '/a/node_modules/lib/index.ts' does not exist.",
"File '/a/node_modules/lib/index.tsx' does not exist.",
"File '/a/node_modules/lib/index.d.ts' does not exist.",
"File '/a/node_modules/lib/index.js' does not exist.",
"File '/a/node_modules/lib/index.jsx' does not exist.",
"File '/a/node_modules/@types/lib.ts' does not exist.",
"File '/a/node_modules/@types/lib.tsx' does not exist.",
"File '/a/node_modules/@types/lib.d.ts' does not exist.",
"File '/a/node_modules/@types/lib.js' does not exist.",
"File '/a/node_modules/@types/lib.jsx' does not exist.",
"File '/a/node_modules/@types/lib/package.json' does not exist.",
"File '/a/node_modules/@types/lib/index.ts' does not exist.",
"File '/a/node_modules/@types/lib/index.tsx' does not exist.",
"File '/a/node_modules/@types/lib/index.d.ts' does not exist.",
"File '/a/node_modules/@types/lib/index.js' does not exist.",
"File '/a/node_modules/@types/lib/index.jsx' does not exist.",
"File '/node_modules/lib.ts' does not exist.",
"File '/node_modules/lib.tsx' does not exist.",
"File '/node_modules/lib.d.ts' does not exist.",
"File '/node_modules/lib.js' does not exist.",
"File '/node_modules/lib.jsx' does not exist.",
"File '/node_modules/lib/package.json' does not exist.",
"File '/node_modules/lib/index.ts' does not exist.",
"File '/node_modules/lib/index.tsx' does not exist.",
"File '/node_modules/lib/index.d.ts' does not exist.",
"File '/node_modules/lib/index.js' does not exist.",
"File '/node_modules/lib/index.jsx' does not exist.",
"File '/node_modules/@types/lib.ts' does not exist.",
"File '/node_modules/@types/lib.tsx' does not exist.",
"File '/node_modules/@types/lib.d.ts' does not exist.",
"File '/node_modules/@types/lib.js' does not exist.",
"File '/node_modules/@types/lib.jsx' does not exist.",
"File '/node_modules/@types/lib/package.json' does not exist.",
"File '/node_modules/@types/lib/index.ts' does not exist.",
"File '/node_modules/@types/lib/index.tsx' does not exist.",
"File '/node_modules/@types/lib/index.d.ts' does not exist.",
"File '/node_modules/@types/lib/index.js' does not exist.",
"File '/node_modules/@types/lib/index.jsx' does not exist.",
"======== Module name 'lib' was not resolved. ========",
`Auto discovery for typings is enabled in project '${proj.getProjectName()}'. Running extra resolution pass for module 'lib' using cache location '/a/cache'.`,
"File '/a/cache/node_modules/lib.ts' does not exist.",
"File '/a/cache/node_modules/lib.tsx' does not exist.",
"File '/a/cache/node_modules/lib.d.ts' does not exist.",
"File '/a/cache/node_modules/lib.js' does not exist.",
"File '/a/cache/node_modules/lib.jsx' does not exist.",
"File '/a/cache/node_modules/lib/package.json' does not exist.",
"File '/a/cache/node_modules/lib/index.ts' does not exist.",
"File '/a/cache/node_modules/lib/index.tsx' does not exist.",
"File '/a/cache/node_modules/lib/index.d.ts' does not exist.",
"File '/a/cache/node_modules/lib/index.js' does not exist.",
"File '/a/cache/node_modules/lib/index.jsx' does not exist.",
"File '/a/cache/node_modules/@types/lib.ts' does not exist.",
"File '/a/cache/node_modules/@types/lib.tsx' does not exist.",
"File '/a/cache/node_modules/@types/lib.d.ts' does not exist.",
"File '/a/cache/node_modules/@types/lib.js' does not exist.",
"File '/a/cache/node_modules/@types/lib.jsx' does not exist.",
"File '/a/cache/node_modules/@types/lib/package.json' does not exist.",
"File '/a/cache/node_modules/@types/lib/index.ts' does not exist.",
"File '/a/cache/node_modules/@types/lib/index.tsx' does not exist.",
"File '/a/cache/node_modules/@types/lib/index.d.ts' exist - use it as a name resolution result.",
]);
checkProjectActualFiles(proj, [ file1.path, lib.path ]);
});
});
}

View File

@@ -183,17 +183,16 @@ namespace ts.server {
public readonly logger: Logger,
public readonly cancellationToken: HostCancellationToken,
private readonly useSingleInferredProject: boolean,
private typingsInstaller: ITypingsInstaller,
readonly typingsInstaller: ITypingsInstaller = nullTypingsInstaller,
private readonly eventHandler?: ProjectServiceEventHandler) {
this.toCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames);
this.directoryWatchers = new DirectoryWatchers(this);
this.throttledOperations = new ThrottledOperations(host);
const installer = typingsInstaller || nullTypingsInstaller;
installer.attach(this);
this.typingsInstaller.attach(this);
this.typingsCache = new TypingsCache(installer);
this.typingsCache = new TypingsCache(this.typingsInstaller);
// ts.disableIncrementalParsing = true;

View File

@@ -9,10 +9,37 @@ namespace ts.server {
private readonly resolvedTypeReferenceDirectives: ts.FileMap<Map<ResolvedTypeReferenceDirectiveWithFailedLookupLocations>>;
private readonly getCanonicalFileName: (fileName: string) => string;
private readonly resolveModuleName: typeof resolveModuleName;
readonly trace: (s: string) => void;
constructor(private readonly host: ServerHost, private readonly project: Project, private readonly cancellationToken: HostCancellationToken) {
this.getCanonicalFileName = ts.createGetCanonicalFileName(this.host.useCaseSensitiveFileNames);
this.resolvedModuleNames = createFileMap<Map<ResolvedModuleWithFailedLookupLocations>>();
this.resolvedTypeReferenceDirectives = createFileMap<Map<ResolvedTypeReferenceDirectiveWithFailedLookupLocations>>();
if (host.trace) {
this.trace = s => host.trace(s);
}
this.resolveModuleName = (moduleName, containingFile, compilerOptions, host) => {
const primaryResult = resolveModuleName(moduleName, containingFile, compilerOptions, host);
if (primaryResult.resolvedModule) {
return primaryResult;
}
const globalCache = this.project.projectService.typingsInstaller.globalTypingsCacheLocation;
if (this.project.getTypingOptions().enableAutoDiscovery && globalCache) {
const traceEnabled = isTraceEnabled(compilerOptions, host);
if (traceEnabled) {
trace(host, Diagnostics.Auto_discovery_for_typings_is_enabled_in_project_0_Running_extra_resolution_pass_for_module_1_using_cache_location_2, this.project.getProjectName(), moduleName, globalCache);
}
const state: ModuleResolutionState = { compilerOptions, host, skipTsx: false, traceEnabled };
const resolvedName = loadModuleFromNodeModules(moduleName, globalCache, primaryResult.failedLookupLocations, state, /*checkOneLevel*/ true);
if (resolvedName) {
return createResolvedModule(resolvedName, /*isExternalLibraryImport*/ true, primaryResult.failedLookupLocations);
}
}
return primaryResult;
};
}
private resolveNamesWithLocalCache<T extends { failedLookupLocations: string[] }, R>(
@@ -89,7 +116,7 @@ namespace ts.server {
}
resolveModuleNames(moduleNames: string[], containingFile: string): ResolvedModule[] {
return this.resolveNamesWithLocalCache(moduleNames, containingFile, this.resolvedModuleNames, resolveModuleName, m => m.resolvedModule);
return this.resolveNamesWithLocalCache(moduleNames, containingFile, this.resolvedModuleNames, this.resolveModuleName, m => m.resolvedModule);
}
getDefaultLibFileName() {

View File

@@ -13,6 +13,29 @@ namespace ts.server {
fork(modulePath: string, args: string[], options?: { execArgv: string[], env?: MapLike<string> }): NodeChildProcess;
} = require("child_process");
const os: {
homedir(): string
} = require("os");
function getGlobalTypingsCacheLocation() {
let basePath: string;
switch (process.platform) {
case "win32":
basePath = process.env.LOCALAPPDATA || process.env.APPDATA || os.homedir();
break;
case "linux":
basePath = os.homedir();
break;
case "darwin":
basePath = combinePaths(os.homedir(), "Library/Application Support/");
break;
}
Debug.assert(basePath !== undefined);
return combinePaths(normalizeSlashes(basePath), "Microsoft/TypeScript");
}
interface NodeChildProcess {
send(message: any, sendHandle?: any): void;
on(message: "message", f: (m: any) => void): void;
@@ -167,7 +190,11 @@ namespace ts.server {
private socket: NodeSocket;
private projectService: ProjectService;
constructor(private readonly logger: server.Logger, private readonly eventPort: number, private newLine: string) {
constructor(
private readonly logger: server.Logger,
private readonly eventPort: number,
readonly globalTypingsCacheLocation: string,
private newLine: string) {
if (eventPort) {
const s = net.connect({ port: eventPort }, () => {
this.socket = s;
@@ -181,7 +208,7 @@ namespace ts.server {
this.logger.info("Binding...");
}
const args: string[] = [];
const args: string[] = ["--globalTypingsCacheLocation", this.globalTypingsCacheLocation];
if (this.logger.loggingEnabled() && this.logger.getLogFileName()) {
args.push("--logFile", combinePaths(getDirectoryPath(normalizeSlashes(this.logger.getLogFileName())), `ti-${process.pid}.log`));
}
@@ -238,12 +265,13 @@ namespace ts.server {
installerEventPort: number,
canUseEvents: boolean,
useSingleInferredProject: boolean,
globalTypingsCacheLocation: string,
logger: server.Logger) {
super(
host,
cancellationToken,
useSingleInferredProject,
new NodeTypingsInstaller(logger, installerEventPort, host.newLine),
new NodeTypingsInstaller(logger, installerEventPort, globalTypingsCacheLocation, host.newLine),
Buffer.byteLength,
process.hrtime,
logger,
@@ -442,12 +470,6 @@ namespace ts.server {
}
}
function writeCompressedData(prefix: string, compressed: CompressedData, suffix: string): void {
sys.write(prefix);
writeMessage(compressed.data);
sys.write(suffix);
}
const sys = <ServerHost>ts.sys;
// Override sys.write because fs.writeSync is not reliable on Node 4
@@ -463,7 +485,6 @@ namespace ts.server {
sys.clearTimeout = clearTimeout;
sys.setImmediate = setImmediate;
sys.clearImmediate = clearImmediate;
sys.writeCompressedData = writeCompressedData;
if (typeof global !== "undefined" && global.gc) {
sys.gc = () => global.gc();
}
@@ -491,7 +512,14 @@ namespace ts.server {
}
const useSingleInferredProject = sys.args.indexOf("--useSingleInferredProject") >= 0;
const ioSession = new IOSession(sys, cancellationToken, eventPort, /*canUseEvents*/ eventPort === undefined, useSingleInferredProject, logger);
const ioSession = new IOSession(
sys,
cancellationToken,
eventPort,
/*canUseEvents*/ eventPort === undefined,
useSingleInferredProject,
getGlobalTypingsCacheLocation(),
logger);
process.on("uncaughtException", function (err: Error) {
ioSession.logError(err, "unknown");
});

View File

@@ -16,6 +16,7 @@ declare namespace ts.server {
clearImmediate(timeoutId: any): void;
writeCompressedData(prefix: string, data: CompressedData, suffix: string): void;
gc?(): void;
trace?(s: string): void;
}
export interface TypingInstallerRequest {

View File

@@ -5,12 +5,14 @@ namespace ts.server {
enqueueInstallTypingsRequest(p: Project, typingOptions: TypingOptions): void;
attach(projectService: ProjectService): void;
onProjectClosed(p: Project): void;
readonly globalTypingsCacheLocation: string;
}
export const nullTypingsInstaller: ITypingsInstaller = {
enqueueInstallTypingsRequest: () => {},
attach: (projectService: ProjectService) => {},
onProjectClosed: (p: Project) => {}
onProjectClosed: (p: Project) => {},
globalTypingsCacheLocation: undefined
};
class TypingsCacheEntry {

View File

@@ -3,32 +3,10 @@
namespace ts.server.typingsInstaller {
const os: {
homedir(): string
} = require("os");
const fs: {
appendFileSync(file: string, content: string): void
} = require("fs");
function getGlobalCacheLocation() {
let basePath: string;
switch (process.platform) {
case "win32":
basePath = process.env.LOCALAPPDATA || process.env.APPDATA || os.homedir();
break;
case "linux":
basePath = os.homedir();
break;
case "darwin":
basePath = combinePaths(os.homedir(), "Library/Application Support/");
break;
}
Debug.assert(basePath !== undefined);
return combinePaths(normalizeSlashes(basePath), "Microsoft/TypeScript");
}
class FileLog implements Log {
constructor(private readonly logFile?: string) {
}
@@ -49,8 +27,8 @@ namespace ts.server.typingsInstaller {
private tsdRunCount = 1;
readonly installTypingHost: InstallTypingHost = sys;
constructor(log?: Log) {
super(getGlobalCacheLocation(), toPath("typingSafeList.json", __dirname, createGetCanonicalFileName(sys.useCaseSensitiveFileNames)), log);
constructor(globalTypingsCacheLocation: string, log: Log) {
super(globalTypingsCacheLocation, toPath("typingSafeList.json", __dirname, createGetCanonicalFileName(sys.useCaseSensitiveFileNames)), log);
if (this.log.isEnabled()) {
this.log.writeLine(`Process id: ${process.pid}`);
}
@@ -158,23 +136,27 @@ namespace ts.server.typingsInstaller {
}
}
let logFilePath: string;
{
const logFileIndex = sys.args.indexOf("--logFile");
if (logFileIndex >= 0 && logFileIndex < sys.args.length - 1) {
logFilePath = sys.args[logFileIndex + 1];
}
function findArgument(argumentName: string) {
const index = sys.args.indexOf(argumentName);
return index >= 0 && index < sys.args.length - 1
? sys.args[index]
: undefined;
}
const logFilePath = findArgument("--logFile");
const globalTypingsCacheLocation = findArgument("--globalTypingsCacheLocation");
const log = new FileLog(logFilePath);
if (log.isEnabled()) {
process.on("uncaughtException", (e: Error) => {
log.writeLine(`Unhandled exception: ${e} at ${e.stack}`);
});
process.on("disconnect", () => {
log.writeLine(`Parent process has exited, shutting down...`);
process.exit(0);
});
}
const installer = new NodeTypingsInstaller(log);
process.on("disconnect", () => {
if (log.isEnabled()) {
log.writeLine(`Parent process has exited, shutting down...`);
}
process.exit(0);
});
const installer = new NodeTypingsInstaller(globalTypingsCacheLocation, log);
installer.init();
}