Просмотр исходного кода

Merge pull request #64 from microsoft/let_ts_resolve_libs

Alexandru Dima 4 лет назад
Родитель
Сommit
6d0fbd443f
6 измененных файлов с 254 добавлено и 127 удалено
  1. 25 92
      scripts/importTypescript.js
  2. 126 13
      src/languageFeatures.ts
  3. 65 0
      src/lib/lib.index.ts
  4. 0 1
      src/lib/lib.ts
  5. 5 3
      src/tsMode.ts
  6. 33 18
      src/tsWorker.ts

+ 25 - 92
scripts/importTypescript.js

@@ -105,110 +105,43 @@ export = ts;
 })();
 
 function importLibs() {
-	function getFileName(name) {
-		return (name === '' ? 'lib.d.ts' : `lib.${name}.d.ts`);
-	}
-	function getVariableName(name) {
-		return (name === '' ? 'lib_dts' : `lib_${name.replace(/\./g, '_')}_dts`);
-	}
 	function readLibFile(name) {
-		var srcPath = path.join(TYPESCRIPT_LIB_SOURCE, getFileName(name));
+		var srcPath = path.join(TYPESCRIPT_LIB_SOURCE, name);
 		return fs.readFileSync(srcPath).toString();
 	}
 
-	var queue = [];
-	var in_queue = {};
-
-	var enqueue = function (name) {
-		if (in_queue[name]) {
-			return;
-		}
-		in_queue[name] = true;
-		queue.push(name);
-	};
-
-	enqueue('');
-	enqueue('es2015');
-
-	var result = [];
-	while (queue.length > 0) {
-		var name = queue.shift();
-		var contents = readLibFile(name);
-		var lines = contents.split(/\r\n|\r|\n/);
-
-		var output = '';
-		var writeOutput = function (text) {
-			if (output.length === 0) {
-				output = text;
-			} else {
-				output += ` + ${text}`;
-			}
-		};
-		var outputLines = [];
-		var flushOutputLines = function () {
-			writeOutput(`"${escapeText(outputLines.join('\n'))}"`);
-			outputLines = [];
-		};
-		var deps = [];
-		for (let i = 0; i < lines.length; i++) {
-			let m = lines[i].match(/\/\/\/\s*<reference\s*lib="([^"]+)"/);
-			if (m) {
-				flushOutputLines();
-				writeOutput(getVariableName(m[1]));
-				deps.push(getVariableName(m[1]));
-				enqueue(m[1]);
-				continue;
-			}
-			outputLines.push(lines[i]);
-		}
-		flushOutputLines();
-
-		result.push({
-			name: getVariableName(name),
-			deps: deps,
-			output: output
-		});
-	}
-
-	var strResult = `/*---------------------------------------------------------------------------------------------
+	var strLibResult = `/*---------------------------------------------------------------------------------------------
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-${generatedNote}`;
-	// Do a topological sort
-	while (result.length > 0) {
-		for (let i = result.length - 1; i >= 0; i--) {
-			if (result[i].deps.length === 0) {
-				// emit this node
-				strResult += `\nexport const ${result[i].name}: string = ${result[i].output};\n`;
-
-				// mark dep as resolved
-				for (let j = 0; j < result.length; j++) {
-					for (let k = 0; k < result[j].deps.length; k++) {
-						if (result[j].deps[k] === result[i].name) {
-							result[j].deps.splice(k, 1);
-							break;
-						}
-					}
-				}
+${generatedNote}
 
-				// remove from result
-				result.splice(i, 1);
-				break;
-			}
-		}
-	}
+/** Contains all the lib files */
+export const libFileMap: Record<string, string> = {}
+`
+;
 
-	strResult += `
-/** This is the DTS which is used when the target is ES6 or below */
-export const lib_es5_bundled_dts = lib_dts;
+	var strIndexResult = `/*---------------------------------------------------------------------------------------------
+ *  Copyright (c) Microsoft Corporation. All rights reserved.
+ *  Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+${generatedNote}
 
-/** This is the DTS which is used by default in monaco-typescript, and when the target is 2015 or above */
-export const lib_es2015_bundled_dts = lib_es2015_dts + "" + lib_dom_dts + "" + lib_webworker_importscripts_dts + "" + lib_scripthost_dts + "";
+/** Contains all the lib files */
+export const libFileSet: Record<string, boolean> = {}
 `
