Skip to content

Commit

Permalink
Use resolveModuleNames when making an extension cache, if available. …
Browse files Browse the repository at this point in the history
…Implement loadExtension on LSHost
  • Loading branch information
weswigham committed Aug 18, 2016
1 parent a900aa3 commit d8aec99
Show file tree
Hide file tree
Showing 16 changed files with 119 additions and 93 deletions.
4 changes: 2 additions & 2 deletions src/compiler/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1542,8 +1542,8 @@ namespace ts {
memo = [];
}

const aKeys = ts.getKeys(a);
const bKeys = ts.getKeys(b);
const aKeys = Object.keys(a);
const bKeys = Object.keys(b);
aKeys.sort();
bKeys.sort();

Expand Down
9 changes: 4 additions & 5 deletions src/compiler/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -2824,6 +2824,10 @@
"category": "Message",
"code": 6136
},
"Property '{0}' is declared but never used.": {
"category": "Error",
"code": 6138
},

"List of compiler extensions to require.": {
"category": "Message",
Expand All @@ -2833,11 +2837,6 @@
"category": "Error",
"code": 6151
},
"Property '{0}' is declared but never used.": {
"category": "Error",
"code": 6138
},

"Extension '{0}' exported member '{1}' has extension kind '{2}', but was type '{3}' when type '{4}' was expected.": {
"category": "Error",
"code": 6152
Expand Down
70 changes: 31 additions & 39 deletions src/compiler/extensions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,11 @@ namespace ts {

export interface ExtensionBase {
name: string;
args: any;
args: {};
kind: ExtensionKind;
}

export interface ProfileData {
globalBucket: string;
task: string;
start: number;
length?: number;
// Include a default case which just puts the extension unchecked onto the base extension
// This can allow language service extensions to query for custom extension kinds
extension: {};
}

export type Extension = ExtensionBase;
Expand All @@ -30,6 +26,7 @@ namespace ts {

export interface ExtensionHost extends ModuleResolutionHost {
loadExtension?(name: string): any;
resolveModuleNames?(moduleNames: string[], containingFile: string, loadJs?: boolean): ResolvedModule[];
}

export interface Program {
Expand All @@ -49,8 +46,6 @@ namespace ts {
getCompilerExtensions(): ExtensionCollectionMap;
}

export const perfTraces: Map<ProfileData> = {};

function getExtensionRootName(qualifiedName: string) {
return qualifiedName.substring(0, qualifiedName.indexOf("[")) || qualifiedName;
}
Expand All @@ -59,41 +54,33 @@ namespace ts {
return `${task}|${qualifiedName}`;
}

export function startProfile(enabled: boolean, key: string, bucket?: string) {
function startProfile(enabled: boolean, key: string) {
if (!enabled) return;
performance.emit(`start|${key}`);
perfTraces[key] = {
task: key,
start: performance.mark(),
length: undefined,
globalBucket: bucket
};
performance.mark(`start|${key}`);
}

export function completeProfile(enabled: boolean, key: string) {
function completeProfile(enabled: boolean, key: string, bucket: string) {
if (!enabled) return;
Debug.assert(!!perfTraces[key], "Completed profile did not have a corresponding start.");
perfTraces[key].length = performance.measure(perfTraces[key].globalBucket, perfTraces[key].start);
performance.emit(`end|${key}`);
performance.measure(bucket, `start|${key}`);
}

export function startExtensionProfile(enabled: boolean, qualifiedName: string, task: string) {
if (!enabled) return;
const longTask = createTaskName(qualifiedName, task);
startProfile(/*enabled*/true, longTask, getExtensionRootName(qualifiedName));
startProfile(/*enabled*/true, longTask);
}

export function completeExtensionProfile(enabled: boolean, qualifiedName: string, task: string) {
if (!enabled) return;
const longTask = createTaskName(qualifiedName, task);
completeProfile(/*enabled*/true, longTask);
completeProfile(/*enabled*/true, longTask, getExtensionRootName(qualifiedName));
}

export function createExtensionCache(options: CompilerOptions, host: ExtensionHost, resolvedExtensionNames?: Map<string>): ExtensionCache {

const diagnostics: Diagnostic[] = [];
const extOptions = options.extensions;
const extensionNames = (extOptions instanceof Array) ? extOptions : getKeys(extOptions);
const extensionNames = (extOptions instanceof Array) ? extOptions : extOptions ? Object.keys(extOptions) : [];
// Eagerly evaluate extension paths, but lazily execute their contents
resolvedExtensionNames = resolvedExtensionNames || resolveExtensionNames();
let extensions: ExtensionCollectionMap;
Expand All @@ -113,11 +100,22 @@ namespace ts {
};
return cache;

// Defer to the host's `resolveModuleName` method if it has it, otherwise use it as a ModuleResolutionHost.
function resolveModuleName(name: string, fromLocation: string) {
if (host.resolveModuleNames) {
const results = host.resolveModuleNames([name], fromLocation, /*loadJs*/true);
return results && results[0];
}
else {
return ts.resolveModuleName(name, fromLocation, options, host, /*loadJs*/true).resolvedModule;
}
}

function resolveExtensionNames(): Map<string> {
const basePath = options.configFilePath || combinePaths(host.getCurrentDirectory ? host.getCurrentDirectory() : "", "tsconfig.json");
const extMap: Map<string> = {};
const extMap = createMap<string>();
forEach(extensionNames, name => {
const resolved = resolveModuleName(name, basePath, options, host, /*loadJs*/true).resolvedModule;
const resolved = resolveModuleName(name, basePath);
if (resolved) {
extMap[name] = resolved.resolvedFileName;
}
Expand All @@ -136,9 +134,9 @@ namespace ts {
}
if (resolved && host.loadExtension) {
try {
startProfile(profilingEnabled, name, name);
startProfile(profilingEnabled, name);
result = host.loadExtension(resolved);
completeProfile(profilingEnabled, name);
completeProfile(profilingEnabled, name, name);
}
catch (e) {
error = e;
Expand All @@ -158,7 +156,7 @@ namespace ts {
return [];
}
const aggregate: Extension[] = [];
forEachKey(res.result, key => {
forEach(Object.keys(res.result), key => {
const potentialExtension = res.result[key];
if (!potentialExtension) {
return; // Avoid errors on explicitly exported null/undefined (why would someone do that, though?)
Expand All @@ -169,17 +167,11 @@ namespace ts {
}
const ext: ExtensionBase = {
name: key !== "default" ? `${res.name}[${key}]` : res.name,
args: extensionNames === extOptions ? undefined : (extOptions as Map<any>)[res.name],
args: extensionNames === extOptions ? undefined : (extOptions as MapLike<any>)[res.name],
kind: annotatedKind as ExtensionKind,
extension: potentialExtension
};
switch (ext.kind) {
default:
// Include a default case which just puts the extension unchecked onto the base extension
// This can allow language service extensions to query for custom extension kinds
(ext as any).__extension = potentialExtension;
break;
}
aggregate.push(ext as Extension);
aggregate.push(ext);
});
return aggregate;
});
Expand Down
3 changes: 1 addition & 2 deletions src/compiler/performance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,8 @@ namespace ts.performance {
if (enabled) {
const end = endMarkName && marks[endMarkName] || timestamp();
const start = startMarkName && marks[startMarkName] || profilerStart;
return measures[measureName] = (measures[measureName] || 0) + (end - start);
measures[measureName] = (measures[measureName] || 0) + (end - start);
}
return 0;
}

/**
Expand Down
2 changes: 1 addition & 1 deletion src/compiler/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
"declarationEmitter.ts",
"emitter.ts",
"program.ts",
"extensions.ts",
"extensions.ts",
"commandLineParser.ts",
"tsc.ts",
"diagnosticInformationMap.generated.ts"
Expand Down
4 changes: 2 additions & 2 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2652,7 +2652,7 @@ namespace ts {
typeRoots?: string[];
/*@internal*/ version?: boolean;
/*@internal*/ watch?: boolean;
extensions?: string[] | Map<any>;
extensions?: string[] | MapLike<any>;

[option: string]: CompilerOptionsValue | undefined;
}
Expand Down Expand Up @@ -2981,7 +2981,7 @@ namespace ts {
* If resolveModuleNames is implemented then implementation for members from ModuleResolutionHost can be just
* 'throw new Error("NotImplemented")'
*/
resolveModuleNames?(moduleNames: string[], containingFile: string): ResolvedModule[];
resolveModuleNames?(moduleNames: string[], containingFile: string, loadJs?: boolean): ResolvedModule[];
/**
* This method is a companion for 'resolveModuleNames' and is used to resolve 'types' references to actual type declaration files
*/
Expand Down
52 changes: 28 additions & 24 deletions src/harness/extensionRunner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,10 @@ class ExtensionRunner extends RunnerBase {
private extensionPath = ts.combinePaths(this.basePath, "available");
private sourcePath = ts.combinePaths(this.basePath, "source");
private fourslashPath = ts.combinePaths(this.basePath, "fourslash");
private extensionAPI: ts.Map<string> = {};
private extensions: ts.Map<ts.Map<string>> = {};
private virtualLib: ts.Map<string> = {};
private virtualFs: ts.Map<string> = {};
private extensionAPI = ts.createMap<string>();
private extensions = ts.createMap<ts.Map<string>>();
private virtualLib = ts.createMap<string>();
private virtualFs = ts.createMap<string>();

prettyPrintDiagnostic(diagnostic: ts.Diagnostic): string {
const message = ts.flattenDiagnosticMessageText(diagnostic.messageText, "\n");
Expand All @@ -41,12 +41,12 @@ class ExtensionRunner extends RunnerBase {
ts.Debug.assert(set !== this.virtualFs, "You cannot try to load the fs into itself.");

// Load a fileset at the given location, but exclude the 'lib' kind files from the added set (they'll be reloaded at the top level before compilation)
ts.forEachKey(set, key => ts.forEachKey(this.virtualLib, path => key === path) ? void 0 : void (this.virtualFs[this.getCanonicalFileName(`${prefix}/${key}`)] = set[key]));
ts.forEach(Object.keys(set), key => ts.forEach(Object.keys(this.virtualLib), path => key === path) ? void 0 : void (this.virtualFs[this.getCanonicalFileName(`${prefix}/${key}`)] = set[key]));
}

loadSetIntoFs(set: ts.Map<string>) {
ts.Debug.assert(set !== this.virtualFs, "You cannot try to load the fs into itself.");
ts.forEachKey(set, key => void (this.virtualFs[this.getCanonicalFileName(key)] = set[key]));
ts.forEach(Object.keys(set), key => void (this.virtualFs[this.getCanonicalFileName(key)] = set[key]));
}

private traces: string[] = [];
Expand All @@ -62,7 +62,7 @@ class ExtensionRunner extends RunnerBase {
},
directoryExists: (path) => {
const fullPath = this.mockHost.getCanonicalFileName(path);
return ts.forEach(ts.getKeys(this.virtualFs), key => ts.startsWith(key, fullPath));
return ts.forEach(Object.keys(this.virtualFs), key => ts.startsWith(key, fullPath));
},
getCurrentDirectory(): string { return "/"; },
getSourceFile: (path, languageVersion, onError): ts.SourceFile => {
Expand All @@ -76,7 +76,7 @@ class ExtensionRunner extends RunnerBase {
getCanonicalFileName: this.getCanonicalFileName,
getDirectories: (path) => {
path = this.mockHost.getCanonicalFileName(path);
return ts.filter(ts.map(ts.filter(ts.getKeys(this.virtualFs),
return ts.filter(ts.map(ts.filter(Object.keys(this.virtualFs),
fullpath => ts.startsWith(fullpath, path) && fullpath.substr(path.length, 1) === "/"),
fullpath => fullpath.substr(path.length + 1).indexOf("/") >= 0 ? fullpath.substr(0, 1 + path.length + fullpath.substr(path.length + 1).indexOf("/")) : fullpath),
fullpath => fullpath.lastIndexOf(".") === -1);
Expand Down Expand Up @@ -118,7 +118,7 @@ class ExtensionRunner extends RunnerBase {
};
host.getScriptInfo = (fileName: string) => {
fileName = this.getCanonicalFileName(fileName);
return ts.lookUp(host.fileNameToScript, fileName);
return host.fileNameToScript[fileName];
};
host.getDirectories = (s: string) => this.mockHost.getDirectories(s);
host.addScript = (fileName: string, content: string, isRootFile: boolean): void => {
Expand Down Expand Up @@ -149,7 +149,7 @@ class ExtensionRunner extends RunnerBase {

languageServiceCompile(typescriptFiles: string[], options: ts.CompilerOptions): Harness.Compiler.CompilerResult {
const self = this;
const host = this.makeMockLSHost(ts.getKeys(this.virtualFs), options);
const host = this.makeMockLSHost(Object.keys(this.virtualFs), options);
const service = ts.createLanguageService(host);
const fileResults: Harness.Compiler.GeneratedFile[] = [];

Expand Down Expand Up @@ -199,7 +199,7 @@ class ExtensionRunner extends RunnerBase {
this.loadSetIntoFs(fileset);

// Consider all TS files in the passed fileset as the root files, but not any under a node_modules folder
const typescriptFiles = ts.filter(ts.getKeys(fileset), name => ts.endsWith(name, ".ts") && !(name.indexOf("node_modules") >= 0));
const typescriptFiles = ts.filter(Object.keys(fileset), name => ts.endsWith(name, ".ts") && !(name.indexOf("node_modules") >= 0));
return compileFunc(typescriptFiles, options);
}

Expand All @@ -212,22 +212,24 @@ class ExtensionRunner extends RunnerBase {
}
throw new Error("Compiling test harness extension API code resulted in errors.");
}
ts.copyMap(this.virtualFs, out);
this.virtualFs = {};
for (const key in this.virtualFs) {
out[key] = this.virtualFs[key];
}
this.virtualFs = ts.createMap<string>();
return results;
}

private loadExtensions() {
this.extensionAPI = {
this.extensionAPI = ts.createMap({
"package.json": Harness.IO.readFile(ts.combinePaths(this.extensionPath, "extension-api/package.json")),
"index.ts": Harness.IO.readFile(ts.combinePaths(this.extensionPath, "extension-api/index.ts")),
};
});
this.buildMap((str, opts) => this.programCompile(str, opts), this.extensionAPI, this.extensionAPI, { module: ts.ModuleKind.CommonJS, declaration: true }, /*shouldError*/true);

ts.forEach(Harness.IO.getDirectories(this.extensionPath), path => {
if (path === "extension-api" || path === "typescript") return; // Since these are dependencies of every actual test extension, we handle them specially
const packageDir = ts.combinePaths(this.extensionPath, path);
const extensionFileset: ts.Map<string> = {};
const extensionFileset = ts.createMap<string>();
const extensionFiles = this.enumerateFiles(packageDir, /*regex*/ undefined, { recursive: true });
ts.forEach(extensionFiles, name => {
const shortName = name.substring(packageDir.length + 1);
Expand All @@ -244,10 +246,10 @@ class ExtensionRunner extends RunnerBase {
super();
const {content: libContent} = Harness.getDefaultLibraryFile(Harness.IO);
const tsLibContents = Harness.IO.readFile("built/local/typescript.d.ts");
this.virtualLib = {
this.virtualLib = ts.createMap({
"/lib/lib.d.ts": libContent,
"/node_modules/typescript/index.d.ts": tsLibContents
};
});
this.loadExtensions();
}

Expand Down Expand Up @@ -304,7 +306,7 @@ class ExtensionRunner extends RunnerBase {
shortCasePath = caseName.substring(this.scenarioPath.length + 1).replace(/\.json$/, "");
testConfigText = Harness.IO.readFile(caseName);
testConfig = JSON.parse(testConfigText);
inputSources = {};
inputSources = ts.createMap<string>();
inputTestFiles = [];
ts.forEach(testConfig.inputFiles, name => {
inputSources[name] = Harness.IO.readFile(ts.combinePaths(this.sourcePath, name));
Expand All @@ -329,9 +331,11 @@ class ExtensionRunner extends RunnerBase {
let result: Harness.Compiler.CompilerResult;
before(() => {
this.traces = []; // Clear out any traces from tests which made traces, but didn't specify traceResolution
this.virtualFs = {}; // In case a fourslash test was run last (which doesn't clear FS on end like buildMap does), clear the FS
sources = {};
ts.copyMap(inputSources, sources);
this.virtualFs = ts.createMap<string>(); // In case a fourslash test was run last (which doesn't clear FS on end like buildMap does), clear the FS
sources = ts.createMap<string>();
for (const key in inputSources) {
sources[key] = inputSources[key];
}
ts.forEach(testConfig.availableExtensions, ext => this.loadSetIntoFsAt(this.extensions[ext], `/node_modules/${ext}`));
result = this.buildMap(compileCb, sources, sources, testConfig.compilerOptions, /*shouldError*/false);
});
Expand Down Expand Up @@ -465,7 +469,7 @@ class ExtensionRunner extends RunnerBase {

it("passes fourslash verification", () => {
if (testConfig.fourslashTest) {
this.virtualFs = {};
this.virtualFs = ts.createMap<string>();
const testFile = `${this.fourslashPath}/${testConfig.fourslashTest}`;
let testFileContents = Harness.IO.readFile(testFile);
testFileContents = testFileContents.replace(`/// <reference path="../../fourslash/fourslash.ts" />`, "");
Expand All @@ -482,7 +486,7 @@ class ExtensionRunner extends RunnerBase {
this.loadSetIntoFs(this.virtualLib);
ts.forEach(testConfig.availableExtensions, ext => this.loadSetIntoFsAt(this.extensions[ext], `/node_modules/${ext}`));

const adapterFactory = (token: ts.HostCancellationToken) => this.makeLSMockAdapter(ts.getKeys(this.virtualFs), testConfig.compilerOptions, token);
const adapterFactory = (token: ts.HostCancellationToken) => this.makeLSMockAdapter(Object.keys(this.virtualFs), testConfig.compilerOptions, token);

FourSlash.runFourSlashTestContent(shortCasePath, adapterFactory, finishedTestContent, testFile);
}
Expand Down
Loading

0 comments on commit d8aec99

Please sign in to comment.