internal/lsp/protocol: bring the typescript code up to date

These versions of go.ts and requests.ts generate tsprotocol.go,
tsserver.go, and tsclient.go. README.md now gives the version of the
vscode source used, showing that the typescript code and tsprotocol.go
are matched to the same git commit of vscode.

Many of the diffs are just whitespace from vscode's formatting.

Fixes golang/go#34225

Change-Id: Ib66dad9476b452f332a4c0e990faf2c6060a588e
Reviewed-on: https://go-review.googlesource.com/c/tools/+/195297
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
This commit is contained in:
Peter Weinberger 2019-09-13 13:28:50 -04:00
parent 3720d1ec36
commit 0240832f5c
3 changed files with 132 additions and 55 deletions

View File

@ -3,15 +3,16 @@
## Setup
1. Make sure `node` is installed.
As explained at the [node site](<https://nodejs.org> Node)
As explained at the [node site](<https://nodejs.org>)
you may need `npm install @types/node` for the node runtime types
2. Install the typescript compiler, with `npm install typescript`.
2. Install the typescript compiler, with `npm install typescript`
3. Make sure `tsc` and `node` are in your execution path.
4. Get the typescript code for the jsonrpc protocol with `git clone git@github.com:microsoft/vscode-languageserver-node.git`
5. go.ts and requests.ts, and the files they generate, are from commit 8801c20b667945f455d7e023c71d2f741caeda25
## Usage
To generated the protocol types (x/tools/internal/lsp/protocol/tsprotocol.go)
To generate the protocol types (x/tools/internal/lsp/protocol/tsprotocol.go)
```tsc go.ts && node go.js [-d dir] [-o out.go]```
and for simple checking
@ -31,4 +32,4 @@ To generate the client and server boilerplate (tsclient.go and tsserver.go)
## Note
`go.ts` uses the Typescript compiler's API, which is [introduced](<https://github.com/Microsoft/TypeScript/wiki/Architectural-Overview> API) in their wiki.
`go.ts` and `requests.ts` use the Typescript compiler's API, which is [introduced](https://github.com/Microsoft/TypeScript/wiki/Architectural-Overview) in their wiki.

View File

@ -1,9 +1,8 @@
import * as fs from 'fs';
import * as ts from 'typescript';
// 1. Everything that returns a Go thing should have unusable?: boolean
// 2. Remember what gets exported, and don't print the others (so _ can stay)
// 3. Merge all intersection types, and probably Heritage types too
// Need a general strategy for union types. This code tries (heuristically)
// to choose one, but sometimes defaults (heuristically) to interface{}
interface Const {
typeName: string // repeated in each const
goType: string
@ -103,7 +102,7 @@ function generate(files: string[], options: ts.CompilerOptions): void {
// Ignore top-level items that produce no output
if (ts.isExpressionStatement(node) || ts.isFunctionDeclaration(node) ||
ts.isImportDeclaration(node) || ts.isVariableStatement(node) ||
ts.isExportDeclaration(node) ||
ts.isExportDeclaration(node) || ts.isEmptyStatement(node) ||
node.kind == ts.SyntaxKind.EndOfFileToken) {
return;
}
@ -171,6 +170,7 @@ function generate(files: string[], options: ts.CompilerOptions): void {
Structs.push(ans)
}
// called only from doClassDeclaration
function fromPropDecl(node: ts.PropertyDeclaration): Field {
let id: ts.Identifier = (ts.isIdentifier(node.name) && node.name);
let opt = node.questionToken != undefined;
@ -270,6 +270,7 @@ function generate(files: string[], options: ts.CompilerOptions): void {
return ans
}
// optional gen is the contents of <T>
function genProp(node: ts.PropertySignature, gen: ts.Identifier): Field {
let id: ts.Identifier
let thing: ts.Node
@ -417,7 +418,7 @@ function generate(files: string[], options: ts.CompilerOptions): void {
function doTypeAlias(node: ts.TypeAliasDeclaration) {
// these are all Export Identifier alias
let id: ts.Identifier = node.name;
let alias: ts.Node = node.type;
let alias: ts.TypeNode = node.type;
let ans = {
me: node,
id: id,
@ -427,11 +428,14 @@ function generate(files: string[], options: ts.CompilerOptions): void {
};
if (ts.isUnionTypeNode(alias)) {
ans.goType = weirdUnionType(alias)
if (id.text == 'DocumentFilter')
if (ans.goType == undefined) {
// these are mostly redundant; maybe sort them out later
return
}
if (ans.goType == 'interface{}') {
// we didn't know what to do, so explain the choice
ans.stuff = `// ` + getText(alias)
}
Types.push(ans)
return
}
@ -441,8 +445,9 @@ function generate(files: string[], options: ts.CompilerOptions): void {
if (ts.isTypeReferenceNode(n)) {
const s = toGoName(computeType(n).goType)
embeds.push(s)
// It's here just for embedding, and not used independently
dontEmit.set(s, true);
// It's here just for embedding, and not used independently, maybe
// PJW!
// dontEmit.set(s, true); // PJW: do we need this?
} else
throw new Error(`expected TypeRef ${strKind(n)} ${loc(n)}`)
})
@ -492,13 +497,16 @@ function generate(files: string[], options: ts.CompilerOptions): void {
aString = true;
return;
}
if (n.kind == ts.SyntaxKind.NumberKeyword ||
n.kind == ts.SyntaxKind.StringKeyword) {
n.kind == ts.SyntaxKind.NumberKeyword ? aNumber = true : aString = true;
return
}
bad = true
})
if (bad) return; // none of these are useful (so far)
if (aNumber) {
if (aString)
throw new Error(
`weirdUnionType is both number and string ${loc(node)}`);
if (aString) return 'interface{}';
return 'float64';
}
if (aString) return 'string';
@ -513,6 +521,7 @@ function generate(files: string[], options: ts.CompilerOptions): void {
return ans
}
// complex and filled with heuristics
function computeType(node: ts.Node):
{goType: string, gostuff?: string, optional?: boolean, fields?: Field[]} {
switch (node.kind) {
@ -540,13 +549,14 @@ function generate(files: string[], options: ts.CompilerOptions): void {
if (ts.isQualifiedName(tn)) {
throw new Error(`qualified name at ${loc(node)}`);
} else if (ts.isIdentifier(tn)) {
return {goType: tn.text};
return {goType: toGoName(tn.text)};
} else {
throw new Error(
`expected identifier got ${strKind(node.typeName)} at ${loc(tn)}`)
}
} else if (ts.isLiteralTypeNode(node)) {
// string|float64 (are there other possibilities?)
// as of 20190908: only see string
const txt = getText(node);
let typ = 'float64'
if (txt.charAt(0) == '\'') {
@ -554,6 +564,7 @@ function generate(files: string[], options: ts.CompilerOptions): void {
}
return {goType: typ, gostuff: getText(node)};
} else if (ts.isTypeLiteralNode(node)) {
// {[uri:string]: TextEdit[];} -> map[string][]TextEdit
let x: Field[] = [];
let indexCnt = 0
node.forEachChild((n: ts.Node) => {
@ -575,13 +586,16 @@ function generate(files: string[], options: ts.CompilerOptions): void {
}
return ({goType: 'embedded!', fields: x})
} else if (ts.isUnionTypeNode(node)) {
// The major heuristics
let x = new Array<{goType: string, gostuff?: string, optiona?: boolean}>()
node.forEachChild((n: ts.Node) => {x.push(computeType(n))})
if (x.length == 2 && x[1].goType == 'nil') {
// Foo | null, or Foo | undefined
return x[0] // make it optional somehow? TODO
}
if (x[0].goType == 'bool') { // take it
if (x[1].goType == 'RenameOptions') {
if (x[0].goType == 'bool') { // take it, mostly
if (x[1].goType == 'RenameOptions' ||
x[1].goType == 'CodeActionOptions') {
return ({goType: 'interface{}', gostuff: getText(node)})
}
return ({goType: 'bool', gostuff: getText(node)})
@ -592,6 +606,7 @@ function generate(files: string[], options: ts.CompilerOptions): void {
return ({goType: 'string', gostuff: gostuff})
}
if (x[0].goType == 'TextDocumentSyncOptions') {
// TextDocumentSyncOptions | TextDocumentSyncKind
return ({goType: 'interface{}', gostuff: gostuff})
}
if (x[0].goType == 'float64' && x[1].goType == 'string') {
@ -617,12 +632,13 @@ function generate(files: string[], options: ts.CompilerOptions): void {
throw new Error('in UnionType, weird')
} else if (ts.isParenthesizedTypeNode(node)) {
// check that this is (TextDocumentEdit | CreateFile | RenameFile |
// DeleteFile) TODO(pjw)
// DeleteFile) TODO(pjw) IT IS NOT! FIX THIS! ALSO:
// (variousOptions & StaticFegistrationOptions)
return {
goType: 'TextDocumentEdit', gostuff: getText(node)
}
} else if (ts.isTupleTypeNode(node)) {
// string | [number, number]. TODO(pjw): check it really is
// in string | [number, number]. TODO(pjw): check it really is
return {
goType: 'string', gostuff: getText(node)
}
@ -774,7 +790,7 @@ function emitTypes() {
let stuff = (t.stuff == undefined) ? '' : t.stuff;
prgo(`// ${t.goName} is a type\n`)
prgo(`${getComments(t.me)}`)
prgo(`type ${t.goName} ${t.goType}${stuff}\n`)
prgo(`type ${t.goName} = ${t.goType}${stuff}\n`)
seenConstTypes.set(t.goName, true);
}
}
@ -856,7 +872,7 @@ let byName = new Map<string, Struct>();
fields.set(f.goName, x);
}
}
fields.forEach((val) => {
fields.forEach((val, key) => {
if (val.length > 1) {
// merge the fields with the same name
prgo(strField(val[0], noopt, val));
@ -878,6 +894,7 @@ let byName = new Map<string, Struct>();
}
// Turn a Field into an output string
// flds is for merging
function strField(f: Field, noopt?: boolean, flds?: Field[]): string {
let ans: string[] = [];
let opt = (!noopt && f.optional) ? '*' : ''
@ -895,6 +912,20 @@ let byName = new Map<string, Struct>();
ans.push(`\t${f.goName} ${opt}${f.goType} ${f.json}${stuff}\n`)
}
else if (flds !== undefined) {
// The logic that got us here is imprecise, so it is possible that
// the fields are really all the same, and don't need to be
// combined into a struct.
let simple = true;
for (const ff of flds) {
if (ff.substruct !== undefined || byName.get(ff.goType) !== undefined) {
simple = false
break
}
}
if (simple) {
// should check that the ffs are really all the same
return strField(flds[0], noopt)
}
ans.push(`\t${f.goName} ${opt}struct{\n`);
for (const ff of flds) {
if (ff.substruct !== undefined) {
@ -926,8 +957,9 @@ let byName = new Map<string, Struct>();
// need the consts too! Generate modifying prefixes and suffixes to ensure
// consts are unique. (Go consts are package-level, but Typescript's are
// not.) Use suffixes to minimize changes to gopls.
let pref = new Map<string, string>(
[['DiagnosticSeverity', 'Severity'], ['WatchKind', 'Watch']]) // typeName->prefix
let pref = new Map<string, string>([
['DiagnosticSeverity', 'Severity'], ['WatchKind', 'Watch']
]) // typeName->prefix
let suff = new Map<string, string>([
['CompletionItemKind', 'Completion'], ['InsertTextFormat', 'TextFormat']
])

View File

@ -29,6 +29,7 @@ function prb(s: string) {
}
let program: ts.Program;
function generate(files: string[], options: ts.CompilerOptions): void {
program = ts.createProgram(files, options);
program.getTypeChecker();
@ -62,15 +63,11 @@ function generate(files: string[], options: ts.CompilerOptions): void {
// 2. func (h *serverHandler) Deliver(...) { switch r.method }
// 3. func (x *xDispatcher) Method(ctx, parm)
not.forEach(
(v, k) => {
receives.get(k) == 'client' ? goNot(client, k) :
goNot(server, k)
});
(v, k) => {receives.get(k) == 'client' ? goNot(client, k) :
goNot(server, k)});
req.forEach(
(v, k) => {
receives.get(k) == 'client' ? goReq(client, k) :
goReq(server, k)
});
(v, k) => {receives.get(k) == 'client' ? goReq(client, k) :
goReq(server, k)});
// and print the Go code
output(client);
output(server);
@ -104,7 +101,7 @@ const notNil = `if r.Params != nil {
// Go code for notifications. Side is client or server, m is the request method
function goNot(side: side, m: string) {
const n = not.get(m);
let a = goType(m, n.typeArguments[0]);
let a = goType(side, m, n.typeArguments[0]);
// let b = goType(m, n.typeArguments[1]); These are registration options
const nm = methodName(m);
side.methods.push(sig(nm, a, ''));
@ -140,8 +137,8 @@ function goReq(side: side, m: string) {
const n = req.get(m);
const nm = methodName(m);
let a = goType(m, n.typeArguments[0]);
let b = goType(m, n.typeArguments[1]);
let a = goType(side, m, n.typeArguments[0]);
let b = goType(side, m, n.typeArguments[1]);
if (n.getText().includes('Type0')) {
b = a;
a = ''; // workspace/workspaceFolders and shutdown
@ -233,7 +230,8 @@ function output(side: side) {
f(`type ${a} interface {`);
side.methods.forEach((v) => {f(v)});
f('}\n');
f(`func (h ${side.name}Handler) Deliver(ctx context.Context, r *jsonrpc2.Request, delivered bool) bool {
f(`func (h ${
side.name}Handler) Deliver(ctx context.Context, r *jsonrpc2.Request, delivered bool) bool {
if delivered {
return false
}
@ -266,21 +264,36 @@ function output(side: side) {
*/
ID jsonrpc2.ID \`json:"id"\`
}`);
f(`// Types constructed to avoid structs as formal argument types`)
side.ourTypes.forEach((val, key) => f(`type ${val} ${key}`));
}
interface side {
methods: string[];
cases: string[];
calls: string[];
ourTypes: Map<string, string>;
name: string; // client or server
goName: string; // Client or Server
outputFile?: string;
fd?: number
}
let client: side =
{ methods: [], cases: [], calls: [], name: 'client', goName: 'Client' };
let server: side =
{ methods: [], cases: [], calls: [], name: 'server', goName: 'Server' };
let client: side = {
methods: [],
cases: [],
calls: [],
name: 'client',
goName: 'Client',
ourTypes: new Map<string, string>()
};
let server: side = {
methods: [],
cases: [],
calls: [],
name: 'server',
goName: 'Server',
ourTypes: new Map<string, string>()
};
let req = new Map<string, ts.NewExpression>(); // requests
let not = new Map<string, ts.NewExpression>(); // notifications
@ -308,12 +321,12 @@ function setReceives() {
})
}
function goType(m: string, n: ts.Node): string {
function goType(side: side, m: string, n: ts.Node): string {
if (n === undefined) return '';
if (ts.isTypeReferenceNode(n)) return n.typeName.getText();
if (n.kind == ts.SyntaxKind.VoidKeyword) return '';
if (n.kind == ts.SyntaxKind.AnyKeyword) return 'interface{}';
if (ts.isArrayTypeNode(n)) return '[]' + goType(m, n.elementType);
if (ts.isArrayTypeNode(n)) return '[]' + goType(side, m, n.elementType);
// special cases, before we get confused
switch (m) {
case 'textDocument/completion':
@ -328,7 +341,8 @@ function goType(m: string, n: ts.Node): string {
if (ts.isUnionTypeNode(n)) {
let x: string[] = [];
n.types.forEach(
(v) => { v.kind != ts.SyntaxKind.NullKeyword && x.push(goType(m, v)) });
(v) => {v.kind != ts.SyntaxKind.NullKeyword &&
x.push(goType(side, m, v))});
if (x.length == 1) return x[0];
prb(`===========${m} ${x}`)
@ -338,6 +352,28 @@ function goType(m: string, n: ts.Node): string {
if (x[1] == '[]' + x[0] + 'Link') return x[1];
throw new Error(`${m}, ${x} unexpected types`)
}
if (ts.isIntersectionTypeNode(n)) {
// we expect only TypeReferences, and put out a struct with embedded types
// This is not good, as it uses a struct where a type name ought to be.
let x: string[] = [];
n.types.forEach((v) => {
// expect only TypeReferences
if (!ts.isTypeReferenceNode(v)) {
throw new Error(
`expected only TypeReferences in Intersection ${getText(n)}`)
}
x.push(goType(side, m, v));
x.push(';')
})
x.push('}')
let ans = 'struct {'.concat(...x);
// If ans does not have a type, create it
if (side.ourTypes.get(ans) == undefined) {
side.ourTypes.set(ans, 'Param' + getText(n).substring(0, 6))
}
// Return the type
return side.ourTypes.get(ans)
}
return '?';
}
@ -369,6 +405,14 @@ function genStuff(node: ts.Node) {
v.set(s, node);
}
// find the text of a node
function getText(node: ts.Node): string {
let sf = node.getSourceFile();
let start = node.getStart(sf)
let end = node.getEnd()
return sf.text.substring(start, end)
}
function lookUp(n: ts.NewExpression): ts.NodeArray<ts.TypeNode> {
// parent should be VariableDeclaration. its children should be
// Identifier('type') ???