+;
+
+	var dtsFiles = fs.readdirSync(TYPESCRIPT_LIB_SOURCE).filter(f => f.includes("lib."));
+	while (dtsFiles.length > 0) {
+		var name = dtsFiles.shift();
+		var output = readLibFile(name).replace(/\r\n/g, '\n');
+		strLibResult += `libFileMap['${name}'] = "${escapeText(output)}";\n`;
+		strIndexResult += `libFileSet['${name}'] = true;\n`;
+	}
 
-	var dstPath = path.join(TYPESCRIPT_LIB_DESTINATION, 'lib.ts');
-	fs.writeFileSync(dstPath, strResult);
+	fs.writeFileSync(path.join(TYPESCRIPT_LIB_DESTINATION, 'lib.ts'), strLibResult);
+	fs.writeFileSync(path.join(TYPESCRIPT_LIB_DESTINATION, 'lib.index.ts'), strIndexResult);
 }
 
 /**

+ 126 - 13
src/languageFeatures.ts

@@ -7,6 +7,7 @@
 import { LanguageServiceDefaultsImpl } from './monaco.contribution';
 import * as ts from './lib/typescriptServices';
 import { TypeScriptWorker } from './tsWorker';
+import { libFileSet } from "./lib/lib.index"
 
 import Uri = monaco.Uri;
 import Position = monaco.Position;
@@ -58,7 +59,7 @@ function displayPartsToString(displayParts: ts.SymbolDisplayPart[] | undefined):
 
 export abstract class Adapter {
 
-	constructor(protected _worker: (first: Uri, ...more: Uri[]) => Promise<TypeScriptWorker>) {
+	constructor(protected _worker: (...uris: Uri[]) => Promise<TypeScriptWorker>) {
 	}
 
 	// protected _positionToOffset(model: monaco.editor.ITextModel, position: monaco.IPosition): number {
@@ -78,6 +79,75 @@ export abstract class Adapter {
 	}
 }
 
+// --- lib files
+
+export class LibFiles {
+
+	private _libFiles: Record<string, string>;
+	private _hasFetchedLibFiles: boolean;
+	private _fetchLibFilesPromise: Promise<void> | null;
+
+	constructor(
+		private readonly _worker: (...uris: Uri[]) => Promise<TypeScriptWorker>
+	) {
+		this._libFiles = {};
+		this._hasFetchedLibFiles = false;
+		this._fetchLibFilesPromise = null;
+	}
+
+	public isLibFile(uri: Uri | null): boolean {
+		if (!uri) {
+			return false;
+		}
+		if (uri.path.indexOf("/lib.") === 0) {
+			return !!libFileSet[uri.path.slice(1)];
+		}
+		return false;
+	}
+
+	public getOrCreateModel(uri: Uri): monaco.editor.ITextModel | null {
+		const model = monaco.editor.getModel(uri);
+		if (model) {
+			return model;
+		}
+		if (this.isLibFile(uri) && this._hasFetchedLibFiles) {
+			return monaco.editor.createModel(this._libFiles[uri.path.slice(1)], "javascript", uri);
+		}
+		return null;
+	}
+
+	private _containsLibFile(uris: (Uri | null)[]): boolean {
+		for (let uri of uris) {
+			if (this.isLibFile(uri)) {
+				return true;
+			}
+		}
+		return false;
+	}
+
+	public async fetchLibFilesIfNecessary(uris: (Uri | null)[]): Promise<void> {
+		if (!this._containsLibFile(uris)) {
+			// no lib files necessary
+			return;
+		}
+		await this._fetchLibFiles();
+	}
+
+	private _fetchLibFiles(): Promise<void> {
+		if (!this._fetchLibFilesPromise) {
+			this._fetchLibFilesPromise = (
+				this._worker()
+					.then(w => w.getLibFiles())
+					.then((libFiles) => {
+						this._hasFetchedLibFiles = true;
+						this._libFiles = libFiles;
+					})
+			);
+		}
+		return this._fetchLibFilesPromise;
+	}
+}
+
 // --- diagnostics --- ---
 
 enum DiagnosticCategory {
@@ -92,8 +162,11 @@ export class DiagnosticsAdapter extends Adapter {
 	private _disposables: IDisposable[] = [];
 	private _listener: { [uri: string]: IDisposable } = Object.create(null);
 
-	constructor(private _defaults: LanguageServiceDefaultsImpl, private _selector: string,
-		worker: (first: Uri, ...more: Uri[]) => Promise<TypeScriptWorker>
+	constructor(
+		private readonly _libFiles: LibFiles,
+		private _defaults: LanguageServiceDefaultsImpl,
+		private _selector: string,
+		worker: (...uris: Uri[]) => Promise<TypeScriptWorker>
 	) {
 		super(worker);
 
@@ -180,19 +253,31 @@ export class DiagnosticsAdapter extends Adapter {
 			promises.push(worker.getSuggestionDiagnostics(model.uri.toString()));
 		}
 
-		const diagnostics = await Promise.all(promises);
+		const allDiagnostics = await Promise.all(promises);
 
-		if (!diagnostics || model.isDisposed()) {
+		if (!allDiagnostics || model.isDisposed()) {
 			// model was disposed in the meantime
 			return;
 		}
 
-		const markers = diagnostics
+		const diagnostics = allDiagnostics
 			.reduce((p, c) => c.concat(p), [])
-			.filter(d => (this._defaults.getDiagnosticsOptions().diagnosticCodesToIgnore || []).indexOf(d.code) === -1)
-			.map(d => this._convertDiagnostics(model, d));
+			.filter(d => (this._defaults.getDiagnosticsOptions().diagnosticCodesToIgnore || []).indexOf(d.code) === -1);
 
-		monaco.editor.setModelMarkers(model, this._selector, markers);
+		// Fetch lib files if necessary
+		const relatedUris = diagnostics
+			.map(d => d.relatedInformation || [])
+			.reduce((p, c) => c.concat(p), [])
+			.map(relatedInformation => relatedInformation.file ? monaco.Uri.parse(relatedInformation.file.fileName) : null);
+
+		await this._libFiles.fetchLibFilesIfNecessary(relatedUris);
+
+		if (model.isDisposed()) {
+			// model was disposed in the meantime
+			return;
+		}
+
+		monaco.editor.setModelMarkers(model, this._selector, diagnostics.map(d => this._convertDiagnostics(model, d)));
 	}
 
 	private _convertDiagnostics(model: monaco.editor.ITextModel, diag: ts.Diagnostic): monaco.editor.IMarkerData {
@@ -224,7 +309,7 @@ export class DiagnosticsAdapter extends Adapter {
 			let relatedResource: monaco.editor.ITextModel | null = model;
 			if (info.file) {
 				const relatedResourceUri = monaco.Uri.parse(info.file.fileName);
-				relatedResource = monaco.editor.getModel(relatedResourceUri);
+				relatedResource = this._libFiles.getOrCreateModel(relatedResourceUri);
 			}
 
 			if (!relatedResource) {
@@ -479,6 +564,13 @@ export class OccurrencesAdapter extends Adapter implements monaco.languages.Docu
 
 export class DefinitionAdapter extends Adapter {
 
+	constructor(
+		private readonly _libFiles: LibFiles,
+		worker: (...uris: Uri[]) => Promise<TypeScriptWorker>
+	) {
+		super(worker);
+	}
+
 	public async provideDefinition(model: monaco.editor.ITextModel, position: Position, token: CancellationToken): Promise<monaco.languages.Definition | undefined> {
 		const resource = model.uri;
 		const offset = model.getOffsetAt(position);
@@ -489,10 +581,17 @@ export class DefinitionAdapter extends Adapter {
 			return;
 		}
 
+		// Fetch lib files if necessary
+		await this._libFiles.fetchLibFilesIfNecessary(entries.map(entry => Uri.parse(entry.fileName)));
+
+		if (model.isDisposed()) {
+			return;
+		}
+
 		const result: monaco.languages.Location[] = [];
 		for (let entry of entries) {
 			const uri = Uri.parse(entry.fileName);
-			const refModel = monaco.editor.getModel(uri);
+			const refModel = this._libFiles.getOrCreateModel(uri);
 			if (refModel) {
 				result.push({
 					uri: uri,
@@ -508,6 +607,13 @@ export class DefinitionAdapter extends Adapter {
 
 export class ReferenceAdapter extends Adapter implements monaco.languages.ReferenceProvider {
 
+	constructor(
+		private readonly _libFiles: LibFiles,
+		worker: (...uris: Uri[]) => Promise<TypeScriptWorker>
+	) {
+		super(worker);
+	}
+
 	public async provideReferences(model: monaco.editor.ITextModel, position: Position, context: monaco.languages.ReferenceContext, token: CancellationToken): Promise<monaco.languages.Location[] | undefined> {
 		const resource = model.uri;
 		const offset = model.getOffsetAt(position);
@@ -518,10 +624,17 @@ export class ReferenceAdapter extends Adapter implements monaco.languages.Refere
 			return;
 		}
 
+		// Fetch lib files if necessary
+		await this._libFiles.fetchLibFilesIfNecessary(entries.map(entry => Uri.parse(entry.fileName)));
+
+		if (model.isDisposed()) {
+			return;
+		}
+
 		const result: monaco.languages.Location[] = [];
 		for (let entry of entries) {
 			const uri = Uri.parse(entry.fileName);
-			const refModel = monaco.editor.getModel(uri);
+			const refModel = this._libFiles.getOrCreateModel(uri);
 			if (refModel) {
 				result.push({
 					uri: uri,
@@ -701,7 +814,7 @@ export class CodeActionAdaptor extends FormatHelper implements monaco.languages.
 		const codeFixes = await worker.getCodeFixesAtPosition(resource.toString(), start, end, errorCodes, formatOptions);
 
 		if (!codeFixes || model.isDisposed()) {
-			return { actions: [], dispose:() => {} };
+			return { actions: [], dispose: () => { } };
 		}
 
 		const actions = codeFixes.filter(fix => {

+ 65 - 0
src/lib/lib.index.ts

@@ -0,0 +1,65 @@
+/*---------------------------------------------------------------------------------------------
+ *  Copyright (c) Microsoft Corporation. All rights reserved.
+ *  Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+//
+// **NOTE**: Do not edit directly! This file is generated using `npm run import-typescript`
+//
+
+
+/** Contains all the lib files */
+export const libFileSet: Record<string, boolean> = {}
+libFileSet['lib.d.ts'] = true;
+libFileSet['lib.dom.d.ts'] = true;
+libFileSet['lib.dom.iterable.d.ts'] = true;
+libFileSet['lib.es2015.collection.d.ts'] = true;
+libFileSet['lib.es2015.core.d.ts'] = true;
+libFileSet['lib.es2015.d.ts'] = true;
+libFileSet['lib.es2015.generator.d.ts'] = true;
+libFileSet['lib.es2015.iterable.d.ts'] = true;
+libFileSet['lib.es2015.promise.d.ts'] = true;
+libFileSet['lib.es2015.proxy.d.ts'] = true;
+libFileSet['lib.es2015.reflect.d.ts'] = true;
+libFileSet['lib.es2015.symbol.d.ts'] = true;
+libFileSet['lib.es2015.symbol.wellknown.d.ts'] = true;
+libFileSet['lib.es2016.array.include.d.ts'] = true;
+libFileSet['lib.es2016.d.ts'] = true;
+libFileSet['lib.es2016.full.d.ts'] = true;
+libFileSet['lib.es2017.d.ts'] = true;
+libFileSet['lib.es2017.full.d.ts'] = true;
+libFileSet['lib.es2017.intl.d.ts'] = true;
+libFileSet['lib.es2017.object.d.ts'] = true;
+libFileSet['lib.es2017.sharedmemory.d.ts'] = true;
+libFileSet['lib.es2017.string.d.ts'] = true;
+libFileSet['lib.es2017.typedarrays.d.ts'] = true;
+libFileSet['lib.es2018.asyncgenerator.d.ts'] = true;
+libFileSet['lib.es2018.asynciterable.d.ts'] = true;
+libFileSet['lib.es2018.d.ts'] = true;
+libFileSet['lib.es2018.full.d.ts'] = true;
+libFileSet['lib.es2018.intl.d.ts'] = true;
+libFileSet['lib.es2018.promise.d.ts'] = true;
+libFileSet['lib.es2018.regexp.d.ts'] = true;
+libFileSet['lib.es2019.array.d.ts'] = true;
+libFileSet['lib.es2019.d.ts'] = true;
+libFileSet['lib.es2019.full.d.ts'] = true;
+libFileSet['lib.es2019.object.d.ts'] = true;
+libFileSet['lib.es2019.string.d.ts'] = true;
+libFileSet['lib.es2019.symbol.d.ts'] = true;
+libFileSet['lib.es2020.bigint.d.ts'] = true;
+libFileSet['lib.es2020.d.ts'] = true;
+libFileSet['lib.es2020.full.d.ts'] = true;
+libFileSet['lib.es2020.promise.d.ts'] = true;
+libFileSet['lib.es2020.string.d.ts'] = true;
+libFileSet['lib.es2020.symbol.wellknown.d.ts'] = true;
+libFileSet['lib.es5.d.ts'] = true;
+libFileSet['lib.es6.d.ts'] = true;
+libFileSet['lib.esnext.array.d.ts'] = true;
+libFileSet['lib.esnext.asynciterable.d.ts'] = true;
+libFileSet['lib.esnext.bigint.d.ts'] = true;
+libFileSet['lib.esnext.d.ts'] = true;
+libFileSet['lib.esnext.full.d.ts'] = true;
+libFileSet['lib.esnext.intl.d.ts'] = true;
+libFileSet['lib.esnext.symbol.d.ts'] = true;
+libFileSet['lib.scripthost.d.ts'] = true;
+libFileSet['lib.webworker.d.ts'] = true;
+libFileSet['lib.webworker.importscripts.d.ts'] = true;

