From a74c79e300fbd71fe4793b320e2c475e251a84bc Mon Sep 17 00:00:00 2001 From: Tom van Ommeren Date: Mon, 4 Sep 2017 18:15:25 +0200 Subject: [PATCH 1/7] fix: Move lookup of completion details to completionItem/resolve --- src/test/typescript-service-helpers.ts | 155 ++++++++++++++----------- src/typescript-service.ts | 71 +++++++---- 2 files changed, 139 insertions(+), 87 deletions(-) diff --git a/src/test/typescript-service-helpers.ts b/src/test/typescript-service-helpers.ts index e896e0bec..1084d8f7a 100644 --- a/src/test/typescript-service-helpers.ts +++ b/src/test/typescript-service-helpers.ts @@ -5,7 +5,7 @@ import { applyReducer, Operation } from 'fast-json-patch'; import { IBeforeAndAfterContext, ISuiteCallbackContext, ITestCallbackContext } from 'mocha'; import * as sinon from 'sinon'; import * as ts from 'typescript'; -import { CompletionItemKind, CompletionList, DiagnosticSeverity, InsertTextFormat, TextDocumentIdentifier, TextDocumentItem, WorkspaceEdit } from 'vscode-languageserver'; +import { CompletionItemKind, CompletionList, DiagnosticSeverity, InsertTextFormat, TextDocumentIdentifier, TextDocumentItem, WorkspaceEdit, CompletionItem } from 'vscode-languageserver'; import { Command, Diagnostic, Hover, Location, SignatureHelp, SymbolInformation, SymbolKind } from 'vscode-languageserver-types'; import { LanguageClient, RemoteLanguageClient } from '../lang-handler'; import { DependencyReference, PackageInformation, ReferenceInformation, TextDocumentContentParams, WorkspaceFilesParams } from '../request-type'; @@ -2169,44 +2169,21 @@ export function describeTypeScriptService(createService: TypeScriptServiceFactor // * the end of the snippet. Placeholders with equal identifiers are linked, // * that is typing in one will update others too. assert.equal(result.isIncomplete, false); - assert.sameDeepMembers(result.items, [ - { - label: 'bar', - kind: CompletionItemKind.Method, - documentation: 'bar doc', - sortText: '0', - insertTextFormat: InsertTextFormat.Snippet, - insertText: 'bar(${1:num})', - detail: '(method) A.bar(num: number): number' - }, - { - label: 'baz', - kind: CompletionItemKind.Method, - documentation: 'baz doc', - sortText: '0', - insertTextFormat: InsertTextFormat.Snippet, - insertText: 'baz(${1:num})', - detail: '(method) A.baz(num: number): string' - }, - { - label: 'foo', - kind: CompletionItemKind.Method, - documentation: 'foo doc', - sortText: '0', - insertTextFormat: InsertTextFormat.Snippet, - insertText: 'foo()', - detail: '(method) A.foo(): void' - }, - { - label: 'qux', - kind: CompletionItemKind.Property, - documentation: 'qux doc', - sortText: '0', - insertTextFormat: InsertTextFormat.Snippet, - insertText: 'qux', - detail: '(property) A.qux: number' - } - ]); + + const barItem = result.items.find(i => i.label === "bar") as CompletionItem; + const resolved: CompletionItem = await this.service.completionItemResolve(barItem) + .reduce(applyReducer, null as any).toPromise(); + + assert.deepEqual(resolved, { + label: 'bar', + kind: CompletionItemKind.Method, + documentation: 'bar doc', + insertText: 'bar(${1:num})', + insertTextFormat: InsertTextFormat.Snippet, + sortText: '0', + detail: '(method) A.bar(num: number): number' + }); + }); }); @@ -2259,42 +2236,76 @@ export function describeTypeScriptService(createService: TypeScriptServiceFactor assert.equal(result.isIncomplete, false); assert.sameDeepMembers(result.items, [ { + data: { + entryName: 'bar', + offset: 188, + uri: rootUri + 'a.ts' + }, label: 'bar', kind: CompletionItemKind.Method, - documentation: 'bar doc', - insertText: 'bar', - insertTextFormat: InsertTextFormat.PlainText, - sortText: '0', - detail: '(method) A.bar(): number' + // documentation: 'bar doc', + // insertText: 'bar', + // insertTextFormat: InsertTextFormat.PlainText, + sortText: '0' + // detail: '(method) A.bar(): number' }, { + data: { + entryName: 'baz', + offset: 188, + uri: rootUri + 'a.ts' + }, label: 'baz', kind: CompletionItemKind.Method, - documentation: 'baz doc', - insertText: 'baz', - insertTextFormat: InsertTextFormat.PlainText, - sortText: '0', - detail: '(method) A.baz(): string' + // documentation: 'baz doc', + // insertText: 'baz', + // insertTextFormat: InsertTextFormat.PlainText, + sortText: '0' + // detail: '(method) A.baz(): string' }, { + data: { + entryName: 'foo', + offset: 188, + uri: rootUri + 'a.ts' + }, label: 'foo', kind: CompletionItemKind.Method, - documentation: 'foo doc', - insertText: 'foo', - insertTextFormat: InsertTextFormat.PlainText, - sortText: '0', - detail: '(method) A.foo(): void' + // documentation: 'foo doc', + // insertText: 'foo', + // insertTextFormat: InsertTextFormat.PlainText, + sortText: '0' + // detail: '(method) A.foo(): void' }, { + data: { + entryName: 'qux', + offset: 188, + uri: rootUri + 'a.ts' + }, label: 'qux', kind: CompletionItemKind.Property, - documentation: 'qux doc', - insertText: 'qux', - insertTextFormat: InsertTextFormat.PlainText, - sortText: '0', - detail: '(property) A.qux: number' + // documentation: 'qux doc', + // insertText: 'qux', + // insertTextFormat: InsertTextFormat.PlainText, + sortText: '0' + // detail: '(property) A.qux: number' } ]); + + const barItem = result.items.find(i => i.label === "bar") as CompletionItem; + const resolved: CompletionItem = await this.service.completionItemResolve(barItem) + .reduce(applyReducer, null as any).toPromise(); + + assert.deepEqual(resolved, { + label: 'bar', + kind: CompletionItemKind.Method, + documentation: 'bar doc', + insertText: 'bar', + insertTextFormat: InsertTextFormat.PlainText, + sortText: '0', + detail: '(method) A.bar(): number' + }); }); it('produces completions for imported symbols', async function (this: TestContext & ITestCallbackContext) { @@ -2310,12 +2321,17 @@ export function describeTypeScriptService(createService: TypeScriptServiceFactor assert.deepEqual(result, { isIncomplete: false, items: [{ + data: { + entryName: 'd', + offset: 32, + uri: rootUri + "uses-import.ts" + }, label: 'd', kind: CompletionItemKind.Function, - documentation: 'd doc', - insertText: 'd', - insertTextFormat: InsertTextFormat.PlainText, - detail: 'function d(): void', + // documentation: 'd doc', + // insertText: 'd', + // insertTextFormat: InsertTextFormat.PlainText, + // detail: 'function d(): void', sortText: '0' }] }); @@ -2333,13 +2349,18 @@ export function describeTypeScriptService(createService: TypeScriptServiceFactor assert.deepEqual(result, { isIncomplete: false, items: [{ + data: { + entryName: 'bar', + offset: 51, + uri: rootUri + "uses-reference.ts" + }, label: 'bar', kind: CompletionItemKind.Interface, - documentation: 'bar doc', - insertText: 'bar', - insertTextFormat: InsertTextFormat.PlainText, + // documentation: 'bar doc', + // insertText: 'bar', + // insertTextFormat: InsertTextFormat.PlainText, sortText: '0', - detail: 'interface foo.bar' + // detail: 'interface foo.bar' }] }); }); diff --git a/src/typescript-service.ts b/src/typescript-service.ts index 349987338..5b69ed2f9 100644 --- a/src/typescript-service.ts +++ b/src/typescript-service.ts @@ -1022,26 +1022,14 @@ export class TypeScriptService { if (entry.sortText) { item.sortText = entry.sortText; } - const details = configuration.getService().getCompletionEntryDetails(fileName, offset, entry.name); - if (details) { - item.documentation = ts.displayPartsToString(details.documentation); - item.detail = ts.displayPartsToString(details.displayParts); - if (this.supportsCompletionWithSnippets) { - item.insertTextFormat = InsertTextFormat.Snippet; - if (entry.kind === 'property') { - item.insertText = details.name; - } else { - const parameters = details.displayParts - .filter(p => p.kind === 'parameterName') - .map((p, i) => '${' + `${i + 1}:${p.text}` + '}'); - const paramString = parameters.join(', '); - item.insertText = details.name + `(${paramString})`; - } - } else { - item.insertTextFormat = InsertTextFormat.PlainText; - item.insertText = details.name; - } - } + + // context for future resolve requests: + item.data = { + uri: uri, + offset: offset, + entryName: entry.name + }; + return { op: 'add', path: '/items/-', value: item } as Operation; }) .startWith({ op: 'add', path: '/isIncomplete', value: false } as Operation); @@ -1049,6 +1037,49 @@ export class TypeScriptService { .startWith({ op: 'add', path: '', value: { isIncomplete: true, items: [] } as CompletionList } as Operation); } + /** + * The completionItem/resolve request is used to fill in additional details from an incomplete + * CompletionItem returned from the textDocument/completions call. + * + * @return Observable of JSON Patches that build a `CompletionItem` result + */ + completionItemResolve(item: CompletionItem, span = new Span()): Observable { + const {uri, offset, entryName} = item.data; + const fileName: string = uri2path(uri); + return this.projectManager.ensureReferencedFiles(uri, undefined, undefined, span) + .toArray() + .map(() => { + + const configuration = this.projectManager.getConfiguration(fileName); + configuration.ensureBasicFiles(span); + + const details = configuration.getService().getCompletionEntryDetails(fileName, offset, entryName); + if (details) { + item.documentation = ts.displayPartsToString(details.documentation); + item.detail = ts.displayPartsToString(details.displayParts); + if (this.supportsCompletionWithSnippets) { + item.insertTextFormat = InsertTextFormat.Snippet; + if (details.kind === 'method' || details.kind === 'function') { + const parameters = details.displayParts + .filter(p => p.kind === 'parameterName') + .map((p, i) => '${' + `${i + 1}:${p.text}` + '}'); + const paramString = parameters.join(', '); + item.insertText = details.name + `(${paramString})`; + } else { + item.insertText = details.name; + + } + } else { + item.insertTextFormat = InsertTextFormat.PlainText; + item.insertText = details.name; + } + delete item.data; + } + return item; + }) + .map(completionItem => ({ op: 'add', path: '', value: completionItem }) as Operation); + } + /** * The signature help request is sent from the client to the server to request signature * information at a given cursor position. From 47b8bb0245196105100c9469ec40e45715225c6f Mon Sep 17 00:00:00 2001 From: Tom van Ommeren Date: Mon, 4 Sep 2017 20:32:36 +0200 Subject: [PATCH 2/7] fix: lint errors --- src/test/typescript-service-helpers.ts | 22 ++++++++++++---------- src/typescript-service.ts | 4 ++-- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/src/test/typescript-service-helpers.ts b/src/test/typescript-service-helpers.ts index 1084d8f7a..21f721aab 100644 --- a/src/test/typescript-service-helpers.ts +++ b/src/test/typescript-service-helpers.ts @@ -5,7 +5,7 @@ import { applyReducer, Operation } from 'fast-json-patch'; import { IBeforeAndAfterContext, ISuiteCallbackContext, ITestCallbackContext } from 'mocha'; import * as sinon from 'sinon'; import * as ts from 'typescript'; -import { CompletionItemKind, CompletionList, DiagnosticSeverity, InsertTextFormat, TextDocumentIdentifier, TextDocumentItem, WorkspaceEdit, CompletionItem } from 'vscode-languageserver'; +import { CompletionItem, CompletionItemKind, CompletionList, DiagnosticSeverity, InsertTextFormat, TextDocumentIdentifier, TextDocumentItem, WorkspaceEdit } from 'vscode-languageserver'; import { Command, Diagnostic, Hover, Location, SignatureHelp, SymbolInformation, SymbolKind } from 'vscode-languageserver-types'; import { LanguageClient, RemoteLanguageClient } from '../lang-handler'; import { DependencyReference, PackageInformation, ReferenceInformation, TextDocumentContentParams, WorkspaceFilesParams } from '../request-type'; @@ -2170,9 +2170,10 @@ export function describeTypeScriptService(createService: TypeScriptServiceFactor // * that is typing in one will update others too. assert.equal(result.isIncomplete, false); - const barItem = result.items.find(i => i.label === "bar") as CompletionItem; - const resolved: CompletionItem = await this.service.completionItemResolve(barItem) - .reduce(applyReducer, null as any).toPromise(); + const barItem = result.items.find(i => i.label === 'bar') as CompletionItem; + const resolved: CompletionItem = await this.service + .completionItemResolve(barItem) + .reduce(applyReducer, null as any).toPromise(); assert.deepEqual(resolved, { label: 'bar', @@ -2293,9 +2294,10 @@ export function describeTypeScriptService(createService: TypeScriptServiceFactor } ]); - const barItem = result.items.find(i => i.label === "bar") as CompletionItem; - const resolved: CompletionItem = await this.service.completionItemResolve(barItem) - .reduce(applyReducer, null as any).toPromise(); + const barItem = result.items.find(i => i.label === 'bar') as CompletionItem; + const resolved: CompletionItem = await this.service + .completionItemResolve(barItem) + .reduce(applyReducer, null as any).toPromise(); assert.deepEqual(resolved, { label: 'bar', @@ -2324,7 +2326,7 @@ export function describeTypeScriptService(createService: TypeScriptServiceFactor data: { entryName: 'd', offset: 32, - uri: rootUri + "uses-import.ts" + uri: rootUri + 'uses-import.ts' }, label: 'd', kind: CompletionItemKind.Function, @@ -2352,14 +2354,14 @@ export function describeTypeScriptService(createService: TypeScriptServiceFactor data: { entryName: 'bar', offset: 51, - uri: rootUri + "uses-reference.ts" + uri: rootUri + 'uses-reference.ts' }, label: 'bar', kind: CompletionItemKind.Interface, // documentation: 'bar doc', // insertText: 'bar', // insertTextFormat: InsertTextFormat.PlainText, - sortText: '0', + sortText: '0' // detail: 'interface foo.bar' }] }); diff --git a/src/typescript-service.ts b/src/typescript-service.ts index 5b69ed2f9..94890acde 100644 --- a/src/typescript-service.ts +++ b/src/typescript-service.ts @@ -1025,8 +1025,8 @@ export class TypeScriptService { // context for future resolve requests: item.data = { - uri: uri, - offset: offset, + uri, + offset, entryName: entry.name }; From afa33f9be174000f369b554696274be256d2d57b Mon Sep 17 00:00:00 2001 From: Tom van Ommeren Date: Mon, 4 Sep 2017 22:47:08 +0200 Subject: [PATCH 3/7] fix: Restrict CompletionItem.data, restore full test cases for resolved completions --- src/request-type.ts | 27 +++++ src/test/typescript-service-helpers.ts | 144 +++++++++++++++++++------ src/typescript-service.ts | 7 +- 3 files changed, 145 insertions(+), 33 deletions(-) diff --git a/src/request-type.ts b/src/request-type.ts index e1b8fa31f..7cbd3c3e0 100644 --- a/src/request-type.ts +++ b/src/request-type.ts @@ -234,3 +234,30 @@ export interface PartialResultParams { */ patch: Operation[]; } + +/** + * Restriction on vscode's CompletionItem interface + */ +export interface CompletionItem extends vscode.CompletionItem { + data?: CompletionItemData; +} + +/** + * The necessary fields for a completion item details to be resolved by typescript + */ +export interface CompletionItemData { + /** + * The document from which the completion was requested + */ + uri: string; + + /** + * The offset into the document at which the completion was requested + */ + offset: number; + + /** + * The name field from typescript's returned completion entry + */ + entryName: string; +} diff --git a/src/test/typescript-service-helpers.ts b/src/test/typescript-service-helpers.ts index 21f721aab..79b5e9a8d 100644 --- a/src/test/typescript-service-helpers.ts +++ b/src/test/typescript-service-helpers.ts @@ -5,11 +5,11 @@ import { applyReducer, Operation } from 'fast-json-patch'; import { IBeforeAndAfterContext, ISuiteCallbackContext, ITestCallbackContext } from 'mocha'; import * as sinon from 'sinon'; import * as ts from 'typescript'; -import { CompletionItem, CompletionItemKind, CompletionList, DiagnosticSeverity, InsertTextFormat, TextDocumentIdentifier, TextDocumentItem, WorkspaceEdit } from 'vscode-languageserver'; +import { CompletionItemKind, CompletionList, DiagnosticSeverity, InsertTextFormat, TextDocumentIdentifier, TextDocumentItem, WorkspaceEdit } from 'vscode-languageserver'; import { Command, Diagnostic, Hover, Location, SignatureHelp, SymbolInformation, SymbolKind } from 'vscode-languageserver-types'; import { LanguageClient, RemoteLanguageClient } from '../lang-handler'; import { DependencyReference, PackageInformation, ReferenceInformation, TextDocumentContentParams, WorkspaceFilesParams } from '../request-type'; -import { ClientCapabilities, SymbolLocationInformation } from '../request-type'; +import { ClientCapabilities, CompletionItem, SymbolLocationInformation } from '../request-type'; import { TypeScriptService, TypeScriptServiceFactory } from '../typescript-service'; import { observableFromIterable, toUnixPath, uri2path } from '../util'; @@ -2170,20 +2170,54 @@ export function describeTypeScriptService(createService: TypeScriptServiceFactor // * that is typing in one will update others too. assert.equal(result.isIncomplete, false); - const barItem = result.items.find(i => i.label === 'bar') as CompletionItem; - const resolved: CompletionItem = await this.service - .completionItemResolve(barItem) - .reduce(applyReducer, null as any).toPromise(); - - assert.deepEqual(resolved, { - label: 'bar', - kind: CompletionItemKind.Method, - documentation: 'bar doc', - insertText: 'bar(${1:num})', - insertTextFormat: InsertTextFormat.Snippet, - sortText: '0', - detail: '(method) A.bar(num: number): number' - }); + const resolveItem = (item: CompletionItem) => this.service + .completionItemResolve(item) + .reduce(applyReducer, null as any).toPromise(); + + const resolvedItems = await Promise.all(result.items.map(resolveItem)); + + assert.sameDeepMembers(resolvedItems, [ + { + label: 'bar', + kind: CompletionItemKind.Method, + documentation: 'bar doc', + sortText: '0', + insertTextFormat: InsertTextFormat.Snippet, + insertText: 'bar(${1:num})', + detail: '(method) A.bar(num: number): number', + data: undefined + }, + { + label: 'baz', + kind: CompletionItemKind.Method, + documentation: 'baz doc', + sortText: '0', + insertTextFormat: InsertTextFormat.Snippet, + insertText: 'baz(${1:num})', + detail: '(method) A.baz(num: number): string', + data: undefined + }, + { + label: 'foo', + kind: CompletionItemKind.Method, + documentation: 'foo doc', + sortText: '0', + insertTextFormat: InsertTextFormat.Snippet, + insertText: 'foo()', + detail: '(method) A.foo(): void', + data: undefined + }, + { + label: 'qux', + kind: CompletionItemKind.Property, + documentation: 'qux doc', + sortText: '0', + insertTextFormat: InsertTextFormat.Snippet, + insertText: 'qux', + detail: '(property) A.qux: number', + data: undefined + } + ]); }); }); @@ -2235,6 +2269,69 @@ export function describeTypeScriptService(createService: TypeScriptServiceFactor } }).reduce(applyReducer, null as any).toPromise(); assert.equal(result.isIncomplete, false); + + const resolveItem = (item: CompletionItem) => this.service + .completionItemResolve(item) + .reduce(applyReducer, null as any).toPromise(); + + const resolvedItems = await Promise.all(result.items.map(resolveItem)); + + assert.sameDeepMembers(resolvedItems, [ + { + label: 'bar', + kind: CompletionItemKind.Method, + documentation: 'bar doc', + insertText: 'bar', + insertTextFormat: InsertTextFormat.PlainText, + sortText: '0', + detail: '(method) A.bar(): number', + data: undefined + }, + { + label: 'baz', + kind: CompletionItemKind.Method, + documentation: 'baz doc', + insertText: 'baz', + insertTextFormat: InsertTextFormat.PlainText, + sortText: '0', + detail: '(method) A.baz(): string', + data: undefined + }, + { + label: 'foo', + kind: CompletionItemKind.Method, + documentation: 'foo doc', + insertText: 'foo', + insertTextFormat: InsertTextFormat.PlainText, + sortText: '0', + detail: '(method) A.foo(): void', + data: undefined + }, + { + label: 'qux', + kind: CompletionItemKind.Property, + documentation: 'qux doc', + insertText: 'qux', + insertTextFormat: InsertTextFormat.PlainText, + sortText: '0', + detail: '(property) A.qux: number', + data: undefined + } + ]); + + }); + + it('resolves completions in the same file', async function (this: TestContext & ITestCallbackContext) { + const result: CompletionList = await this.service.textDocumentCompletion({ + textDocument: { + uri: rootUri + 'a.ts' + }, + position: { + line: 11, + character: 2 + } + }).reduce(applyReducer, null as any).toPromise(); + assert.equal(result.isIncomplete, false); assert.sameDeepMembers(result.items, [ { data: { @@ -2293,21 +2390,6 @@ export function describeTypeScriptService(createService: TypeScriptServiceFactor // detail: '(property) A.qux: number' } ]); - - const barItem = result.items.find(i => i.label === 'bar') as CompletionItem; - const resolved: CompletionItem = await this.service - .completionItemResolve(barItem) - .reduce(applyReducer, null as any).toPromise(); - - assert.deepEqual(resolved, { - label: 'bar', - kind: CompletionItemKind.Method, - documentation: 'bar doc', - insertText: 'bar', - insertTextFormat: InsertTextFormat.PlainText, - sortText: '0', - detail: '(method) A.bar(): number' - }); }); it('produces completions for imported symbols', async function (this: TestContext & ITestCallbackContext) { diff --git a/src/typescript-service.ts b/src/typescript-service.ts index 94890acde..7f1d9cf3d 100644 --- a/src/typescript-service.ts +++ b/src/typescript-service.ts @@ -10,7 +10,6 @@ import * as url from 'url'; import { CodeActionParams, Command, - CompletionItem, CompletionItemKind, CompletionList, DidChangeConfigurationParams, @@ -44,6 +43,7 @@ import { InMemoryFileSystem, isTypeScriptLibrary } from './memfs'; import { extractDefinitelyTypedPackageName, extractNodeModulesPackageName, PackageJson, PackageManager } from './packages'; import { ProjectConfiguration, ProjectManager } from './project-manager'; import { + CompletionItem, DependencyReference, InitializeParams, InitializeResult, @@ -1044,6 +1044,9 @@ export class TypeScriptService { * @return Observable of JSON Patches that build a `CompletionItem` result */ completionItemResolve(item: CompletionItem, span = new Span()): Observable { + if (item.data == null) { + throw new Error('Cannot resolve completion item without data'); + } const {uri, offset, entryName} = item.data; const fileName: string = uri2path(uri); return this.projectManager.ensureReferencedFiles(uri, undefined, undefined, span) @@ -1073,7 +1076,7 @@ export class TypeScriptService { item.insertTextFormat = InsertTextFormat.PlainText; item.insertText = details.name; } - delete item.data; + item.data = undefined; } return item; }) From ba9de9f77fc55b45fe5283f537610a5c10955966 Mon Sep 17 00:00:00 2001 From: Tom van Ommeren Date: Tue, 5 Sep 2017 07:41:03 +0200 Subject: [PATCH 4/7] fix: Restore unresolved completion test with snippets --- src/test/typescript-service-helpers.ts | 187 ++++++++++++++++--------- 1 file changed, 123 insertions(+), 64 deletions(-) diff --git a/src/test/typescript-service-helpers.ts b/src/test/typescript-service-helpers.ts index 79b5e9a8d..9f78bad6f 100644 --- a/src/test/typescript-service-helpers.ts +++ b/src/test/typescript-service-helpers.ts @@ -2154,7 +2154,66 @@ export function describeTypeScriptService(createService: TypeScriptServiceFactor afterEach(shutdownService); - it('should produce completions with snippets if supported', async function (this: TestContext & ITestCallbackContext) { + it('should produce completions', async function (this: TestContext & ITestCallbackContext) { + const result: CompletionList = await this.service.textDocumentCompletion({ + textDocument: { + uri: rootUri + 'a.ts' + }, + position: { + line: 11, + character: 2 + } + }).reduce(applyReducer, null as any).toPromise(); + // * A snippet can define tab stops and placeholders with `$1`, `$2` + // * and `${3:foo}`. `$0` defines the final tab stop, it defaults to + // * the end of the snippet. Placeholders with equal identifiers are linked, + // * that is typing in one will update others too. + assert.equal(result.isIncomplete, false); + assert.sameDeepMembers(result.items, [ + { + label: 'bar', + kind: CompletionItemKind.Method, + sortText: '0', + data: { + entryName: 'bar', + offset: 210, + uri: rootUri + 'a.ts' + } + }, + { + label: 'baz', + kind: CompletionItemKind.Method, + sortText: '0', + data: { + entryName: 'baz', + offset: 210, + uri: rootUri + 'a.ts' + } + }, + { + label: 'foo', + kind: CompletionItemKind.Method, + sortText: '0', + data: { + entryName: 'foo', + offset: 210, + uri: rootUri + 'a.ts' + } + }, + { + label: 'qux', + kind: CompletionItemKind.Property, + sortText: '0', + data: { + entryName: 'qux', + offset: 210, + uri: rootUri + 'a.ts' + } + } + ]); + }); + + it('should resolve completions with snippets', async function (this: TestContext & ITestCallbackContext) { const result: CompletionList = await this.service.textDocumentCompletion({ textDocument: { uri: rootUri + 'a.ts' @@ -2269,69 +2328,6 @@ export function describeTypeScriptService(createService: TypeScriptServiceFactor } }).reduce(applyReducer, null as any).toPromise(); assert.equal(result.isIncomplete, false); - - const resolveItem = (item: CompletionItem) => this.service - .completionItemResolve(item) - .reduce(applyReducer, null as any).toPromise(); - - const resolvedItems = await Promise.all(result.items.map(resolveItem)); - - assert.sameDeepMembers(resolvedItems, [ - { - label: 'bar', - kind: CompletionItemKind.Method, - documentation: 'bar doc', - insertText: 'bar', - insertTextFormat: InsertTextFormat.PlainText, - sortText: '0', - detail: '(method) A.bar(): number', - data: undefined - }, - { - label: 'baz', - kind: CompletionItemKind.Method, - documentation: 'baz doc', - insertText: 'baz', - insertTextFormat: InsertTextFormat.PlainText, - sortText: '0', - detail: '(method) A.baz(): string', - data: undefined - }, - { - label: 'foo', - kind: CompletionItemKind.Method, - documentation: 'foo doc', - insertText: 'foo', - insertTextFormat: InsertTextFormat.PlainText, - sortText: '0', - detail: '(method) A.foo(): void', - data: undefined - }, - { - label: 'qux', - kind: CompletionItemKind.Property, - documentation: 'qux doc', - insertText: 'qux', - insertTextFormat: InsertTextFormat.PlainText, - sortText: '0', - detail: '(property) A.qux: number', - data: undefined - } - ]); - - }); - - it('resolves completions in the same file', async function (this: TestContext & ITestCallbackContext) { - const result: CompletionList = await this.service.textDocumentCompletion({ - textDocument: { - uri: rootUri + 'a.ts' - }, - position: { - line: 11, - character: 2 - } - }).reduce(applyReducer, null as any).toPromise(); - assert.equal(result.isIncomplete, false); assert.sameDeepMembers(result.items, [ { data: { @@ -2392,6 +2388,69 @@ export function describeTypeScriptService(createService: TypeScriptServiceFactor ]); }); + it('resolves completions in the same file', async function (this: TestContext & ITestCallbackContext) { + const result: CompletionList = await this.service.textDocumentCompletion({ + textDocument: { + uri: rootUri + 'a.ts' + }, + position: { + line: 11, + character: 2 + } + }).reduce(applyReducer, null as any).toPromise(); + assert.equal(result.isIncomplete, false); + + const resolveItem = (item: CompletionItem) => this.service + .completionItemResolve(item) + .reduce(applyReducer, null as any).toPromise(); + + const resolvedItems = await Promise.all(result.items.map(resolveItem)); + + assert.sameDeepMembers(resolvedItems, [ + { + label: 'bar', + kind: CompletionItemKind.Method, + documentation: 'bar doc', + insertText: 'bar', + insertTextFormat: InsertTextFormat.PlainText, + sortText: '0', + detail: '(method) A.bar(): number', + data: undefined + }, + { + label: 'baz', + kind: CompletionItemKind.Method, + documentation: 'baz doc', + insertText: 'baz', + insertTextFormat: InsertTextFormat.PlainText, + sortText: '0', + detail: '(method) A.baz(): string', + data: undefined + }, + { + label: 'foo', + kind: CompletionItemKind.Method, + documentation: 'foo doc', + insertText: 'foo', + insertTextFormat: InsertTextFormat.PlainText, + sortText: '0', + detail: '(method) A.foo(): void', + data: undefined + }, + { + label: 'qux', + kind: CompletionItemKind.Property, + documentation: 'qux doc', + insertText: 'qux', + insertTextFormat: InsertTextFormat.PlainText, + sortText: '0', + detail: '(property) A.qux: number', + data: undefined + } + ]); + + }); + it('produces completions for imported symbols', async function (this: TestContext & ITestCallbackContext) { const result: CompletionList = await this.service.textDocumentCompletion({ textDocument: { From 90a6515a67b467171c9457426f15d15277460a12 Mon Sep 17 00:00:00 2001 From: Tom van Ommeren Date: Wed, 6 Sep 2017 23:54:43 +0200 Subject: [PATCH 5/7] fix: Clean commented out code + unneeded documentation --- src/test/typescript-service-helpers.ts | 28 -------------------------- 1 file changed, 28 deletions(-) diff --git a/src/test/typescript-service-helpers.ts b/src/test/typescript-service-helpers.ts index 9f78bad6f..04e46f8f1 100644 --- a/src/test/typescript-service-helpers.ts +++ b/src/test/typescript-service-helpers.ts @@ -2164,10 +2164,6 @@ export function describeTypeScriptService(createService: TypeScriptServiceFactor character: 2 } }).reduce(applyReducer, null as any).toPromise(); - // * A snippet can define tab stops and placeholders with `$1`, `$2` - // * and `${3:foo}`. `$0` defines the final tab stop, it defaults to - // * the end of the snippet. Placeholders with equal identifiers are linked, - // * that is typing in one will update others too. assert.equal(result.isIncomplete, false); assert.sameDeepMembers(result.items, [ { @@ -2337,11 +2333,7 @@ export function describeTypeScriptService(createService: TypeScriptServiceFactor }, label: 'bar', kind: CompletionItemKind.Method, - // documentation: 'bar doc', - // insertText: 'bar', - // insertTextFormat: InsertTextFormat.PlainText, sortText: '0' - // detail: '(method) A.bar(): number' }, { data: { @@ -2351,11 +2343,7 @@ export function describeTypeScriptService(createService: TypeScriptServiceFactor }, label: 'baz', kind: CompletionItemKind.Method, - // documentation: 'baz doc', - // insertText: 'baz', - // insertTextFormat: InsertTextFormat.PlainText, sortText: '0' - // detail: '(method) A.baz(): string' }, { data: { @@ -2365,11 +2353,7 @@ export function describeTypeScriptService(createService: TypeScriptServiceFactor }, label: 'foo', kind: CompletionItemKind.Method, - // documentation: 'foo doc', - // insertText: 'foo', - // insertTextFormat: InsertTextFormat.PlainText, sortText: '0' - // detail: '(method) A.foo(): void' }, { data: { @@ -2379,11 +2363,7 @@ export function describeTypeScriptService(createService: TypeScriptServiceFactor }, label: 'qux', kind: CompletionItemKind.Property, - // documentation: 'qux doc', - // insertText: 'qux', - // insertTextFormat: InsertTextFormat.PlainText, sortText: '0' - // detail: '(property) A.qux: number' } ]); }); @@ -2471,10 +2451,6 @@ export function describeTypeScriptService(createService: TypeScriptServiceFactor }, label: 'd', kind: CompletionItemKind.Function, - // documentation: 'd doc', - // insertText: 'd', - // insertTextFormat: InsertTextFormat.PlainText, - // detail: 'function d(): void', sortText: '0' }] }); @@ -2499,11 +2475,7 @@ export function describeTypeScriptService(createService: TypeScriptServiceFactor }, label: 'bar', kind: CompletionItemKind.Interface, - // documentation: 'bar doc', - // insertText: 'bar', - // insertTextFormat: InsertTextFormat.PlainText, sortText: '0' - // detail: 'interface foo.bar' }] }); }); From 5bfe83600899049818221593d7ffcbf411106524 Mon Sep 17 00:00:00 2001 From: Felix Becker Date: Fri, 8 Sep 2017 03:02:46 -0700 Subject: [PATCH 6/7] refactor: use rx --- src/test/typescript-service-helpers.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/test/typescript-service-helpers.ts b/src/test/typescript-service-helpers.ts index 04e46f8f1..494a1ce88 100644 --- a/src/test/typescript-service-helpers.ts +++ b/src/test/typescript-service-helpers.ts @@ -2225,11 +2225,13 @@ export function describeTypeScriptService(createService: TypeScriptServiceFactor // * that is typing in one will update others too. assert.equal(result.isIncomplete, false); - const resolveItem = (item: CompletionItem) => this.service + const resolvedItems = await Observable.from(result.items) + .mergeMap(item => this.service .completionItemResolve(item) - .reduce(applyReducer, null as any).toPromise(); - - const resolvedItems = await Promise.all(result.items.map(resolveItem)); + .reduce(applyReducer, null as any) + ) + .toArray() + .toPromise(); assert.sameDeepMembers(resolvedItems, [ { From 9b2f9eae9fe0f1269cd19c5a18c2ded84a51b287 Mon Sep 17 00:00:00 2001 From: Felix Becker Date: Fri, 8 Sep 2017 03:04:35 -0700 Subject: [PATCH 7/7] style: don't compare with null --- src/typescript-service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/typescript-service.ts b/src/typescript-service.ts index 7f1d9cf3d..dcee67343 100644 --- a/src/typescript-service.ts +++ b/src/typescript-service.ts @@ -1044,7 +1044,7 @@ export class TypeScriptService { * @return Observable of JSON Patches that build a `CompletionItem` result */ completionItemResolve(item: CompletionItem, span = new Span()): Observable { - if (item.data == null) { + if (!item.data) { throw new Error('Cannot resolve completion item without data'); } const {uri, offset, entryName} = item.data;