typeScriptServiceClientHost.ts 10.7 KB
Newer Older
I
isidor 已提交
1 2 3 4
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/
E
Erich Gamma 已提交
5 6 7 8 9 10

/* --------------------------------------------------------------------------------------------
 * Includes code from typescript-sublime-plugin project, obtained from
 * https://github.com/Microsoft/TypeScript-Sublime-Plugin/blob/master/TypeScript%20Indent.tmPreferences
 * ------------------------------------------------------------------------------------------ */

11
import { workspace, Memento, Diagnostic, Range, Disposable, Uri, DiagnosticSeverity, DiagnosticTag } from 'vscode';
12

E
Erich Gamma 已提交
13
import * as Proto from './protocol';
14
import * as PConst from './protocol.const';
D
Dirk Baeumer 已提交
15

E
Erich Gamma 已提交
16
import TypeScriptServiceClient from './typescriptServiceClient';
17
import LanguageProvider from './languageProvider';
E
Erich Gamma 已提交
18

19
import TypingsStatus, { AtaProgressReporter } from './utils/typingsStatus';
M
Matt Bierner 已提交
20
import VersionStatus from './utils/versionStatus';
21
import { TypeScriptServerPlugin } from './utils/plugins';
22
import * as typeConverters from './utils/typeConverters';
23
import { CommandManager } from './utils/commandManager';
24
import { LanguageDescription } from './utils/languageDescription';
25
import LogDirectoryProvider from './utils/logDirectoryProvider';
M
Matt Bierner 已提交
26
import { disposeAll } from './utils/dispose';
M
Matt Bierner 已提交
27
import { DiagnosticKind } from './features/diagnostics';
28

29 30 31 32 33 34 35 36 37 38
// Style check diagnostics that can be reported as warnings
const styleCheckDiagnostics = [
	6133, 	// variable is declared but never used
	6138, 	// property is declared but its value is never read
	7027,	// unreachable code detected
	7028,	// unused label
	7029,	// fall through case in switch
	7030	// not all code paths return a value
];

39
export default class TypeScriptServiceClientHost {
M
Matt Bierner 已提交
40 41 42 43 44
	private readonly ataProgressReporter: AtaProgressReporter;
	private readonly typingsStatus: TypingsStatus;
	private readonly client: TypeScriptServiceClient;
	private readonly languages: LanguageProvider[] = [];
	private readonly languagePerId = new Map<string, LanguageProvider>();
45
	private readonly disposables: Disposable[] = [];
M
Matt Bierner 已提交
46
	private readonly versionStatus: VersionStatus;
47
	private reportStyleCheckAsWarnings: boolean = true;
E
Erich Gamma 已提交
48

M
Matt Bierner 已提交
49 50
	constructor(
		descriptions: LanguageDescription[],
51
		workspaceState: Memento,
52
		plugins: TypeScriptServerPlugin[],
53 54
		private readonly commandManager: CommandManager,
		logDirectoryProvider: LogDirectoryProvider
M
Matt Bierner 已提交
55 56
	) {
		const handleProjectCreateOrDelete = () => {
E
Erich Gamma 已提交
57 58 59
			this.client.execute('reloadProjects', null, false);
			this.triggerAllDiagnostics();
		};
M
Matt Bierner 已提交
60
		const handleProjectChange = () => {
D
Dirk Baeumer 已提交
61 62 63
			setTimeout(() => {
				this.triggerAllDiagnostics();
			}, 1500);
A
tslint  
Alex Dima 已提交
64
		};
65 66 67 68 69
		const configFileWatcher = workspace.createFileSystemWatcher('**/[tj]sconfig.json');
		this.disposables.push(configFileWatcher);
		configFileWatcher.onDidCreate(handleProjectCreateOrDelete, this, this.disposables);
		configFileWatcher.onDidDelete(handleProjectCreateOrDelete, this, this.disposables);
		configFileWatcher.onDidChange(handleProjectChange, this, this.disposables);
E
Erich Gamma 已提交
70

71
		this.client = new TypeScriptServiceClient(workspaceState, version => this.versionStatus.onDidChangeTypeScriptVersion(version), plugins, logDirectoryProvider);
72 73
		this.disposables.push(this.client);

M
Matt Bierner 已提交
74 75 76 77
		this.client.onDiagnosticsReceived(({ kind, resource, diagnostics }) => {
			this.diagnosticsReceived(kind, resource, diagnostics);
		}, null, this.disposables);

78
		this.client.onConfigDiagnosticsReceived(diag => this.configFileDiagnosticsReceived(diag), null, this.disposables);
79
		this.client.onResendModelsRequested(() => this.populateService(), null, this.disposables);
80

81 82 83
		this.versionStatus = new VersionStatus(resource => this.client.normalizePath(resource));
		this.disposables.push(this.versionStatus);

84 85 86
		this.typingsStatus = new TypingsStatus(this.client);
		this.ataProgressReporter = new AtaProgressReporter(this.client);

87
		for (const description of descriptions) {
88
			const manager = new LanguageProvider(this.client, description, this.commandManager, this.client.telemetryReporter, this.typingsStatus);
89
			this.languages.push(manager);
90
			this.disposables.push(manager);
M
Matt Bierner 已提交
91
			this.languagePerId.set(description.id, manager);
92
		}
93

M
Matt Bierner 已提交
94
		this.client.ensureServiceStarted();
95
		this.client.onReady(() => {
96 97 98 99
			if (!this.client.apiVersion.has230Features()) {
				return;
			}

100
			const languages = new Set<string>();
101 102
			for (const plugin of plugins) {
				for (const language of plugin.languages) {
103
					languages.add(language);
104 105
				}
			}
106
			if (languages.size) {
107 108
				const description: LanguageDescription = {
					id: 'typescript-plugins',
109
					modeIds: Array.from(languages.values()),
110
					diagnosticSource: 'ts-plugins',
111
					diagnosticOwner: 'typescript',
112
					isExternal: true
113
				};
114
				const manager = new LanguageProvider(this.client, description, this.commandManager, this.client.telemetryReporter, this.typingsStatus);
115 116
				this.languages.push(manager);
				this.disposables.push(manager);
M
Matt Bierner 已提交
117
				this.languagePerId.set(description.id, manager);
118 119
			}
		});
120 121 122 123

		this.client.onTsServerStarted(() => {
			this.triggerAllDiagnostics();
		});
124

125
		workspace.onDidChangeConfiguration(this.configurationChanged, this, this.disposables);
126
		this.configurationChanged();
127 128 129
	}