Разница между файлами не показана из-за своего большого размера
+ 0 - 1
src/lib/lib.ts


+ 5 - 3
src/tsMode.ts

@@ -55,18 +55,20 @@ function setupMode(defaults: LanguageServiceDefaultsImpl, modeId: string): (...u
 		return client.getLanguageServiceWorker(...uris);
 	};
 
+	const libFiles = new languageFeatures.LibFiles(worker);
+
 	monaco.languages.registerCompletionItemProvider(modeId, new languageFeatures.SuggestAdapter(worker));
 	monaco.languages.registerSignatureHelpProvider(modeId, new languageFeatures.SignatureHelpAdapter(worker));
 	monaco.languages.registerHoverProvider(modeId, new languageFeatures.QuickInfoAdapter(worker));
 	monaco.languages.registerDocumentHighlightProvider(modeId, new languageFeatures.OccurrencesAdapter(worker));
-	monaco.languages.registerDefinitionProvider(modeId, new languageFeatures.DefinitionAdapter(worker));
-	monaco.languages.registerReferenceProvider(modeId, new languageFeatures.ReferenceAdapter(worker));
+	monaco.languages.registerDefinitionProvider(modeId, new languageFeatures.DefinitionAdapter(libFiles, worker));
+	monaco.languages.registerReferenceProvider(modeId, new languageFeatures.ReferenceAdapter(libFiles, worker));
 	monaco.languages.registerDocumentSymbolProvider(modeId, new languageFeatures.OutlineAdapter(worker));
 	monaco.languages.registerDocumentRangeFormattingEditProvider(modeId, new languageFeatures.FormatAdapter(worker));
 	monaco.languages.registerOnTypeFormattingEditProvider(modeId, new languageFeatures.FormatOnTypeAdapter(worker));
 	monaco.languages.registerCodeActionProvider(modeId, new languageFeatures.CodeActionAdaptor(worker));
 	monaco.languages.registerRenameProvider(modeId, new languageFeatures.RenameAdapter(worker));
