test(language-service): test `@angular/language-service` can be loaded by tsserver.js (#14721)

This commit is contained in:
Chuck Jazdzewski 2017-03-01 13:22:46 -08:00 committed by Igor Minar
parent 79fc1e3959
commit 7a66a4115b
21 changed files with 833 additions and 0 deletions

View File

@ -0,0 +1 @@
*.js

View File

@ -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.

View File

@ -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"
}
]
}
]

View File

@ -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: `<h1>Hello {{name}}</h1>`,\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
}
}
]

View File

@ -0,0 +1,13 @@
[
{
"type": "response",
"command": "configure",
"success": true
},
{
"type": "response",
"command": "compilerOptionsForInferredProjects",
"success": true,
"body": true
}
]

View File

@ -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"
]
}
}
]

View File

@ -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"
}
}

View File

@ -0,0 +1,7 @@
import { Component } from '@angular/core';
@Component({
selector: 'my-app',
template: `<h1>Hello {{name}}</h1>`,
})
export class AppComponent { name = 'Angular'; }

View File

@ -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 { }

View File

@ -0,0 +1,5 @@
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app.module';
platformBrowserDynamic().bootstrapModule(AppModule);

View File

@ -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" }
]
}
}

View File

@ -0,0 +1,2 @@
TYPESCRIPTS=2.3
FIXTURES="smokeTest getCompletions"

View File

@ -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

View File

@ -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

View File

@ -0,0 +1,8 @@
#!/usr/bin/env bash
set -ex -o pipefail
cd `dirname $0`
cd ..
UPDATE_GOLDEN=true scripts/test.sh

View File

@ -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"
]
}

View File

@ -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 <file-name> [--pwd <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);
}

View File

@ -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 <file-name> | --golden] [--pwd <dir>]`);
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);
}
}
});

View File

@ -0,0 +1,13 @@
{
"compilerOptions": {
"target": "es5",
"module": "commonjs",
"moduleResolution": "node",
"sourceMap": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"lib": [ "es2015", "dom" ],
"noImplicitAny": true,
"suppressImplicitAnyIndexErrors": true
}
}

View File

@ -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"
}
}

View File

@ -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"