	public dispose(): void {
130
		disposeAll(this.disposables);
131 132
		this.typingsStatus.dispose();
		this.ataProgressReporter.dispose();
E
Erich Gamma 已提交
133 134 135 136 137 138
	}

	public get serviceClient(): TypeScriptServiceClient {
		return this.client;
	}

D
Dirk Baeumer 已提交
139 140 141 142 143
	public reloadProjects(): void {
		this.client.execute('reloadProjects', null, false);
		this.triggerAllDiagnostics();
	}

144 145
	public handles(resource: Uri): boolean {
		return !!this.findLanguage(resource);
D
Dirk Baeumer 已提交
146 147
	}

148
	private configurationChanged(): void {
149 150 151
		const typescriptConfig = workspace.getConfiguration('typescript');

		this.reportStyleCheckAsWarnings = typescriptConfig.get('reportStyleChecksAsWarnings', true);
152 153
	}

154
	private async findLanguage(resource: Uri): Promise<LanguageProvider | undefined> {
M
Matt Bierner 已提交
155
		try {
156 157
			const doc = await workspace.openTextDocument(resource);
			return this.languages.find(language => language.handles(resource, doc));
M
Matt Bierner 已提交
158 159 160
		} catch {
			return undefined;
		}
E
Erich Gamma 已提交
161 162 163
	}

