diff --git a/integration/language_service_plugin/.gitignore b/integration/language_service_plugin/.gitignore
new file mode 100644
index 0000000000..4c43fe68f6
--- /dev/null
+++ b/integration/language_service_plugin/.gitignore
@@ -0,0 +1 @@
+*.js
\ No newline at end of file
diff --git a/integration/language_service_plugin/README.md b/integration/language_service_plugin/README.md
new file mode 100644
index 0000000000..3873350e21
--- /dev/null
+++ b/integration/language_service_plugin/README.md
@@ -0,0 +1,36 @@
+# Angular Language Service Test
+
+This directory is an integration test for `@angular/language-service` to ensure
+that various versions of the server can be loaded in the supported versions of
+TypeScript's language service.
+
+## New supported version of TypeScript
+
+To add a new supported version of TypeScript:
+
+1) Create directory in `typescripts` to hold the new version following the pattern
+ of the other versions.
+2) Add the directory name to the end of the `TYPESCRIPTS` variable in the
+ `scripts/env.sh` file.
+3) Run `scripts/update_golden.sh` to generate the expected files.
+4) Verify the expected output is reasonable by comparing to a known good output
+ from a previous version.
+
+## Update golden files
+
+If the expected output needs to be updated run `scripts/update_golden.sh` to
+update the expected output of the server.
+
+## Adding a new fixture
+
+Currently there is no automated way to produce a new fixture. The way the
+current fixtures were created was to hack a version of tsserver.js to write the
+commands from `VSCode` to a file while performing the operation to be tested.
+I also hand modified the input to remove superfluous request.
+
+Once a new fixture is created:
+
+1) Add the fixture base name (without the .json) to `FIXTURES` in
+ `scripts/env.sh`.
+2) Run `scripts/udpate_golden.sh` to produce the expected output files.
+3) Hand validate the expected output is reasonable.
diff --git a/integration/language_service_plugin/fixtures/getCompletions-expected-2.3.json b/integration/language_service_plugin/fixtures/getCompletions-expected-2.3.json
new file mode 100644
index 0000000000..3560e80927
--- /dev/null
+++ b/integration/language_service_plugin/fixtures/getCompletions-expected-2.3.json
@@ -0,0 +1,260 @@
+[
+ {
+ "type": "response",
+ "command": "configure",
+ "success": true
+ },
+ {
+ "type": "response",
+ "command": "compilerOptionsForInferredProjects",
+ "success": true,
+ "body": true
+ },
+ {
+ "type": "response",
+ "command": "completions",
+ "success": true,
+ "body": [
+ {
+ "name": "anchor",
+ "kind": "method",
+ "kindModifiers": "",
+ "sortText": "anchor"
+ },
+ {
+ "name": "big",
+ "kind": "method",
+ "kindModifiers": "",
+ "sortText": "big"
+ },
+ {
+ "name": "blink",
+ "kind": "method",
+ "kindModifiers": "",
+ "sortText": "blink"
+ },
+ {
+ "name": "bold",
+ "kind": "method",
+ "kindModifiers": "",
+ "sortText": "bold"
+ },
+ {
+ "name": "charAt",
+ "kind": "method",
+ "kindModifiers": "",
+ "sortText": "charAt"
+ },
+ {
+ "name": "charCodeAt",
+ "kind": "method",
+ "kindModifiers": "",
+ "sortText": "charCodeAt"
+ },
+ {
+ "name": "codePointAt",
+ "kind": "method",
+ "kindModifiers": "",
+ "sortText": "codePointAt"
+ },
+ {
+ "name": "concat",
+ "kind": "method",
+ "kindModifiers": "",
+ "sortText": "concat"
+ },
+ {
+ "name": "endsWith",
+ "kind": "method",
+ "kindModifiers": "",
+ "sortText": "endsWith"
+ },
+ {
+ "name": "fixed",
+ "kind": "method",
+ "kindModifiers": "",
+ "sortText": "fixed"
+ },
+ {
+ "name": "fontcolor",
+ "kind": "method",
+ "kindModifiers": "",
+ "sortText": "fontcolor"
+ },
+ {
+ "name": "fontsize",
+ "kind": "method",
+ "kindModifiers": "",
+ "sortText": "fontsize"
+ },
+ {
+ "name": "includes",
+ "kind": "method",
+ "kindModifiers": "",
+ "sortText": "includes"
+ },
+ {
+ "name": "indexOf",
+ "kind": "method",
+ "kindModifiers": "",
+ "sortText": "indexOf"
+ },
+ {
+ "name": "italics",
+ "kind": "method",
+ "kindModifiers": "",
+ "sortText": "italics"
+ },
+ {
+ "name": "lastIndexOf",
+ "kind": "method",
+ "kindModifiers": "",
+ "sortText": "lastIndexOf"
+ },
+ {
+ "name": "length",
+ "kind": "property",
+ "kindModifiers": "",
+ "sortText": "length"
+ },
+ {
+ "name": "link",
+ "kind": "method",
+ "kindModifiers": "",
+ "sortText": "link"
+ },
+ {
+ "name": "localeCompare",
+ "kind": "method",
+ "kindModifiers": "",
+ "sortText": "localeCompare"
+ },
+ {
+ "name": "match",
+ "kind": "method",
+ "kindModifiers": "",
+ "sortText": "match"
+ },
+ {
+ "name": "normalize",
+ "kind": "method",
+ "kindModifiers": "",
+ "sortText": "normalize"
+ },
+ {
+ "name": "repeat",
+ "kind": "method",
+ "kindModifiers": "",
+ "sortText": "repeat"
+ },
+ {
+ "name": "replace",
+ "kind": "method",
+ "kindModifiers": "",
+ "sortText": "replace"
+ },
+ {
+ "name": "search",
+ "kind": "method",
+ "kindModifiers": "",
+ "sortText": "search"
+ },
+ {
+ "name": "slice",
+ "kind": "method",
+ "kindModifiers": "",
+ "sortText": "slice"
+ },
+ {
+ "name": "small",
+ "kind": "method",
+ "kindModifiers": "",
+ "sortText": "small"
+ },
+ {
+ "name": "split",
+ "kind": "method",
+ "kindModifiers": "",
+ "sortText": "split"
+ },
+ {
+ "name": "startsWith",
+ "kind": "method",
+ "kindModifiers": "",
+ "sortText": "startsWith"
+ },
+ {
+ "name": "strike",
+ "kind": "method",
+ "kindModifiers": "",
+ "sortText": "strike"
+ },
+ {
+ "name": "sub",
+ "kind": "method",
+ "kindModifiers": "",
+ "sortText": "sub"
+ },
+ {
+ "name": "substr",
+ "kind": "method",
+ "kindModifiers": "",
+ "sortText": "substr"
+ },
+ {
+ "name": "substring",
+ "kind": "method",
+ "kindModifiers": "",
+ "sortText": "substring"
+ },
+ {
+ "name": "sup",
+ "kind": "method",
+ "kindModifiers": "",
+ "sortText": "sup"
+ },
+ {
+ "name": "toLocaleLowerCase",
+ "kind": "method",
+ "kindModifiers": "",
+ "sortText": "toLocaleLowerCase"
+ },
+ {
+ "name": "toLocaleUpperCase",
+ "kind": "method",
+ "kindModifiers": "",
+ "sortText": "toLocaleUpperCase"
+ },
+ {
+ "name": "toLowerCase",
+ "kind": "method",
+ "kindModifiers": "",
+ "sortText": "toLowerCase"
+ },
+ {
+ "name": "toString",
+ "kind": "method",
+ "kindModifiers": "",
+ "sortText": "toString"
+ },
+ {
+ "name": "toUpperCase",
+ "kind": "method",
+ "kindModifiers": "",
+ "sortText": "toUpperCase"
+ },
+ {
+ "name": "trim",
+ "kind": "method",
+ "kindModifiers": "",
+ "sortText": "trim"
+ },
+ {
+ "name": "valueOf",
+ "kind": "method",
+ "kindModifiers": "",
+ "sortText": "valueOf"
+ }
+ ]
+ }
+]
diff --git a/integration/language_service_plugin/fixtures/getCompletions.json b/integration/language_service_plugin/fixtures/getCompletions.json
new file mode 100644
index 0000000000..94be1ebe38
--- /dev/null
+++ b/integration/language_service_plugin/fixtures/getCompletions.json
@@ -0,0 +1,68 @@
+[
+ {
+ "seq": 0,
+ "type": "request",
+ "command": "configure",
+ "arguments": {
+ "hostInfo": "vscode"
+ }
+ },
+ {
+ "seq": 1,
+ "type": "request",
+ "command": "compilerOptionsForInferredProjects",
+ "arguments": {
+ "options": {
+ "module": "CommonJS",
+ "target": "ES6",
+ "allowSyntheticDefaultImports": true,
+ "allowNonTsExtensions": true,
+ "allowJs": true,
+ "jsx": "Preserve"
+ }
+ }
+ },
+ {
+ "seq": 4,
+ "type": "request",
+ "command": "open",
+ "arguments": {
+ "file": "$$PWD$$/project/app/app.component.ts",
+ "fileContent": "import { Component } from '@angular/core';\n\n@Component({\n selector: 'my-app',\n template: `
Hello {{name}}
`,\n})\nexport class AppComponent { name = 'Angular'; }\n"
+ }
+ },
+ {
+ "seq": 7,
+ "type": "request",
+ "command": "geterr",
+ "arguments": {
+ "delay": 0,
+ "files": [
+ "$$PWD$$/project/app/app.component.ts"
+ ]
+ }
+ },
+ {
+ "seq": 12,
+ "type": "request",
+ "command": "change",
+ "arguments": {
+ "file": "$$PWD$$/project/app/app.component.ts",
+ "line": 5,
+ "offset": 30,
+ "endLine": 5,
+ "endOffset": 30,
+ "insertString": "."
+ }
+ },
+ {
+ "seq": 13,
+ "type": "request",
+ "command": "completions",
+ "arguments": {
+ "file": "$$PWD$$/project/app/app.component.ts",
+ "line": 5,
+ "offset": 31
+ }
+ }
+]
\ No newline at end of file
diff --git a/integration/language_service_plugin/fixtures/smokeTest-expected-2.3.json b/integration/language_service_plugin/fixtures/smokeTest-expected-2.3.json
new file mode 100644
index 0000000000..55645cf60d
--- /dev/null
+++ b/integration/language_service_plugin/fixtures/smokeTest-expected-2.3.json
@@ -0,0 +1,13 @@
+[
+ {
+ "type": "response",
+ "command": "configure",
+ "success": true
+ },
+ {
+ "type": "response",
+ "command": "compilerOptionsForInferredProjects",
+ "success": true,
+ "body": true
+ }
+]
diff --git a/integration/language_service_plugin/fixtures/smokeTest.json b/integration/language_service_plugin/fixtures/smokeTest.json
new file mode 100644
index 0000000000..7ddc202c9a
--- /dev/null
+++ b/integration/language_service_plugin/fixtures/smokeTest.json
@@ -0,0 +1,45 @@
+[
+ {
+ "seq": 0,
+ "type": "request",
+ "command": "configure",
+ "arguments": {
+ "hostInfo": "vscode"
+ }
+ },
+ {
+ "seq": 1,
+ "type": "request",
+ "command": "compilerOptionsForInferredProjects",
+ "arguments": {
+ "options": {
+ "module": "CommonJS",
+ "target": "ES6",
+ "allowSyntheticDefaultImports": true,
+ "allowNonTsExtensions": true,
+ "allowJs": true,
+ "jsx": "Preserve"
+ }
+ }
+ },
+ {
+ "seq": 2,
+ "type": "request",
+ "command": "open",
+ "arguments": {
+ "file": "$$PWD$$/app/app.module.ts",
+ "fileContent": ""
+ }
+ },
+ {
+ "seq": 3,
+ "type": "request",
+ "command": "geterr",
+ "arguments": {
+ "delay": 0,
+ "files": [
+ "$$PWD$$/app/app.module.ts"
+ ]
+ }
+ }
+]
\ No newline at end of file
diff --git a/integration/language_service_plugin/package.json b/integration/language_service_plugin/package.json
new file mode 100644
index 0000000000..65645d7a4f
--- /dev/null
+++ b/integration/language_service_plugin/package.json
@@ -0,0 +1,26 @@
+{
+ "name": "language_service_plugin",
+ "version": "0.0.0",
+ "license": "MIT",
+ "decription": "Angular Langauge Service plugin integration test",
+ "dependencies": {
+ "@angular/common": "file:../../dist/packages-dist/common",
+ "@angular/compiler": "file:../../dist/packages-dist/compiler",
+ "@angular/compiler-cli": "file:../../dist/packages-dist/compiler-cli",
+ "@angular/core": "file:../../dist/packages-dist/core",
+ "@angular/language-service": "file:../../dist/packages-dist/language-service",
+ "@angular/platform-browser": "file:../../dist/packages-dist/platform-browser",
+ "@angular/platform-server": "file:../../dist/packages-dist/platform-server",
+ "@angular/tsc-wrapped": "file:../../dist/tools/@angular/tsc-wrapped",
+ "@types/minimist": "^1.2.0",
+ "@types/node": "^7.0.5",
+ "minimist": "^1.2.0",
+ "rxjs": "file:../../node_modules/rxjs",
+ "typescript": "^2.1.5",
+ "zone.js": "0.7.6"
+ },
+ "scripts": {
+ "postinstall": "scripts/install.sh",
+ "test": "tsc -p tools && scripts/test.sh"
+ }
+}
diff --git a/integration/language_service_plugin/project/app/app.component.ts b/integration/language_service_plugin/project/app/app.component.ts
new file mode 100644
index 0000000000..7fb173cd01
--- /dev/null
+++ b/integration/language_service_plugin/project/app/app.component.ts
@@ -0,0 +1,7 @@
+import { Component } from '@angular/core';
+
+@Component({
+ selector: 'my-app',
+ template: `Hello {{name}}
`,
+})
+export class AppComponent { name = 'Angular'; }
diff --git a/integration/language_service_plugin/project/app/app.module.ts b/integration/language_service_plugin/project/app/app.module.ts
new file mode 100644
index 0000000000..357b003a5a
--- /dev/null
+++ b/integration/language_service_plugin/project/app/app.module.ts
@@ -0,0 +1,11 @@
+import { NgModule } from '@angular/core';
+import { BrowserModule } from '@angular/platform-browser';
+
+import { AppComponent } from './app.component';
+
+@NgModule({
+ imports: [ BrowserModule ],
+ declarations: [ AppComponent ],
+ bootstrap: [ AppComponent ]
+})
+export class AppModule { }
diff --git a/integration/language_service_plugin/project/app/main.ts b/integration/language_service_plugin/project/app/main.ts
new file mode 100644
index 0000000000..6af7a5b2ae
--- /dev/null
+++ b/integration/language_service_plugin/project/app/main.ts
@@ -0,0 +1,5 @@
+import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
+
+import { AppModule } from './app.module';
+
+platformBrowserDynamic().bootstrapModule(AppModule);
diff --git a/integration/language_service_plugin/project/tsconfig.json b/integration/language_service_plugin/project/tsconfig.json
new file mode 100644
index 0000000000..6a81fdd7df
--- /dev/null
+++ b/integration/language_service_plugin/project/tsconfig.json
@@ -0,0 +1,16 @@
+{
+ "compilerOptions": {
+ "target": "es5",
+ "module": "commonjs",
+ "moduleResolution": "node",
+ "sourceMap": true,
+ "emitDecoratorMetadata": true,
+ "experimentalDecorators": true,
+ "lib": [ "es2015", "dom" ],
+ "noImplicitAny": true,
+ "suppressImplicitAnyIndexErrors": true,
+ "plugins": [
+ { "name": "@angular/language-service" }
+ ]
+ }
+}
diff --git a/integration/language_service_plugin/scripts/env.sh b/integration/language_service_plugin/scripts/env.sh
new file mode 100755
index 0000000000..321f42a45c
--- /dev/null
+++ b/integration/language_service_plugin/scripts/env.sh
@@ -0,0 +1,2 @@
+TYPESCRIPTS=2.3
+FIXTURES="smokeTest getCompletions"
diff --git a/integration/language_service_plugin/scripts/install.sh b/integration/language_service_plugin/scripts/install.sh
new file mode 100755
index 0000000000..d835479259
--- /dev/null
+++ b/integration/language_service_plugin/scripts/install.sh
@@ -0,0 +1,16 @@
+#!/usr/bin/env bash
+
+set -ex -o pipefail
+
+cd `dirname $0`
+cd ..
+source scripts/env.sh
+
+# Setup TypeScripts
+for TYPESCRIPT in ${TYPESCRIPTS[@]}
+do
+ (
+ cd typescripts/$TYPESCRIPT
+ yarn
+ )
+done
\ No newline at end of file
diff --git a/integration/language_service_plugin/scripts/test.sh b/integration/language_service_plugin/scripts/test.sh
new file mode 100755
index 0000000000..29f5d34be2
--- /dev/null
+++ b/integration/language_service_plugin/scripts/test.sh
@@ -0,0 +1,25 @@
+#!/usr/bin/env bash
+
+set -ex -o pipefail
+
+cd `dirname $0`
+cd ..
+source scripts/env.sh
+
+HOST="node tools/typescript_host.js"
+VALIDATE="node tools/typescript_validator.js"
+
+for TYPESCRIPT in ${TYPESCRIPTS[@]}
+do
+ SERVER="node typescripts/$TYPESCRIPT/node_modules/typescript/lib/tsserver.js"
+ for FIXTURE_BASE in ${FIXTURES[@]}
+ do
+ FIXTURE=fixtures/$FIXTURE_BASE.json
+ EXPECTED=fixtures/$FIXTURE_BASE-expected-$TYPESCRIPT.json
+ if [[ ${UPDATE_GOLDEN} == true ]]; then
+ $HOST --file $FIXTURE --pwd $(pwd) | $SERVER | $VALIDATE --golden > $EXPECTED
+ else
+ $HOST --file $FIXTURE --pwd $(pwd) | $SERVER | $VALIDATE --expect $EXPECTED
+ fi
+ done
+done
\ No newline at end of file
diff --git a/integration/language_service_plugin/scripts/update_golden.sh b/integration/language_service_plugin/scripts/update_golden.sh
new file mode 100755
index 0000000000..4fd0d2484d
--- /dev/null
+++ b/integration/language_service_plugin/scripts/update_golden.sh
@@ -0,0 +1,8 @@
+#!/usr/bin/env bash
+
+set -ex -o pipefail
+
+cd `dirname $0`
+cd ..
+
+UPDATE_GOLDEN=true scripts/test.sh
\ No newline at end of file
diff --git a/integration/language_service_plugin/tools/tsconfig.json b/integration/language_service_plugin/tools/tsconfig.json
new file mode 100644
index 0000000000..c576365a6e
--- /dev/null
+++ b/integration/language_service_plugin/tools/tsconfig.json
@@ -0,0 +1,18 @@
+{
+ "compilerOptions": {
+ "module": "commonjs",
+ "target": "es5",
+ "noImplicitAny": true,
+ "skipLibCheck": true,
+ "sourceMap": false,
+ "lib": ["es2015", "dom"],
+ "types": [
+ "node",
+ "minimist"
+ ]
+ },
+ "files": [
+ "typescript_host.ts",
+ "typescript_validator.ts"
+ ]
+}
\ No newline at end of file
diff --git a/integration/language_service_plugin/tools/typescript_host.ts b/integration/language_service_plugin/tools/typescript_host.ts
new file mode 100644
index 0000000000..4dc2f415c8
--- /dev/null
+++ b/integration/language_service_plugin/tools/typescript_host.ts
@@ -0,0 +1,77 @@
+import * as fs from 'fs';
+import * as minimist from 'minimist';
+
+const RE_PWD = /\$\$PWD\$\$/g;
+
+let errorsDetected = false;
+
+function reportError(arg: string): boolean {
+ console.error(`Unknown argument: ${arg}`);
+ errorsDetected = true;
+ return false;
+}
+
+function help() {
+ console.log('TypeScript Host')
+ console.log(`${process.argv[1]} --file [--pwd ]`);
+ console.log(`
+ Send JSON message using the JSON RPC protocol to stdout.
+ `)
+}
+
+let args = minimist(process.argv.slice(2), { string: ['file', 'pwd'], unknown: reportError });
+
+if (errorsDetected) {
+ help();
+ process.exit(2);
+}
+
+const file = args['file'];
+if (!file) {
+ console.log('stdin form not supported yet.')
+ process.exit(1);
+}
+
+// Sender
+const pending: string[] = [];
+let writing = false;
+
+function writeMessage(message: string) {
+ writing = true;
+ process.stdout.write(message + '\n', checkPending);
+}
+
+function checkPending() {
+ writing = false;
+ if (pending.length) {
+ writeMessage(pending.shift());
+ }
+}
+
+function send(message: string) {
+ if (writing) {
+ pending.push(message);
+ } else {
+ writeMessage(message);
+ }
+}
+
+try {
+ let content = fs.readFileSync(file, 'utf8');
+ if (args['pwd']) {
+ content = content.replace(RE_PWD, args['pwd']);
+ }
+
+ const json = JSON.parse(content);
+
+ if (Array.isArray(json)) {
+ for (const message of json) {
+ send(JSON.stringify(message));
+ }
+ } else {
+ throw Error('Expected an array for input messages.')
+ }
+} catch(e) {
+ console.error(`Error: ${e.message}`);
+ process.exit(2);
+}
\ No newline at end of file
diff --git a/integration/language_service_plugin/tools/typescript_validator.ts b/integration/language_service_plugin/tools/typescript_validator.ts
new file mode 100644
index 0000000000..0bad6a4d07
--- /dev/null
+++ b/integration/language_service_plugin/tools/typescript_validator.ts
@@ -0,0 +1,164 @@
+import * as fs from 'fs';
+import * as minimist from 'minimist';
+
+let errorsDetected = false;
+
+const start = Date.now();
+
+function reportError(arg: string): boolean {
+ console.error(`Unknown argument: ${arg}`);
+ errorsDetected = true;
+ return false;
+}
+
+function help() {
+ console.log('TypeScript Validator')
+ console.log(`${process.argv[1]} [--expect | --golden] [--pwd ]`);
+ console.log(`
+ Validate that the emitted output produces the expect JSON.`)
+}
+
+let args = minimist(process.argv.slice(2), { string: ['expect', 'pwd'], boolean: ['golden'], unknown: reportError });
+
+if (!args.golden && !args.expect) {
+ console.log('Expected -golden or -expect');
+ errorsDetected = true;
+}
+
+if (args.golden && args.expect) {
+ console.log('Expected -golded or -expect but not both');
+ errorsDetected = true;
+}
+
+if (errorsDetected) {
+ help();
+ process.exit(2);
+}
+
+var expected: any;
+if (args.expect) {
+ expected = JSON.parse(fs.readFileSync(args.expect, 'utf8'));
+}
+
+// Reader
+let pending = Buffer.alloc(0);
+
+const prefix = 'Content-Length: ';
+
+function tryReadMessage(cb: (message: any) => void) {
+ const firstLine = pending.indexOf(10);
+ if (firstLine >= 1) {
+ const line = pending.toString('utf8', 0, firstLine);
+ if (!line.startsWith(prefix)) {
+ throw Error(`Unexpected input: ${line}`);
+ }
+ const length = +line.substring(prefix.length, firstLine - 1);
+ const dataStart = firstLine + 2;
+ const messageText = pending.toString('utf8', dataStart, dataStart + length);
+ const message = JSON.parse(messageText);
+ pending = pending.slice(dataStart + length + 1);
+ cb(message);
+ tryReadMessage(cb);
+ }
+}
+
+function collect(cb: (error: any, messages: any[]) => void) {
+ const result: any[] = [];
+
+ function report(error: any, messages: any[]) {
+ cb(error, messages);
+ cb = () => {};
+ }
+
+ process.stdin.on('error', report);
+ process.stdin.on('data', (data: Buffer) => {
+ try {
+ pending = Buffer.concat([pending, data], pending.length + data.length);
+ tryReadMessage((message: any) => {
+ result.push(message);
+ });
+ } catch (e) {
+ report(e, []);
+ }
+ });
+
+ process.stdin.on('close', () => {
+ report(null, result);
+ });
+}
+
+function sanitize(messages: any[]): any[] {
+ return messages.filter((message: any) => {
+ return message && message.type == 'response';
+ }).map((message: any) => {
+ // Only preserve a fixed set of fields.
+ const result: any = {};
+ if (message.type != null) result.type = message.type;
+ if (message.command != null) result.command = message.command;
+ if (message.success != null) result.success = message.success;
+ if (message.body != null) result.body = message.body;
+ return result;
+ });
+}
+
+
+function isPrimitive(value: any): boolean {
+ return Object(value) !== value;
+}
+
+function expectPrimitive(received: any, expected: any) {
+ if (received !== expected) {
+ throw new Error(`Expected ${expected} but received ${received}`);
+ }
+}
+
+function expectArray(received: any, expected: any[]) {
+ if (!Array.isArray(received)) {
+ throw new Error(`Expected an array, received ${JSON.stringify(received)}`);
+ }
+ if (received.length != expected.length) {
+ throw new Error(`Expected an array length ${expected.length}, received ${JSON.stringify(received)}`);
+ }
+ for (let i = 0; i < expected.length; i++) {
+ expect(received[i], expected[i]);
+ }
+}
+
+function expectObject(received: any, expected: any) {
+ for (const name of Object.getOwnPropertyNames(expected)) {
+ if (!received.hasOwnProperty(name)) {
+ throw new Error(`Expected object an object containing a field ${name}, received ${JSON.stringify(expected)}`);
+ }
+ expect(received[name], expected[name]);
+ }
+}
+
+function expect(received: any, expected: any) {
+ if (isPrimitive(expected)) {
+ expectPrimitive(received, expected);
+ } else if (Array.isArray(expected)) {
+ expectArray(received, expected);
+ } else {
+ expectObject(received, expected);
+ }
+}
+
+
+collect((err: any, messages: any[]) => {
+ if (err) {
+ console.error(err.message);
+ process.exit(1);
+ }
+ if (args.golden) {
+ console.log(JSON.stringify(sanitize(messages), null, ' '));
+ } else {
+ try {
+ expect(sanitize(messages), expected);
+ console.log('PASSED:', Date.now() - start, 'ms');
+ process.exit(0);
+ } catch(e) {
+ console.log('FAILED:', e.message);
+ process.exit(1);
+ }
+ }
+});
diff --git a/integration/language_service_plugin/tsconfig.json b/integration/language_service_plugin/tsconfig.json
new file mode 100644
index 0000000000..2c7260d1bc
--- /dev/null
+++ b/integration/language_service_plugin/tsconfig.json
@@ -0,0 +1,13 @@
+{
+ "compilerOptions": {
+ "target": "es5",
+ "module": "commonjs",
+ "moduleResolution": "node",
+ "sourceMap": true,
+ "emitDecoratorMetadata": true,
+ "experimentalDecorators": true,
+ "lib": [ "es2015", "dom" ],
+ "noImplicitAny": true,
+ "suppressImplicitAnyIndexErrors": true
+ }
+}
diff --git a/integration/language_service_plugin/typescripts/2.3/package.json b/integration/language_service_plugin/typescripts/2.3/package.json
new file mode 100644
index 0000000000..1654fdfe97
--- /dev/null
+++ b/integration/language_service_plugin/typescripts/2.3/package.json
@@ -0,0 +1,15 @@
+{
+ "name": "2.3",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "keywords": [],
+ "author": "",
+ "license": "ISC",
+ "dependencies": {
+ "typescript": "2.3.0-dev.20170223"
+ }
+}
diff --git a/integration/language_service_plugin/typescripts/2.3/yarn.lock b/integration/language_service_plugin/typescripts/2.3/yarn.lock
new file mode 100644
index 0000000000..600ef21a14
--- /dev/null
+++ b/integration/language_service_plugin/typescripts/2.3/yarn.lock
@@ -0,0 +1,7 @@
+# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
+# yarn lockfile v1
+
+
+typescript@2.3.0-dev.20170223:
+ version "2.3.0-dev.20170223"
+ resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.3.0-dev.20170223.tgz#286494c36625ea2eb26f963ed205cd9ca5c41447"