javascript-typescript-langserver
Version:
Implementation of the Language Server Protocol for JavaScript and TypeScript
3,365 lines • 145 kB
JavaScript
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
const chai = require("chai");
const chaiAsPromised = require("chai-as-promised");
const fast_json_patch_1 = require("fast-json-patch");
const rxjs_1 = require("rxjs");
const sinon = require("sinon");
const ts = require("typescript");
const vscode_languageserver_1 = require("vscode-languageserver");
const vscode_languageserver_types_1 = require("vscode-languageserver-types");
const lang_handler_1 = require("../lang-handler");
const util_1 = require("../util");
chai.use(chaiAsPromised);
const assert = chai.assert;
const DEFAULT_CAPABILITIES = {
xcontentProvider: true,
xfilesProvider: true,
};
/**
* Returns a function that initializes the test context with a TypeScriptService instance and initializes it (to be used in `beforeEach`)
*
* @param createService A factory that creates the TypeScript service. Allows to test subclasses of TypeScriptService
* @param files A Map from URI to file content of files that should be available in the workspace
*/
exports.initializeTypeScriptService = (createService, rootUri, files, clientCapabilities = DEFAULT_CAPABILITIES) => function () {
return __awaiter(this, void 0, void 0, function* () {
// Stub client
this.client = sinon.createStubInstance(lang_handler_1.RemoteLanguageClient);
this.client.textDocumentXcontent.callsFake((params) => {
if (!files.has(params.textDocument.uri)) {
return rxjs_1.Observable.throw(new Error(`Text document ${params.textDocument.uri} does not exist`));
}
return rxjs_1.Observable.of({
uri: params.textDocument.uri,
text: files.get(params.textDocument.uri),
version: 1,
languageId: '',
});
});
this.client.workspaceXfiles.callsFake((params) => util_1.observableFromIterable(files.keys())
.map(uri => ({ uri }))
.toArray());
this.client.xcacheGet.callsFake(() => rxjs_1.Observable.of(null));
this.client.workspaceApplyEdit.callsFake(() => rxjs_1.Observable.of({ applied: true }));
this.service = createService(this.client);
yield this.service
.initialize({
processId: process.pid,
rootUri,
capabilities: clientCapabilities || DEFAULT_CAPABILITIES,
workspaceFolders: [
{
uri: rootUri,
name: 'test',
},
],
})
.toPromise();
});
};
/**
* Shuts the TypeScriptService down (to be used in `afterEach()`)
*/
function shutdownTypeScriptService() {
return __awaiter(this, void 0, void 0, function* () {
yield this.service.shutdown().toPromise();
});
}
exports.shutdownTypeScriptService = shutdownTypeScriptService;
/**
* Describe a TypeScriptService class
*
* @param createService Factory function to create the TypeScriptService instance to describe
*/
function describeTypeScriptService(createService, shutdownService = shutdownTypeScriptService, rootUri) {
describe('Workspace without project files', () => {
beforeEach(exports.initializeTypeScriptService(createService, rootUri, new Map([
[rootUri + 'a.ts', 'const abc = 1; console.log(abc);'],
[rootUri + 'foo/b.ts', ['/* This is class Foo */', 'export class Foo {}'].join('\n')],
[rootUri + 'foo/c.ts', 'import {Foo} from "./b";'],
[rootUri + 'd.ts', ['export interface I {', ' target: string;', '}'].join('\n')],
[
rootUri + 'local_callback.ts',
'function local(): void { function act(handle: () => void): void { handle() } }',
],
[
rootUri + 'e.ts',
[
'import * as d from "./d";',
'',
'let i: d.I = { target: "hi" };',
'let target = i.target;',
].join('\n'),
],
[rootUri + 'foo/f.ts', ['import {Foo} from "./b";', '', 'let foo: Foo = Object({});'].join('\n')],
[rootUri + 'foo/g.ts', ['class Foo = {}', '', 'let foo: Foo = Object({});'].join('\n')],
])));
afterEach(shutdownService);
describe('textDocumentDefinition()', () => {
specify('in same file', function () {
return __awaiter(this, void 0, void 0, function* () {
const result = yield this.service
.textDocumentDefinition({
textDocument: {
uri: rootUri + 'a.ts',
},
position: {
line: 0,
character: 29,
},
})
.reduce(fast_json_patch_1.applyReducer, null)
.toPromise();
assert.deepEqual(result, [
{
uri: rootUri + 'a.ts',
range: {
start: {
line: 0,
character: 6,
},
end: {
line: 0,
character: 9,
},
},
},
]);
});
});
specify('on keyword (non-null)', function () {
return __awaiter(this, void 0, void 0, function* () {
const result = yield this.service
.textDocumentDefinition({
textDocument: {
uri: rootUri + 'a.ts',
},
position: {
line: 0,
character: 0,
},
})
.reduce(fast_json_patch_1.applyReducer, null)
.toPromise();
assert.deepEqual(result, []);
});
});
specify('in other file', function () {
return __awaiter(this, void 0, void 0, function* () {
const result = yield this.service
.textDocumentDefinition({
textDocument: {
uri: rootUri + 'foo/c.ts',
},
position: {
line: 0,
character: 9,
},
})
.reduce(fast_json_patch_1.applyReducer, null)
.toPromise();
assert.deepEqual(result, [
{
uri: rootUri + 'foo/b.ts',
range: {
start: {
line: 1,
character: 13,
},
end: {
line: 1,
character: 16,
},
},
},
]);
});
});
});
describe('textDocumentTypeDefinition()', () => {
specify('in other file', function () {
return __awaiter(this, void 0, void 0, function* () {
const result = yield this.service
.textDocumentTypeDefinition({
textDocument: {
uri: rootUri + 'foo/f.ts',
},
position: {
line: 2,
character: 5,
},
})
.reduce(fast_json_patch_1.applyReducer, null)
.toPromise();
assert.deepEqual(result, [
{
uri: rootUri + 'foo/b.ts',
range: {
start: {
line: 1,
character: 13,
},
end: {
line: 1,
character: 16,
},
},
},
]);
});
});
specify('in same file', function () {
return __awaiter(this, void 0, void 0, function* () {
const result = yield this.service
.textDocumentTypeDefinition({
textDocument: {
uri: rootUri + 'foo/g.ts',
},
position: {
line: 2,
character: 5,
},
})
.reduce(fast_json_patch_1.applyReducer, null)
.toPromise();
assert.deepEqual(result, [
{
uri: rootUri + 'foo/g.ts',
range: {
start: {
line: 0,
character: 6,
},
end: {
line: 0,
character: 9,
},
},
},
]);
});
});
});
describe('textDocumentXdefinition()', () => {
specify('on interface field reference', function () {
return __awaiter(this, void 0, void 0, function* () {
const result = yield this.service
.textDocumentXdefinition({
textDocument: {
uri: rootUri + 'e.ts',
},
position: {
line: 3,
character: 15,
},
})
.reduce(fast_json_patch_1.applyReducer, null)
.toPromise();
assert.deepEqual(result, [
{
location: {
uri: rootUri + 'd.ts',
range: {
start: {
line: 1,
character: 2,
},
end: {
line: 1,
character: 8,
},
},
},
symbol: {
filePath: 'd.ts',
containerName: 'd.I',
containerKind: '',
kind: 'property',
name: 'target',
},
},
]);
});
});
specify('in same file', function () {
return __awaiter(this, void 0, void 0, function* () {
const result = yield this.service
.textDocumentXdefinition({
textDocument: {
uri: rootUri + 'a.ts',
},
position: {
line: 0,
character: 29,
},
})
.reduce(fast_json_patch_1.applyReducer, null)
.toPromise();
assert.deepEqual(result, [
{
location: {
uri: rootUri + 'a.ts',
range: {
start: {
line: 0,
character: 6,
},
end: {
line: 0,
character: 9,
},
},
},
symbol: {
filePath: 'a.ts',
containerName: '"a"',
containerKind: 'module',
kind: 'const',
name: 'abc',
},
},
]);
});
});
});
describe('textDocumentHover()', () => {
specify('in same file', function () {
return __awaiter(this, void 0, void 0, function* () {
const result = yield this.service
.textDocumentHover({
textDocument: {
uri: rootUri + 'a.ts',
},
position: {
line: 0,
character: 29,
},
})
.reduce(fast_json_patch_1.applyReducer, null)
.toPromise();
assert.deepEqual(result, {
range: {
start: {
line: 0,
character: 27,
},
end: {
line: 0,
character: 30,
},
},
contents: [{ language: 'typescript', value: 'const abc: 1' }, '**const**'],
});
});
});
specify('local function with callback argument', function () {
return __awaiter(this, void 0, void 0, function* () {
const result = yield this.service
.textDocumentHover({
textDocument: {
uri: rootUri + 'local_callback.ts',
},
position: {
line: 0,
character: 36,
},
})
.reduce(fast_json_patch_1.applyReducer, null)
.toPromise();
assert.deepEqual(result, {
range: {
start: {
line: 0,
character: 34,
},
end: {
line: 0,
character: 37,
},
},
contents: [
{ language: 'typescript', value: 'act(handle: () => void): void' },
'**local function**',
],
});
});
});
specify('in other file', function () {
return __awaiter(this, void 0, void 0, function* () {
const result = yield this.service
.textDocumentHover({
textDocument: {
uri: rootUri + 'foo/c.ts',
},
position: {
line: 0,
character: 9,
},
})
.reduce(fast_json_patch_1.applyReducer, null)
.toPromise();
assert.deepEqual(result, {
range: {
end: {
line: 0,
character: 11,
},
start: {
line: 0,
character: 8,
},
},
contents: [{ language: 'typescript', value: 'class Foo\nimport Foo' }, '**alias**'],
});
});
});
specify('over keyword (non-null)', function () {
return __awaiter(this, void 0, void 0, function* () {
const result = yield this.service
.textDocumentHover({
textDocument: {
uri: rootUri + 'a.ts',
},
position: {
line: 0,
character: 0,
},
})
.reduce(fast_json_patch_1.applyReducer, null)
.toPromise();
assert.deepEqual(result, { contents: [] });
});
});
specify('over non-existent file', function () {
return __awaiter(this, void 0, void 0, function* () {
yield Promise.resolve(assert.isRejected(this.service
.textDocumentHover({
textDocument: {
uri: rootUri + 'foo/a.ts',
},
position: {
line: 0,
character: 0,
},
})
.toPromise()));
});
});
});
});
describe('Workspace with typings directory', () => {
beforeEach(exports.initializeTypeScriptService(createService, rootUri, new Map([
[rootUri + 'src/a.ts', "import * as m from 'dep';"],
[rootUri + 'typings/dep.d.ts', "declare module 'dep' {}"],
[
rootUri + 'src/tsconfig.json',
[
'{',
' "compilerOptions": {',
' "target": "ES5",',
' "module": "commonjs",',
' "sourceMap": true,',
' "noImplicitAny": false,',
' "removeComments": false,',
' "preserveConstEnums": true',
' }',
'}',
].join('\n'),
],
[rootUri + 'src/tsd.d.ts', '/// <reference path="../typings/dep.d.ts" />'],
[rootUri + 'src/dir/index.ts', 'import * as m from "dep";'],
])));
afterEach(shutdownService);
describe('textDocumentDefinition()', () => {
specify('with tsd.d.ts', function () {
return __awaiter(this, void 0, void 0, function* () {
const result = yield this.service
.textDocumentDefinition({
textDocument: {
uri: rootUri + 'src/dir/index.ts',
},
position: {
line: 0,
character: 20,
},
})
.reduce(fast_json_patch_1.applyReducer, null)
.toPromise();
assert.deepEqual(result, [
{
uri: rootUri + 'typings/dep.d.ts',
range: {
start: {
line: 0,
character: 15,
},
end: {
line: 0,
character: 20,
},
},
},
]);
});
});
describe('on file in project root', () => {
specify('on import alias', function () {
return __awaiter(this, void 0, void 0, function* () {
const result = yield this.service
.textDocumentDefinition({
textDocument: {
uri: rootUri + 'src/a.ts',
},
position: {
line: 0,
character: 12,
},
})
.reduce(fast_json_patch_1.applyReducer, null)
.toPromise();
assert.deepEqual(result, [
{
uri: rootUri + 'typings/dep.d.ts',
range: {
start: {
line: 0,
character: 15,
},
end: {
line: 0,
character: 20,
},
},
},
]);
});
});
specify('on module name', function () {
return __awaiter(this, void 0, void 0, function* () {
const result = yield this.service
.textDocumentDefinition({
textDocument: {
uri: rootUri + 'src/a.ts',
},
position: {
line: 0,
character: 20,
},
})
.reduce(fast_json_patch_1.applyReducer, null)
.toPromise();
assert.deepEqual(result, [
{
uri: rootUri + 'typings/dep.d.ts',
range: {
start: {
line: 0,
character: 15,
},
end: {
line: 0,
character: 20,
},
},
},
]);
});
});
});
});
});
describe('DefinitelyTyped', () => {
beforeEach(exports.initializeTypeScriptService(createService, rootUri, new Map([
[
rootUri + 'package.json',
JSON.stringify({
private: true,
name: 'definitely-typed',
version: '0.0.1',
homepage: 'https://github.com/DefinitelyTyped/DefinitelyTyped',
repository: {
type: 'git',
url: 'git+https://github.com/DefinitelyTyped/DefinitelyTyped.git',
},
license: 'MIT',
bugs: {
url: 'https://github.com/DefinitelyTyped/DefinitelyTyped/issues',
},
engines: {
node: '>= 6.9.1',
},
scripts: {
'compile-scripts': 'tsc -p scripts',
'new-package': 'node scripts/new-package.js',
'not-needed': 'node scripts/not-needed.js',
lint: 'node scripts/lint.js',
test: 'node node_modules/types-publisher/bin/tester/test.js --run-from-definitely-typed --nProcesses 1',
},
devDependencies: {
'types-publisher': 'Microsoft/types-publisher#production',
},
}, null, 4),
],
[
rootUri + 'types/resolve/index.d.ts',
[
'/// <reference types="node" />',
'',
'type resolveCallback = (err: Error, resolved?: string) => void;',
'declare function resolve(id: string, cb: resolveCallback): void;',
'',
].join('\n'),
],
[
rootUri + 'types/resolve/tsconfig.json',
JSON.stringify({
compilerOptions: {
module: 'commonjs',
lib: ['es6'],
noImplicitAny: true,
noImplicitThis: true,
strictNullChecks: false,
baseUrl: '../',
typeRoots: ['../'],
types: [],
noEmit: true,
forceConsistentCasingInFileNames: true,
},
files: ['index.d.ts'],
}),
],
[
rootUri + 'types/notResolve/index.d.ts',
[
'/// <reference types="node" />',
'',
'type resolveCallback = (err: Error, resolved?: string) => void;',
'declare function resolve(id: string, cb: resolveCallback): void;',
'',
].join('\n'),
],
[
rootUri + 'types/notResolve/tsconfig.json',
JSON.stringify({
compilerOptions: {
module: 'commonjs',
lib: ['es6'],
noImplicitAny: true,
noImplicitThis: true,
strictNullChecks: false,
baseUrl: '../',
typeRoots: ['../'],
types: [],
noEmit: true,
forceConsistentCasingInFileNames: true,
},
files: ['index.d.ts'],
}),
],
])));
afterEach(shutdownService);
describe('workspaceSymbol()', () => {
it('should find a symbol by SymbolDescriptor query with name and package name', function () {
return __awaiter(this, void 0, void 0, function* () {
const result = yield this.service
.workspaceSymbol({
symbol: { name: 'resolveCallback', package: { name: '@types/resolve' } },
})
.reduce(fast_json_patch_1.applyReducer, null)
.toPromise();
assert.deepEqual(result, [
{
kind: vscode_languageserver_types_1.SymbolKind.Variable,
location: {
range: {
end: {
character: 63,
line: 2,
},
start: {
character: 0,
line: 2,
},
},
uri: rootUri + 'types/resolve/index.d.ts',
},
name: 'resolveCallback',
},
]);
});
});
it('should find a symbol by SymbolDescriptor query with name, containerKind and package name', function () {
return __awaiter(this, void 0, void 0, function* () {
const result = yield this.service
.workspaceSymbol({
symbol: {
name: 'resolveCallback',
containerKind: 'module',
package: {
name: '@types/resolve',
},
},
})
.reduce(fast_json_patch_1.applyReducer, null)
.toPromise();
assert.deepEqual(result[0], {
kind: vscode_languageserver_types_1.SymbolKind.Variable,
location: {
range: {
end: {
character: 63,
line: 2,
},
start: {
character: 0,
line: 2,
},
},
uri: rootUri + 'types/resolve/index.d.ts',
},
name: 'resolveCallback',
});
});
});
});
});
describe('Workspace with root package.json', () => {
beforeEach(exports.initializeTypeScriptService(createService, rootUri, new Map([
[rootUri + 'a.ts', 'class a { foo() { const i = 1;} }'],
[
rootUri + 'foo/b.ts',
'class b { bar: number; baz(): number { return this.bar;}}; function qux() {}',
],
[rootUri + 'c.ts', 'import { x } from "dep/dep";'],
[rootUri + 'package.json', JSON.stringify({ name: 'mypkg' })],
[rootUri + 'node_modules/dep/dep.ts', 'export var x = 1;'],
[rootUri + 'node_modules/dep/package.json', JSON.stringify({ name: 'dep' })],
])));
afterEach(shutdownService);
describe('workspaceSymbol()', () => {
describe('with SymbolDescriptor query', () => {
it('should find a symbol by name, kind and package name', function () {
return __awaiter(this, void 0, void 0, function* () {
const result = yield this.service
.workspaceSymbol({
symbol: {
name: 'a',
kind: 'class',
package: {
name: 'mypkg',
},
},
})
.reduce(fast_json_patch_1.applyReducer, null)
.toPromise();
assert.deepEqual(result[0], {
kind: vscode_languageserver_types_1.SymbolKind.Class,
location: {
range: {
end: {
character: 33,
line: 0,
},
start: {
character: 0,
line: 0,
},
},
uri: rootUri + 'a.ts',
},
name: 'a',
});
});
});
it('should find a symbol by name, kind, package name and ignore package version', function () {
return __awaiter(this, void 0, void 0, function* () {
const result = yield this.service
.workspaceSymbol({
symbol: { name: 'a', kind: 'class', package: { name: 'mypkg', version: '203940234' } },
})
.reduce(fast_json_patch_1.applyReducer, null)
.toPromise();
assert.deepEqual(result[0], {
kind: vscode_languageserver_types_1.SymbolKind.Class,
location: {
range: {
end: {
character: 33,
line: 0,
},
start: {
character: 0,
line: 0,
},
},
uri: rootUri + 'a.ts',
},
name: 'a',
});
});
});
it('should find a symbol by name', function () {
return __awaiter(this, void 0, void 0, function* () {
const result = yield this.service
.workspaceSymbol({
symbol: {
name: 'a',
},
})
.reduce(fast_json_patch_1.applyReducer, null)
.toPromise();
assert.deepEqual(result, [
{
kind: vscode_languageserver_types_1.SymbolKind.Class,
location: {
range: {
end: {
character: 33,
line: 0,
},
start: {
character: 0,
line: 0,
},
},
uri: rootUri + 'a.ts',
},
name: 'a',
},
]);
});
});
it('should return no result if the PackageDescriptor does not match', function () {
return __awaiter(this, void 0, void 0, function* () {
const result = yield this.service
.workspaceSymbol({
symbol: {
name: 'a',
kind: 'class',
package: {
name: 'not-mypkg',
},
},
})
.reduce(fast_json_patch_1.applyReducer, null)
.toPromise();
assert.deepEqual(result, []);
});
});
});
describe('with text query', () => {
it('should find a symbol', function () {
return __awaiter(this, void 0, void 0, function* () {
const result = yield this.service
.workspaceSymbol({ query: 'a' })
.reduce(fast_json_patch_1.applyReducer, null)
.toPromise();
assert.deepEqual(result, [
{
kind: vscode_languageserver_types_1.SymbolKind.Class,
location: {
range: {
end: {
character: 33,
line: 0,
},
start: {
character: 0,
line: 0,
},
},
uri: rootUri + 'a.ts',
},
name: 'a',
},
]);
});
});
it('should return all symbols for an empty query excluding dependencies', function () {
return __awaiter(this, void 0, void 0, function* () {
const result = yield this.service
.workspaceSymbol({ query: '' })
.reduce(fast_json_patch_1.applyReducer, null)
.toPromise();
assert.deepEqual(result, [
{
name: 'a',
kind: vscode_languageserver_types_1.SymbolKind.Class,
location: {
uri: rootUri + 'a.ts',
range: {
start: {
line: 0,
character: 0,
},
end: {
line: 0,
character: 33,
},
},
},
},
{
name: 'foo',
kind: vscode_languageserver_types_1.SymbolKind.Method,
location: {
uri: rootUri + 'a.ts',
range: {
start: {
line: 0,
character: 10,
},
end: {
line: 0,
character: 31,
},
},
},
containerName: 'a',
},
{
name: 'i',
kind: vscode_languageserver_types_1.SymbolKind.Constant,
location: {
uri: rootUri + 'a.ts',
range: {
start: {
line: 0,
character: 24,
},
end: {
line: 0,
character: 29,
},
},
},
containerName: 'foo',
},
{
name: '"c"',
kind: vscode_languageserver_types_1.SymbolKind.Module,
location: {
uri: rootUri + 'c.ts',
range: {
start: {
line: 0,
character: 0,
},
end: {
line: 0,
character: 28,
},
},
},
},
{
name: 'x',
containerName: '"c"',
kind: vscode_languageserver_types_1.SymbolKind.Variable,
location: {
uri: rootUri + 'c.ts',
range: {
start: {
line: 0,
character: 9,
},
end: {
line: 0,
character: 10,
},
},
},
},
{
name: 'b',
kind: vscode_languageserver_types_1.SymbolKind.Class,
location: {
uri: rootUri + 'foo/b.ts',
range: {
start: {
line: 0,
character: 0,
},
end: {
line: 0,
character: 57,
},
},
},
},
{
name: 'bar',
kind: vscode_languageserver_types_1.SymbolKind.Property,
location: {
uri: rootUri + 'foo/b.ts',
range: {
start: {
line: 0,
character: 10,
},
end: {
line: 0,
character: 22,
},
},
},
containerName: 'b',
},
{
name: 'baz',
kind: vscode_languageserver_types_1.SymbolKind.Method,
location: {
uri: rootUri + 'foo/b.ts',
range: {
start: {
line: 0,
character: 23,
},
end: {
line: 0,
character: 56,
},
},
},
containerName: 'b',
},
{
name: 'qux',
kind: vscode_languageserver_types_1.SymbolKind.Function,
location: {
uri: rootUri + 'foo/b.ts',
range: {
start: {
line: 0,
character: 59,
},
end: {
line: 0,
character: 76,
},
},
},
},
]);
});
});
});
});
describe('workspaceXreferences()', () => {
it('should return all references to a method', function () {
return __awaiter(this, void 0, void 0, function* () {
const result = yield this.service
.workspaceXreferences({ query: { name: 'foo', kind: 'method', containerName: 'a' } })
.reduce(fast_json_patch_1.applyReducer, null)
.toPromise();
assert.deepEqual(result, [
{
symbol: {
filePath: 'a.ts',
containerKind: '',
containerName: 'a',
name: 'foo',
kind: 'method',
},
reference: {
range: {
end: {
character: 13,
line: 0,
},
start: {
character: 9,
line: 0,
},
},
uri: rootUri + 'a.ts',
},
},
]);
});
});
it('should return all references to a method with hinted dependee package name', function () {
return __awaiter(this, void 0, void 0, function* () {
const result = yield this.service
.workspaceXreferences({
query: {
name: 'foo',
kind: 'method',
containerName: 'a',
},
hints: {
dependeePackageName: 'mypkg',
},
})
.reduce(fast_json_patch_1.applyReducer, null)
.toPromise();
assert.deepEqual(result, [
{
symbol: {
filePath: 'a.ts',
containerKind: '',
containerName: 'a',
name: 'foo',
kind: 'method',
},
reference: {
range: {
end: {
character: 13,
line: 0,
},
start: {
character: 9,
line: 0,
},
},
uri: rootUri + 'a.ts',
},
},
]);
});
});
it('should return no references to a method if hinted dependee package name was not found', function () {
return __awaiter(this, void 0, void 0, function* () {
const result = yield this.service
.workspaceXreferences({
query: {
name: 'foo',
kind: 'method',
containerName: 'a',
},
hints: {
dependeePackageName: 'NOT-mypkg',
},
})
.reduce(fast_json_patch_1.applyReducer, null)
.toPromise();
assert.deepEqual(result, []);
});
});
it('should return all references to a symbol from a dependency', function () {
return __awaiter(this, void 0, void 0, function* () {
const result = yield this.service
.workspaceXreferences({ query: { name: 'x' } })
.reduce(fast_json_patch_1.applyReducer, null)
.toPromise();
assert.deepEqual(result, [
{
reference: {
range: {
end: {
character: 10,
line: 0,
},
start: {
character: 8,
line: 0,
},
},
uri: rootUri + 'c.ts',
},
symbol: {
filePath: 'dep/dep.ts',
containerKind: '',
containerName: '"dep/dep"',
kind: 'var',
name: 'x',
},
},
]);
});
});
it('should return all references to a symbol from a dependency with PackageDescriptor query', function () {
return __awaiter(this, void 0, void 0, function* () {
const result = yield this.service
.workspaceXreferences({ query: { name: 'x', package: { name: 'dep' } } })
.reduce(fast_json_patch_1.applyReducer, null)
.toPromise();
assert.deepEqual(result, [
{
reference: {
range: {
end: {
character: 10,
line: 0,
},
start: {
character: 8,
line: 0,
},
},
uri: rootUri + 'c.ts',
},
symbol: {
filePath: 'dep/dep.ts',
containerKind: '',
containerName: '"dep/dep"',
kind: 'var',
name: 'x',
package: {
name: 'dep',
repoURL: undefined,
version: undefined,
},
},
},
]);
});
});
it('should return all references to all symbols if empty SymbolDescriptor query is passed', function () {
return __awaiter(this, void 0, void 0, function* () {
const result = yield this.service
.workspaceXreferences({ query: {} })
.reduce(fast_json_patch_1.applyReducer, null)
.toPromise();
assert.deepEqual(result, [
{
symbol: {
filePath: 'a.ts',
containerName: '"a"',
containerKind: 'module',
kind: 'class',
name: 'a',
},
reference: {
range: {
end: {
character: 7,
line: 0,
},
start: {
character: 5,
line: 0,
},
},
uri: rootUri + 'a.ts',
},
},
{
symbol: {
filePath: 'a.ts',
containerName: 'a',
containerKind: '',
name: 'foo',
kind: 'method',
},
reference: {
range: {
end: {
character: 13,
line: 0,
},
start: {
character: 9,
line: 0,
},
},
uri: rootUri + 'a.ts',
},
},
{
symbol: {
filePath: 'a.ts',
containerName: '"a"',
containerKind: 'module',
name: 'i',
kind: 'const',
},
reference: {
range: {
end: {
character: 25,
line: 0,
},
start: {
character: 23,
line: 0,
},
},
uri: rootUri + 'a.ts',
},
},
{
reference: {
range: {
end: {
character: 10,
line: 0,
},
start: {
character: 8,
line: 0,
},
},
uri: rootUri + 'c.ts',
},
symbol: {
filePath: 'dep/dep.ts',
containerKind: '',
containerName: '"dep/dep"',
kind: 'var',
name: 'x',
},
},
{
symbol: {
filePath: 'foo/b.ts',
containerName: '"foo/b"',
containerKind: 'module',
name: 'b',
kind: 'class',
},
reference: {
range: {
end: {
character: 7,
line: 0,
},
start: {
character: 5,
line: 0,
},
},
uri: rootUri + 'foo/b.ts',
},
},
{
symbol: {
filePath: 'foo/b.ts',
containerName: 'b',
containerKind: '',
name: 'bar',
kind: 'property',
},
reference: {
range: {
end: {
character: 13,
line: 0,
},
start: {
character: 9,
line: 0,
},
},
uri: rootUri + 'foo/b.ts',
},
},
{
symbol: {
filePath: 'foo/b.ts',
containerName: 'b',
containerKind: '',
name: 'baz',
kind: 'method',
},
reference: {
range: {
end: {
character: 26,
line: 0,
},
start: {
character: 22,
line: 0,
},
},
uri: rootUri + 'foo/b.ts',
},
},
{
symbol: {
filePath: 'foo/b.ts',
containerName: 'b',
containerKind: '',
name: 'bar',
kind: 'property',
},
reference: {
range: {
end: {
character: 54,
line: 0,
},
start: {
character: 51,
line: 0,
},
},
uri: rootUri + 'foo/b.ts',
},
},
{
symbol: {
filePath: 'foo/b.ts',
containerName: '"foo/b"',
containerKind: 'module',
name: 'qux',
kind: 'function',
},
reference: {
range: {
end: {
character: 71,
line: 0,
},
start: {
character: 67,
line: 0,
},
},
uri: rootUri + 'foo/b.ts',
},
},
]);
});
});
});
});
describe('Dependency detection', () => {
beforeEach(exports.initializeTypeScriptService(createService, rootUri, new Map([
[
rootUri + 'package.json',
JSON.stringify({
name: 'tslint',
version: '4.0.2',
dependencies: {
'babel-code-frame': '^6.16.0',
'findup-sync': '~0.3.0',
},
devDependencies: {
'@types/babel-code-frame': '^6.16.0',
'@types/optimist': '0.0.29',
chai: '^3.0.0',
tslint: 'latest',
'tslint-test-config-non-relative': 'file:test/external/tslint-test-config-non-relative',
typescript: '2.0.10',
},
peerDependencies: {
typescript: '>=2.0.0',
},
}),
],
[
rootUri + 'node_modules/dep/package.json',
JSON.stringify({
name: 'foo',
dependencies: {
shouldnotinclude: '0.0.0',
},
}),
],
[
rootUri + 'subproject/package.json',
JSON.stringify({
name: 'subproject',
repository: {
url: 'https://github.com/my/subproject',
},
dependencies: {
'subproject-dep': '0.0.0',
},
}),
],
])));
afterEach(shutdownService);
describe('workspaceXdependencies()', () => {
it('should account for all dependencies', function () {
return __awaiter(this, void 0, void 0, function* () {
const result = yield this.service
.workspaceXdependencies()
.reduce(fast_json_patch_1.applyReducer, null)
.toPromise();
assert.deepEqual(result, [
{
attributes: {
name: 'babel-code-frame',
version: '^6.16.0',
},
hints: {
dependeePackageName: 'tslint',
},
},
{
attributes: {
name: 'findup-sync',
version: '~0.3.0',
},
hints: {
dependeePackageName: 'tslint',
},
},
{
attributes: {
name: '@types/babel-code-frame',
version: '^6.16.0',
},
hints: {
dependeePackageName: 'tslint',
},
},
{
attributes: {
name: '@types/optimist',
version: '0.0.29',
},
hints: {
dependeePackageName: 'tslint',
},
},
{
attributes: {
name: 'chai',
version: '^3.0.0',
},
hints: {
dependeePackageName: 'tslint',
},
},
{
attributes: {
name: 'tslint',
version: 'latest',
},
hints: {
dependeePackageName: 'tslint',
},
},
{
attributes: {
name: 'tslint-test-config-non-relative',
version: 'file:test/external/tslint-test-config-non-relative',
},
hints: {
dependeePackageName: 'tslint',
},
},
{
attributes: {
name: 'typescript',
version: '2.0.10',
},
hints: {
dependeePackageName: 'tslint',
},
},
{
attributes: {
name: 'typescript',
version: '>=2.0.0',
},
hints: {
dependeePackageName: 'tslint',
},
},
{
attributes: {
name: 'subproject-dep',
version: '0.0.0',
},
hints: {
dependeePackageName: 'subproject',
},
},
]);
});
});
});
describe('workspaceXpackages()', () => {
it('should accournt for all packages', function () {
return __awaiter(this, void 0, void 0, function* () {
const result = yield this.service
.workspaceXpackages()
.reduce(fast_json_patch_1.applyReducer, null)
.toPromise();
assert.deepEqual(result, [
{
package: {
name: 'tslint',
version: '4.0.2',
repoURL: undefined,
},
dependencies: [
{
attributes: {
name: 'babel-code-frame',
version: '^6.16.0',
},
hints: {
dependeePackageName: 'tslint',
},
},
{
attributes: {
name: 'findup-sync',
version: '~0.3.0',
},
hints: {
dependeePackageName: 'tslint',
},
},
{
attributes: {
name: '@types/babel-code-frame',
version: '^6.16.0',
},
hints: {
dependeePackageName: 'tslint',
},
},
{
attributes: {
name: '@types/optimist',
version: '0.0.29',
},
hints: {
dependeePackageName: 'tslint',
},
},
{
attributes: {
name: 'chai',
version: '^3.0.0',
},
hints: {
dependeePackageName: 'tslint',
},
},
{
attributes: {
name: 'tslint',
version: 'latest',
},
hints: {
dependeePackageName: 'tslint',
},
},
{
attributes: {
name: 'tslint-test-config-non-relative',
version: 'file:test/external/tslint-test-config-non-relative',
},
hints: {
dependeePackageName: 'tslint',
},
},
{
attributes: {
name: 'typescript',
version: '2.0.10',
},
hints: {
dependeePackageName: 'tslint',
},
},
{
attributes: {
name: 'typescript',
version: '>=2.0.0',
},
hints: {
dependeePackageName: 'tslint',
},
},
],
},
{
package: {
name: 'subproject',
version: undefined,
repoURL: 'https://github.com/my/subproject',
},
dependencies: [
{
attributes: { name: 'subproject-dep', version: '0.0.0' },
hints: { dependeePackageName: 'subproject' },
},
],
},
]);
});
});
});
});
describe('TypeScript library', () => {
beforeEach(exports.initializeTypeScriptService(createService, rootUri, new Map([[rootUri + 'a.ts', 'let parameters = [];']])));
afterEach(shutdownService);
specify('type of parameters should be any[]', function () {
return __awaiter(this, void 0, void 0, function* () {
const result = yield this.service
.textDocumentHover({
textDocument: {
uri: rootUri + 'a.ts',
},
position: {
line: 0,
character: 5,
},
})
.reduce(fast_json_patch_1.applyReducer, null)
.toPromise();
assert.deepEqual(result, {
range: {
end: {
character: 14,
line: 0,
},
start: {
character: 4,
line: 0,
},
},
contents: [{ language: 'typescript', value: 'let parameters: any[]' }, '**let**'],
});
});
});
});
describe('Live updates', () => {
beforeEach(exports.initializeTypeScriptService(createService, rootUri, new Map([[rootUri + 'a.ts', 'let parameters = [];']])));
afterEach(shutdownService);
it('should handle didChange when configuration is not yet initialized', function () {
return __awaiter(this, void 0, void 0, function* () {
const hoverParams = {
textDocument: {
uri: rootUri + 'a.ts',
},
position: {
line: 0,
character: 5,
},
};
const range = {
end: {
character: 14,
line: 0,
},
start: {
character: 4,
line: 0,
},
};
yield this.service.textDocumentDidChange({
textDocument: {
uri: rootUri + 'a.ts',
version: 1,
},
contentChanges: [
{
text: 'let parameters: number[]',
},
],
});
const result = yield this.service
.textDocumentHover(hoverParams)
.reduce(fast_json_patch_1.applyReducer, null)
.toPromise();
assert.deepEqual(result, {
range,
contents: [{ language: 'typescript', value: 'let parameters: number[]' }, '**let**'],
});
});
});
it('should handle didClose when configuration is not yet initialized', function () {
return __awaiter(this, void 0, void 0, function* () {
const hoverParams = {
textDocument: {
uri: rootUri + 'a.ts',
},
position: {
line: 0,
character: 5,
},
};
const range = {
end: {
character: 14,
line: 0,
},
start: {
character: 4,
line: 0,
},
};
yield this.service.textDocumentDidClose({
textDocument: {
uri: rootUri + 'a.ts',
},
});
const result = yield this.service
.textDocumentHover(hoverParams)
.reduce(fast_json_patch_1.applyReducer, null)
.toPromise();
assert.deepEqual(result, {
range,
contents: [{ language: 'typescript', value: 'let parameters: any[]' }, '**let**'],
});
});
});
it('should reflect updated content', function () {
return __awaiter(this, void 0, void 0, function* () {
const hoverParams = {
textDocument: {
uri: rootUri + 'a.ts',
},
position: {
line: 0,
character: 5,
},
};
const range = {
end: {
character: 14,
line: 0,
},
start: {
character: 4,
line: 0,
},
};
{
const result = yield this.service
.textDocumentHover(hoverParams)
.reduce(fast_json_patch_1.applyReducer, null)
.toPromise();
assert.deepEqual(result, {
range,
contents: [{ language: 'typescript', value: 'let parameters: any[]' }, '**let**'],
});
}
yield this.service.textDocumentDidOpen({
textDocument: {
uri: rootUri + 'a.ts',
languageId: 'typescript',
version: 1,
text: 'let parameters: string[]',
},
});
{
const result = yield this.service
.textDocumentHover(hoverParams)
.reduce(fast_json_patch_1.applyReducer, null)
.toPromise();
assert.deepEqual(result, {
range,
contents: [{ language: 'typescript', value: 'let parameters: string[]' }, '**let**'],
});
}
yield this.service.textDocumentDidChange({
textDocument: {
uri: rootUri + 'a.ts',
version: 2,
},
contentChanges: [
{
text: 'let parameters: number[]',
},
],
});
{
const result = yield this.service
.textDocumentHover(hoverParams)
.reduce(fast_json_patch_1.applyReducer, null)
.toPromise();
assert.deepEqual(result, {
range,
contents: [{ language: 'typescript', value: 'let parameters: number[]' }, '**let**'],
});
}
yield this.service.textDocumentDidClose({
textDocument: {
uri: rootUri + 'a.ts',
},
});
{
const result = yield this.service
.textDocumentHover(hoverParams)
.reduce(fast_json_patch_1.applyReducer, null)
.toPromise();
assert.deepEqual(result, {
range,
contents: [{ language: 'typescript', value: 'let parameters: any[]' }, '**let**'],
});
}
});
});
});
describe('Diagnostics', () => {
beforeEach(exports.initializeTypeScriptService(createService, rootUri, new Map([[rootUri + 'src/errors.ts', 'const text: string = 33;']])));
afterEach(shutdownService);
it('should publish diagnostics on didOpen', function () {
return __awaiter(this, void 0, void 0, function* () {
yield this.service.textDocumentDidOpen({
textDocument: {
uri: rootUri + 'src/errors.ts',
languageId: 'typescript',
text: 'const text: string = 33;',
version: 1,
},
});
sinon.assert.calledOnce(this.client.textDocumentPublishDiagnostics);
sinon.assert.calledWithExactly(this.client.textDocumentPublishDiagnostics, {
diagnostics: [
{
message: "Type '33' is not assignable to type 'string'.",
range: { end: { character: 10, line: 0 }, start: { character: 6, line: 0 } },
severity: 1,
source: 'ts',
code: 2322,
},
],
uri: rootUri + 'src/errors.ts',
});
});
});
it('should publish diagnostics on didChange', function () {
return __awaiter(this, void 0, void 0, function* () {
yield this.service.textDocumentDidOpen({
textDocument: {
uri: rootUri + 'src/errors.ts',
languageId: 'typescript',
text: 'const text: string = 33;',
version: 1,
},
});
this.client.textDocumentPublishDiagnostics.resetHistory();
yield this.service.textDocumentDidChange({
textDocument: {
uri: rootUri + 'src/errors.ts',
version: 2,
},
contentChanges: [{ text: 'const text: boolean = 33;' }],
});
sinon.assert.calledOnce(this.client.textDocumentPublishDiagnostics);
sinon.assert.calledWithExactly(this.client.textDocumentPublishDiagnostics, {
diagnostics: [
{
message: "Type '33' is not assignable to type 'boolean'.",
range: { end: { character: 10, line: 0 }, start: { character: 6, line: 0 } },
severity: 1,
source: 'ts',
code: 2322,
},
],
uri: rootUri + 'src/errors.ts',
});
});
});
it('should publish empty diagnostics on didChange if error was fixed', function () {
return __awaiter(this, void 0, void 0, function* () {
yield this.service.textDocumentDidOpen({
textDocument: {
uri: rootUri + 'src/errors.ts',
languageId: 'typescript',
text: 'const text: string = 33;',
version: 1,
},
});
this.client.textDocumentPublishDiagnostics.resetHistory();
yield this.service.textDocumentDidChange({
textDocument: {
uri: rootUri + 'src/errors.ts',
version: 2,
},
contentChanges: [{ text: 'const text: number = 33;' }],
});
sinon.assert.calledOnce(this.client.textDocumentPublishDiagnostics);
sinon.assert.calledWithExactly(this.client.textDocumentPublishDiagnostics, {
diagnostics: [],
uri: rootUri + 'src/errors.ts',
});
});
});
it('should clear diagnostics on didClose', function () {
return __awaiter(this, void 0, void 0, function* () {
yield this.service.textDocumentDidClose({
textDocument: {
uri: rootUri + 'src/errors.ts',
},
});
sinon.assert.calledOnce(this.client.textDocumentPublishDiagnostics);
sinon.assert.calledWithExactly(this.client.textDocumentPublishDiagnostics, {
diagnostics: [],
uri: rootUri + 'src/errors.ts',
});
});
});
});
describe('References and imports', () => {
beforeEach(exports.initializeTypeScriptService(createService, rootUri, new Map([
[rootUri + 'a.ts', '/// <reference path="b.ts"/>\nnamespace qux {let f : foo;}'],
[rootUri + 'b.ts', '/// <reference path="foo/c.ts"/>'],
[rootUri + 'c.ts', 'import * as d from "./foo/d"\nd.bar()'],
[rootUri + 'foo/c.ts', 'namespace qux {export interface foo {}}'],
[rootUri + 'foo/d.ts', 'export function bar() {}'],
[rootUri + 'deeprefs/a.ts', '/// <reference path="b.ts"/>\nnamespace qux {\nlet f : foo;\n}'],
[rootUri + 'deeprefs/b.ts', '/// <reference path="c.ts"/>'],
[rootUri + 'deeprefs/c.ts', '/// <reference path="d.ts"/>'],
[rootUri + 'deeprefs/d.ts', '/// <reference path="e.ts"/>'],
[rootUri + 'deeprefs/e.ts', 'namespace qux {\nexport interface foo {}\n}'],
[
rootUri + 'missing/a.ts',
[
'/// <reference path="b.ts"/>',
'/// <reference path="missing.ts"/>',
'namespace t {',
' function foo() : Bar {',
' return null;',
' }',
'}',
].join('\n'),
],
[
rootUri + 'missing/b.ts',
'namespace t {\n export interface Bar {\n id?: number;\n }}',
],
])));
afterEach(shutdownService);
describe('textDocumentDefinition()', () => {
it('should resolve symbol imported with tripe-slash reference', function () {
return __awaiter(this, void 0, void 0, function* () {
const result = yield this.service
.textDocumentDefinition({
textDocument: {
uri: rootUri + 'a.ts',
},
position: {
line: 1,
character: 23,
},
})
.reduce(fast_json_patch_1.applyReducer, null)
.toPromise();
assert.deepEqual(result, [
{
// Note: technically this list should also
// include the 2nd definition of `foo` in
// deeprefs/e.ts, but there's no easy way to
// discover it through file-level imports and
// it is rare enough that we accept this
// omission. (It would probably show up in the
// definition response if the user has already
// navigated to deeprefs/e.ts.)
uri: rootUri + 'foo/c.ts',
range: {
start: {
line: 0,
character: 32,
},
end: {
line: 0,
character: 35,
},
},
},
]);
});
});
it('should resolve symbol imported with import statement', function () {
return __awaiter(this, void 0, void 0, function* () {
const result = yield this.service
.textDocumentDefinition({
textDocument: {
uri: rootUri + 'c.ts',
},
position: {
line: 1,
character: 2,
},
})
.reduce(fast_json_patch_1.applyReducer, null)
.toPromise();
assert.deepEqual(result, [
{
uri: rootUri + 'foo/d.ts',
range: {
start: {
line: 0,
character: 16,
},
end: {
line: 0,
character: 19,
},
},
},
]);
});
});
it('should resolve definition with missing reference', function () {
return __awaiter(this, void 0, void 0, function* () {
const result = yield this.service
.textDocumentDefinition({
textDocument: {
uri: rootUri + 'missing/a.ts',
},
position: {
line: 3,
character: 21,
},
})
.reduce(fast_json_patch_1.applyReducer, null)
.toPromise();
assert.deepEqual(result, [
{
uri: rootUri + 'missing/b.ts',
range: {
start: {
line: 1,
character: 21,
},
end: {
line: 1,
character: 24,
},
},
},
]);
});
});
it('should resolve deep definitions', function () {
return __awaiter(this, void 0, void 0, function* () {
// This test passes only because we expect no response from LSP server
// for definition located in file references with depth 3 or more (a -> b -> c -> d (...))
// This test will fail once we'll increase (or remove) depth limit
const result = yield this.service
.textDocumentDefinition({
textDocument: {
uri: rootUri + 'deeprefs/a.ts',
},
position: {
line: 2,
character: 8,
},
})
.reduce(fast_json_patch_1.applyReducer, null)
.toPromise();
assert.deepEqual(result, [
{
uri: rootUri + 'deeprefs/e.ts',
range: {
start: {
line: 1,
character: 17,
},
end: {
line: 1,
character: 20,
},
},
},
]);
});
});
});
});
describe('TypeScript libraries', () => {
beforeEach(exports.initializeTypeScriptService(createService, rootUri, new Map([
[
rootUri + 'tsconfig.json',
JSON.stringify({
compilerOptions: {
lib: ['es2016', 'dom'],
},
}),
],
[rootUri + 'a.ts', 'function foo(n: Node): {console.log(n.parentNode, NaN})}'],
])));
afterEach(shutdownService);
describe('textDocumentHover()', () => {
it('should load local library file', function () {
return __awaiter(this, void 0, void 0, function* () {
const result = yield this.service
.textDocumentHover({
textDocument: {
uri: rootUri + 'a.ts',
},
position: {
line: 0,
character: 16,
},
})
.reduce(fast_json_patch_1.applyReducer, null)
.toPromise();
assert.deepEqual(result, {
range: {
end: {
character: 20,
line: 0,
},
start: {
character: 16,
line: 0,
},
},
contents: [
{
language: 'typescript',
value: [
'interface Node',
'var Node: {',
' new (): Node;',
' prototype: Node;',
' readonly ATTRIBUTE_NODE: number;',
' readonly CDATA_SECTION_NODE: number;',
' readonly COMMENT_NODE: number;',
' readonly DOCUMENT_FRAGMENT_NODE: number;',
' readonly DOCUMENT_NODE: number;',
' ... 12 more ...;',
' readonly TEXT_NODE: number;',
'}',
].join('\n'),
},
'**var** _(ambient)_',
],
});
});
});
});
describe('textDocumentDefinition()', () => {
it('should resolve TS libraries to github URL', function () {
return __awaiter(this, void 0, void 0, function* () {
assert.deepEqual(yield this.service
.textDocumentDefinition({
textDocument: {
uri: rootUri + 'a.ts',
},
position: {
line: 0,
character: 16,
},
})
.reduce(fast_json_patch_1.applyReducer, null)
.toPromise(), [
{
uri: 'git://github.com/Microsoft/TypeScript?v' + ts.version + '#lib/lib.dom.d.ts',
range: {
start: {
line: 10275,
character: 10,
},
end: {
line: 10275,
character: 14,
},
},
},
{
uri: 'git://github.com/Microsoft/TypeScript?v' + ts.version + '#lib/lib.dom.d.ts',
range: {
start: {
line: 10326,
character: 12,
},
end: {
line: 10326,
character: 16,
},
},
},
]);
assert.deepEqual(yield this.service
.textDocumentDefinition({
textDocument: {
uri: rootUri + 'a.ts',
},
position: {
line: 0,
character: 50,
},
})
.reduce(fast_json_patch_1.applyReducer, null)
.toPromise(), [
{
uri: 'git://github.com/Microsoft/TypeScript?v' + ts.version + '#lib/lib.es5.d.ts',
range: {
start: {
line: 24,
character: 14,
},
end: {
line: 24,
character: 17,
},
},
},
]);
});
});
});
});
describe('textDocumentReferences()', () => {
beforeEach(exports.initializeTypeScriptService(createService, rootUri, new Map([
[
rootUri + 'a.ts',
[
'class A {',
' /** foo doc*/',
' foo() {}',
' /** bar doc*/',
' bar(): number { return 1; }',
' /** ',
' * The Baz function',
' * @param num Number parameter',
' * @param text Text parameter',
' */',
' baz(num: number, text: string): string { return ""; }',
' /** qux doc*/',
' qux: number;',
'}',
'const a = new A();',
'a.baz(32, sd)',
].join('\n'),
],
[rootUri + 'uses-import.ts', ['import * as i from "./import"', 'i.d()'].join('\n')],
[rootUri + 'also-uses-import.ts', ['import {d} from "./import"', 'd()'].join('\n')],
[rootUri + 'import.ts', '/** d doc*/ export function d() {}'],
])));
afterEach(shutdownService);
it('should provide an empty response when no reference is found', function () {
return __awaiter(this, void 0, void 0, function* () {
const result = yield this.service
.textDocumentReferences({
textDocument: {
uri: rootUri + 'a.ts',
},
position: {
line: 0,
character: 0,
},
context: { includeDeclaration: false },
})
.reduce(fast_json_patch_1.applyReducer, null)
.toPromise();
assert.deepEqual(result, []);
});
});
it('should include the declaration if requested', function () {
return __awaiter(this, void 0, void 0, function* () {
const result = yield this.service
.textDocumentReferences({
textDocument: {
uri: rootUri + 'a.ts',
},
position: {
line: 4,
character: 5,
},
context: { includeDeclaration: true },
})
.reduce(fast_json_patch_1.applyReducer, null)
.toPromise();
assert.deepEqual(result, [
{
range: {
end: {
character: 7,
line: 4,
},
start: {
character: 4,
line: 4,
},
},
uri: rootUri + 'a.ts',
},
]);
});
});
it('should provide a reference within the same file', function () {
return __awaiter(this, void 0, void 0, function* () {
const result = yield this.service
.textDocumentReferences({
textDocument: {
uri: rootUri + 'a.ts',
},
position: {
line: 10,
character: 5,
},
context: { includeDeclaration: false },
})
.reduce(fast_json_patch_1.applyReducer, null)
.toPromise();
assert.deepEqual(result, [
{
range: {
end: {
character: 5,
line: 15,
},
start: {
character: 2,
line: 15,
},
},
uri: rootUri + 'a.ts',
},
]);
});
});
it('should provide two references from imports', function () {
return __awaiter(this, void 0, void 0, function* () {
const result = yield this.service
.textDocumentReferences({
textDocument: {
uri: rootUri + 'import.ts',
},
position: {
line: 0,
character: 28,
},
context: { includeDeclaration: false },
})
.reduce(fast_json_patch_1.applyReducer, null)
.toPromise();
assert.deepEqual(result, [
{
range: {
end: {
character: 3,
line: 1,
},
start: {
character: 2,
line: 1,
},
},
uri: rootUri + 'uses-import.ts',
},
{
range: {
end: {
character: 1,
line: 1,
},
start: {
character: 0,
line: 1,
},
},
uri: rootUri + 'also-uses-import.ts',
},
]);
});
});
});
describe('textDocumentSignatureHelp()', () => {
beforeEach(exports.initializeTypeScriptService(createService, rootUri, new Map([
[
rootUri + 'a.ts',
[
'class A {',
' /** foo doc*/',
' foo() {}',
' /** bar doc*/',
' bar(): number { return 1; }',
' /** ',
' * The Baz function',
' * @param num Number parameter',
' * @param text Text parameter',
' */',
' baz(num: number, text: string): string { return ""; }',
' /** qux doc*/',
' qux: number;',
'}',
'const a = new A();',
'a.baz(32, sd)',
].join('\n'),
],
[rootUri + 'uses-import.ts', ['import * as i from "./import"', 'i.d()'].join('\n')],
[rootUri + 'import.ts', '/** d doc*/ export function d() {}'],
[
rootUri + 'uses-reference.ts',
['/// <reference path="reference.ts" />', 'let z : foo.'].join('\n'),
],
[
rootUri + 'reference.ts',
['namespace foo {', ' /** bar doc*/', ' export interface bar {}', '}'].join('\n'),
],
[rootUri + 'empty.ts', ''],
])));
afterEach(shutdownService);
it('should provide a valid empty response when no signature is found', function () {
return __awaiter(this, void 0, void 0, function* () {
const result = yield this.service
.textDocumentSignatureHelp({
textDocument: {
uri: rootUri + 'a.ts',
},
position: {
line: 0,
character: 0,
},
})
.reduce(fast_json_patch_1.applyReducer, null)
.toPromise();
assert.deepEqual(result, {
signatures: [],
activeSignature: 0,
activeParameter: 0,
});
});
});
it('should provide signature help with parameters in the same file', function () {
return __awaiter(this, void 0, void 0, function* () {
const result = yield this.service
.textDocumentSignatureHelp({
textDocument: {
uri: rootUri + 'a.ts',
},
position: {
line: 15,
character: 11,
},
})
.reduce(fast_json_patch_1.applyReducer, null)
.toPromise();
assert.deepEqual(result, {
signatures: [
{
label: 'baz(num: number, text: string): string',
documentation: 'The Baz function',
parameters: [
{
label: 'num: number',
documentation: 'Number parameter',
},
{
label: 'text: string',
documentation: 'Text parameter',
},
],
},
],
activeSignature: 0,
activeParameter: 1,
});
});
});
it('should provide signature help from imported symbols', function () {
return __awaiter(this, void 0, void 0, function* () {
const result = yield this.service
.textDocumentSignatureHelp({
textDocument: {
uri: rootUri + 'uses-import.ts',
},
position: {
line: 1,
character: 4,
},
})
.reduce(fast_json_patch_1.applyReducer, null)
.toPromise();
assert.deepEqual(result, {
activeSignature: 0,
activeParameter: 0,
signatures: [
{
label: 'd(): void',
documentation: 'd doc',
parameters: [],
},
],
});
});
});
});
describe('textDocumentCompletion() with snippets', () => {
beforeEach(exports.initializeTypeScriptService(createService, rootUri, new Map([
[
rootUri + 'a.ts',
[
'class A {',
' /** foo doc*/',
' foo() {}',
' /** bar doc*/',
' bar(num: number): number { return 1; }',
' /** baz doc*/',
' baz(num: number): string { return ""; }',
' /** qux doc*/',
' qux: number;',
'}',
'const a = new A();',
'a.',
].join('\n'),
],
]), Object.assign({ textDocument: {
completion: {
completionItem: {
snippetSupport: true,
},
},
} }, DEFAULT_CAPABILITIES)));
afterEach(shutdownService);
it('should produce completions', function () {
return __awaiter(this, void 0, void 0, function* () {
const result = yield this.service
.textDocumentCompletion({
textDocument: {
uri: rootUri + 'a.ts',
},
position: {
line: 11,
character: 2,
},
})
.reduce(fast_json_patch_1.applyReducer, null)
.toPromise();
assert.equal(result.isIncomplete, false);
assert.sameDeepMembers(result.items, [
{
label: 'bar',
kind: vscode_languageserver_1.CompletionItemKind.Method,
sortText: '0',
data: {
entryName: 'bar',
offset: 222,
uri: rootUri + 'a.ts',
},
},
{
label: 'baz',
kind: vscode_languageserver_1.CompletionItemKind.Method,
sortText: '0',
data: {
entryName: 'baz',
offset: 222,
uri: rootUri + 'a.ts',
},
},
{
label: 'foo',
kind: vscode_languageserver_1.CompletionItemKind.Method,
sortText: '0',
data: {
entryName: 'foo',
offset: 222,
uri: rootUri + 'a.ts',
},
},
{
label: 'qux',
kind: vscode_languageserver_1.CompletionItemKind.Property,
sortText: '0',
data: {
entryName: 'qux',
offset: 222,
uri: rootUri + 'a.ts',
},
},
]);
});
});
it('should resolve completions with snippets', function () {
return __awaiter(this, void 0, void 0, function* () {
const result = yield this.service
.textDocumentCompletion({
textDocument: {
uri: rootUri + 'a.ts',
},
position: {
line: 11,
character: 2,
},
})
.reduce(fast_json_patch_1.applyReducer, null)
.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);
const resolvedItems = yield rxjs_1.Observable.from(result.items)
.mergeMap(item => this.service
.completionItemResolve(item)
.reduce(fast_json_patch_1.applyReducer, null))
.toArray()
.toPromise();
// tslint:disable:no-invalid-template-strings
assert.sameDeepMembers(resolvedItems, [
{
label: 'bar',
kind: vscode_languageserver_1.CompletionItemKind.Method,
documentation: 'bar doc',
sortText: '0',
insertTextFormat: vscode_languageserver_1.InsertTextFormat.Snippet,
insertText: 'bar(${1:num})',
detail: '(method) A.bar(num: number): number',
data: undefined,
},
{
label: 'baz',
kind: vscode_languageserver_1.CompletionItemKind.Method,
documentation: 'baz doc',
sortText: '0',
insertTextFormat: vscode_languageserver_1.InsertTextFormat.Snippet,
insertText: 'baz(${1:num})',
detail: '(method) A.baz(num: number): string',
data: undefined,
},
{
label: 'foo',
kind: vscode_languageserver_1.CompletionItemKind.Method,
documentation: 'foo doc',
sortText: '0',
insertTextFormat: vscode_languageserver_1.InsertTextFormat.Snippet,
insertText: 'foo()',
detail: '(method) A.foo(): void',
data: undefined,
},
{
label: 'qux',
kind: vscode_languageserver_1.CompletionItemKind.Property,
documentation: 'qux doc',
sortText: '0',
insertTextFormat: vscode_languageserver_1.InsertTextFormat.PlainText,
insertText: 'qux',
detail: '(property) A.qux: number',
data: undefined,
},
]);
// tslint:enable:no-invalid-template-strings
});
});
});
describe('textDocumentCompletion()', () => {
beforeEach(exports.initializeTypeScriptService(createService, rootUri, new Map([
[
rootUri + 'a.ts',
[
'class A {',
' /** foo doc*/',
' foo() {}',
' /** bar doc*/',
' bar(): number { return 1; }',
' /** baz doc*/',
' baz(): string { return ""; }',
' /** qux doc*/',
' qux: number;',
'}',
'const a = new A();',
'a.',
].join('\n'),
],
[rootUri + 'uses-import.ts', ['import * as i from "./import"', 'i.'].join('\n')],
[rootUri + 'import.ts', '/** d doc*/ export function d() {}'],
[
rootUri + 'uses-reference.ts',
['/// <reference path="reference.ts" />', 'let z : foo.'].join('\n'),
],
[
rootUri + 'reference.ts',
['namespace foo {', ' /** bar doc*/', ' export interface bar {}', '}'].join('\n'),
],
[rootUri + 'empty.ts', ''],
])));
afterEach(shutdownService);
it('produces completions in the same file', function () {
return __awaiter(this, void 0, void 0, function* () {
const result = yield this.service
.textDocumentCompletion({
textDocument: {
uri: rootUri + 'a.ts',
},
position: {
line: 11,
character: 2,
},
})
.reduce(fast_json_patch_1.applyReducer, null)
.toPromise();
assert.equal(result.isIncomplete, false);
assert.sameDeepMembers(result.items, [
{
data: {
entryName: 'bar',
offset: 200,
uri: rootUri + 'a.ts',
},
label: 'bar',
kind: vscode_languageserver_1.CompletionItemKind.Method,
sortText: '0',
},
{
data: {
entryName: 'baz',
offset: 200,
uri: rootUri + 'a.ts',
},
label: 'baz',
kind: vscode_languageserver_1.CompletionItemKind.Method,
sortText: '0',
},
{
data: {
entryName: 'foo',
offset: 200,
uri: rootUri + 'a.ts',
},
label: 'foo',
kind: vscode_languageserver_1.CompletionItemKind.Method,
sortText: '0',
},
{
data: {
entryName: 'qux',
offset: 200,
uri: rootUri + 'a.ts',
},
label: 'qux',
kind: vscode_languageserver_1.CompletionItemKind.Property,
sortText: '0',
},
]);
});
});
it('resolves completions in the same file', function () {
return __awaiter(this, void 0, void 0, function* () {
const result = yield this.service
.textDocumentCompletion({
textDocument: {
uri: rootUri + 'a.ts',
},
position: {
line: 11,
character: 2,
},
})
.reduce(fast_json_patch_1.applyReducer, null)
.toPromise();
assert.equal(result.isIncomplete, false);
const resolveItem = (item) => this.service
.completionItemResolve(item)
.reduce(fast_json_patch_1.applyReducer, null)
.toPromise();
const resolvedItems = yield Promise.all(result.items.map(resolveItem));
assert.sameDeepMembers(resolvedItems, [
{
label: 'bar',
kind: vscode_languageserver_1.CompletionItemKind.Method,
documentation: 'bar doc',
insertText: 'bar',
insertTextFormat: vscode_languageserver_1.InsertTextFormat.PlainText,
sortText: '0',
detail: '(method) A.bar(): number',
data: undefined,
},
{
label: 'baz',
kind: vscode_languageserver_1.CompletionItemKind.Method,
documentation: 'baz doc',
insertText: 'baz',
insertTextFormat: vscode_languageserver_1.InsertTextFormat.PlainText,
sortText: '0',
detail: '(method) A.baz(): string',
data: undefined,
},
{
label: 'foo',
kind: vscode_languageserver_1.CompletionItemKind.Method,
documentation: 'foo doc',
insertText: 'foo',
insertTextFormat: vscode_languageserver_1.InsertTextFormat.PlainText,
sortText: '0',
detail: '(method) A.foo(): void',
data: undefined,
},
{
label: 'qux',
kind: vscode_languageserver_1.CompletionItemKind.Property,
documentation: 'qux doc',
insertText: 'qux',
insertTextFormat: vscode_languageserver_1.InsertTextFormat.PlainText,
sortText: '0',
detail: '(property) A.qux: number',
data: undefined,
},
]);
});
});
it('produces completions for imported symbols', function () {
return __awaiter(this, void 0, void 0, function* () {
const result = yield this.service
.textDocumentCompletion({
textDocument: {
uri: rootUri + 'uses-import.ts',
},
position: {
line: 1,
character: 2,
},
})
.reduce(fast_json_patch_1.applyReducer, null)
.toPromise();
assert.deepEqual(result, {
isIncomplete: false,
items: [
{
data: {
entryName: 'd',
offset: 32,
uri: rootUri + 'uses-import.ts',
},
label: 'd',
kind: vscode_languageserver_1.CompletionItemKind.Function,
sortText: '0',
},
],
});
});
});
it('produces completions for referenced symbols', function () {
return __awaiter(this, void 0, void 0, function* () {
const result = yield this.service
.textDocumentCompletion({
textDocument: {
uri: rootUri + 'uses-reference.ts',
},
position: {
line: 1,
character: 12,
},
})
.reduce(fast_json_patch_1.applyReducer, null)
.toPromise();
assert.deepEqual(result, {
isIncomplete: false,
items: [
{
data: {
entryName: 'bar',
offset: 50,
uri: rootUri + 'uses-reference.ts',
},
label: 'bar',
kind: vscode_languageserver_1.CompletionItemKind.Interface,
sortText: '0',
},
],
});
});
});
it('produces completions for empty files', function () {
return __awaiter(this, void 0, void 0, function* () {
this.timeout(10000);
const result = yield this.service
.textDocumentCompletion({
textDocument: {
uri: rootUri + 'empty.ts',
},
position: {
line: 0,
character: 0,
},
})
.reduce(fast_json_patch_1.applyReducer, null)
.toPromise();
assert.notDeepEqual(result.items, []);
});
});
});
describe('textDocumentRename()', () => {
beforeEach(exports.initializeTypeScriptService(createService, rootUri, new Map([
[rootUri + 'package.json', JSON.stringify({ name: 'mypkg' })],
[
rootUri + 'a.ts',
[
'class A {',
' /** foo doc*/',
' foo() {}',
' /** bar doc*/',
' bar(): number { return 1; }',
' /** baz doc*/',
' baz(): string { return ""; }',
' /** qux doc*/',
' qux: number;',
'}',
'const a = new A();',
'a.',
].join('\n'),
],
[rootUri + 'uses-import.ts', ['import {d} from "./import"', 'const x = d();'].join('\n')],
[rootUri + 'import.ts', 'export function d(): number { return 55; }'],
])));
afterEach(shutdownService);
it('should error on an invalid symbol', function () {
return __awaiter(this, void 0, void 0, function* () {
yield Promise.resolve(assert.isRejected(this.service
.textDocumentRename({
textDocument: {
uri: rootUri + 'a.ts',
},
position: {
line: 0,
character: 1,
},
newName: 'asdf',
})
.reduce(fast_json_patch_1.applyReducer, null)
.toPromise(), 'This symbol cannot be renamed'));
});
});
it('should return a correct WorkspaceEdit to rename a class', function () {
return __awaiter(this, void 0, void 0, function* () {
const result = yield this.service
.textDocumentRename({
textDocument: {
uri: rootUri + 'a.ts',
},
position: {
line: 0,
character: 6,
},
newName: 'B',
})
.reduce(fast_json_patch_1.applyReducer, null)
.toPromise();
assert.deepEqual(result, {
changes: {
[rootUri + 'a.ts']: [
{
newText: 'B',
range: {
end: {
character: 7,
line: 0,
},
start: {
character: 6,
line: 0,
},
},
},
{
newText: 'B',
range: {
end: {
character: 15,
line: 10,
},
start: {
character: 14,
line: 10,
},
},
},
],
},
});
});
});
it('should return a correct WorkspaceEdit to rename an imported function', function () {
return __awaiter(this, void 0, void 0, function* () {
const result = yield this.service
.textDocumentRename({
textDocument: {
uri: rootUri + 'import.ts',
},
position: {
line: 0,
character: 16,
},
newName: 'f',
})
.reduce(fast_json_patch_1.applyReducer, null)
.toPromise();
assert.deepEqual(result, {
changes: {
[rootUri + 'import.ts']: [
{
newText: 'f',
range: {
end: {
character: 17,
line: 0,
},
start: {
character: 16,
line: 0,
},
},
},
],
[rootUri + 'uses-import.ts']: [
{
newText: 'f',
range: {
end: {
character: 9,
line: 0,
},
start: {
character: 8,
line: 0,
},
},
},
{
newText: 'f',
range: {
end: {
character: 11,
line: 1,
},
start: {
character: 10,
line: 1,
},
},
},
],
},
});
});
});
});
describe('textDocumentCodeAction()', () => {
beforeEach(exports.initializeTypeScriptService(createService, rootUri, new Map([
[rootUri + 'package.json', JSON.stringify({ name: 'mypkg' })],
[
rootUri + 'a.ts',
[
'class A {',
'\tconstructor() {',
'\t\tmissingThis = 33;',
'\t}',
'}',
'const a = new A();',
].join('\n'),
],
])));
afterEach(shutdownService);
it('suggests a missing this', function () {
return __awaiter(this, void 0, void 0, function* () {
yield this.service.textDocumentDidOpen({
textDocument: {
uri: rootUri + 'a.ts',
languageId: 'typescript',
text: [
'class A {',
'\tmissingThis: number;',
'\tconstructor() {',
'\t\tmissingThis = 33;',
'\t}',
'}',
'const a = new A();',
].join('\n'),
version: 1,
},
});
const firstDiagnostic = {
range: {
start: { line: 3, character: 4 },
end: { line: 3, character: 15 },
},
message: "Cannot find name 'missingThis'. Did you mean the instance member 'this.missingThis'?",
severity: vscode_languageserver_1.DiagnosticSeverity.Error,
code: 2663,
source: 'ts',
};
const actions = yield this.service
.textDocumentCodeAction({
textDocument: {
uri: rootUri + 'a.ts',
},
range: firstDiagnostic.range,
context: {
diagnostics: [firstDiagnostic],
},
})
.reduce(fast_json_patch_1.applyReducer, null)
.toPromise();
assert.deepEqual(actions, [
{
title: "Add 'this.' to unresolved variable",
command: 'codeFix',
arguments: [
{
fileName: util_1.toUnixPath(util_1.uri2path(rootUri + 'a.ts')),
textChanges: [
{
span: { start: 51, length: 11 },
newText: 'this.missingThis',
},
],
},
],
},
]);
});
});
});
describe('workspaceExecuteCommand()', () => {
beforeEach(exports.initializeTypeScriptService(createService, rootUri, new Map([
[rootUri + 'package.json', JSON.stringify({ name: 'mypkg' })],
[
rootUri + 'a.ts',
[
'class A {',
' constructor() {',
' missingThis = 33;',
' }',
'}',
'const a = new A();',
].join('\n'),
],
])));
afterEach(shutdownService);
describe('codeFix', () => {
it('should apply a WorkspaceEdit for the passed FileTextChanges', function () {
return __awaiter(this, void 0, void 0, function* () {
yield this.service
.workspaceExecuteCommand({
command: 'codeFix',
arguments: [
{
fileName: util_1.uri2path(rootUri + 'a.ts'),
textChanges: [
{
span: { start: 50, length: 15 },
newText: '\t\tthis.missingThis',
},
],
},
],
})
.reduce(fast_json_patch_1.applyReducer, null)
.toPromise();
sinon.assert.calledOnce(this.client.workspaceApplyEdit);
const workspaceEdit = this.client.workspaceApplyEdit.lastCall.args[0];
assert.deepEqual(workspaceEdit, {
edit: {
changes: {
[rootUri + 'a.ts']: [
{
newText: '\t\tthis.missingThis',
range: {
end: {
character: 9,
line: 5,
},
start: {
character: 0,
line: 3,
},
},
},
],
},
},
});
});
});
});
});
describe('Special file names', () => {
beforeEach(exports.initializeTypeScriptService(createService, rootUri, new Map([
[rootUri + 'keywords-in-path/class/constructor/a.ts', 'export function a() {}'],
[rootUri + 'special-characters-in-path/%40foo/b.ts', 'export function b() {}'],
[rootUri + 'windows/app/master.ts', '/// <reference path="..\\lib\\master.ts" />\nc();'],
[rootUri + 'windows/lib/master.ts', '/// <reference path="..\\lib\\slave.ts" />'],
[rootUri + 'windows/lib/slave.ts', 'function c() {}'],
])));
afterEach(shutdownService);
it('should accept files with TypeScript keywords in path', function () {
return __awaiter(this, void 0, void 0, function* () {
const result = yield this.service
.textDocumentHover({
textDocument: {
uri: rootUri + 'keywords-in-path/class/constructor/a.ts',
},
position: {
line: 0,
character: 16,
},
})
.reduce(fast_json_patch_1.applyReducer, null)
.toPromise();
assert.deepEqual(result, {
range: {
start: {
line: 0,
character: 16,
},
end: {
line: 0,
character: 17,
},
},
contents: [{ language: 'typescript', value: 'function a(): void' }, '**function** _(exported)_'],
});
});
});
it('should accept files with special characters in path', function () {
return __awaiter(this, void 0, void 0, function* () {
const result = yield this.service
.textDocumentHover({
textDocument: {
uri: rootUri + 'special-characters-in-path/%40foo/b.ts',
},
position: {
line: 0,
character: 16,
},
})
.reduce(fast_json_patch_1.applyReducer, null)
.toPromise();
assert.deepEqual(result, {
range: {
start: {
line: 0,
character: 16,
},
end: {
line: 0,
character: 17,
},
},
contents: [{ language: 'typescript', value: 'function b(): void' }, '**function** _(exported)_'],
});
});
});
it('should handle Windows-style paths in triple slash references', function () {
return __awaiter(this, void 0, void 0, function* () {
const result = yield this.service
.textDocumentDefinition({
textDocument: {
uri: rootUri + 'windows/app/master.ts',
},
position: {
line: 1,
character: 0,
},
})
.reduce(fast_json_patch_1.applyReducer, null)
.toPromise();
assert.deepEqual(result, [
{
range: {
start: {
line: 0,
character: 9,
},
end: {
line: 0,
character: 10,
},
},
uri: rootUri + 'windows/lib/slave.ts',
},
]);
});
});
});
}
exports.describeTypeScriptService = describeTypeScriptService;
//# sourceMappingURL=typescript-service-helpers.js.map