	private triggerAllDiagnostics() {
M
Matt Bierner 已提交
164 165 166
		for (const language of this.languagePerId.values()) {
			language.triggerAllDiagnostics();
		}
E
Erich Gamma 已提交
167 168
	}

169
	private populateService(): void {
E
Erich Gamma 已提交
170
		// See https://github.com/Microsoft/TypeScript/issues/5530
M
Matt Bierner 已提交
171 172 173 174
		workspace.saveAll(false).then(() => {
			for (const language of this.languagePerId.values()) {
				language.reInitialize();
			}
E
Erich Gamma 已提交
175 176 177
		});
	}

M
Matt Bierner 已提交
178 179 180 181 182
	private async diagnosticsReceived(
		kind: DiagnosticKind,
		resource: Uri,
		diagnostics: Proto.Diagnostic[]
	): Promise<void> {
183
		const language = await this.findLanguage(resource);
184
		if (language) {
M
Matt Bierner 已提交
185 186
			language.diagnosticsReceived(
				kind,
187
				resource,
188
				this.createMarkerDatas(diagnostics, language.diagnosticSource));
E
Erich Gamma 已提交
189 190 191
		}
	}

192
	private configFileDiagnosticsReceived(event: Proto.ConfigFileDiagnosticEvent): void {
193
		// See https://github.com/Microsoft/TypeScript/issues/10384
194
		const body = event.body;
195
		if (!body || !body.diagnostics || !body.configFile) {
196 197 198
			return;
		}

199
		(this.findLanguage(this.client.asUrl(body.configFile))).then(language => {
200 201 202 203
			if (!language) {
				return;
			}
			if (body.diagnostics.length === 0) {
204
				language.configFileDiagnosticsReceived(this.client.asUrl(body.configFile), []);
205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222
			} else if (body.diagnostics.length >= 1) {
				workspace.openTextDocument(Uri.file(body.configFile)).then((document) => {
					let curly: [number, number, number] | undefined = undefined;
					let nonCurly: [number, number, number] | undefined = undefined;
					let diagnostic: Diagnostic;
					for (let index = 0; index < document.lineCount; index++) {
						const line = document.lineAt(index);
						const text = line.text;
						const firstNonWhitespaceCharacterIndex = line.firstNonWhitespaceCharacterIndex;
						if (firstNonWhitespaceCharacterIndex < text.length) {
							if (text.charAt(firstNonWhitespaceCharacterIndex) === '{') {
								curly = [index, firstNonWhitespaceCharacterIndex, firstNonWhitespaceCharacterIndex + 1];
								break;
							} else {
								const matches = /\s*([^\s]*)(?:\s*|$)/.exec(text.substr(firstNonWhitespaceCharacterIndex));
								if (matches && matches.length >= 1) {
									nonCurly = [index, firstNonWhitespaceCharacterIndex, firstNonWhitespaceCharacterIndex + matches[1].length];
								}
223
							}
224
						}
225
					}
226 227 228 229 230 231 232 233
					const match = curly || nonCurly;
					if (match) {
						diagnostic = new Diagnostic(new Range(match[0], match[1], match[0], match[2]), body.diagnostics[0].text);
					} else {
						diagnostic = new Diagnostic(new Range(0, 0, 0, 0), body.diagnostics[0].text);
					}
					if (diagnostic) {
						diagnostic.source = language.diagnosticSource;
234
						language.configFileDiagnosticsReceived(this.client.asUrl(body.configFile), [diagnostic]);
235 236
					}
				}, _error => {
237
					language.configFileDiagnosticsReceived(this.client.asUrl(body.configFile), [new Diagnostic(new Range(0, 0, 0, 0), body.diagnostics[0].text)]);
238 239 240
				});
			}
		});
241 242
	}

243 244 245 246
	private createMarkerDatas(
		diagnostics: Proto.Diagnostic[],
		source: string
	): (Diagnostic & { reportUnnecessary: any })[] {
247 248 249
		return diagnostics.map(tsDiag => this.tsDiagnosticToVsDiagnostic(tsDiag, source));
	}

250
	private tsDiagnosticToVsDiagnostic(diagnostic: Proto.Diagnostic, source: string): Diagnostic & { reportUnnecessary: any } {
251
		const { start, end, text } = diagnostic;
252
		const range = new Range(typeConverters.Position.fromLocation(start), typeConverters.Position.fromLocation(end));
253 254 255 256 257
		const converted = new Diagnostic(range, text);
		converted.severity = this.getDiagnosticSeverity(diagnostic);
		converted.source = diagnostic.source || source;
		if (diagnostic.code) {
			converted.code = diagnostic.code;
E
Erich Gamma 已提交
258
		}
259 260 261
		if (diagnostic.reportsUnnecessary) {
			converted.customTags = [DiagnosticTag.Unnecessary];
		}
262 263
		(converted as Diagnostic & { reportUnnecessary: any }).reportUnnecessary = diagnostic.reportsUnnecessary;
		return converted as Diagnostic & { reportUnnecessary: any };
E
Erich Gamma 已提交
264
	}
265 266

	private getDiagnosticSeverity(diagnostic: Proto.Diagnostic): DiagnosticSeverity {
267 268 269 270
		if (this.reportStyleCheckAsWarnings
			&& this.isStyleCheckDiagnostic(diagnostic.code)
			&& diagnostic.category === PConst.DiagnosticCategory.error
		) {
271 272 273
			return DiagnosticSeverity.Warning;
		}

274 275 276 277 278 279 280
		switch (diagnostic.category) {
			case PConst.DiagnosticCategory.error:
				return DiagnosticSeverity.Error;

			case PConst.DiagnosticCategory.warning:
				return DiagnosticSeverity.Warning;

M
Matt Bierner 已提交
281 282 283
			case PConst.DiagnosticCategory.suggestion:
				return DiagnosticSeverity.Hint;

284 285 286 287
			default:
				return DiagnosticSeverity.Error;
		}
	}
288 289 290 291 292

	private isStyleCheckDiagnostic(code: number | undefined): boolean {
		return code ? styleCheckDiagnostics.indexOf(code) !== -1 : false;
	}
}