From 22deff959d7715d727704cbdf5bca2dc1e96a23e Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Thu, 1 Dec 2016 13:48:03 -0800 Subject: [PATCH] Use Strict Null Checks In TS Extension (#16244) * Use Strict Null Checks In TS Extension Updates the Ts extension to use strict null checks. * Throw instead of returning undefined in some linkedmap cases * fix small null check in buffersync * Fix for request item null --- .../src/features/bufferSyncSupport.ts | 13 +++-- .../src/features/completionItemProvider.ts | 29 ++++++---- .../src/features/definitionProvider.ts | 20 ++++--- .../src/features/documentHighlightProvider.ts | 7 ++- .../src/features/documentSymbolProvider.ts | 10 +++- .../src/features/formattingProvider.ts | 29 ++++++++-- .../typescript/src/features/hoverProvider.ts | 12 ++-- .../typescript/src/features/linkedMap.ts | 41 +++++++++----- .../src/features/referenceProvider.ts | 9 ++- .../typescript/src/features/renameProvider.ts | 13 ++++- .../src/features/signatureHelpProvider.ts | 13 ++++- .../src/features/workspaceSymbolProvider.ts | 10 +++- extensions/typescript/src/typescriptMain.ts | 12 ++-- .../typescript/src/typescriptService.ts | 2 +- .../typescript/src/typescriptServiceClient.ts | 56 ++++++++++--------- extensions/typescript/src/utils/async.ts | 18 +++--- extensions/typescript/src/utils/electron.ts | 2 +- .../typescript/src/utils/projectStatus.ts | 21 ++++--- .../typescript/src/utils/wireProtocol.ts | 6 +- extensions/typescript/tsconfig.json | 3 +- 20 files changed, 215 insertions(+), 111 deletions(-) diff --git a/extensions/typescript/src/features/bufferSyncSupport.ts b/extensions/typescript/src/features/bufferSyncSupport.ts index cad4ab73d1d..e713bdc8239 100644 --- a/extensions/typescript/src/features/bufferSyncSupport.ts +++ b/extensions/typescript/src/features/bufferSyncSupport.ts @@ -167,7 +167,10 @@ export default class BufferSyncSupport { public dispose(): void { while (this.disposables.length) { - this.disposables.pop().dispose(); + const obj = this.disposables.pop(); + if (obj) { + obj.dispose(); + } } } @@ -191,7 +194,7 @@ export default class BufferSyncSupport { } private onDidCloseTextDocument(document: TextDocument): void { - let filepath: string = this.client.asAbsolutePath(document.uri); + let filepath = this.client.asAbsolutePath(document.uri); if (!filepath) { return; } @@ -208,7 +211,7 @@ export default class BufferSyncSupport { } private onDidChangeTextDocument(e: TextDocumentChangeEvent): void { - let filepath: string = this.client.asAbsolutePath(e.document.uri); + let filepath = this.client.asAbsolutePath(e.document.uri); if (!filepath) { return; } @@ -220,7 +223,7 @@ export default class BufferSyncSupport { } private onDidSaveTextDocument(document: TextDocument): void { - let filepath: string = this.client.asAbsolutePath(document.uri); + let filepath = this.client.asAbsolutePath(document.uri); if (!filepath) { return; } @@ -312,7 +315,7 @@ export default class BufferSyncSupport { return cp.exec(cmd + ' ' + url); } - let tscVersion: string = undefined; + let tscVersion: string | undefined = undefined; try { let out = cp.execSync('tsc --version', { encoding: 'utf8' }); if (out) { diff --git a/extensions/typescript/src/features/completionItemProvider.ts b/extensions/typescript/src/features/completionItemProvider.ts index 972961debb5..9a052483962 100644 --- a/extensions/typescript/src/features/completionItemProvider.ts +++ b/extensions/typescript/src/features/completionItemProvider.ts @@ -92,6 +92,9 @@ export default class TypeScriptCompletionItemProvider implements CompletionItemP public provideCompletionItems(document: TextDocument, position: Position, token: CancellationToken): Promise { let filepath = this.client.asAbsolutePath(document.uri); + if (!filepath) { + return Promise.resolve([]); + } let args: CompletionsRequestArgs = { file: filepath, line: position.line + 1, @@ -120,14 +123,15 @@ export default class TypeScriptCompletionItemProvider implements CompletionItemP let completionItems: CompletionItem[] = []; let body = msg.body; - - for (let i = 0; i < body.length; i++) { - let element = body[i]; - let item = new MyCompletionItem(element); - item.document = document; - item.position = position; - - completionItems.push(item); + if (body) { + for (let i = 0; i < body.length; i++) { + let element = body[i]; + let item = new MyCompletionItem(element); + item.document = document; + item.position = position; + + completionItems.push(item); + } } return completionItems; @@ -139,16 +143,19 @@ export default class TypeScriptCompletionItemProvider implements CompletionItemP public resolveCompletionItem(item: CompletionItem, token: CancellationToken): any | Thenable { if (item instanceof MyCompletionItem) { - + const filepath = this.client.asAbsolutePath(item.document.uri); + if (!filepath) { + return null; + } let args: CompletionDetailsRequestArgs = { - file: this.client.asAbsolutePath(item.document.uri), + file: filepath, line: item.position.line + 1, offset: item.position.character + 1, entryNames: [item.label] }; return this.client.execute('completionEntryDetails', args, token).then((response) => { let details = response.body; - let detail: CompletionEntryDetails = null; + let detail: CompletionEntryDetails | null = null; if (details && details.length > 0) { detail = details[0]; item.documentation = Previewer.plain(detail.documentation); diff --git a/extensions/typescript/src/features/definitionProvider.ts b/extensions/typescript/src/features/definitionProvider.ts index 4d44dff57d2..73d87374783 100644 --- a/extensions/typescript/src/features/definitionProvider.ts +++ b/extensions/typescript/src/features/definitionProvider.ts @@ -5,7 +5,7 @@ 'use strict'; -import { DefinitionProvider, TextDocument, Position, Range, CancellationToken, Location } from 'vscode'; +import { DefinitionProvider, TextDocument, Position, Range, CancellationToken, Definition, Location } from 'vscode'; import * as Proto from '../protocol'; import { ITypescriptServiceClient } from '../typescriptService'; @@ -20,19 +20,23 @@ export default class TypeScriptDefinitionProvider implements DefinitionProvider this.client = client; } - public provideDefinition(document: TextDocument, position: Position, token: CancellationToken): Promise { + public provideDefinition(document: TextDocument, position: Position, token: CancellationToken): Promise { + const filepath = this.client.asAbsolutePath(document.uri); + if (!filepath) { + return Promise.resolve(null); + } let args: Proto.FileLocationRequestArgs = { - file: this.client.asAbsolutePath(document.uri), + file: filepath, line: position.line + 1, offset: position.character + 1 }; if (!args.file) { - return Promise.resolve(null); + return Promise.resolve(null); } return this.client.execute('definition', args, token).then(response => { - let locations: Proto.FileSpan[] = response.body; + let locations: Proto.FileSpan[] = response.body || []; if (!locations || locations.length === 0) { - return null; + return [] as Definition; } return locations.map(location => { let resource = this.client.asUrl(location.file); @@ -41,10 +45,10 @@ export default class TypeScriptDefinitionProvider implements DefinitionProvider } else { return new Location(resource, new Range(location.start.line - 1, location.start.offset - 1, location.end.line - 1, location.end.offset - 1)); } - }); + }).filter(x => x !== null) as Location[]; }, (error) => { this.client.error(`'definition' request failed with error.`, error); - return null; + return [] as Definition; }); } } \ No newline at end of file diff --git a/extensions/typescript/src/features/documentHighlightProvider.ts b/extensions/typescript/src/features/documentHighlightProvider.ts index cdd99452d27..1cf165a2da3 100644 --- a/extensions/typescript/src/features/documentHighlightProvider.ts +++ b/extensions/typescript/src/features/documentHighlightProvider.ts @@ -20,8 +20,12 @@ export default class TypeScriptDocumentHighlightProvider implements DocumentHigh } public provideDocumentHighlights(resource: TextDocument, position: Position, token: CancellationToken): Promise { + const filepath = this.client.asAbsolutePath(resource.uri); + if (!filepath) { + return Promise.resolve([]); + } let args: Proto.FileLocationRequestArgs = { - file: this.client.asAbsolutePath(resource.uri), + file: filepath, line: position.line + 1, offset: position.character + 1 }; @@ -36,6 +40,7 @@ export default class TypeScriptDocumentHighlightProvider implements DocumentHigh item.isWriteAccess ? DocumentHighlightKind.Write : DocumentHighlightKind.Read); }); } + return []; }, (err) => { this.client.error(`'occurrences' request failed with error.`, err); return []; diff --git a/extensions/typescript/src/features/documentSymbolProvider.ts b/extensions/typescript/src/features/documentSymbolProvider.ts index c20bbfc06f0..aa8fc158e61 100644 --- a/extensions/typescript/src/features/documentSymbolProvider.ts +++ b/extensions/typescript/src/features/documentSymbolProvider.ts @@ -40,8 +40,12 @@ export default class TypeScriptDocumentSymbolProvider implements DocumentSymbolP } public provideDocumentSymbols(resource: TextDocument, token: CancellationToken): Promise { + const filepath = this.client.asAbsolutePath(resource.uri); + if (!filepath) { + return Promise.resolve([]); + } let args: Proto.FileRequestArgs = { - file: this.client.asAbsolutePath(resource.uri) + file: filepath }; if (!args.file) { return Promise.resolve([]); @@ -53,7 +57,7 @@ export default class TypeScriptDocumentSymbolProvider implements DocumentSymbolP if (realIndent !== 0 && !foldingMap[key]) { let result = new SymbolInformation(item.text, outlineTypeTable[item.kind] || SymbolKind.Variable, - containerLabel, + '' + containerLabel, new Location(resource.uri, textSpan2Range(item.spans[0]))); foldingMap[key] = result; bucket.push(result); @@ -68,7 +72,7 @@ export default class TypeScriptDocumentSymbolProvider implements DocumentSymbolP function convertNavTree(bucket: SymbolInformation[], item: Proto.NavigationTree, containerLabel?: string): void { let result = new SymbolInformation(item.text, outlineTypeTable[item.kind] || SymbolKind.Variable, - containerLabel, + '' + containerLabel, new Location(resource.uri, textSpan2Range(item.spans[0])) ); if (item.childItems && item.childItems.length > 0) { diff --git a/extensions/typescript/src/features/formattingProvider.ts b/extensions/typescript/src/features/formattingProvider.ts index d8b7a773b48..c5f730c87ad 100644 --- a/extensions/typescript/src/features/formattingProvider.ts +++ b/extensions/typescript/src/features/formattingProvider.ts @@ -71,7 +71,7 @@ export default class TypeScriptFormattingProvider implements DocumentRangeFormat private client: ITypescriptServiceClient; private config: Configuration; - private formatOptions: { [key: string]: Proto.FormatCodeSettings; }; + private formatOptions: { [key: string]: Proto.FormatCodeSettings | undefined; }; public constructor(client: ITypescriptServiceClient) { this.client = client; @@ -106,8 +106,12 @@ export default class TypeScriptFormattingProvider implements DocumentRangeFormat if (currentOptions && currentOptions.tabSize === options.tabSize && currentOptions.indentSize === options.tabSize && currentOptions.convertTabsToSpaces === options.insertSpaces) { return Promise.resolve(currentOptions); } else { + const absPath = this.client.asAbsolutePath(document.uri); + if (!absPath) { + return Promise.resolve(Object.create(null)); + } let args: Proto.ConfigureRequestArguments = { - file: this.client.asAbsolutePath(document.uri), + file: absPath, formatOptions: this.getFormatOptions(options) }; return this.client.execute('configure', args, token).then((response) => { @@ -120,7 +124,11 @@ export default class TypeScriptFormattingProvider implements DocumentRangeFormat private doFormat(document: TextDocument, options: FormattingOptions, args: Proto.FormatRequestArgs, token: CancellationToken): Promise { return this.ensureFormatOptions(document, options, token).then(() => { return this.client.execute('format', args, token).then((response): TextEdit[] => { - return response.body.map(this.codeEdit2SingleEditOperation); + if (response.body) { + return response.body.map(this.codeEdit2SingleEditOperation); + } else { + return []; + } }, (err: any) => { this.client.error(`'format' request failed with error.`, err); return []; @@ -129,8 +137,12 @@ export default class TypeScriptFormattingProvider implements DocumentRangeFormat } public provideDocumentRangeFormattingEdits(document: TextDocument, range: Range, options: FormattingOptions, token: CancellationToken): Promise { + const absPath = this.client.asAbsolutePath(document.uri); + if (!absPath) { + return Promise.resolve([]); + } let args: Proto.FormatRequestArgs = { - file: this.client.asAbsolutePath(document.uri), + file: absPath, line: range.start.line + 1, offset: range.start.character + 1, endLine: range.end.line + 1, @@ -140,8 +152,12 @@ export default class TypeScriptFormattingProvider implements DocumentRangeFormat } public provideOnTypeFormattingEdits(document: TextDocument, position: Position, ch: string, options: FormattingOptions, token: CancellationToken): Promise { + const filepath = this.client.asAbsolutePath(document.uri); + if (!filepath) { + return Promise.resolve([]); + } let args: Proto.FormatOnKeyRequestArgs = { - file: this.client.asAbsolutePath(document.uri), + file: filepath, line: position.line + 1, offset: position.character + 1, key: ch @@ -151,6 +167,9 @@ export default class TypeScriptFormattingProvider implements DocumentRangeFormat return this.client.execute('formatonkey', args, token).then((response): TextEdit[] => { let edits = response.body; let result: TextEdit[] = []; + if (!edits) { + return result; + } for (let edit of edits) { let textEdit = this.codeEdit2SingleEditOperation(edit); let range = textEdit.range; diff --git a/extensions/typescript/src/features/hoverProvider.ts b/extensions/typescript/src/features/hoverProvider.ts index d0c56c80487..8a7440fb498 100644 --- a/extensions/typescript/src/features/hoverProvider.ts +++ b/extensions/typescript/src/features/hoverProvider.ts @@ -18,16 +18,20 @@ export default class TypeScriptHoverProvider implements HoverProvider { this.client = client; } - public provideHover(document: TextDocument, position: Position, token: CancellationToken): Promise { + public provideHover(document: TextDocument, position: Position, token: CancellationToken): Promise { + const filepath = this.client.asAbsolutePath(document.uri); + if (!filepath) { + return Promise.resolve(null); + } let args: Proto.FileLocationRequestArgs = { - file: this.client.asAbsolutePath(document.uri), + file: filepath, line: position.line + 1, offset: position.character + 1 }; if (!args.file) { - return Promise.resolve(null); + return Promise.resolve(null); } - return this.client.execute('quickinfo', args, token).then((response): Hover => { + return this.client.execute('quickinfo', args, token).then((response): Hover | undefined => { let data = response.body; if (data) { return new Hover( diff --git a/extensions/typescript/src/features/linkedMap.ts b/extensions/typescript/src/features/linkedMap.ts index 7d68d4ce4ca..c4e16a6a9c5 100644 --- a/extensions/typescript/src/features/linkedMap.ts +++ b/extensions/typescript/src/features/linkedMap.ts @@ -4,8 +4,8 @@ *--------------------------------------------------------------------------------------------*/ interface Item { - previous: Item; - next: Item; + previous: Item | undefined; + next: Item | undefined; key: string; value: T; } @@ -13,8 +13,8 @@ interface Item { export default class LinkedMap { private map: Map>; - private head: Item; - private tail: Item; + private head: Item | undefined; + private tail: Item | undefined; private _length: number; constructor() { @@ -32,7 +32,7 @@ export default class LinkedMap { return this._length; } - public get(key: string): T { + public get(key: string): T | undefined { const item = this.map[key]; if (!item) { return undefined; @@ -61,7 +61,7 @@ export default class LinkedMap { } } - public remove(key: string): T { + public remove(key: string): T | undefined { const item = this.map[key]; if (!item) { return undefined; @@ -72,10 +72,13 @@ export default class LinkedMap { return item.value; } - public shift(): T { + public shift(): T | undefined { if (!this.head && !this.tail) { return undefined; } + if (!this.head || !this.tail) { + throw new Error('Invalid list'); + } const item = this.head; delete this.map[item.key]; this.removeItem(item); @@ -87,8 +90,9 @@ export default class LinkedMap { // First time Insert if (!this.head && !this.tail) { this.tail = item; - } - else { + } else if (!this.head) { + throw new Error('Invalid list'); + } else { item.next = this.head; this.head.previous = item; } @@ -99,8 +103,9 @@ export default class LinkedMap { // First time Insert if (!this.head && !this.tail) { this.head = item; - } - else { + } else if (!this.tail) { + throw new Error('Invalid list'); + } else { item.previous = this.tail; this.tail.next = item; } @@ -121,6 +126,9 @@ export default class LinkedMap { else { const next = item.next; const previous = item.previous; + if (!next || !previous) { + throw new Error('Invalid list'); + } next.previous = previous; previous.next = next; } @@ -140,13 +148,20 @@ export default class LinkedMap { } else { // Both next and previous are not null since item was neither head nor tail. - next.previous = previous; - previous.next = next; + if (next) { + next.previous = previous; + } + if (previous) { + previous.next = next; + } } // Insert the node at head item.previous = undefined; item.next = this.head; + if (!this.head) { + throw new Error('Invalid list'); + } this.head.previous = item; this.head = item; } diff --git a/extensions/typescript/src/features/referenceProvider.ts b/extensions/typescript/src/features/referenceProvider.ts index ab0b9fb5be5..3c6cfa2a83d 100644 --- a/extensions/typescript/src/features/referenceProvider.ts +++ b/extensions/typescript/src/features/referenceProvider.ts @@ -21,8 +21,12 @@ export default class TypeScriptReferenceSupport implements ReferenceProvider { } public provideReferences(document: TextDocument, position: Position, options: { includeDeclaration: boolean }, token: CancellationToken): Promise { + const filepath = this.client.asAbsolutePath(document.uri); + if (!filepath) { + return Promise.resolve([]); + } let args: Proto.FileLocationRequestArgs = { - file: this.client.asAbsolutePath(document.uri), + file: filepath, line: position.line + 1, offset: position.character + 1 }; @@ -32,6 +36,9 @@ export default class TypeScriptReferenceSupport implements ReferenceProvider { const apiVersion = this.client.apiVersion; return this.client.execute('references', args, token).then((msg) => { let result: Location[] = []; + if (!msg.body) { + return result; + } let refs = msg.body.refs; for (let i = 0; i < refs.length; i++) { let ref = refs[i]; diff --git a/extensions/typescript/src/features/renameProvider.ts b/extensions/typescript/src/features/renameProvider.ts index 77581f93d2f..b36ba412280 100644 --- a/extensions/typescript/src/features/renameProvider.ts +++ b/extensions/typescript/src/features/renameProvider.ts @@ -20,20 +20,27 @@ export default class TypeScriptRenameProvider implements RenameProvider { this.client = client; } - public provideRenameEdits(document: TextDocument, position: Position, newName: string, token: CancellationToken): Promise { + public provideRenameEdits(document: TextDocument, position: Position, newName: string, token: CancellationToken): Promise { + const filepath = this.client.asAbsolutePath(document.uri); + if (!filepath) { + return Promise.resolve(null); + } let args: Proto.RenameRequestArgs = { - file: this.client.asAbsolutePath(document.uri), + file: filepath, line: position.line + 1, offset: position.character + 1, findInStrings: false, findInComments: false }; if (!args.file) { - return Promise.resolve(null); + return Promise.resolve(null); } return this.client.execute('rename', args, token).then((response) => { let renameResponse = response.body; + if (!renameResponse) { + return Promise.resolve(null); + } let renameInfo = renameResponse.info; let result = new WorkspaceEdit(); diff --git a/extensions/typescript/src/features/signatureHelpProvider.ts b/extensions/typescript/src/features/signatureHelpProvider.ts index 134667375c3..5a44440749b 100644 --- a/extensions/typescript/src/features/signatureHelpProvider.ts +++ b/extensions/typescript/src/features/signatureHelpProvider.ts @@ -19,14 +19,18 @@ export default class TypeScriptSignatureHelpProvider implements SignatureHelpPro this.client = client; } - public provideSignatureHelp(document: TextDocument, position: Position, token: CancellationToken): Promise { + public provideSignatureHelp(document: TextDocument, position: Position, token: CancellationToken): Promise { + const filepath = this.client.asAbsolutePath(document.uri); + if (!filepath) { + return Promise.resolve(null); + } let args: Proto.SignatureHelpRequestArgs = { - file: this.client.asAbsolutePath(document.uri), + file: filepath, line: position.line + 1, offset: position.character + 1 }; if (!args.file) { - return Promise.resolve(null); + return Promise.resolve(null); } return this.client.execute('signatureHelp', args, token).then((response) => { let info = response.body; @@ -42,6 +46,9 @@ export default class TypeScriptSignatureHelpProvider implements SignatureHelpPro } info.items.forEach((item, i) => { + if (!info) { + return; + } // keep active parameter in bounds if (i === info.selectedItemIndex && item.isVariadic) { diff --git a/extensions/typescript/src/features/workspaceSymbolProvider.ts b/extensions/typescript/src/features/workspaceSymbolProvider.ts index da63560fcd6..0503fdfcdb1 100644 --- a/extensions/typescript/src/features/workspaceSymbolProvider.ts +++ b/extensions/typescript/src/features/workspaceSymbolProvider.ts @@ -32,7 +32,7 @@ export default class TypeScriptWorkspaceSymbolProvider implements WorkspaceSymbo // typescript wants to have a resource even when asking // general questions so we check the active editor. If this // doesn't match we take the first TS document. - let uri: Uri; + let uri: Uri | undefined = undefined; let editor = window.activeTextEditor; if (editor) { let document = editor.document; @@ -54,8 +54,12 @@ export default class TypeScriptWorkspaceSymbolProvider implements WorkspaceSymbo return Promise.resolve([]); } + const filepath = this.client.asAbsolutePath(uri); + if (!filepath) { + return Promise.resolve([]); + } let args: Proto.NavtoRequestArgs = { - file: this.client.asAbsolutePath(uri), + file: filepath, searchValue: search }; if (!args.file) { @@ -74,7 +78,7 @@ export default class TypeScriptWorkspaceSymbolProvider implements WorkspaceSymbo if (item.kind === 'method' || item.kind === 'function') { label += '()'; } - result.push(new SymbolInformation(label, _kindMapping[item.kind], item.containerName, + result.push(new SymbolInformation(label, _kindMapping[item.kind], '' + item.containerName, new Location(this.client.asUrl(item.file), range))); } return result; diff --git a/extensions/typescript/src/typescriptMain.ts b/extensions/typescript/src/typescriptMain.ts index 918fd358d52..a898f4f529e 100644 --- a/extensions/typescript/src/typescriptMain.ts +++ b/extensions/typescript/src/typescriptMain.ts @@ -103,7 +103,7 @@ class LanguageProvider { private completionItemProvider: CompletionItemProvider; private formattingProvider: FormattingProvider; - private formattingProviderRegistration: Disposable; + private formattingProviderRegistration: Disposable | null; private _validate: boolean; @@ -214,7 +214,7 @@ class LanguageProvider { this.formattingProvider.updateConfiguration(config); if (!this.formattingProvider.isEnabled() && this.formattingProviderRegistration) { this.formattingProviderRegistration.dispose(); - this.formattingProviderRegistration = undefined; + this.formattingProviderRegistration = null; } else if (this.formattingProvider.isEnabled() && !this.formattingProviderRegistration) { this.formattingProviderRegistration = languages.registerDocumentRangeFormattingEditProvider(this.description.modeIds, this.formattingProvider); @@ -228,7 +228,7 @@ class LanguageProvider { return true; } let basename = path.basename(file); - return basename && basename === this.description.configFile; + return !!basename && basename === this.description.configFile; } public get id(): string { @@ -325,7 +325,7 @@ class TypeScriptServiceClientHost implements ITypescriptServiceClientHost { return !!this.findLanguage(file); } - private findLanguage(file: string): LanguageProvider { + private findLanguage(file: string): LanguageProvider | null { for (let i = 0; i < this.languages.length; i++) { let language = this.languages[i]; if (language.handles(file)) { @@ -348,7 +348,7 @@ class TypeScriptServiceClientHost implements ITypescriptServiceClientHost { /* internal */ syntaxDiagnosticsReceived(event: Proto.DiagnosticEvent): void { let body = event.body; - if (body.diagnostics) { + if (body && body.diagnostics) { let language = this.findLanguage(body.file); if (language) { language.syntaxDiagnosticsReceived(body.file, this.createMarkerDatas(body.diagnostics, language.diagnosticSource)); @@ -358,7 +358,7 @@ class TypeScriptServiceClientHost implements ITypescriptServiceClientHost { /* internal */ semanticDiagnosticsReceived(event: Proto.DiagnosticEvent): void { let body = event.body; - if (body.diagnostics) { + if (body && body.diagnostics) { let language = this.findLanguage(body.file); if (language) { language.semanticDiagnosticsReceived(body.file, this.createMarkerDatas(body.diagnostics, language.diagnosticSource)); diff --git a/extensions/typescript/src/typescriptService.ts b/extensions/typescript/src/typescriptService.ts index da4e09ae680..4c466d0b93d 100644 --- a/extensions/typescript/src/typescriptService.ts +++ b/extensions/typescript/src/typescriptService.ts @@ -56,7 +56,7 @@ export class API { } export interface ITypescriptServiceClient { - asAbsolutePath(resource: Uri): string; + asAbsolutePath(resource: Uri): string | null; asUrl(filepath: string): Uri; info(message: string, data?: any): void; diff --git a/extensions/typescript/src/typescriptServiceClient.ts b/extensions/typescript/src/typescriptServiceClient.ts index 8f6db221502..5535bf49eb4 100644 --- a/extensions/typescript/src/typescriptServiceClient.ts +++ b/extensions/typescript/src/typescriptServiceClient.ts @@ -36,8 +36,8 @@ interface CallbackMap { interface RequestItem { request: Proto.Request; - promise: Promise; - callbacks: CallbackItem; + promise: Promise | null; + callbacks: CallbackItem | null; } interface IPackageInfo { @@ -85,13 +85,13 @@ export default class TypeScriptServiceClient implements ITypescriptServiceClient private pathSeparator: string; private _onReady: { promise: Promise; resolve: () => void; reject: () => void; }; - private tsdk: string; + private tsdk: string | null; private _checkGlobalTSCVersion: boolean; private _experimentalAutoBuild: boolean; private trace: Trace; private _output: OutputChannel; - private servicePromise: Promise; - private lastError: Error; + private servicePromise: Promise | null; + private lastError: Error | null; private reader: Reader; private sequenceNumber: number; private exitRequested: boolean; @@ -103,7 +103,7 @@ export default class TypeScriptServiceClient implements ITypescriptServiceClient private pendingResponses: number; private callbacks: CallbackMap; - private _packageInfo: IPackageInfo; + private _packageInfo: IPackageInfo | null; private _apiVersion: API; private telemetryReporter: TelemetryReporter; @@ -114,7 +114,7 @@ export default class TypeScriptServiceClient implements ITypescriptServiceClient this.pathSeparator = path.sep; let p = new Promise((resolve, reject) => { - this._onReady = { promise: null, resolve, reject }; + this._onReady = { promise: Promise.reject(null), resolve, reject }; }); this._onReady.promise = p; @@ -129,7 +129,7 @@ export default class TypeScriptServiceClient implements ITypescriptServiceClient this.pendingResponses = 0; this.callbacks = Object.create(null); const configuration = workspace.getConfiguration(); - this.tsdk = configuration.get('typescript.tsdk', null); + this.tsdk = configuration.get('typescript.tsdk', null); this._experimentalAutoBuild = false; // configuration.get('typescript.tsserver.experimentalAutoBuild', false); this._apiVersion = new API('1.0.0'); this._checkGlobalTSCVersion = true; @@ -137,7 +137,7 @@ export default class TypeScriptServiceClient implements ITypescriptServiceClient workspace.onDidChangeConfiguration(() => { this.trace = this.readTrace(); let oldTsdk = this.tsdk; - this.tsdk = workspace.getConfiguration().get('typescript.tsdk', null); + this.tsdk = workspace.getConfiguration().get('typescript.tsdk', null); if (this.servicePromise === null && oldTsdk !== this.tsdk) { this.startService(); } @@ -230,7 +230,7 @@ export default class TypeScriptServiceClient implements ITypescriptServiceClient // this.output.show(true); } - private get packageInfo(): IPackageInfo { + private get packageInfo(): IPackageInfo | null { if (this._packageInfo !== undefined) { return this._packageInfo; @@ -264,7 +264,10 @@ export default class TypeScriptServiceClient implements ITypescriptServiceClient return Promise.reject(this.lastError); } this.startService(); - return this.servicePromise; + if (this.servicePromise) { + return this.servicePromise; + } + return Promise.reject(new Error('Could not create TS service')); } private startService(resendModels: boolean = false): void { @@ -362,7 +365,7 @@ export default class TypeScriptServiceClient implements ITypescriptServiceClient let version = this.getTypeScriptVersion(modulePath); if (!version) { - version = workspace.getConfiguration().get('typescript.tsdk_version', undefined); + version = workspace.getConfiguration().get('typescript.tsdk_version', undefined); } if (version) { this._apiVersion = new API(version); @@ -469,7 +472,7 @@ export default class TypeScriptServiceClient implements ITypescriptServiceClient let args: Proto.SetCompilerOptionsForInferredProjectsArgs = { options: compilerOptions }; - this.execute('compilerOptionsForInferredProjects', args).then(null, (err) => { + this.execute('compilerOptionsForInferredProjects', args, true).catch((err) => { this.error(`'compilerOptionsForInferredProjects' request failed with error.`, err); }); } @@ -479,7 +482,7 @@ export default class TypeScriptServiceClient implements ITypescriptServiceClient } } - private getTypeScriptVersion(serverPath: string): string { + private getTypeScriptVersion(serverPath: string): string | undefined { let p = serverPath.split(path.sep); if (p.length <= 2) { return undefined; @@ -491,13 +494,13 @@ export default class TypeScriptServiceClient implements ITypescriptServiceClient return undefined; } let contents = fs.readFileSync(fileName).toString(); - let desc = null; + let desc: any = null; try { desc = JSON.parse(contents); } catch (err) { return undefined; } - if (!desc.version) { + if (!desc || !desc.version) { return undefined; } return desc.version; @@ -528,7 +531,7 @@ export default class TypeScriptServiceClient implements ITypescriptServiceClient } } - public asAbsolutePath(resource: Uri): string { + public asAbsolutePath(resource: Uri): string | null { if (resource.scheme !== 'file') { return null; } @@ -563,7 +566,7 @@ export default class TypeScriptServiceClient implements ITypescriptServiceClient promise: null, callbacks: null }; - let result: Promise = null; + let result: Promise = Promise.resolve(null); if (expectsResult) { result = new Promise((resolve, reject) => { requestInfo.callbacks = { c: resolve, e: reject, start: Date.now() }; @@ -584,7 +587,10 @@ export default class TypeScriptServiceClient implements ITypescriptServiceClient private sendNextRequests(): void { while (this.pendingResponses === 0 && this.requestQueue.length > 0) { - this.sendRequest(this.requestQueue.shift()); + const item = this.requestQueue.shift(); + if (item) { + this.sendRequest(item); + } } } @@ -691,9 +697,9 @@ export default class TypeScriptServiceClient implements ITypescriptServiceClient if (this.trace === Trace.Off) { return; } - let data: string = undefined; + let data: string | undefined = undefined; if (this.trace === Trace.Verbose && request.arguments) { - data = `Arguments: ${JSON.stringify(request.arguments, null, 4)}`; + data = `Arguments: ${JSON.stringify(request.arguments, [], 4)}`; } this.logTrace(`Sending request: ${request.command} (${request.seq}). Response expected: ${responseExpected ? 'yes' : 'no'}. Current queue length: ${this.requestQueue.length}`, data); } @@ -702,9 +708,9 @@ export default class TypeScriptServiceClient implements ITypescriptServiceClient if (this.trace === Trace.Off) { return; } - let data: string = undefined; + let data: string | undefined = undefined; if (this.trace === Trace.Verbose && response.body) { - data = `Result: ${JSON.stringify(response.body, null, 4)}`; + data = `Result: ${JSON.stringify(response.body, [], 4)}`; } this.logTrace(`Response received: ${response.command} (${response.request_seq}). Request took ${Date.now() - startTime} ms. Success: ${response.success} ${!response.success ? '. Message: ' + response.message : ''}`, data); } @@ -713,9 +719,9 @@ export default class TypeScriptServiceClient implements ITypescriptServiceClient if (this.trace === Trace.Off) { return; } - let data: string = undefined; + let data: string | undefined = undefined; if (this.trace === Trace.Verbose && event.body) { - data = `Data: ${JSON.stringify(event.body, null, 4)}`; + data = `Data: ${JSON.stringify(event.body, [], 4)}`; } this.logTrace(`Event received: ${event.event} (${event.seq}).`, data); } diff --git a/extensions/typescript/src/utils/async.ts b/extensions/typescript/src/utils/async.ts index 59a2eac310b..35aa1ea4afe 100644 --- a/extensions/typescript/src/utils/async.ts +++ b/extensions/typescript/src/utils/async.ts @@ -13,9 +13,9 @@ export class Delayer { public defaultDelay: number; private timeout: any; // Timer - private completionPromise: Promise; - private onSuccess: (value?: T | Thenable) => void; - private task: ITask; + private completionPromise: Promise | null; + private onSuccess: ((value?: T | Thenable) => void) | null; + private task: ITask | null; constructor(defaultDelay: number) { this.defaultDelay = defaultDelay; @@ -37,7 +37,7 @@ export class Delayer { }).then(() => { this.completionPromise = null; this.onSuccess = null; - var result = this.task(); + var result = this.task && this.task(); this.task = null; return result; }); @@ -46,20 +46,24 @@ export class Delayer { if (delay >= 0 || this.timeout === null) { this.timeout = setTimeout(() => { this.timeout = null; - this.onSuccess(null); + if (this.onSuccess) { + this.onSuccess(undefined); + } }, delay >= 0 ? delay : this.defaultDelay); } return this.completionPromise; } - public forceDelivery(): Promise { + public forceDelivery(): Promise | null { if (!this.completionPromise) { return null; } this.cancelTimeout(); let result = this.completionPromise; - this.onSuccess(null); + if (this.onSuccess) { + this.onSuccess(undefined); + } return result; } diff --git a/extensions/typescript/src/utils/electron.ts b/extensions/typescript/src/utils/electron.ts index 847c7a098bc..c5a28ad4f49 100644 --- a/extensions/typescript/src/utils/electron.ts +++ b/extensions/typescript/src/utils/electron.ts @@ -53,7 +53,7 @@ function generatePatchedEnv(env: any, stdInPipeName: string, stdOutPipeName: str return newEnv; } -export function fork(modulePath: string, args: string[], options: IForkOptions, callback: (error: any, cp: cp.ChildProcess) => void): void { +export function fork(modulePath: string, args: string[], options: IForkOptions, callback: (error: any, cp: cp.ChildProcess | null) => void): void { var callbackCalled = false; var resolve = (result: cp.ChildProcess) => { diff --git a/extensions/typescript/src/utils/projectStatus.ts b/extensions/typescript/src/utils/projectStatus.ts index 5ecdb13486f..360ae2d5c45 100644 --- a/extensions/typescript/src/utils/projectStatus.ts +++ b/extensions/typescript/src/utils/projectStatus.ts @@ -31,8 +31,8 @@ export function create(client: ITypescriptServiceClient, isOpen: (path: string) const projectHintIgnoreList = memento.get('projectHintIgnoreList', []); for (let path of projectHintIgnoreList) { - if (path === null) { - path = undefined; + if (!path) { + path = 'undefined'; } projectHinted[path] = true; } @@ -53,7 +53,7 @@ export function create(client: ITypescriptServiceClient, isOpen: (path: string) delete projectHinted[e.document.fileName]; })); - function onEditor(editor: vscode.TextEditor): void { + function onEditor(editor: vscode.TextEditor | undefined): void { if (!editor || !vscode.languages.match(selector, editor.document) || !client.asAbsolutePath(editor.document.uri)) { @@ -63,20 +63,26 @@ export function create(client: ITypescriptServiceClient, isOpen: (path: string) } const file = client.asAbsolutePath(editor.document.uri); + if (!file) { + return; + } + isOpen(file).then(value => { if (!value) { return; } return client.execute('projectInfo', { file, needFileNameList: true }).then(res => { - + if (!res.body) { + return; + } let {configFileName, fileNames} = res.body; if (projectHinted[configFileName] === true) { return; } - if (fileNames.length > fileLimit) { + if (fileNames && fileNames.length > fileLimit) { let largeRoots = computeLargeRoots(configFileName, fileNames).map(f => `'/${f}/'`).join(', '); currentHint = { @@ -91,10 +97,11 @@ export function create(client: ITypescriptServiceClient, isOpen: (path: string) item.hide(); let configFileUri: vscode.Uri; - if (dirname(configFileName).indexOf(vscode.workspace.rootPath) === 0) { + let rootPath = vscode.workspace.rootPath; + if (rootPath && dirname(configFileName).indexOf('' + rootPath) === 0) { configFileUri = vscode.Uri.file(configFileName); } else { - configFileUri = vscode.Uri.parse('untitled://' + join(vscode.workspace.rootPath, 'jsconfig.json')); + configFileUri = vscode.Uri.parse('untitled://' + join(rootPath, 'jsconfig.json')); } return vscode.workspace.openTextDocument(configFileUri) diff --git a/extensions/typescript/src/utils/wireProtocol.ts b/extensions/typescript/src/utils/wireProtocol.ts index 7d554d3cced..0227b3be7e6 100644 --- a/extensions/typescript/src/utils/wireProtocol.ts +++ b/extensions/typescript/src/utils/wireProtocol.ts @@ -25,7 +25,7 @@ class ProtocolBuffer { } public append(data: string | Buffer): void { - let toAppend: Buffer = null; + let toAppend: Buffer | null = null; if (Buffer.isBuffer(data)) { toAppend = data; } else { @@ -70,7 +70,7 @@ class ProtocolBuffer { return result; } - public tryReadContent(length: number): string { + public tryReadContent(length: number): string | null { if (this.index < length) { return null; } @@ -84,7 +84,7 @@ class ProtocolBuffer { return result; } - public tryReadLine(): string { + public tryReadLine(): string | null { let end: number = 0; while (end < this.index && this.buffer[end] !== BackslashR && this.buffer[end] !== BackslashN) { end++; diff --git a/extensions/typescript/tsconfig.json b/extensions/typescript/tsconfig.json index 85ebcc72519..17918ca332a 100644 --- a/extensions/typescript/tsconfig.json +++ b/extensions/typescript/tsconfig.json @@ -6,7 +6,8 @@ "es5", "es2015.promise" ], - "outDir": "./out" + "outDir": "./out", + "strictNullChecks": true }, "exclude": [ "node_modules", -- GitLab