-	new languageFeatures.DiagnosticsAdapter(defaults, modeId, worker);
+	new languageFeatures.DiagnosticsAdapter(libFiles, defaults, modeId, worker);
 
 	return worker;
 }

+ 33 - 18
src/tsWorker.ts

@@ -5,21 +5,11 @@
 'use strict';
 
 import * as ts from './lib/typescriptServices';
-import { lib_es5_dts, lib_es2015_bundled_dts } from './lib/lib';
+import { libFileMap } from './lib/lib';
 import { IExtraLibs } from './monaco.contribution';
 
 import IWorkerContext = monaco.worker.IWorkerContext;
 
-const DEFAULT_ES5_LIB = {
-	NAME: 'defaultLib:lib.d.ts',
-	CONTENTS: lib_es5_dts
-};
-
-const ES2015_LIB = {
-	NAME: 'defaultLib:lib.es2015.d.ts',
-	CONTENTS: lib_es2015_bundled_dts
-};
-
 export class TypeScriptWorker implements ts.LanguageServiceHost, monaco.languages.typescript.TypeScriptWorker {
 
 	// --- model sync -----------------------
@@ -79,15 +69,12 @@ export class TypeScriptWorker implements ts.LanguageServiceHost, monaco.language
 		if (model) {
 			// a true editor model
 			text = model.getValue();
+		} else if (fileName in libFileMap) {
+			text = libFileMap[fileName];
 
 		} else if (fileName in this._extraLibs) {
 			// extra lib
 			text = this._extraLibs[fileName].content;
-
-		} else if (fileName === DEFAULT_ES5_LIB.NAME) {
-			text = DEFAULT_ES5_LIB.CONTENTS;
-		} else if (fileName === ES2015_LIB.NAME) {
-			text = ES2015_LIB.CONTENTS;
 		} else {
 			return;
 		}
@@ -126,14 +113,42 @@ export class TypeScriptWorker implements ts.LanguageServiceHost, monaco.language
 	}
 
 	getDefaultLibFileName(options: ts.CompilerOptions): string {
-		// TODO@joh support lib.es7.d.ts
-		return (options.target || ts.ScriptTarget.ES2015) < ts.ScriptTarget.ES2015 ? DEFAULT_ES5_LIB.NAME : ES2015_LIB.NAME;
+		switch (options.target) {
+			case 99 /* ESNext */:
+				const esnext = "lib.esnext.full.d.ts";
+				if (esnext in libFileMap || esnext in this._extraLibs) return esnext
+			case 7 /* ES2020 */:
+			case 6 /* ES2019 */:
+			case 5 /* ES2018 */:
+			case 4 /* ES2017 */:
+			case 3 /* ES2016 */:
+			case 2 /* ES2015 */:
+			default:
+				// Support a dynamic lookup for the ES20XX version based on the target
+				// which is safe unless TC39 changes their numbering system
+				const eslib = `lib.es${2013 + (options.target || 99)}.full.d.ts`;
+				// Note: This also looks in _extraLibs, If you want
+				// to add support for additional target options, you will need to
+				// add the extra dts files to _extraLibs via the API.
+				if (eslib in libFileMap || eslib in this._extraLibs) {
+					return eslib;
+				}
+
+				return "lib.es6.d.ts"; // We don't use lib.es2015.full.d.ts due to breaking change.
+			case 1:
+			case 0:
+				return "lib.d.ts";
+		}
 	}
 
 	isDefaultLibFileName(fileName: string): boolean {
 		return fileName === this.getDefaultLibFileName(this._compilerOptions);
 	}
 
+	getLibFiles(): Promise<Record<string, string>> {
+		return Promise.resolve(libFileMap);
+	}
+
 	// --- language features
 
 	private static clearFiles(diagnostics: ts.Diagnostic[]): monaco.languages.typescript.Diagnostic[] {

Некоторые файлы не были показаны из-за большого количества измененных файлов