Skip to content

Commit d8aec99

Browse files
committed
Use resolveModuleNames when making an extension cache, if available. Implement loadExtension on LSHost
1 parent a900aa3 commit d8aec99

File tree

16 files changed

+119
-93
lines changed

16 files changed

+119
-93
lines changed

src/compiler/core.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -1542,8 +1542,8 @@ namespace ts {
15421542
memo = [];
15431543
}
15441544

1545-
const aKeys = ts.getKeys(a);
1546-
const bKeys = ts.getKeys(b);
1545+
const aKeys = Object.keys(a);
1546+
const bKeys = Object.keys(b);
15471547
aKeys.sort();
15481548
bKeys.sort();
15491549

src/compiler/diagnosticMessages.json

+4-5
Original file line numberDiff line numberDiff line change
@@ -2824,6 +2824,10 @@
28242824
"category": "Message",
28252825
"code": 6136
28262826
},
2827+
"Property '{0}' is declared but never used.": {
2828+
"category": "Error",
2829+
"code": 6138
2830+
},
28272831

28282832
"List of compiler extensions to require.": {
28292833
"category": "Message",
@@ -2833,11 +2837,6 @@
28332837
"category": "Error",
28342838
"code": 6151
28352839
},
2836-
"Property '{0}' is declared but never used.": {
2837-
"category": "Error",
2838-
"code": 6138
2839-
},
2840-
28412840
"Extension '{0}' exported member '{1}' has extension kind '{2}', but was type '{3}' when type '{4}' was expected.": {
28422841
"category": "Error",
28432842
"code": 6152

src/compiler/extensions.ts

+31-39
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,11 @@ namespace ts {
1010

1111
export interface ExtensionBase {
1212
name: string;
13-
args: any;
13+
args: {};
1414
kind: ExtensionKind;
15-
}
16-
17-
export interface ProfileData {
18-
globalBucket: string;
19-
task: string;
20-
start: number;
21-
length?: number;
15+
// Include a default case which just puts the extension unchecked onto the base extension
16+
// This can allow language service extensions to query for custom extension kinds
17+
extension: {};
2218
}
2319

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

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

3532
export interface Program {
@@ -49,8 +46,6 @@ namespace ts {
4946
getCompilerExtensions(): ExtensionCollectionMap;
5047
}
5148

52-
export const perfTraces: Map<ProfileData> = {};
53-
5449
function getExtensionRootName(qualifiedName: string) {
5550
return qualifiedName.substring(0, qualifiedName.indexOf("[")) || qualifiedName;
5651
}
@@ -59,41 +54,33 @@ namespace ts {
5954
return `${task}|${qualifiedName}`;
6055
}
6156

62-
export function startProfile(enabled: boolean, key: string, bucket?: string) {
57+
function startProfile(enabled: boolean, key: string) {
6358
if (!enabled) return;
64-
performance.emit(`start|${key}`);
65-
perfTraces[key] = {
66-
task: key,
67-
start: performance.mark(),
68-
length: undefined,
69-
globalBucket: bucket
70-
};
59+
performance.mark(`start|${key}`);
7160
}
7261

73-
export function completeProfile(enabled: boolean, key: string) {
62+
function completeProfile(enabled: boolean, key: string, bucket: string) {
7463
if (!enabled) return;
75-
Debug.assert(!!perfTraces[key], "Completed profile did not have a corresponding start.");
76-
perfTraces[key].length = performance.measure(perfTraces[key].globalBucket, perfTraces[key].start);
77-
performance.emit(`end|${key}`);
64+
performance.measure(bucket, `start|${key}`);
7865
}
7966

8067
export function startExtensionProfile(enabled: boolean, qualifiedName: string, task: string) {
8168
if (!enabled) return;
8269
const longTask = createTaskName(qualifiedName, task);
83-
startProfile(/*enabled*/true, longTask, getExtensionRootName(qualifiedName));
70+
startProfile(/*enabled*/true, longTask);
8471
}
8572

8673
export function completeExtensionProfile(enabled: boolean, qualifiedName: string, task: string) {
8774
if (!enabled) return;
8875
const longTask = createTaskName(qualifiedName, task);
89-
completeProfile(/*enabled*/true, longTask);
76+
completeProfile(/*enabled*/true, longTask, getExtensionRootName(qualifiedName));
9077
}
9178

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

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

103+
// Defer to the host's `resolveModuleName` method if it has it, otherwise use it as a ModuleResolutionHost.
104+
function resolveModuleName(name: string, fromLocation: string) {
105+
if (host.resolveModuleNames) {
106+
const results = host.resolveModuleNames([name], fromLocation, /*loadJs*/true);
107+
return results && results[0];
108+
}
109+
else {
110+
return ts.resolveModuleName(name, fromLocation, options, host, /*loadJs*/true).resolvedModule;
111+
}
112+
}
113+
116114
function resolveExtensionNames(): Map<string> {
117115
const basePath = options.configFilePath || combinePaths(host.getCurrentDirectory ? host.getCurrentDirectory() : "", "tsconfig.json");
118-
const extMap: Map<string> = {};
116+
const extMap = createMap<string>();
119117
forEach(extensionNames, name => {
120-
const resolved = resolveModuleName(name, basePath, options, host, /*loadJs*/true).resolvedModule;
118+
const resolved = resolveModuleName(name, basePath);
121119
if (resolved) {
122120
extMap[name] = resolved.resolvedFileName;
123121
}
@@ -136,9 +134,9 @@ namespace ts {
136134
}
137135
if (resolved && host.loadExtension) {
138136
try {
139-
startProfile(profilingEnabled, name, name);
137+
startProfile(profilingEnabled, name);
140138
result = host.loadExtension(resolved);
141-
completeProfile(profilingEnabled, name);
139+
completeProfile(profilingEnabled, name, name);
142140
}
143141
catch (e) {
144142
error = e;
@@ -158,7 +156,7 @@ namespace ts {
158156
return [];
159157
}
160158
const aggregate: Extension[] = [];
161-
forEachKey(res.result, key => {
159+
forEach(Object.keys(res.result), key => {
162160
const potentialExtension = res.result[key];
163161
if (!potentialExtension) {
164162
return; // Avoid errors on explicitly exported null/undefined (why would someone do that, though?)
@@ -169,17 +167,11 @@ namespace ts {
169167
}
170168
const ext: ExtensionBase = {
171169
name: key !== "default" ? `${res.name}[${key}]` : res.name,
172-
args: extensionNames === extOptions ? undefined : (extOptions as Map<any>)[res.name],
170+
args: extensionNames === extOptions ? undefined : (extOptions as MapLike<any>)[res.name],
173171
kind: annotatedKind as ExtensionKind,
172+
extension: potentialExtension
174173
};
175-
switch (ext.kind) {
176-
default:
177-
// Include a default case which just puts the extension unchecked onto the base extension
178-
// This can allow language service extensions to query for custom extension kinds
179-
(ext as any).__extension = potentialExtension;
180-
break;
181-
}
182-
aggregate.push(ext as Extension);
174+
aggregate.push(ext);
183175
});
184176
return aggregate;
185177
});

src/compiler/performance.ts

+1-2
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,8 @@ namespace ts.performance {
4646
if (enabled) {
4747
const end = endMarkName && marks[endMarkName] || timestamp();
4848
const start = startMarkName && marks[startMarkName] || profilerStart;
49-
return measures[measureName] = (measures[measureName] || 0) + (end - start);
49+
measures[measureName] = (measures[measureName] || 0) + (end - start);
5050
}
51-
return 0;
5251
}
5352

5453
/**

src/compiler/tsconfig.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
"declarationEmitter.ts",
2525
"emitter.ts",
2626
"program.ts",
27-
"extensions.ts",
27+
"extensions.ts",
2828
"commandLineParser.ts",
2929
"tsc.ts",
3030
"diagnosticInformationMap.generated.ts"

src/compiler/types.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -2652,7 +2652,7 @@ namespace ts {
26522652
typeRoots?: string[];
26532653
/*@internal*/ version?: boolean;
26542654
/*@internal*/ watch?: boolean;
2655-
extensions?: string[] | Map<any>;
2655+
extensions?: string[] | MapLike<any>;
26562656

26572657
[option: string]: CompilerOptionsValue | undefined;
26582658
}
@@ -2981,7 +2981,7 @@ namespace ts {
29812981
* If resolveModuleNames is implemented then implementation for members from ModuleResolutionHost can be just
29822982
* 'throw new Error("NotImplemented")'
29832983
*/
2984-
resolveModuleNames?(moduleNames: string[], containingFile: string): ResolvedModule[];
2984+
resolveModuleNames?(moduleNames: string[], containingFile: string, loadJs?: boolean): ResolvedModule[];
29852985
/**
29862986
* This method is a companion for 'resolveModuleNames' and is used to resolve 'types' references to actual type declaration files
29872987
*/

src/harness/extensionRunner.ts

+28-24
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,10 @@ class ExtensionRunner extends RunnerBase {
1717
private extensionPath = ts.combinePaths(this.basePath, "available");
1818
private sourcePath = ts.combinePaths(this.basePath, "source");
1919
private fourslashPath = ts.combinePaths(this.basePath, "fourslash");
20-
private extensionAPI: ts.Map<string> = {};
21-
private extensions: ts.Map<ts.Map<string>> = {};
22-
private virtualLib: ts.Map<string> = {};
23-
private virtualFs: ts.Map<string> = {};
20+
private extensionAPI = ts.createMap<string>();
21+
private extensions = ts.createMap<ts.Map<string>>();
22+
private virtualLib = ts.createMap<string>();
23+
private virtualFs = ts.createMap<string>();
2424

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

4343
// 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)
44-
ts.forEachKey(set, key => ts.forEachKey(this.virtualLib, path => key === path) ? void 0 : void (this.virtualFs[this.getCanonicalFileName(`${prefix}/${key}`)] = set[key]));
44+
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]));
4545
}
4646

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

5252
private traces: string[] = [];
@@ -62,7 +62,7 @@ class ExtensionRunner extends RunnerBase {
6262
},
6363
directoryExists: (path) => {
6464
const fullPath = this.mockHost.getCanonicalFileName(path);
65-
return ts.forEach(ts.getKeys(this.virtualFs), key => ts.startsWith(key, fullPath));
65+
return ts.forEach(Object.keys(this.virtualFs), key => ts.startsWith(key, fullPath));
6666
},
6767
getCurrentDirectory(): string { return "/"; },
6868
getSourceFile: (path, languageVersion, onError): ts.SourceFile => {
@@ -76,7 +76,7 @@ class ExtensionRunner extends RunnerBase {
7676
getCanonicalFileName: this.getCanonicalFileName,
7777
getDirectories: (path) => {
7878
path = this.mockHost.getCanonicalFileName(path);
79-
return ts.filter(ts.map(ts.filter(ts.getKeys(this.virtualFs),
79+
return ts.filter(ts.map(ts.filter(Object.keys(this.virtualFs),
8080
fullpath => ts.startsWith(fullpath, path) && fullpath.substr(path.length, 1) === "/"),
8181
fullpath => fullpath.substr(path.length + 1).indexOf("/") >= 0 ? fullpath.substr(0, 1 + path.length + fullpath.substr(path.length + 1).indexOf("/")) : fullpath),
8282
fullpath => fullpath.lastIndexOf(".") === -1);
@@ -118,7 +118,7 @@ class ExtensionRunner extends RunnerBase {
118118
};
119119
host.getScriptInfo = (fileName: string) => {
120120
fileName = this.getCanonicalFileName(fileName);
121-
return ts.lookUp(host.fileNameToScript, fileName);
121+
return host.fileNameToScript[fileName];
122122
};
123123
host.getDirectories = (s: string) => this.mockHost.getDirectories(s);
124124
host.addScript = (fileName: string, content: string, isRootFile: boolean): void => {
@@ -149,7 +149,7 @@ class ExtensionRunner extends RunnerBase {
149149

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

@@ -199,7 +199,7 @@ class ExtensionRunner extends RunnerBase {
199199
this.loadSetIntoFs(fileset);
200200

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

@@ -212,22 +212,24 @@ class ExtensionRunner extends RunnerBase {
212212
}
213213
throw new Error("Compiling test harness extension API code resulted in errors.");
214214
}
215-
ts.copyMap(this.virtualFs, out);
216-
this.virtualFs = {};
215+
for (const key in this.virtualFs) {
216+
out[key] = this.virtualFs[key];
217+
}
218+
this.virtualFs = ts.createMap<string>();
217219
return results;
218220
}
219221

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

227229
ts.forEach(Harness.IO.getDirectories(this.extensionPath), path => {
228230
if (path === "extension-api" || path === "typescript") return; // Since these are dependencies of every actual test extension, we handle them specially
229231
const packageDir = ts.combinePaths(this.extensionPath, path);
230-
const extensionFileset: ts.Map<string> = {};
232+
const extensionFileset = ts.createMap<string>();
231233
const extensionFiles = this.enumerateFiles(packageDir, /*regex*/ undefined, { recursive: true });
232234
ts.forEach(extensionFiles, name => {
233235
const shortName = name.substring(packageDir.length + 1);
@@ -244,10 +246,10 @@ class ExtensionRunner extends RunnerBase {
244246
super();
245247
const {content: libContent} = Harness.getDefaultLibraryFile(Harness.IO);
246248
const tsLibContents = Harness.IO.readFile("built/local/typescript.d.ts");
247-
this.virtualLib = {
249+
this.virtualLib = ts.createMap({
248250
"/lib/lib.d.ts": libContent,
249251
"/node_modules/typescript/index.d.ts": tsLibContents
250-
};
252+
});
251253
this.loadExtensions();
252254
}
253255

@@ -304,7 +306,7 @@ class ExtensionRunner extends RunnerBase {
304306
shortCasePath = caseName.substring(this.scenarioPath.length + 1).replace(/\.json$/, "");
305307
testConfigText = Harness.IO.readFile(caseName);
306308
testConfig = JSON.parse(testConfigText);
307-
inputSources = {};
309+
inputSources = ts.createMap<string>();
308310
inputTestFiles = [];
309311
ts.forEach(testConfig.inputFiles, name => {
310312
inputSources[name] = Harness.IO.readFile(ts.combinePaths(this.sourcePath, name));
@@ -329,9 +331,11 @@ class ExtensionRunner extends RunnerBase {
329331
let result: Harness.Compiler.CompilerResult;
330332
before(() => {
331333
this.traces = []; // Clear out any traces from tests which made traces, but didn't specify traceResolution
332-
this.virtualFs = {}; // In case a fourslash test was run last (which doesn't clear FS on end like buildMap does), clear the FS
333-
sources = {};
334-
ts.copyMap(inputSources, sources);
334+
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
335+
sources = ts.createMap<string>();
336+
for (const key in inputSources) {
337+
sources[key] = inputSources[key];
338+
}
335339
ts.forEach(testConfig.availableExtensions, ext => this.loadSetIntoFsAt(this.extensions[ext], `/node_modules/${ext}`));
336340
result = this.buildMap(compileCb, sources, sources, testConfig.compilerOptions, /*shouldError*/false);
337341
});
@@ -465,7 +469,7 @@ class ExtensionRunner extends RunnerBase {
465469

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

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

487491
FourSlash.runFourSlashTestContent(shortCasePath, adapterFactory, finishedTestContent, testFile);
488492
}

0 commit comments

Comments
 (0)