tools/internal/lsp/protocol: generate LSP protocol types from source
The existing code implementing the jsonrpc LSP (language server protocol) relies on hand-translated definitions for the needed Go data types. Unfortunately Microsoft makes changes, not always backwards compatibly. This code generates the Go data types directly from the Typescript source. Adapting gopls to the new data definitions will happen in a future CL. Change-Id: I032c69a16b6f2614370765dcd6dbdb38e9f40ab6 Reviewed-on: https://go-review.googlesource.com/c/tools/+/166277 Reviewed-by: Rebecca Stambler <rstambler@golang.org>
This commit is contained in:
parent
6a988fd03a
commit
0bb0c0a6e8
|
@ -0,0 +1,30 @@
|
|||
# Generate Go types for the LSP protocol
|
||||
|
||||
## Setup
|
||||
|
||||
1. Make sure `node` is installed.
|
||||
* As explained at the [node site](https://nodejs.org Node)
|
||||
* You may need `node install @types/node` for the node runtime types
|
||||
2. Install the typescript compiler, with `node 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 vscode-lanuageserver-node.git`
|
||||
|
||||
## Usage
|
||||
|
||||
```tsc go.ts && node go.js [-d dir] [-o out.go]```
|
||||
|
||||
and for simple checking
|
||||
|
||||
```gofmt -w out.go && golint out.go && go build out.go```
|
||||
|
||||
`-d dir` names the directory into which the `vscode-languageserver-node` repository was cloned.
|
||||
It defaults to `$(HOME)`.
|
||||
|
||||
`-o out.go` says where the generated go code goes.
|
||||
It defaults to `/tmp/tsprotocol.go`.
|
||||
|
||||
(The output file cannot yet be used to build `gopls`. That will be fixed in a future CL.)
|
||||
|
||||
## Note
|
||||
|
||||
`go.ts` uses the Typescript compiler's API, which is [introduced](https://github.com/Microsoft/TypeScript/wiki/Architectural-Overview API) in their wiki.
|
|
@ -0,0 +1,889 @@
|
|||
import * as fs from 'fs';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
|
||||
interface Const {
|
||||
typeName: string // repeated in each const
|
||||
goType: string
|
||||
me: ts.Node
|
||||
name: string // constant's name
|
||||
value: string // constant's value
|
||||
}
|
||||
let Consts: Const[] = [];
|
||||
let seenConstTypes = new Map<string, boolean>();
|
||||
|
||||
interface Struct {
|
||||
me: ts.Node
|
||||
name: string
|
||||
embeds: string[]
|
||||
fields?: Field[]
|
||||
}
|
||||
let Structs: Struct[] = [];
|
||||
|
||||
interface Field {
|
||||
me: ts.Node
|
||||
id: ts.Identifier
|
||||
goName: string
|
||||
optional: boolean
|
||||
goType: string
|
||||
json: string
|
||||
gostuff?: string
|
||||
substruct?: Field[] // embedded struct from TypeLiteral
|
||||
}
|
||||
|
||||
interface Type {
|
||||
me: ts.Node
|
||||
goName: string
|
||||
goType: string
|
||||
stuff: string
|
||||
}
|
||||
let Types: Type[] = [];
|
||||
|
||||
// Used in printing the AST
|
||||
let seenThings = new Map<string, number>();
|
||||
function seenAdd(x: string) {
|
||||
seenThings[x] = (seenThings[x] === undefined ? 1 : seenThings[x] + 1)
|
||||
}
|
||||
|
||||
let dir = process.env['HOME'];
|
||||
let fnames = [
|
||||
`/vscode-languageserver-node/protocol/src/protocol.ts`,
|
||||
`/vscode-languageserver-node/types/src/main.ts`
|
||||
];
|
||||
let outFname = '/tmp/tsprotocol.go';
|
||||
let fda: number, fdb: number, fde: number; // file descriptors
|
||||
|
||||
function createOutputFiles() {
|
||||
fda = fs.openSync('/tmp/ts-a', 'w') // dump of AST
|
||||
fdb = fs.openSync('/tmp/ts-b', 'w') // unused, for debugging
|
||||
fde = fs.openSync(outFname, 'w') // generated Go
|
||||
}
|
||||
function pra(s: string) {
|
||||
return (fs.writeSync(fda, s))
|
||||
}
|
||||
function prb(s: string) {
|
||||
return (fs.writeSync(fdb, s))
|
||||
}
|
||||
function prgo(s: string) {
|
||||
return (fs.writeSync(fde, s))
|
||||
}
|
||||
|
||||
function generate(files: string[], options: ts.CompilerOptions): void {
|
||||
let program = ts.createProgram(files, options);
|
||||
program.getTypeChecker(); // used for side-effects
|
||||
|
||||
// dump the ast, for debugging
|
||||
for (const sourceFile of program.getSourceFiles()) {
|
||||
if (!sourceFile.isDeclarationFile) {
|
||||
// walk the tree to do stuff
|
||||
ts.forEachChild(sourceFile, describe);
|
||||
}
|
||||
}
|
||||
pra('\n')
|
||||
for (const key of Object.keys(seenThings).sort()) {
|
||||
pra(`${key}: ${seenThings[key]}\n`)
|
||||
}
|
||||
|
||||
// visit every sourceFile in the program, generating types
|
||||
for (const sourceFile of program.getSourceFiles()) {
|
||||
if (!sourceFile.isDeclarationFile) {
|
||||
ts.forEachChild(sourceFile, genTypes)
|
||||
}
|
||||
}
|
||||
return;
|
||||
|
||||
function genTypes(node: ts.Node) {
|
||||
// Ignore top-level items with no output
|
||||
if (ts.isExpressionStatement(node) || ts.isFunctionDeclaration(node) ||
|
||||
ts.isImportDeclaration(node) || ts.isVariableStatement(node) ||
|
||||
ts.isExportDeclaration(node) ||
|
||||
node.kind == ts.SyntaxKind.EndOfFileToken) {
|
||||
return;
|
||||
}
|
||||
if (ts.isInterfaceDeclaration(node)) {
|
||||
doInterface(node)
|
||||
return;
|
||||
} else if (ts.isTypeAliasDeclaration(node)) {
|
||||
doTypeAlias(node)
|
||||
} else if (ts.isModuleDeclaration(node)) {
|
||||
doModuleDeclaration(node)
|
||||
} else if (ts.isEnumDeclaration(node)) {
|
||||
doEnumDecl(node)
|
||||
} else if (ts.isClassDeclaration(node)) {
|
||||
doClassDeclaration(node)
|
||||
} else {
|
||||
throw new Error(`unexpected ${ts.SyntaxKind[node.kind]} ${loc(node)}`)
|
||||
}
|
||||
}
|
||||
|
||||
function doClassDeclaration(node: ts.ClassDeclaration) {
|
||||
let id: ts.Identifier
|
||||
let props = new Array<ts.PropertyDeclaration>()
|
||||
let extend: ts.HeritageClause;
|
||||
let bad = false
|
||||
node.forEachChild((n: ts.Node) => {
|
||||
if (ts.isIdentifier(n)) {
|
||||
id = n;
|
||||
return
|
||||
}
|
||||
if (ts.isPropertyDeclaration(n)) {
|
||||
props.push(n);
|
||||
return
|
||||
}
|
||||
if (n.kind == ts.SyntaxKind.ExportKeyword) {
|
||||
return
|
||||
}
|
||||
if (n.kind == ts.SyntaxKind.Constructor || ts.isMethodDeclaration(n) ||
|
||||
ts.isGetAccessor(n) || ts.isTypeParameterDeclaration(n)) {
|
||||
bad = true;
|
||||
return
|
||||
}
|
||||
if (ts.isHeritageClause(n)) {
|
||||
extend = n;
|
||||
return
|
||||
}
|
||||
throw new Error(`doClass ${loc(n)} ${kinds(n)}`)
|
||||
})
|
||||
if (bad) {
|
||||
// the class is not useful for Go.
|
||||
return
|
||||
} // might we want the PropertyDecls? (don't think so)
|
||||
let fields: Field[] = [];
|
||||
for (const pr of props) {
|
||||
fields.push(fromPropDecl(pr))
|
||||
}
|
||||
let ans = {
|
||||
me: node,
|
||||
name: toGoName(getText(id)),
|
||||
embeds: heritageStrs(extend),
|
||||
fields: fields
|
||||
};
|
||||
Structs.push(ans)
|
||||
}
|
||||
|
||||
function fromPropDecl(node: ts.PropertyDeclaration): Field {
|
||||
let id: ts.Identifier;
|
||||
let opt = false
|
||||
let typ: ts.Node
|
||||
node.forEachChild((n: ts.Node) => {
|
||||
if (ts.isIdentifier(n)) {
|
||||
id = n;
|
||||
return
|
||||
}
|
||||
if (n.kind == ts.SyntaxKind.QuestionToken) {
|
||||
opt = true;
|
||||
return
|
||||
}
|
||||
if (typ != undefined)
|
||||
throw new Error(`fromPropDecl too long ${loc(node)}`)
|
||||
typ = n
|
||||
})
|
||||
let goType = computeType(typ).goType
|
||||
let ans = {
|
||||
me: node,
|
||||
id: id,
|
||||
goName: toGoName(getText(id)),
|
||||
optional: opt,
|
||||
goType: goType,
|
||||
json: `\`json:"${id.text}${opt ? ',omitempty' : ''}"\``
|
||||
};
|
||||
return ans
|
||||
}
|
||||
|
||||
function doInterface(node: ts.InterfaceDeclaration) {
|
||||
// name: Identifier;
|
||||
// typeParameters?: NodeArray<TypeParameterDeclaration>;
|
||||
// heritageClauses?: NodeArray<HeritageClause>;
|
||||
// members: NodeArray<TypeElement>;
|
||||
|
||||
// find the Identifier from children
|
||||
// process the PropertySignature children
|
||||
// the members might have generic info, but so do the children
|
||||
let id: ts.Identifier;
|
||||
let extend: ts.HeritageClause
|
||||
let generid: ts.Identifier
|
||||
let properties = new Array<ts.PropertySignature>()
|
||||
let index: ts.IndexSignatureDeclaration // generate some sort of map
|
||||
node.forEachChild((n: ts.Node) => {
|
||||
if (n.kind == ts.SyntaxKind.ExportKeyword || ts.isMethodSignature(n)) {
|
||||
// ignore
|
||||
} else if (ts.isIdentifier(n)) {
|
||||
id = n;
|
||||
} else if (ts.isHeritageClause(n)) {
|
||||
extend = n;
|
||||
} else if (ts.isTypeParameterDeclaration(n)) {
|
||||
// Act as if this is <T = any>
|
||||
generid = n.name;
|
||||
} else if (ts.isPropertySignature(n)) {
|
||||
properties.push(n);
|
||||
} else if (ts.isIndexSignatureDeclaration(n)) {
|
||||
if (index !== undefined) {
|
||||
throw new Error(`${loc(n)} multiple index expressions`)
|
||||
}
|
||||
index = n
|
||||
} else {
|
||||
throw new Error(`${loc(n)} doInterface ${ts.SyntaxKind[n.kind]} `)
|
||||
}
|
||||
})
|
||||
let fields: Field[] = [];
|
||||
for (const p of properties) {
|
||||
fields.push(genProp(p, generid))
|
||||
}
|
||||
if (index != undefined) {
|
||||
fields.push(fromIndexSignature(index))
|
||||
}
|
||||
const ans = {
|
||||
me: node,
|
||||
name: toGoName(getText(id)),
|
||||
embeds: heritageStrs(extend),
|
||||
fields: fields
|
||||
};
|
||||
|
||||
Structs.push(ans)
|
||||
}
|
||||
|
||||
function heritageStrs(node: ts.HeritageClause): string[] {
|
||||
// ExpressionWithTypeArguments+, and each is an Identifier
|
||||
let ans: string[] = [];
|
||||
if (node == undefined) {
|
||||
return ans
|
||||
}
|
||||
let x: ts.ExpressionWithTypeArguments[] = []
|
||||
node.forEachChild((n: ts.Node) => {
|
||||
if (ts.isExpressionWithTypeArguments(n)) x.push(n)
|
||||
})
|
||||
for (const p of x) {
|
||||
p.forEachChild((n: ts.Node) => {
|
||||
if (ts.isIdentifier(n)) {
|
||||
ans.push(toGoName(getText(n)));
|
||||
return;
|
||||
}
|
||||
if (ts.isTypeReferenceNode(n)) {
|
||||
// don't want these, ignore them
|
||||
return;
|
||||
}
|
||||
throw new Error(`expected Identifier ${loc(n)} ${kinds(p)} `)
|
||||
})
|
||||
}
|
||||
return ans
|
||||
}
|
||||
|
||||
function genProp(node: ts.PropertySignature, gen: ts.Identifier): Field {
|
||||
let id: ts.Identifier
|
||||
let thing: ts.Node
|
||||
let opt = false
|
||||
node.forEachChild((n: ts.Node) => {
|
||||
if (ts.isIdentifier(n)) {
|
||||
id = n
|
||||
} else if (n.kind == ts.SyntaxKind.QuestionToken) {
|
||||
opt = true
|
||||
} else if (n.kind == ts.SyntaxKind.ReadonlyKeyword) {
|
||||
return
|
||||
} else {
|
||||
if (thing !== undefined) {
|
||||
throw new Error(`${loc(n)} weird`)
|
||||
}
|
||||
thing = n
|
||||
}
|
||||
})
|
||||
let goName = toGoName(id.text)
|
||||
let { goType, gostuff, optional, fields } = computeType(thing)
|
||||
// Generics
|
||||
if (gen && gen.text == goType) goType = 'interface{}';
|
||||
opt = opt || optional;
|
||||
let ans = {
|
||||
me: node,
|
||||
id: id,
|
||||
goName: goName,
|
||||
optional: opt,
|
||||
goType: goType,
|
||||
gostuff: gostuff,
|
||||
substruct: fields,
|
||||
json: `\`json:"${id.text}${opt ? ',omitempty' : ''}"\``
|
||||
};
|
||||
// Rather than checking that goName is a const type, just do
|
||||
switch (goType) {
|
||||
case 'CompletionItemKind':
|
||||
case 'TextDocumentSyncKind':
|
||||
case 'CodeActionKind':
|
||||
case 'InsertTextFormat': // float64
|
||||
case 'DiagnosticSeverity':
|
||||
ans.optional = false
|
||||
}
|
||||
return ans
|
||||
}
|
||||
|
||||
function doModuleDeclaration(node: ts.ModuleDeclaration) {
|
||||
// Export Identifier ModuleBlock
|
||||
let id: ts.Identifier;
|
||||
let mb: ts.ModuleBlock;
|
||||
node.forEachChild((n: ts.Node) => {
|
||||
if ((ts.isIdentifier(n) && (id = n)) ||
|
||||
(ts.isModuleBlock(n) && mb === undefined && (mb = n)) ||
|
||||
(n.kind == ts.SyntaxKind.ExportKeyword)) {
|
||||
return;
|
||||
}
|
||||
throw new Error(`doModuleDecl ${loc(n)} ${ts.SyntaxKind[n.kind]}`)
|
||||
})
|
||||
// Don't want FunctionDeclarations
|
||||
// mb has VariableStatement and useless TypeAliasDeclaration
|
||||
// some of the VariableStatement are consts, and want their comments
|
||||
// and each VariableStatement is Export, VariableDeclarationList
|
||||
// and each VariableDeclarationList is a single VariableDeclaration
|
||||
let v: ts.VariableDeclaration[] = [];
|
||||
function f(n: ts.Node) {
|
||||
if (ts.isVariableDeclaration(n)) {
|
||||
v.push(n);
|
||||
return
|
||||
}
|
||||
if (ts.isFunctionDeclaration(n)) {
|
||||
return
|
||||
}
|
||||
n.forEachChild(f)
|
||||
}
|
||||
f(node)
|
||||
for (const vx of v) {
|
||||
if (hasNewExpression(vx)) {
|
||||
return
|
||||
}
|
||||
buildConst(getText(id), vx)
|
||||
}
|
||||
}
|
||||
|
||||
function buildConst(tname: string, node: ts.VariableDeclaration): Const {
|
||||
// node is Identifier, optional-goo, (FirstLiteralToken|StringLiteral)
|
||||
let id: ts.Identifier
|
||||
let str: string
|
||||
let first: string
|
||||
node.forEachChild((n: ts.Node) => {
|
||||
if (ts.isIdentifier(n)) {
|
||||
id = n
|
||||
} else if (ts.isStringLiteral(n)) {
|
||||
str = getText(n)
|
||||
} else if (n.kind == ts.SyntaxKind.FirstLiteralToken) {
|
||||
first = getText(n)
|
||||
}
|
||||
})
|
||||
if (str == undefined && first == undefined) {
|
||||
return
|
||||
} // various
|
||||
const ty = (str != undefined) ? 'string' : 'float64'
|
||||
const val = (str != undefined) ? str.replace(/'/g, '"') : first
|
||||
const name = toGoName(getText(id))
|
||||
const c = {
|
||||
typeName: tname,
|
||||
goType: ty,
|
||||
me: node.parent.parent,
|
||||
name: name,
|
||||
value: val
|
||||
};
|
||||
Consts.push(c)
|
||||
return c
|
||||
}
|
||||
|
||||
// is node an ancestor of a NewExpression
|
||||
function hasNewExpression(n: ts.Node): boolean {
|
||||
let ans = false;
|
||||
n.forEachChild((n: ts.Node) => {
|
||||
if (ts.isNewExpression(n)) ans = true;
|
||||
})
|
||||
return ans
|
||||
}
|
||||
|
||||
function doEnumDecl(node: ts.EnumDeclaration) {
|
||||
// Generates Consts. Identifier EnumMember+
|
||||
// EnumMember: Identifier StringLiteral
|
||||
let id: ts.Identifier
|
||||
let mems: ts.EnumMember[] = []
|
||||
node.forEachChild((n: ts.Node) => {
|
||||
if (ts.isIdentifier(n)) {
|
||||
id = n // check for uniqueness?
|
||||
} else if (ts.isEnumMember(n)) {
|
||||
mems.push(n)
|
||||
} else if (n.kind != ts.SyntaxKind.ExportKeyword) {
|
||||
throw new Error(`doEnumDecl ${ts.SyntaxKind[n.kind]} ${loc(n)}`)
|
||||
}
|
||||
})
|
||||
for (const m of mems) {
|
||||
let name: string
|
||||
let value: string
|
||||
m.forEachChild((n: ts.Node) => {
|
||||
if (ts.isIdentifier(n)) {
|
||||
name = getText(n)
|
||||
} else if (ts.isStringLiteral(n)) {
|
||||
value = getText(n).replace(/'/g, '"')
|
||||
} else {
|
||||
throw new Error(`in doEnumDecl ${ts.SyntaxKind[n.kind]} ${loc(n)}`)
|
||||
}
|
||||
})
|
||||
let ans = {
|
||||
typeName: getText(id),
|
||||
goType: 'string',
|
||||
me: m,
|
||||
name: name,
|
||||
value: value
|
||||
};
|
||||
Consts.push(ans)
|
||||
}
|
||||
}
|
||||
|
||||
// top-level TypeAlias
|
||||
function doTypeAlias(node: ts.TypeAliasDeclaration) {
|
||||
// these are all Export Identifier alias
|
||||
let id: ts.Identifier;
|
||||
let alias: ts.Node;
|
||||
let genid: ts.TypeParameterDeclaration // <T>, but we don't care
|
||||
node.forEachChild((n: ts.Node) => {
|
||||
if ((ts.isIdentifier(n) && (id = n)) ||
|
||||
(n.kind == ts.SyntaxKind.ExportKeyword) ||
|
||||
ts.isTypeParameterDeclaration(n) && (genid = n) ||
|
||||
(alias === undefined && (alias = n))) {
|
||||
return
|
||||
}
|
||||
throw new Error(`doTypeAlias ${loc(n)} ${ts.SyntaxKind[n.kind]}`)
|
||||
})
|
||||
let ans = {
|
||||
me: node,
|
||||
id: id,
|
||||
goName: toGoName(getText(id)),
|
||||
goType: '?',
|
||||
stuff: ''
|
||||
};
|
||||
if (id.text.indexOf('--') != -1) {
|
||||
return
|
||||
} // don't care
|
||||
if (ts.isUnionTypeNode(alias)) {
|
||||
ans.goType = weirdUnionType(alias)
|
||||
if (ans.goType == undefined) { // these are redundant
|
||||
return
|
||||
}
|
||||
Types.push(ans)
|
||||
return
|
||||
}
|
||||
if (ts.isIntersectionTypeNode(alias)) { // a Struct, not a Type
|
||||
let embeds: string[] = []
|
||||
alias.forEachChild((n: ts.Node) => {
|
||||
if (ts.isTypeReferenceNode(n)) {
|
||||
embeds.push(toGoName(computeType(n).goType))
|
||||
} else
|
||||
throw new Error(`expected TypeRef ${ts.SyntaxKind[n.kind]} ${loc(n)}`)
|
||||
})
|
||||
let ans = { me: node, name: toGoName(getText(id)), embeds: embeds };
|
||||
Structs.push(ans)
|
||||
return
|
||||
}
|
||||
if (ts.isArrayTypeNode(alias)) { // []DocumentFilter
|
||||
ans.goType = '[]DocumentFilter';
|
||||
Types.push(ans)
|
||||
return
|
||||
}
|
||||
if (ts.isLiteralTypeNode(alias)) {
|
||||
return // type A = 1, so nope
|
||||
}
|
||||
if (ts.isTypeReferenceNode(alias)) {
|
||||
ans.goType = computeType(alias).goType
|
||||
if (ans.goType.match(/und/) != null) throw new Error('396')
|
||||
Types.push(ans) // type A B
|
||||
return
|
||||
}
|
||||
if (alias.kind == ts.SyntaxKind.StringKeyword) { // type A string
|
||||
ans.goType = 'string';
|
||||
Types.push(ans);
|
||||
return
|
||||
}
|
||||
throw new Error(`in doTypeAlias ${loc(node)} ${kinds(node)} ${
|
||||
ts.SyntaxKind[alias.kind]}\n`)
|
||||
}
|
||||
|
||||
// extract the one useful but weird case ()
|
||||
function weirdUnionType(node: ts.UnionTypeNode): string {
|
||||
let bad = false
|
||||
let tl: ts.TypeLiteralNode[] = []
|
||||
node.forEachChild((n: ts.Node) => {
|
||||
if (ts.isTypeLiteralNode(n)) {
|
||||
tl.push(n)
|
||||
} else
|
||||
bad = true
|
||||
})
|
||||
if (bad) return // none of these are useful (so far)
|
||||
let x = computeType(tl[0])
|
||||
x.fields[0].json = x.fields[0].json.replace(/"`/, ',omitempty"`')
|
||||
let out: string[] = [];
|
||||
for (const f of x.fields) {
|
||||
out.push(strField(f))
|
||||
}
|
||||
out.push('}\n')
|
||||
let ans = 'struct {\n'.concat(...out);
|
||||
return ans
|
||||
}
|
||||
|
||||
function computeType(node: ts.Node): { goType: string, gostuff?: string, optional?: boolean, fields?: Field[] } {
|
||||
switch (node.kind) {
|
||||
case ts.SyntaxKind.AnyKeyword:
|
||||
case ts.SyntaxKind.ObjectKeyword:
|
||||
return { goType: 'interface{}' };
|
||||
case ts.SyntaxKind.BooleanKeyword:
|
||||
return { goType: 'bool' };
|
||||
case ts.SyntaxKind.NumberKeyword:
|
||||
return { goType: 'float64' };
|
||||
case ts.SyntaxKind.StringKeyword:
|
||||
return { goType: 'string' };
|
||||
case ts.SyntaxKind.NullKeyword:
|
||||
case ts.SyntaxKind.UndefinedKeyword:
|
||||
return { goType: 'nil' };
|
||||
}
|
||||
if (ts.isArrayTypeNode(node)) {
|
||||
let { goType, gostuff, optional } = computeType(node.elementType)
|
||||
return ({ goType: '[]' + goType, gostuff: gostuff, optional: optional })
|
||||
} else if (ts.isTypeReferenceNode(node)) {
|
||||
// typeArguments?: NodeArray<TypeNode>;typeName: EntityName;
|
||||
// typeArguments won't show up in the generated Go
|
||||
// EntityName: Identifier|QualifiedName
|
||||
let tn: ts.EntityName = node.typeName;
|
||||
if (ts.isQualifiedName(tn)) {
|
||||
throw new Error(`qualified name at ${loc(node)}`);
|
||||
} else if (ts.isIdentifier(tn)) {
|
||||
return { goType: tn.text };
|
||||
} else {
|
||||
throw new Error(`expected identifier got ${
|
||||
ts.SyntaxKind[node.typeName.kind]} at ${loc(tn)}`)
|
||||
}
|
||||
} else if (ts.isLiteralTypeNode(node)) {
|
||||
// string|float64 (are there other possibilities?)
|
||||
const txt = getText(node);
|
||||
let typ = 'float64'
|
||||
if (txt.charAt(0) == '\'') {
|
||||
typ = 'string'
|
||||
}
|
||||
return { goType: typ, gostuff: getText(node) };
|
||||
} else if (ts.isTypeLiteralNode(node)) {
|
||||
let x: Field[] = [];
|
||||
let indexCnt = 0
|
||||
node.forEachChild((n: ts.Node) => {
|
||||
if (ts.isPropertySignature(n)) {
|
||||
x.push(genProp(n, undefined))
|
||||
return
|
||||
} else if (ts.isIndexSignatureDeclaration(n)) {
|
||||
indexCnt++
|
||||
x.push(fromIndexSignature(n))
|
||||
return
|
||||
}
|
||||
throw new Error(
|
||||
`${loc(n)} gotype ${ts.SyntaxKind[n.kind]}, not expected`)
|
||||
});
|
||||
if (indexCnt > 0) {
|
||||
if (indexCnt != 1 || x.length != 1)
|
||||
throw new Error(`undexpected Index ${loc(x[0].me)}`)
|
||||
// instead of {map...} just the map
|
||||
return ({ goType: x[0].goType, gostuff: x[0].gostuff })
|
||||
}
|
||||
return ({ goType: 'embedded!', fields: x })
|
||||
} else if (ts.isUnionTypeNode(node)) {
|
||||
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') {
|
||||
return x[0] // make it optional somehow? TODO
|
||||
}
|
||||
if (x[0].goType == 'bool') { // take it
|
||||
return ({ goType: 'bool', gostuff: getText(node) })
|
||||
}
|
||||
// these are special cases from looking at the source
|
||||
let gostuff = getText(node);
|
||||
if (x[0].goType == `"off"` || x[0].goType == 'string') {
|
||||
return ({ goType: 'string', gostuff: gostuff })
|
||||
}
|
||||
if (x[0].goType == 'TextDocumentSyncOptions') {
|
||||
return ({ goType: 'interface{}', gostuff: gostuff })
|
||||
}
|
||||
if (x[0].goType == 'float64' && x[1].goType == 'string') {
|
||||
return {
|
||||
goType: 'interface{}', gostuff: gostuff
|
||||
}
|
||||
}
|
||||
if (x[0].goType == 'MarkupContent' && x[1].goType == 'MarkedString') {
|
||||
return {
|
||||
goType: 'MarkupContent', gostuff: gostuff
|
||||
}
|
||||
}
|
||||
// Fail loudly
|
||||
console.log(`UnionType ${loc(node)}`)
|
||||
for (const v of x) {
|
||||
console.log(`${v.goType}`)
|
||||
}
|
||||
throw new Error('in UnionType, weird')
|
||||
} else if (ts.isParenthesizedTypeNode(node)) {
|
||||
// check that this is (TextDocumentEdit | CreateFile | RenameFile |
|
||||
// DeleteFile)
|
||||
return {
|
||||
goType: 'TextDocumentEdit', gostuff: getText(node)
|
||||
}
|
||||
} else if (ts.isTupleTypeNode(node)) {
|
||||
// string | [number, number]
|
||||
return {
|
||||
goType: 'string', gostuff: getText(node)
|
||||
}
|
||||
}
|
||||
throw new Error(`unknown ${ts.SyntaxKind[node.kind]} at ${loc(node)}`)
|
||||
}
|
||||
|
||||
function fromIndexSignature(node: ts.IndexSignatureDeclaration): Field {
|
||||
let parm: ts.ParameterDeclaration
|
||||
let at: ts.Node
|
||||
node.forEachChild((n: ts.Node) => {
|
||||
if (ts.isParameter(n)) {
|
||||
parm = n
|
||||
} else if (
|
||||
ts.isArrayTypeNode(n) || n.kind == ts.SyntaxKind.AnyKeyword ||
|
||||
ts.isUnionTypeNode(n)) {
|
||||
at = n
|
||||
} else
|
||||
throw new Error(`fromIndexSig ${ts.SyntaxKind[n.kind]} ${loc(n)}`)
|
||||
})
|
||||
let goType = computeType(at).goType
|
||||
let id: ts.Identifier
|
||||
parm.forEachChild((n: ts.Node) => {
|
||||
if (ts.isIdentifier(n)) {
|
||||
id = n
|
||||
} else if (n.kind != ts.SyntaxKind.StringKeyword) {
|
||||
throw new Error(
|
||||
`fromIndexSig expected string, ${ts.SyntaxKind[n.kind]} ${loc(n)}`)
|
||||
}
|
||||
})
|
||||
goType = `map[string]${goType}`
|
||||
return {
|
||||
me: node, goName: toGoName(id.text), id: null, goType: goType,
|
||||
optional: false, json: `\`json:"${id.text}"\``,
|
||||
gostuff: `${getText(node)}`
|
||||
}
|
||||
}
|
||||
|
||||
function toGoName(s: string): string {
|
||||
let ans = s
|
||||
if (s.charAt(0) == '_') {
|
||||
ans = 'Inner' + s.substring(1)
|
||||
}
|
||||
else { ans = s.substring(0, 1).toUpperCase() + s.substring(1) };
|
||||
ans = ans.replace(/Uri$/, 'URI')
|
||||
ans = ans.replace(/Id$/, 'ID')
|
||||
return ans
|
||||
}
|
||||
|
||||
|
||||
// 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)
|
||||
}
|
||||
// return a string of the kinds of the immediate descendants
|
||||
function kinds(n: ts.Node): string {
|
||||
let res = 'Seen ' + ts.SyntaxKind[n.kind];
|
||||
function f(n: ts.Node): void { res += ' ' + ts.SyntaxKind[n.kind] };
|
||||
ts.forEachChild(n, f)
|
||||
return res
|
||||
}
|
||||
|
||||
function describe(node: ts.Node) {
|
||||
if (node === undefined) {
|
||||
return
|
||||
}
|
||||
let indent = '';
|
||||
|
||||
function f(n: ts.Node) {
|
||||
seenAdd(kinds(n))
|
||||
if (ts.isIdentifier(n)) {
|
||||
pra(`${indent} ${loc(n)} ${ts.SyntaxKind[n.kind]} ${n.text}\n`)
|
||||
}
|
||||
else if (ts.isPropertySignature(n) || ts.isEnumMember(n)) {
|
||||
pra(`${indent} ${loc(n)} ${ts.SyntaxKind[n.kind]}\n`)
|
||||
}
|
||||
else if (ts.isTypeLiteralNode(n)) {
|
||||
let m = n.members
|
||||
pra(`${indent} ${loc(n)} ${ts.SyntaxKind[n.kind]} ${m.length}\n`)
|
||||
}
|
||||
else { pra(`${indent} ${loc(n)} ${ts.SyntaxKind[n.kind]}\n`) };
|
||||
indent += ' '
|
||||
ts.forEachChild(n, f)
|
||||
indent = indent.slice(0, indent.length - 2)
|
||||
}
|
||||
f(node)
|
||||
}
|
||||
|
||||
|
||||
function loc(node: ts.Node): string {
|
||||
const sf = node.getSourceFile()
|
||||
const start = node.getStart()
|
||||
const x = sf.getLineAndCharacterOfPosition(start)
|
||||
const full = node.getFullStart()
|
||||
const y = sf.getLineAndCharacterOfPosition(full)
|
||||
let fn = sf.fileName
|
||||
const n = fn.search(/-node./)
|
||||
fn = fn.substring(n + 6)
|
||||
return `${fn} ${x.line + 1}:${x.character + 1} (${y.line + 1}:${
|
||||
y.character + 1})`
|
||||
}
|
||||
}
|
||||
|
||||
function getComments(node: ts.Node): string {
|
||||
const sf = node.getSourceFile();
|
||||
const start = node.getStart(sf, false)
|
||||
const starta = node.getStart(sf, true)
|
||||
const x = sf.text.substring(starta, start)
|
||||
return x
|
||||
}
|
||||
|
||||
function emitTypes() {
|
||||
for (const t of Types) {
|
||||
if (t.goName == 'CodeActionKind') continue; // consts better choice
|
||||
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`)
|
||||
}
|
||||
}
|
||||
|
||||
function emitStructs() {
|
||||
let seenName = new Map<string, boolean>()
|
||||
for (const str of Structs) {
|
||||
if (str.name == 'InitializeError') {
|
||||
// only want the consts
|
||||
continue
|
||||
}
|
||||
if (seenName[str.name]) {
|
||||
continue
|
||||
}
|
||||
seenName[str.name] = true
|
||||
prgo(genComments(str.name, getComments(str.me)))
|
||||
/* prgo(`// ${str.name} is:\n`)
|
||||
prgo(getComments(str.me))*/
|
||||
prgo(`type ${str.name} struct {\n`)
|
||||
for (const s of str.embeds) {
|
||||
prgo(`\t${s}\n`)
|
||||
}
|
||||
if (str.fields != undefined) {
|
||||
for (const f of str.fields) {
|
||||
prgo(strField(f))
|
||||
}
|
||||
}
|
||||
prgo(`}\n`)
|
||||
}
|
||||
}
|
||||
|
||||
function genComments(name: string, maybe: string): string {
|
||||
if (maybe == '') return `\n\t// ${name} is\n`;
|
||||
if (maybe.indexOf('/**') == 0) {
|
||||
return maybe.replace('/**', `\n/*${name} defined:`)
|
||||
}
|
||||
throw new Error(`weird comment ${maybe.indexOf('/**')}`)
|
||||
}
|
||||
|
||||
// Turn a Field into an output string
|
||||
function strField(f: Field): string {
|
||||
let ans: string[] = [];
|
||||
let opt = f.optional ? '*' : ''
|
||||
switch (f.goType.charAt(0)) {
|
||||
case 's': // string
|
||||
case 'b': // bool
|
||||
case 'f': // float64
|
||||
case 'i': // interface{}
|
||||
case '[': // []foo
|
||||
opt = ''
|
||||
}
|
||||
let stuff = (f.gostuff == undefined) ? '' : ` // ${f.gostuff}`
|
||||
ans.push(genComments(f.goName, getComments(f.me)))
|
||||
if (f.substruct == undefined) {
|
||||
ans.push(`\t${f.goName} ${opt}${f.goType} ${f.json}${stuff}\n`)
|
||||
}
|
||||
else {
|
||||
ans.push(`\t${f.goName} ${opt}struct {\n`)
|
||||
for (const x of f.substruct) {
|
||||
ans.push(strField(x))
|
||||
}
|
||||
ans.push(`\t} ${f.json}${stuff}\n`)
|
||||
}
|
||||
return (''.concat(...ans))
|
||||
}
|
||||
|
||||
function emitConsts() {
|
||||
// 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']]) // typeName->prefix
|
||||
let suff = new Map<string, string>([
|
||||
['CompletionItemKind', 'Completion'], ['InsertTextFormat', 'TextFormat']
|
||||
])
|
||||
for (const c of Consts) {
|
||||
if (seenConstTypes[c.typeName]) {
|
||||
continue
|
||||
}
|
||||
seenConstTypes[c.typeName] = true
|
||||
if (pref.get(c.typeName) == undefined) {
|
||||
pref.set(c.typeName, '') // initialize to empty value
|
||||
}
|
||||
if (suff.get(c.typeName) == undefined) {
|
||||
suff.set(c.typeName, '')
|
||||
}
|
||||
prgo(`// ${c.typeName} defines constants\n`)
|
||||
prgo(`type ${c.typeName} ${c.goType}\n`)
|
||||
}
|
||||
prgo('const (\n')
|
||||
let seenConsts = new Map<string, boolean>() // to avoid duplicates
|
||||
for (const c of Consts) {
|
||||
const x = `${pref.get(c.typeName)}${c.name}${suff.get(c.typeName)}`
|
||||
if (seenConsts.get(x)) {
|
||||
continue
|
||||
}
|
||||
seenConsts.set(x, true)
|
||||
prgo(genComments(x, getComments(c.me)))
|
||||
prgo(`\t${x} ${c.typeName} = ${c.value}\n`)
|
||||
}
|
||||
prgo(')\n')
|
||||
}
|
||||
|
||||
function emitHeader(files: string[]) {
|
||||
let lastMod = 0
|
||||
let lastDate: Date
|
||||
for (const f of files) {
|
||||
const st = fs.statSync(f)
|
||||
if (st.mtimeMs > lastMod) {
|
||||
lastMod = st.mtimeMs
|
||||
lastDate = st.mtime
|
||||
}
|
||||
}
|
||||
prgo(`// Package protocol contains data types for LSP jsonrpcs\n`)
|
||||
prgo(`// generated automatically from vscode-languageserver-node
|
||||
// version of ${lastDate}\n`)
|
||||
prgo('package protocol\n\n')
|
||||
};
|
||||
|
||||
// ad hoc argument parsing: [-d dir] [-o outputfile], and order matters
|
||||
function main() {
|
||||
let args = process.argv.slice(2) // effective command line
|
||||
if (args.length > 0) {
|
||||
let j = 0;
|
||||
if (args[j] == '-d') {
|
||||
dir = args[j + 1]
|
||||
j += 2
|
||||
}
|
||||
if (args[j] == '-o') {
|
||||
outFname = args[j + 1]
|
||||
j += 2
|
||||
}
|
||||
if (j != args.length) throw new Error(`incomprehensible args ${args}`)
|
||||
}
|
||||
let files: string[] = [];
|
||||
for (let i = 0; i < fnames.length; i++) {
|
||||
files.push(`${dir}${fnames[i]}`)
|
||||
}
|
||||
createOutputFiles()
|
||||
generate(
|
||||
files, { target: ts.ScriptTarget.ES5, module: ts.ModuleKind.CommonJS });
|
||||
emitHeader(files)
|
||||
emitStructs()
|
||||
emitConsts()
|
||||
emitTypes()
|
||||
}
|
||||
|
||||
main()
|
Loading…
Reference in New Issue