internal/lsp/protocol: bring the code generating programs up to date
This is the typescript code that generates the current versions of tsprotocol.go, tsserver.go, and tsclient.go. Change-Id: If40cd7a46e5e7d646d99670da5e04831b6ddc222 Reviewed-on: https://go-review.googlesource.com/c/tools/+/180477 Reviewed-by: Ian Cottrell <iancottrell@google.com>
This commit is contained in:
parent
d4e310b4a8
commit
027b3b4d7b
|
@ -3,14 +3,15 @@
|
||||||
## Setup
|
## Setup
|
||||||
|
|
||||||
1. Make sure `node` is installed.
|
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> Node)
|
||||||
* You may need `node install @types/node` for the node runtime types
|
you may need `node install @types/node` for the node runtime types
|
||||||
2. Install the typescript compiler, with `node install typescript`.
|
2. Install the typescript compiler, with `node install typescript`.
|
||||||
3. Make sure `tsc` and `node` are in your execution path.
|
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`
|
4. Get the typescript code for the jsonrpc protocol with `git clone vscode-lanuageserver-node.git`
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
|
To generated the protocol types (x/tools/internal/lsp/protocol/tsprotocol.go)
|
||||||
```tsc go.ts && node go.js [-d dir] [-o out.go]```
|
```tsc go.ts && node go.js [-d dir] [-o out.go]```
|
||||||
|
|
||||||
and for simple checking
|
and for simple checking
|
||||||
|
@ -23,8 +24,11 @@ It defaults to `$(HOME)`.
|
||||||
`-o out.go` says where the generated go code goes.
|
`-o out.go` says where the generated go code goes.
|
||||||
It defaults to `/tmp/tsprotocol.go`.
|
It defaults to `/tmp/tsprotocol.go`.
|
||||||
|
|
||||||
(The output file cannot yet be used to build `gopls`. That will be fixed in a future CL.)
|
To generate the client and server boilerplate (tsclient.go and tsserver.go)
|
||||||
|
```tsc requests.ts && node requests.js [-d dir] && gofmt -w tsclient.go tsserver.go```
|
||||||
|
|
||||||
|
-d dir is the same as above. The output files are written into the current directory.
|
||||||
|
|
||||||
## Note
|
## 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` uses the Typescript compiler's API, which is [introduced](<https://github.com/Microsoft/TypeScript/wiki/Architectural-Overview> API) in their wiki.
|
|
@ -1,7 +1,9 @@
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import * as ts from 'typescript';
|
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
|
||||||
interface Const {
|
interface Const {
|
||||||
typeName: string // repeated in each const
|
typeName: string // repeated in each const
|
||||||
goType: string
|
goType: string
|
||||||
|
@ -15,8 +17,9 @@ let seenConstTypes = new Map<string, boolean>();
|
||||||
interface Struct {
|
interface Struct {
|
||||||
me: ts.Node
|
me: ts.Node
|
||||||
name: string
|
name: string
|
||||||
embeds: string[]
|
embeds?: string[]
|
||||||
fields?: Field[]
|
fields?: Field[];
|
||||||
|
extends?: string[]
|
||||||
}
|
}
|
||||||
let Structs: Struct[] = [];
|
let Structs: Struct[] = [];
|
||||||
|
|
||||||
|
@ -46,9 +49,10 @@ function seenAdd(x: string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
let dir = process.env['HOME'];
|
let dir = process.env['HOME'];
|
||||||
|
const srcDir = '/vscode-languageserver-node'
|
||||||
let fnames = [
|
let fnames = [
|
||||||
`/vscode-languageserver-node/protocol/src/protocol.ts`,
|
`${srcDir}/protocol/src/protocol.ts`, `${srcDir}/types/src/main.ts`,
|
||||||
`/vscode-languageserver-node/types/src/main.ts`
|
`${srcDir}/jsonrpc/src/main.ts`
|
||||||
];
|
];
|
||||||
let outFname = '/tmp/tsprotocol.go';
|
let outFname = '/tmp/tsprotocol.go';
|
||||||
let fda: number, fdb: number, fde: number; // file descriptors
|
let fda: number, fdb: number, fde: number; // file descriptors
|
||||||
|
@ -68,6 +72,9 @@ function prgo(s: string) {
|
||||||
return (fs.writeSync(fde, s))
|
return (fs.writeSync(fde, s))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// struct names that don't need to go in the output
|
||||||
|
let dontEmit = new Map<string, boolean>();
|
||||||
|
|
||||||
function generate(files: string[], options: ts.CompilerOptions): void {
|
function generate(files: string[], options: ts.CompilerOptions): void {
|
||||||
let program = ts.createProgram(files, options);
|
let program = ts.createProgram(files, options);
|
||||||
program.getTypeChecker(); // used for side-effects
|
program.getTypeChecker(); // used for side-effects
|
||||||
|
@ -93,7 +100,7 @@ function generate(files: string[], options: ts.CompilerOptions): void {
|
||||||
return;
|
return;
|
||||||
|
|
||||||
function genTypes(node: ts.Node) {
|
function genTypes(node: ts.Node) {
|
||||||
// Ignore top-level items with no output
|
// Ignore top-level items that produce no output
|
||||||
if (ts.isExpressionStatement(node) || ts.isFunctionDeclaration(node) ||
|
if (ts.isExpressionStatement(node) || ts.isFunctionDeclaration(node) ||
|
||||||
ts.isImportDeclaration(node) || ts.isVariableStatement(node) ||
|
ts.isImportDeclaration(node) || ts.isVariableStatement(node) ||
|
||||||
ts.isExportDeclaration(node) ||
|
ts.isExportDeclaration(node) ||
|
||||||
|
@ -112,18 +119,17 @@ function generate(files: string[], options: ts.CompilerOptions): void {
|
||||||
} else if (ts.isClassDeclaration(node)) {
|
} else if (ts.isClassDeclaration(node)) {
|
||||||
doClassDeclaration(node)
|
doClassDeclaration(node)
|
||||||
} else {
|
} else {
|
||||||
throw new Error(`unexpected ${ts.SyntaxKind[node.kind]} ${loc(node)}`)
|
throw new Error(`unexpected ${strKind(node)} ${loc(node)}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function doClassDeclaration(node: ts.ClassDeclaration) {
|
function doClassDeclaration(node: ts.ClassDeclaration) {
|
||||||
let id: ts.Identifier
|
let id: ts.Identifier = node.name;
|
||||||
let props = new Array<ts.PropertyDeclaration>()
|
let props = new Array<ts.PropertyDeclaration>()
|
||||||
let extend: ts.HeritageClause;
|
let extend: ts.HeritageClause;
|
||||||
let bad = false
|
let bad = false
|
||||||
node.forEachChild((n: ts.Node) => {
|
node.forEachChild((n: ts.Node) => {
|
||||||
if (ts.isIdentifier(n)) {
|
if (ts.isIdentifier(n)) {
|
||||||
id = n;
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (ts.isPropertyDeclaration(n)) {
|
if (ts.isPropertyDeclaration(n)) {
|
||||||
|
@ -134,20 +140,24 @@ function generate(files: string[], options: ts.CompilerOptions): void {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (n.kind == ts.SyntaxKind.Constructor || ts.isMethodDeclaration(n) ||
|
if (n.kind == ts.SyntaxKind.Constructor || ts.isMethodDeclaration(n) ||
|
||||||
ts.isGetAccessor(n) || ts.isTypeParameterDeclaration(n)) {
|
ts.isGetAccessor(n) || ts.isSetAccessor(n) ||
|
||||||
|
ts.isTypeParameterDeclaration(n)) {
|
||||||
bad = true;
|
bad = true;
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (ts.isHeritageClause(n)) {
|
if (ts.isHeritageClause(n)) {
|
||||||
extend = n;
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if (n.kind == ts.SyntaxKind.AbstractKeyword) {
|
||||||
|
bad = true; // we think all of these are useless, but are unsure
|
||||||
|
return;
|
||||||
|
}
|
||||||
throw new Error(`doClass ${loc(n)} ${kinds(n)}`)
|
throw new Error(`doClass ${loc(n)} ${kinds(n)}`)
|
||||||
})
|
})
|
||||||
if (bad) {
|
if (bad) {
|
||||||
// the class is not useful for Go.
|
// the class is not useful for Go.
|
||||||
return
|
return
|
||||||
} // might we want the PropertyDecls? (don't think so)
|
}
|
||||||
let fields: Field[] = [];
|
let fields: Field[] = [];
|
||||||
for (const pr of props) {
|
for (const pr of props) {
|
||||||
fields.push(fromPropDecl(pr))
|
fields.push(fromPropDecl(pr))
|
||||||
|
@ -155,37 +165,26 @@ function generate(files: string[], options: ts.CompilerOptions): void {
|
||||||
let ans = {
|
let ans = {
|
||||||
me: node,
|
me: node,
|
||||||
name: toGoName(getText(id)),
|
name: toGoName(getText(id)),
|
||||||
embeds: heritageStrs(extend),
|
extends: heritageStrs(extend),
|
||||||
fields: fields
|
fields: fields
|
||||||
};
|
};
|
||||||
Structs.push(ans)
|
Structs.push(ans)
|
||||||
}
|
}
|
||||||
|
|
||||||
function fromPropDecl(node: ts.PropertyDeclaration): Field {
|
function fromPropDecl(node: ts.PropertyDeclaration): Field {
|
||||||
let id: ts.Identifier;
|
let id: ts.Identifier = (ts.isIdentifier(node.name) && node.name);
|
||||||
let opt = false
|
let opt = node.questionToken != undefined;
|
||||||
let typ: ts.Node
|
let typ: ts.Node = node.type;
|
||||||
node.forEachChild((n: ts.Node) => {
|
const computed = computeType(typ);
|
||||||
if (ts.isIdentifier(n)) {
|
let goType = computed.goType
|
||||||
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 = {
|
let ans = {
|
||||||
me: node,
|
me: node,
|
||||||
id: id,
|
id: id,
|
||||||
goName: toGoName(getText(id)),
|
goName: toGoName(getText(id)),
|
||||||
optional: opt,
|
optional: opt,
|
||||||
goType: goType,
|
goType: goType,
|
||||||
json: `\`json:"${id.text}${opt ? ',omitempty' : ''}"\``
|
json: `\`json:"${id.text}${opt ? ',omitempty' : ''}"\``,
|
||||||
|
substruct: computed.fields
|
||||||
};
|
};
|
||||||
return ans
|
return ans
|
||||||
}
|
}
|
||||||
|
@ -199,16 +198,16 @@ function generate(files: string[], options: ts.CompilerOptions): void {
|
||||||
// find the Identifier from children
|
// find the Identifier from children
|
||||||
// process the PropertySignature children
|
// process the PropertySignature children
|
||||||
// the members might have generic info, but so do the children
|
// the members might have generic info, but so do the children
|
||||||
let id: ts.Identifier;
|
let id: ts.Identifier = node.name
|
||||||
let extend: ts.HeritageClause
|
let extend: ts.HeritageClause
|
||||||
let generid: ts.Identifier
|
let generid: ts.Identifier
|
||||||
let properties = new Array<ts.PropertySignature>()
|
let properties = new Array<ts.PropertySignature>()
|
||||||
let index: ts.IndexSignatureDeclaration // generate some sort of map
|
let index: ts.IndexSignatureDeclaration // generate some sort of map
|
||||||
|
let bad = false; // maybe we don't care about this one at all
|
||||||
node.forEachChild((n: ts.Node) => {
|
node.forEachChild((n: ts.Node) => {
|
||||||
if (n.kind == ts.SyntaxKind.ExportKeyword || ts.isMethodSignature(n)) {
|
if (n.kind == ts.SyntaxKind.ExportKeyword || ts.isMethodSignature(n)) {
|
||||||
// ignore
|
// ignore
|
||||||
} else if (ts.isIdentifier(n)) {
|
} else if (ts.isIdentifier(n)) {
|
||||||
id = n;
|
|
||||||
} else if (ts.isHeritageClause(n)) {
|
} else if (ts.isHeritageClause(n)) {
|
||||||
extend = n;
|
extend = n;
|
||||||
} else if (ts.isTypeParameterDeclaration(n)) {
|
} else if (ts.isTypeParameterDeclaration(n)) {
|
||||||
|
@ -221,10 +220,13 @@ function generate(files: string[], options: ts.CompilerOptions): void {
|
||||||
throw new Error(`${loc(n)} multiple index expressions`)
|
throw new Error(`${loc(n)} multiple index expressions`)
|
||||||
}
|
}
|
||||||
index = n
|
index = n
|
||||||
|
} else if (n.kind == ts.SyntaxKind.CallSignature) {
|
||||||
|
bad = true;
|
||||||
} else {
|
} else {
|
||||||
throw new Error(`${loc(n)} doInterface ${ts.SyntaxKind[n.kind]} `)
|
throw new Error(`${loc(n)} doInterface ${strKind(n)} `)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
if (bad) return;
|
||||||
let fields: Field[] = [];
|
let fields: Field[] = [];
|
||||||
for (const p of properties) {
|
for (const p of properties) {
|
||||||
fields.push(genProp(p, generid))
|
fields.push(genProp(p, generid))
|
||||||
|
@ -235,7 +237,7 @@ function generate(files: string[], options: ts.CompilerOptions): void {
|
||||||
const ans = {
|
const ans = {
|
||||||
me: node,
|
me: node,
|
||||||
name: toGoName(getText(id)),
|
name: toGoName(getText(id)),
|
||||||
embeds: heritageStrs(extend),
|
extends: heritageStrs(extend),
|
||||||
fields: fields
|
fields: fields
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -301,11 +303,12 @@ function generate(files: string[], options: ts.CompilerOptions): void {
|
||||||
substruct: fields,
|
substruct: fields,
|
||||||
json: `\`json:"${id.text}${opt ? ',omitempty' : ''}"\``
|
json: `\`json:"${id.text}${opt ? ',omitempty' : ''}"\``
|
||||||
};
|
};
|
||||||
// Rather than checking that goName is a const type, just do
|
// These are not structs, so '*' would be wrong.
|
||||||
switch (goType) {
|
switch (goType) {
|
||||||
case 'CompletionItemKind':
|
case 'CompletionItemKind':
|
||||||
case 'TextDocumentSyncKind':
|
case 'TextDocumentSyncKind':
|
||||||
case 'CodeActionKind':
|
case 'CodeActionKind':
|
||||||
|
case 'FailureHandlingKind': // string
|
||||||
case 'InsertTextFormat': // float64
|
case 'InsertTextFormat': // float64
|
||||||
case 'DiagnosticSeverity':
|
case 'DiagnosticSeverity':
|
||||||
ans.optional = false
|
ans.optional = false
|
||||||
|
@ -315,18 +318,8 @@ function generate(files: string[], options: ts.CompilerOptions): void {
|
||||||
|
|
||||||
function doModuleDeclaration(node: ts.ModuleDeclaration) {
|
function doModuleDeclaration(node: ts.ModuleDeclaration) {
|
||||||
// Export Identifier ModuleBlock
|
// Export Identifier ModuleBlock
|
||||||
let id: ts.Identifier;
|
let id: ts.Identifier = (ts.isIdentifier(node.name) && node.name);
|
||||||
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
|
// Don't want FunctionDeclarations
|
||||||
// mb has VariableStatement and useless TypeAliasDeclaration
|
|
||||||
// some of the VariableStatement are consts, and want their comments
|
// some of the VariableStatement are consts, and want their comments
|
||||||
// and each VariableStatement is Export, VariableDeclarationList
|
// and each VariableStatement is Export, VariableDeclarationList
|
||||||
// and each VariableDeclarationList is a single VariableDeclaration
|
// and each VariableDeclarationList is a single VariableDeclaration
|
||||||
|
@ -352,15 +345,13 @@ function generate(files: string[], options: ts.CompilerOptions): void {
|
||||||
|
|
||||||
function buildConst(tname: string, node: ts.VariableDeclaration): Const {
|
function buildConst(tname: string, node: ts.VariableDeclaration): Const {
|
||||||
// node is Identifier, optional-goo, (FirstLiteralToken|StringLiteral)
|
// node is Identifier, optional-goo, (FirstLiteralToken|StringLiteral)
|
||||||
let id: ts.Identifier
|
let id: ts.Identifier = (ts.isIdentifier(node.name) && node.name);
|
||||||
let str: string
|
let str: string
|
||||||
let first: string
|
let first: string
|
||||||
node.forEachChild((n: ts.Node) => {
|
node.forEachChild((n: ts.Node) => {
|
||||||
if (ts.isIdentifier(n)) {
|
if (ts.isStringLiteral(n)) {
|
||||||
id = n
|
|
||||||
} else if (ts.isStringLiteral(n)) {
|
|
||||||
str = getText(n)
|
str = getText(n)
|
||||||
} else if (n.kind == ts.SyntaxKind.FirstLiteralToken) {
|
} else if (n.kind == ts.SyntaxKind.NumericLiteral) {
|
||||||
first = getText(n)
|
first = getText(n)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -393,17 +384,9 @@ function generate(files: string[], options: ts.CompilerOptions): void {
|
||||||
function doEnumDecl(node: ts.EnumDeclaration) {
|
function doEnumDecl(node: ts.EnumDeclaration) {
|
||||||
// Generates Consts. Identifier EnumMember+
|
// Generates Consts. Identifier EnumMember+
|
||||||
// EnumMember: Identifier StringLiteral
|
// EnumMember: Identifier StringLiteral
|
||||||
let id: ts.Identifier
|
let id: ts.Identifier = node.name
|
||||||
let mems: ts.EnumMember[] = []
|
let mems = node.members
|
||||||
node.forEachChild((n: ts.Node) => {
|
let theType = 'string';
|
||||||
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) {
|
for (const m of mems) {
|
||||||
let name: string
|
let name: string
|
||||||
let value: string
|
let value: string
|
||||||
|
@ -412,13 +395,16 @@ function generate(files: string[], options: ts.CompilerOptions): void {
|
||||||
name = getText(n)
|
name = getText(n)
|
||||||
} else if (ts.isStringLiteral(n)) {
|
} else if (ts.isStringLiteral(n)) {
|
||||||
value = getText(n).replace(/'/g, '"')
|
value = getText(n).replace(/'/g, '"')
|
||||||
|
} else if (ts.isNumericLiteral(n)) {
|
||||||
|
value = getText(n);
|
||||||
|
theType = 'float64';
|
||||||
} else {
|
} else {
|
||||||
throw new Error(`in doEnumDecl ${ts.SyntaxKind[n.kind]} ${loc(n)}`)
|
throw new Error(`in doEnumDecl ${strKind(n)} ${loc(n)}`)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
let ans = {
|
let ans = {
|
||||||
typeName: getText(id),
|
typeName: getText(id),
|
||||||
goType: 'string',
|
goType: theType,
|
||||||
me: m,
|
me: m,
|
||||||
name: name,
|
name: name,
|
||||||
value: value
|
value: value
|
||||||
|
@ -430,31 +416,20 @@ function generate(files: string[], options: ts.CompilerOptions): void {
|
||||||
// top-level TypeAlias
|
// top-level TypeAlias
|
||||||
function doTypeAlias(node: ts.TypeAliasDeclaration) {
|
function doTypeAlias(node: ts.TypeAliasDeclaration) {
|
||||||
// these are all Export Identifier alias
|
// these are all Export Identifier alias
|
||||||
let id: ts.Identifier;
|
let id: ts.Identifier = node.name;
|
||||||
let alias: ts.Node;
|
let alias: ts.Node = node.type;
|
||||||
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 = {
|
let ans = {
|
||||||
me: node,
|
me: node,
|
||||||
id: id,
|
id: id,
|
||||||
goName: toGoName(getText(id)),
|
goName: toGoName(getText(id)),
|
||||||
goType: '?',
|
goType: '?', // filled in later in this function
|
||||||
stuff: ''
|
stuff: ''
|
||||||
};
|
};
|
||||||
if (id.text.indexOf('--') != -1) {
|
|
||||||
return
|
|
||||||
} // don't care
|
|
||||||
if (ts.isUnionTypeNode(alias)) {
|
if (ts.isUnionTypeNode(alias)) {
|
||||||
ans.goType = weirdUnionType(alias)
|
ans.goType = weirdUnionType(alias)
|
||||||
if (ans.goType == undefined) { // these are redundant
|
if (id.text == 'DocumentFilter')
|
||||||
|
if (ans.goType == undefined) {
|
||||||
|
// these are mostly redundant; maybe sort them out later
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
Types.push(ans)
|
Types.push(ans)
|
||||||
|
@ -464,9 +439,12 @@ function generate(files: string[], options: ts.CompilerOptions): void {
|
||||||
let embeds: string[] = []
|
let embeds: string[] = []
|
||||||
alias.forEachChild((n: ts.Node) => {
|
alias.forEachChild((n: ts.Node) => {
|
||||||
if (ts.isTypeReferenceNode(n)) {
|
if (ts.isTypeReferenceNode(n)) {
|
||||||
embeds.push(toGoName(computeType(n).goType))
|
const s = toGoName(computeType(n).goType)
|
||||||
|
embeds.push(s)
|
||||||
|
// It's here just for embedding, and not used independently
|
||||||
|
dontEmit.set(s, true);
|
||||||
} else
|
} else
|
||||||
throw new Error(`expected TypeRef ${ts.SyntaxKind[n.kind]} ${loc(n)}`)
|
throw new Error(`expected TypeRef ${strKind(n)} ${loc(n)}`)
|
||||||
})
|
})
|
||||||
let ans = {me: node, name: toGoName(getText(id)), embeds: embeds};
|
let ans = {me: node, name: toGoName(getText(id)), embeds: embeds};
|
||||||
Structs.push(ans)
|
Structs.push(ans)
|
||||||
|
@ -480,6 +458,9 @@ function generate(files: string[], options: ts.CompilerOptions): void {
|
||||||
if (ts.isLiteralTypeNode(alias)) {
|
if (ts.isLiteralTypeNode(alias)) {
|
||||||
return // type A = 1, so nope
|
return // type A = 1, so nope
|
||||||
}
|
}
|
||||||
|
if (ts.isTypeLiteralNode(alias)) {
|
||||||
|
return; // type A = {...}
|
||||||
|
}
|
||||||
if (ts.isTypeReferenceNode(alias)) {
|
if (ts.isTypeReferenceNode(alias)) {
|
||||||
ans.goType = computeType(alias).goType
|
ans.goType = computeType(alias).goType
|
||||||
if (ans.goType.match(/und/) != null) throw new Error('396')
|
if (ans.goType.match(/und/) != null) throw new Error('396')
|
||||||
|
@ -491,21 +472,36 @@ function generate(files: string[], options: ts.CompilerOptions): void {
|
||||||
Types.push(ans);
|
Types.push(ans);
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
throw new Error(`in doTypeAlias ${loc(node)} ${kinds(node)} ${
|
throw new Error(
|
||||||
ts.SyntaxKind[alias.kind]}\n`)
|
`in doTypeAlias ${loc(alias)} ${kinds(node)}: ${strKind(alias)}\n`)
|
||||||
}
|
}
|
||||||
|
|
||||||
// extract the one useful but weird case ()
|
// string, or number, or DocumentFilter
|
||||||
function weirdUnionType(node: ts.UnionTypeNode): string {
|
function weirdUnionType(node: ts.UnionTypeNode): string {
|
||||||
let bad = false
|
let bad = false;
|
||||||
|
let aNumber = false;
|
||||||
|
let aString = false;
|
||||||
let tl: ts.TypeLiteralNode[] = []
|
let tl: ts.TypeLiteralNode[] = []
|
||||||
node.forEachChild((n: ts.Node) => {
|
node.forEachChild((n: ts.Node) => {
|
||||||
if (ts.isTypeLiteralNode(n)) {
|
if (ts.isTypeLiteralNode(n)) {
|
||||||
tl.push(n)
|
tl.push(n);
|
||||||
} else
|
return;
|
||||||
|
}
|
||||||
|
if (ts.isLiteralTypeNode(n)) {
|
||||||
|
n.literal.kind == ts.SyntaxKind.NumericLiteral ? aNumber = true :
|
||||||
|
aString = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
bad = true
|
bad = true
|
||||||
})
|
})
|
||||||
if (bad) return // none of these are useful (so far)
|
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)}`);
|
||||||
|
return 'float64';
|
||||||
|
}
|
||||||
|
if (aString) return 'string';
|
||||||
let x = computeType(tl[0])
|
let x = computeType(tl[0])
|
||||||
x.fields[0].json = x.fields[0].json.replace(/"`/, ',omitempty"`')
|
x.fields[0].json = x.fields[0].json.replace(/"`/, ',omitempty"`')
|
||||||
let out: string[] = [];
|
let out: string[] = [];
|
||||||
|
@ -517,7 +513,8 @@ function generate(files: string[], options: ts.CompilerOptions): void {
|
||||||
return ans
|
return ans
|
||||||
}
|
}
|
||||||
|
|
||||||
function computeType(node: ts.Node): { goType: string, gostuff?: string, optional?: boolean, fields?: Field[] } {
|
function computeType(node: ts.Node):
|
||||||
|
{goType: string, gostuff?: string, optional?: boolean, fields?: Field[]} {
|
||||||
switch (node.kind) {
|
switch (node.kind) {
|
||||||
case ts.SyntaxKind.AnyKeyword:
|
case ts.SyntaxKind.AnyKeyword:
|
||||||
case ts.SyntaxKind.ObjectKeyword:
|
case ts.SyntaxKind.ObjectKeyword:
|
||||||
|
@ -545,8 +542,8 @@ function generate(files: string[], options: ts.CompilerOptions): void {
|
||||||
} else if (ts.isIdentifier(tn)) {
|
} else if (ts.isIdentifier(tn)) {
|
||||||
return {goType: tn.text};
|
return {goType: tn.text};
|
||||||
} else {
|
} else {
|
||||||
throw new Error(`expected identifier got ${
|
throw new Error(
|
||||||
ts.SyntaxKind[node.typeName.kind]} at ${loc(tn)}`)
|
`expected identifier got ${strKind(node.typeName)} at ${loc(tn)}`)
|
||||||
}
|
}
|
||||||
} else if (ts.isLiteralTypeNode(node)) {
|
} else if (ts.isLiteralTypeNode(node)) {
|
||||||
// string|float64 (are there other possibilities?)
|
// string|float64 (are there other possibilities?)
|
||||||
|
@ -568,8 +565,7 @@ function generate(files: string[], options: ts.CompilerOptions): void {
|
||||||
x.push(fromIndexSignature(n))
|
x.push(fromIndexSignature(n))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
throw new Error(
|
throw new Error(`${loc(n)} gotype ${strKind(n)}, not expected`)
|
||||||
`${loc(n)} gotype ${ts.SyntaxKind[n.kind]}, not expected`)
|
|
||||||
});
|
});
|
||||||
if (indexCnt > 0) {
|
if (indexCnt > 0) {
|
||||||
if (indexCnt != 1 || x.length != 1)
|
if (indexCnt != 1 || x.length != 1)
|
||||||
|
@ -585,6 +581,9 @@ function generate(files: string[], options: ts.CompilerOptions): void {
|
||||||
return x[0] // make it optional somehow? TODO
|
return x[0] // make it optional somehow? TODO
|
||||||
}
|
}
|
||||||
if (x[0].goType == 'bool') { // take it
|
if (x[0].goType == 'bool') { // take it
|
||||||
|
if (x[1].goType == 'RenameOptions') {
|
||||||
|
return ({goType: 'RenameOptions', gostuff: getText(node)})
|
||||||
|
}
|
||||||
return ({goType: 'bool', gostuff: getText(node)})
|
return ({goType: 'bool', gostuff: getText(node)})
|
||||||
}
|
}
|
||||||
// these are special cases from looking at the source
|
// these are special cases from looking at the source
|
||||||
|
@ -605,6 +604,11 @@ function generate(files: string[], options: ts.CompilerOptions): void {
|
||||||
goType: 'MarkupContent', gostuff: gostuff
|
goType: 'MarkupContent', gostuff: gostuff
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (x[0].goType == 'RequestMessage' && x[1].goType == 'ResponseMessage') {
|
||||||
|
return {
|
||||||
|
goType: 'interface{}', gostuff: gostuff
|
||||||
|
}
|
||||||
|
}
|
||||||
// Fail loudly
|
// Fail loudly
|
||||||
console.log(`UnionType ${loc(node)}`)
|
console.log(`UnionType ${loc(node)}`)
|
||||||
for (const v of x) {
|
for (const v of x) {
|
||||||
|
@ -613,17 +617,22 @@ function generate(files: string[], options: ts.CompilerOptions): void {
|
||||||
throw new Error('in UnionType, weird')
|
throw new Error('in UnionType, weird')
|
||||||
} else if (ts.isParenthesizedTypeNode(node)) {
|
} else if (ts.isParenthesizedTypeNode(node)) {
|
||||||
// check that this is (TextDocumentEdit | CreateFile | RenameFile |
|
// check that this is (TextDocumentEdit | CreateFile | RenameFile |
|
||||||
// DeleteFile)
|
// DeleteFile) TODO(pjw)
|
||||||
return {
|
return {
|
||||||
goType: 'TextDocumentEdit', gostuff: getText(node)
|
goType: 'TextDocumentEdit', gostuff: getText(node)
|
||||||
}
|
}
|
||||||
} else if (ts.isTupleTypeNode(node)) {
|
} else if (ts.isTupleTypeNode(node)) {
|
||||||
// string | [number, number]
|
// string | [number, number]. TODO(pjw): check it really is
|
||||||
return {
|
return {
|
||||||
goType: 'string', gostuff: getText(node)
|
goType: 'string', gostuff: getText(node)
|
||||||
}
|
}
|
||||||
|
} else if (ts.isFunctionTypeNode(node)) {
|
||||||
|
// we don't want these members; mark them
|
||||||
|
return {
|
||||||
|
goType: 'bad', gostuff: getText(node)
|
||||||
}
|
}
|
||||||
throw new Error(`unknown ${ts.SyntaxKind[node.kind]} at ${loc(node)}`)
|
}
|
||||||
|
throw new Error(`computeType unknown ${strKind(node)} at ${loc(node)}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
function fromIndexSignature(node: ts.IndexSignatureDeclaration): Field {
|
function fromIndexSignature(node: ts.IndexSignatureDeclaration): Field {
|
||||||
|
@ -637,7 +646,7 @@ function generate(files: string[], options: ts.CompilerOptions): void {
|
||||||
ts.isUnionTypeNode(n)) {
|
ts.isUnionTypeNode(n)) {
|
||||||
at = n
|
at = n
|
||||||
} else
|
} else
|
||||||
throw new Error(`fromIndexSig ${ts.SyntaxKind[n.kind]} ${loc(n)}`)
|
throw new Error(`fromIndexSig ${strKind(n)} ${loc(n)}`)
|
||||||
})
|
})
|
||||||
let goType = computeType(at).goType
|
let goType = computeType(at).goType
|
||||||
let id: ts.Identifier
|
let id: ts.Identifier
|
||||||
|
@ -645,8 +654,7 @@ function generate(files: string[], options: ts.CompilerOptions): void {
|
||||||
if (ts.isIdentifier(n)) {
|
if (ts.isIdentifier(n)) {
|
||||||
id = n
|
id = n
|
||||||
} else if (n.kind != ts.SyntaxKind.StringKeyword) {
|
} else if (n.kind != ts.SyntaxKind.StringKeyword) {
|
||||||
throw new Error(
|
throw new Error(`fromIndexSig expected string, ${strKind(n)} ${loc(n)}`)
|
||||||
`fromIndexSig expected string, ${ts.SyntaxKind[n.kind]} ${loc(n)}`)
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
goType = `map[string]${goType}`
|
goType = `map[string]${goType}`
|
||||||
|
@ -668,7 +676,6 @@ function generate(files: string[], options: ts.CompilerOptions): void {
|
||||||
return ans
|
return ans
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// find the text of a node
|
// find the text of a node
|
||||||
function getText(node: ts.Node): string {
|
function getText(node: ts.Node): string {
|
||||||
let sf = node.getSourceFile();
|
let sf = node.getSourceFile();
|
||||||
|
@ -678,12 +685,39 @@ function generate(files: string[], options: ts.CompilerOptions): void {
|
||||||
}
|
}
|
||||||
// return a string of the kinds of the immediate descendants
|
// return a string of the kinds of the immediate descendants
|
||||||
function kinds(n: ts.Node): string {
|
function kinds(n: ts.Node): string {
|
||||||
let res = 'Seen ' + ts.SyntaxKind[n.kind];
|
let res = 'Seen ' + strKind(n);
|
||||||
function f(n: ts.Node): void { res += ' ' + ts.SyntaxKind[n.kind] };
|
function f(n: ts.Node): void{res += ' ' + strKind(n)};
|
||||||
ts.forEachChild(n, f)
|
ts.forEachChild(n, f)
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function strKind(n: ts.Node): string {
|
||||||
|
const x = ts.SyntaxKind[n.kind];
|
||||||
|
// some of these have two names
|
||||||
|
switch (x) {
|
||||||
|
default:
|
||||||
|
return x;
|
||||||
|
case 'FirstAssignment':
|
||||||
|
return 'EqualsToken';
|
||||||
|
case 'FirstBinaryOperator':
|
||||||
|
return 'LessThanToken';
|
||||||
|
case 'FirstCompoundAssignment':
|
||||||
|
return 'PlusEqualsToken';
|
||||||
|
case 'FirstContextualKeyword':
|
||||||
|
return 'AbstractKeyword';
|
||||||
|
case 'FirstLiteralToken':
|
||||||
|
return 'NumericLiteral';
|
||||||
|
case 'FirstNode':
|
||||||
|
return 'QualifiedName';
|
||||||
|
case 'FirstTemplateToken':
|
||||||
|
return 'NoSubstitutionTemplateLiteral';
|
||||||
|
case 'LastTemplateToken':
|
||||||
|
return 'TemplateTail';
|
||||||
|
case 'FirstTypeNode':
|
||||||
|
return 'TypePredicate';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function describe(node: ts.Node) {
|
function describe(node: ts.Node) {
|
||||||
if (node === undefined) {
|
if (node === undefined) {
|
||||||
return
|
return
|
||||||
|
@ -693,36 +727,22 @@ function generate(files: string[], options: ts.CompilerOptions): void {
|
||||||
function f(n: ts.Node) {
|
function f(n: ts.Node) {
|
||||||
seenAdd(kinds(n))
|
seenAdd(kinds(n))
|
||||||
if (ts.isIdentifier(n)) {
|
if (ts.isIdentifier(n)) {
|
||||||
pra(`${indent} ${loc(n)} ${ts.SyntaxKind[n.kind]} ${n.text}\n`)
|
pra(`${indent} ${loc(n)} ${strKind(n)} ${n.text}\n`)
|
||||||
}
|
}
|
||||||
else if (ts.isPropertySignature(n) || ts.isEnumMember(n)) {
|
else if (ts.isPropertySignature(n) || ts.isEnumMember(n)) {
|
||||||
pra(`${indent} ${loc(n)} ${ts.SyntaxKind[n.kind]}\n`)
|
pra(`${indent} ${loc(n)} ${strKind(n)}\n`)
|
||||||
}
|
}
|
||||||
else if (ts.isTypeLiteralNode(n)) {
|
else if (ts.isTypeLiteralNode(n)) {
|
||||||
let m = n.members
|
let m = n.members
|
||||||
pra(`${indent} ${loc(n)} ${ts.SyntaxKind[n.kind]} ${m.length}\n`)
|
pra(`${indent} ${loc(n)} ${strKind(n)} ${m.length}\n`)
|
||||||
}
|
}
|
||||||
else { pra(`${indent} ${loc(n)} ${ts.SyntaxKind[n.kind]}\n`) };
|
else {pra(`${indent} ${loc(n)} ${strKind(n)}\n`)};
|
||||||
indent += ' '
|
indent += ' '
|
||||||
ts.forEachChild(n, f)
|
ts.forEachChild(n, f)
|
||||||
indent = indent.slice(0, indent.length - 2)
|
indent = indent.slice(0, indent.length - 2)
|
||||||
}
|
}
|
||||||
f(node)
|
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 {
|
function getComments(node: ts.Node): string {
|
||||||
|
@ -733,40 +753,119 @@ function getComments(node: ts.Node): string {
|
||||||
return x
|
return x
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 emitTypes() {
|
function emitTypes() {
|
||||||
|
seenConstTypes.set('MessageQueue', true); // skip
|
||||||
for (const t of Types) {
|
for (const t of Types) {
|
||||||
|
if (seenConstTypes.get(t.goName)) continue;
|
||||||
if (t.goName == 'CodeActionKind') continue; // consts better choice
|
if (t.goName == 'CodeActionKind') continue; // consts better choice
|
||||||
|
if (t.goType === undefined) continue;
|
||||||
let stuff = (t.stuff == undefined) ? '' : t.stuff;
|
let stuff = (t.stuff == undefined) ? '' : t.stuff;
|
||||||
prgo(`// ${t.goName} is a type\n`)
|
prgo(`// ${t.goName} is a type\n`)
|
||||||
prgo(`${getComments(t.me)}`)
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let byName = new Map<string, Struct>();
|
||||||
function emitStructs() {
|
function emitStructs() {
|
||||||
|
dontEmit.set('Thenable', true);
|
||||||
|
dontEmit.set('EmitterOptions', true);
|
||||||
|
dontEmit.set('MessageReader', true);
|
||||||
|
dontEmit.set('MessageWriter', true);
|
||||||
|
dontEmit.set('CancellationToken', true);
|
||||||
|
dontEmit.set('PipeTransport', true);
|
||||||
|
dontEmit.set('SocketTransport', true);
|
||||||
|
dontEmit.set('Item', true);
|
||||||
|
dontEmit.set('Event', true);
|
||||||
|
dontEmit.set('Logger', true);
|
||||||
|
dontEmit.set('Disposable', true);
|
||||||
|
dontEmit.set('PartialMessageInfo', true);
|
||||||
|
dontEmit.set('MessageConnection', true);
|
||||||
|
dontEmit.set('ResponsePromise', true);
|
||||||
|
dontEmit.set('ResponseMessage', true);
|
||||||
|
dontEmit.set('ErrorMessage', true);
|
||||||
|
dontEmit.set('NotificationMessage', true);
|
||||||
|
dontEmit.set('RequestHandlerElement', true);
|
||||||
|
dontEmit.set('RequestMessage', true);
|
||||||
|
dontEmit.set('NotificationHandlerElement', true);
|
||||||
|
dontEmit.set('Message', true); // duplicate of jsonrpc2:wire.go
|
||||||
|
dontEmit.set('LSPLogMessage', true);
|
||||||
|
dontEmit.set('InnerEM', true);
|
||||||
|
dontEmit.set('ResponseErrorLiteral', true);
|
||||||
|
dontEmit.set('TraceOptions', true);
|
||||||
|
dontEmit.set('MessageType', true); // want the enum
|
||||||
|
// backwards compatibility, done in requests.ts:
|
||||||
|
dontEmit.set('CancelParams', true);
|
||||||
|
|
||||||
|
for (const str of Structs) {
|
||||||
|
byName.set(str.name, str)
|
||||||
|
}
|
||||||
let seenName = new Map<string, boolean>()
|
let seenName = new Map<string, boolean>()
|
||||||
for (const str of Structs) {
|
for (const str of Structs) {
|
||||||
if (str.name == 'InitializeError') {
|
if (str.name == 'InitializeError') {
|
||||||
// only want the consts
|
// only want its consts, not the struct
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if (seenName[str.name]) {
|
if (seenName.get(str.name) || dontEmit.get(str.name)) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
seenName[str.name] = true
|
let noopt = false;
|
||||||
|
seenName.set(str.name, true)
|
||||||
prgo(genComments(str.name, getComments(str.me)))
|
prgo(genComments(str.name, getComments(str.me)))
|
||||||
/* prgo(`// ${str.name} is:\n`)
|
|
||||||
prgo(getComments(str.me))*/
|
|
||||||
prgo(`type ${str.name} struct {\n`)
|
prgo(`type ${str.name} struct {\n`)
|
||||||
for (const s of str.embeds) {
|
// if it has fields, generate them
|
||||||
prgo(`\t${s}\n`)
|
|
||||||
}
|
|
||||||
if (str.fields != undefined) {
|
if (str.fields != undefined) {
|
||||||
for (const f of str.fields) {
|
for (const f of str.fields) {
|
||||||
prgo(strField(f))
|
prgo(strField(f, noopt))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
prgo(`}\n`)
|
if (str.extends) {
|
||||||
|
// ResourceOperation just repeats the Kind field
|
||||||
|
for (const s of str.extends) {
|
||||||
|
if (s != 'ResourceOperation')
|
||||||
|
prgo(`\t${s}\n`) // what this type extends.
|
||||||
|
}
|
||||||
|
} else if (str.embeds) {
|
||||||
|
prb(`embeds: ${str.name}\n`);
|
||||||
|
noopt = (str.name == 'ClientCapabilities');
|
||||||
|
// embedded struct. the hard case is from intersection types,
|
||||||
|
// where fields with the same name have to be combined into
|
||||||
|
// a single struct
|
||||||
|
let fields = new Map<string, Field[]>();
|
||||||
|
for (const e of str.embeds) {
|
||||||
|
const nm = byName.get(e);
|
||||||
|
if (nm.embeds) throw new Error(`${nm.name} is an embedded embed`);
|
||||||
|
// each of these fields might be a something that needs amalgamating
|
||||||
|
for (const f of nm.fields) {
|
||||||
|
let x = fields.get(f.goName);
|
||||||
|
if (x === undefined) x = [];
|
||||||
|
x.push(f);
|
||||||
|
fields.set(f.goName, x);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fields.forEach((val) => {
|
||||||
|
if (val.length > 1) {
|
||||||
|
// merge the fields with the same name
|
||||||
|
prgo(strField(val[0], noopt, val));
|
||||||
|
} else {
|
||||||
|
prgo(strField(val[0], noopt));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
prgo(`}\n`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -779,9 +878,9 @@ function genComments(name: string, maybe: string): string {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Turn a Field into an output string
|
// Turn a Field into an output string
|
||||||
function strField(f: Field): string {
|
function strField(f: Field, noopt?: boolean, flds?: Field[]): string {
|
||||||
let ans: string[] = [];
|
let ans: string[] = [];
|
||||||
let opt = f.optional ? '*' : ''
|
let opt = (!noopt && f.optional) ? '*' : ''
|
||||||
switch (f.goType.charAt(0)) {
|
switch (f.goType.charAt(0)) {
|
||||||
case 's': // string
|
case 's': // string
|
||||||
case 'b': // bool
|
case 'b': // bool
|
||||||
|
@ -792,13 +891,31 @@ function strField(f: Field): string {
|
||||||
}
|
}
|
||||||
let stuff = (f.gostuff == undefined) ? '' : ` // ${f.gostuff}`
|
let stuff = (f.gostuff == undefined) ? '' : ` // ${f.gostuff}`
|
||||||
ans.push(genComments(f.goName, getComments(f.me)))
|
ans.push(genComments(f.goName, getComments(f.me)))
|
||||||
if (f.substruct == undefined) {
|
if (flds === undefined && f.substruct == undefined) {
|
||||||
ans.push(`\t${f.goName} ${opt}${f.goType} ${f.json}${stuff}\n`)
|
ans.push(`\t${f.goName} ${opt}${f.goType} ${f.json}${stuff}\n`)
|
||||||
}
|
}
|
||||||
|
else if (flds !== undefined) {
|
||||||
|
ans.push(`\t${f.goName} ${opt}struct{\n`);
|
||||||
|
for (const ff of flds) {
|
||||||
|
if (ff.substruct !== undefined) {
|
||||||
|
for (const x of ff.substruct) {
|
||||||
|
ans.push(strField(x, noopt))
|
||||||
|
}
|
||||||
|
} else if (byName.get(ff.goType) !== undefined) {
|
||||||
|
const st = byName.get(ff.goType);
|
||||||
|
for (let i = 0; i < st.fields.length; i++) {
|
||||||
|
ans.push(strField(st.fields[i], noopt))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ans.push(strField(ff, noopt));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ans.push(`\t} ${f.json}${stuff}\n`);
|
||||||
|
}
|
||||||
else {
|
else {
|
||||||
ans.push(`\t${f.goName} ${opt}struct {\n`)
|
ans.push(`\t${f.goName} ${opt}struct {\n`)
|
||||||
for (const x of f.substruct) {
|
for (const x of f.substruct) {
|
||||||
ans.push(strField(x))
|
ans.push(strField(x, noopt))
|
||||||
}
|
}
|
||||||
ans.push(`\t} ${f.json}${stuff}\n`)
|
ans.push(`\t} ${f.json}${stuff}\n`)
|
||||||
}
|
}
|
||||||
|
@ -806,19 +923,19 @@ function strField(f: Field): string {
|
||||||
}
|
}
|
||||||
|
|
||||||
function emitConsts() {
|
function emitConsts() {
|
||||||
// Generate modifying prefixes and suffixes to ensure consts are
|
// need the consts too! Generate modifying prefixes and suffixes to ensure
|
||||||
// unique. (Go consts are package-level, but Typescript's are not.)
|
// consts are unique. (Go consts are package-level, but Typescript's are
|
||||||
// Use suffixes to minimize changes to gopls.
|
// not.) Use suffixes to minimize changes to gopls.
|
||||||
let pref = new Map<string, string>(
|
let pref = new Map<string, string>(
|
||||||
[['DiagnosticSeverity', 'Severity']]) // typeName->prefix
|
[['DiagnosticSeverity', 'Severity']]) // typeName->prefix
|
||||||
let suff = new Map<string, string>([
|
let suff = new Map<string, string>([
|
||||||
['CompletionItemKind', 'Completion'], ['InsertTextFormat', 'TextFormat']
|
['CompletionItemKind', 'Completion'], ['InsertTextFormat', 'TextFormat']
|
||||||
])
|
])
|
||||||
for (const c of Consts) {
|
for (const c of Consts) {
|
||||||
if (seenConstTypes[c.typeName]) {
|
if (seenConstTypes.get(c.typeName)) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
seenConstTypes[c.typeName] = true
|
seenConstTypes.set(c.typeName, true);
|
||||||
if (pref.get(c.typeName) == undefined) {
|
if (pref.get(c.typeName) == undefined) {
|
||||||
pref.set(c.typeName, '') // initialize to empty value
|
pref.set(c.typeName, '') // initialize to empty value
|
||||||
}
|
}
|
||||||
|
@ -836,6 +953,8 @@ function emitConsts() {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
seenConsts.set(x, true)
|
seenConsts.set(x, true)
|
||||||
|
if (c.value === undefined) continue; // didn't figure it out
|
||||||
|
if (x.startsWith('undefined')) continue; // what's going on here?
|
||||||
prgo(genComments(x, getComments(c.me)))
|
prgo(genComments(x, getComments(c.me)))
|
||||||
prgo(`\t${x} ${c.typeName} = ${c.value}\n`)
|
prgo(`\t${x} ${c.typeName} = ${c.value}\n`)
|
||||||
}
|
}
|
||||||
|
@ -852,10 +971,13 @@ function emitHeader(files: string[]) {
|
||||||
lastDate = st.mtime
|
lastDate = st.mtime
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
prgo(`// Package protocol contains data types for LSP jsonrpcs\n`)
|
let a = fs.readFileSync(`${dir}${srcDir}/.git/refs/heads/master`);
|
||||||
prgo(`// generated automatically from vscode-languageserver-node
|
prgo(`// Package protocol contains data types and code for LSP jsonrpcs\n`)
|
||||||
// version of ${lastDate}\n`)
|
prgo(`// generated automatically from vscode-languageserver-node\n`)
|
||||||
|
prgo(`// commit: ${a.toString()}`)
|
||||||
|
prgo(`// last fetched ${lastDate}\n`)
|
||||||
prgo('package protocol\n\n')
|
prgo('package protocol\n\n')
|
||||||
|
prgo(`// Code generated (see typescript/README.md) DO NOT EDIT.\n`);
|
||||||
};
|
};
|
||||||
|
|
||||||
// ad hoc argument parsing: [-d dir] [-o outputfile], and order matters
|
// ad hoc argument parsing: [-d dir] [-o outputfile], and order matters
|
||||||
|
|
|
@ -0,0 +1,472 @@
|
||||||
|
import * as fs from 'fs';
|
||||||
|
import * as ts from 'typescript';
|
||||||
|
|
||||||
|
// generate tsclient.go and tsserver.go, which are the definitions and stubs for
|
||||||
|
// supporting the LPS protocol. These files have 3 sections:
|
||||||
|
// 1. define the Client or Server type
|
||||||
|
// 2. fill out the clientHandler or serveHandler which is basically a large
|
||||||
|
// switch on the requests and notifications received by the client/server.
|
||||||
|
// 3. The methods corresponding to these. (basically parse the request,
|
||||||
|
// call something, and perhaps send a response.)
|
||||||
|
|
||||||
|
let dir = process.env['HOME'];
|
||||||
|
let fnames = [
|
||||||
|
`/vscode-languageserver-node/protocol/src/protocol.ts`,
|
||||||
|
`/vscode-languageserver-node/jsonrpc/src/main.ts`
|
||||||
|
];
|
||||||
|
|
||||||
|
let fda: number, fdy: number; // file descriptors
|
||||||
|
|
||||||
|
function createOutputFiles() {
|
||||||
|
fda = fs.openSync('/tmp/ts-a', 'w') // dump of AST
|
||||||
|
fdy = fs.openSync('/tmp/ts-c', 'w') // unused, for debugging
|
||||||
|
}
|
||||||
|
function pra(s: string) {
|
||||||
|
return (fs.writeSync(fda, s))
|
||||||
|
}
|
||||||
|
function prb(s: string) {
|
||||||
|
return (fs.writeSync(fdy, s + '\n'))
|
||||||
|
}
|
||||||
|
|
||||||
|
let program: ts.Program;
|
||||||
|
function generate(files: string[], options: ts.CompilerOptions): void {
|
||||||
|
program = ts.createProgram(files, options);
|
||||||
|
program.getTypeChecker();
|
||||||
|
|
||||||
|
dumpAST(); // for debugging
|
||||||
|
|
||||||
|
// visit every sourceFile in the program, collecting information
|
||||||
|
for (const sourceFile of program.getSourceFiles()) {
|
||||||
|
if (!sourceFile.isDeclarationFile) {
|
||||||
|
ts.forEachChild(sourceFile, genStuff)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// when 4 args, they are param, result, error, registration options, e.g.:
|
||||||
|
// RequestType<TextDocumentPositionParams, Definition | DefinitionLink[] |
|
||||||
|
// null,
|
||||||
|
// void, TextDocumentRegistrationOptions>('textDocument/implementation');
|
||||||
|
// 3 args is RequestType0('shutdown')<void, void, void>
|
||||||
|
// and RequestType0('workspace/workspaceFolders)<WorkspaceFolder[]|null, void,
|
||||||
|
// void>
|
||||||
|
|
||||||
|
// the two args are the notification data and the registration data
|
||||||
|
// except for textDocument/selectionRange and a NotificationType0('exit')
|
||||||
|
// selectionRange is the following, but for now do it by hand, special case.
|
||||||
|
// RequestType<TextDocumentPositionParams, SelectionRange[] | null, any, any>
|
||||||
|
// = new RequestType('textDocument/selectionRange')
|
||||||
|
// and foldingRange has the same problem.
|
||||||
|
|
||||||
|
setReceives(); // distinguish client and server
|
||||||
|
// for each of Client and Server there are 3 parts to the output:
|
||||||
|
// 1. type X interface {methods}
|
||||||
|
// 2. serverHandler(...) { return func(...) { switch r.method}}
|
||||||
|
// 3. func (x *xDispatcher) Method(ctx, parm)
|
||||||
|
not.forEach(
|
||||||
|
(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)});
|
||||||
|
// and print the Go code
|
||||||
|
output(client);
|
||||||
|
output(server);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Go signatures for methods.
|
||||||
|
function sig(nm: string, a: string, b: string, names?: boolean): string {
|
||||||
|
if (a != '') {
|
||||||
|
if (names)
|
||||||
|
a = ', params *' + a;
|
||||||
|
else
|
||||||
|
a = ', *' + a;
|
||||||
|
}
|
||||||
|
let ret = 'error';
|
||||||
|
if (b != '') {
|
||||||
|
b.startsWith('[]') || b.startsWith('interface') || (b = '*' + b);
|
||||||
|
ret = `(${b}, error)`;
|
||||||
|
}
|
||||||
|
let start = `${nm}(`;
|
||||||
|
if (names) {
|
||||||
|
start = start + 'ctx ';
|
||||||
|
}
|
||||||
|
return `${start}context.Context${a}) ${ret}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const notNil = `if r.Params != nil {
|
||||||
|
conn.Reply(ctx, r, nil, jsonrpc2.NewErrorf(jsonrpc2.CodeInvalidParams, "Expected no params"))
|
||||||
|
return
|
||||||
|
}`;
|
||||||
|
// 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 b = goType(m, n.typeArguments[1]); These are registration options
|
||||||
|
const nm = methodName(m);
|
||||||
|
side.methods.push(sig(nm, a, ''));
|
||||||
|
const caseHdr = `case "${m}": // notif`;
|
||||||
|
let case1 = notNil;
|
||||||
|
if (a != '') {
|
||||||
|
case1 = `var params ${a}
|
||||||
|
if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
|
||||||
|
sendParseError(ctx, log, conn, r, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := ${side.name}.${nm}(ctx, ¶ms); err != nil {
|
||||||
|
log.Errorf(ctx, "%v", err)
|
||||||
|
}`;
|
||||||
|
} else {
|
||||||
|
case1 = `if err := ${side.name}.${nm}(ctx); err != nil {
|
||||||
|
log.Errorf(ctx, "%v", err)
|
||||||
|
}`;
|
||||||
|
}
|
||||||
|
side.cases.push(`${caseHdr}\n${case1}`);
|
||||||
|
|
||||||
|
const arg3 = a == '' ? 'nil' : 'params';
|
||||||
|
side.calls.push(`
|
||||||
|
func (s *${side.name}Dispatcher) ${sig(nm, a, '', true)} {
|
||||||
|
return s.Conn.Notify(ctx, "${m}", ${arg3})
|
||||||
|
}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Go code for requests.
|
||||||
|
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]);
|
||||||
|
if (n.getText().includes('Type0')) {
|
||||||
|
b = a;
|
||||||
|
a = ''; // workspace/workspaceFolders and shutdown
|
||||||
|
}
|
||||||
|
prb(`${side.name} req ${a != ''},${b != ''} ${nm} ${m} ${loc(n)}`)
|
||||||
|
side.methods.push(sig(nm, a, b));
|
||||||
|
|
||||||
|
const caseHdr = `case "${m}": // req`;
|
||||||
|
let case1 = notNil;
|
||||||
|
if (a != '') {
|
||||||
|
case1 = `var params ${a}
|
||||||
|
if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
|
||||||
|
sendParseError(ctx, log, conn, r, err)
|
||||||
|
return
|
||||||
|
}`;
|
||||||
|
}
|
||||||
|
const arg2 = a == '' ? '' : ', ¶ms';
|
||||||
|
let case2 = `if err := ${side.name}.${nm}(ctx${arg2}); err != nil {
|
||||||
|
log.Errorf(ctx, "%v", err)
|
||||||
|
}`;
|
||||||
|
if (b != '') {
|
||||||
|
case2 = `resp, err := ${side.name}.${nm}(ctx${arg2})
|
||||||
|
if err := conn.Reply(ctx, r, resp, err); err != nil {
|
||||||
|
log.Errorf(ctx, "%v", err)
|
||||||
|
}`;
|
||||||
|
} else { // response is nil
|
||||||
|
case2 = `err := ${side.name}.${nm}(ctx${arg2})
|
||||||
|
if err := conn.Reply(ctx, r, nil, err); err != nil {
|
||||||
|
log.Errorf(ctx, "%v", err)
|
||||||
|
}`
|
||||||
|
}
|
||||||
|
|
||||||
|
side.cases.push(`${caseHdr}\n${case1}\n${case2}`);
|
||||||
|
|
||||||
|
const callHdr = `func (s *${side.name}Dispatcher) ${sig(nm, a, b, true)} {`;
|
||||||
|
let callBody = `return s.Conn.Call(ctx, "${m}", nil, nil)\n}`;
|
||||||
|
if (b != '') {
|
||||||
|
const p2 = a == '' ? 'nil' : 'params';
|
||||||
|
let theRet = `result`;
|
||||||
|
!b.startsWith('[]') && !b.startsWith('interface') && (theRet = '&result');
|
||||||
|
callBody = `var result ${b}
|
||||||
|
if err := s.Conn.Call(ctx, "${m}", ${
|
||||||
|
p2}, &result); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return ${theRet}, nil
|
||||||
|
}`;
|
||||||
|
} else if (a != '') {
|
||||||
|
callBody = `return s.Conn.Call(ctx, "${m}", params, nil) // Call, not Notify
|
||||||
|
}`
|
||||||
|
}
|
||||||
|
side.calls.push(`${callHdr}\n${callBody}\n`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// make sure method names are unique
|
||||||
|
let seenNames = new Set<string>();
|
||||||
|
function methodName(m: string): string {
|
||||||
|
const i = m.indexOf('/');
|
||||||
|
let s = m.substring(i + 1);
|
||||||
|
let x = s[0].toUpperCase() + s.substring(1);
|
||||||
|
if (seenNames.has(x)) {
|
||||||
|
x += m[0].toUpperCase() + m.substring(1, i);
|
||||||
|
}
|
||||||
|
seenNames.add(x);
|
||||||
|
return x;
|
||||||
|
}
|
||||||
|
|
||||||
|
function output(side: side) {
|
||||||
|
if (side.outputFile === undefined) side.outputFile = `ts${side.name}.go`;
|
||||||
|
side.fd = fs.openSync(side.outputFile, 'w');
|
||||||
|
const f = function(s: string) {
|
||||||
|
fs.writeSync(side.fd, s);
|
||||||
|
fs.writeSync(side.fd, '\n');
|
||||||
|
};
|
||||||
|
f(`package protocol`);
|
||||||
|
f(`// Code generated (see typescript/README.md) DO NOT EDIT.\n`);
|
||||||
|
f(`
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
|
||||||
|
"golang.org/x/tools/internal/jsonrpc2"
|
||||||
|
"golang.org/x/tools/internal/lsp/xlog"
|
||||||
|
)
|
||||||
|
`);
|
||||||
|
const a = side.name[0].toUpperCase() + side.name.substring(1)
|
||||||
|
f(`type ${a} interface {`);
|
||||||
|
side.methods.forEach((v) => {f(v)});
|
||||||
|
f('}\n');
|
||||||
|
f(`func ${side.name}Handler(log xlog.Logger, ${side.name} ${
|
||||||
|
side.goName}) jsonrpc2.Handler {
|
||||||
|
return func(ctx context.Context, conn *jsonrpc2.Conn, r *jsonrpc2.Request) {
|
||||||
|
switch r.Method {
|
||||||
|
case "$/cancelRequest":
|
||||||
|
var params CancelParams
|
||||||
|
if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
|
||||||
|
sendParseError(ctx, log, conn, r, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
conn.Cancel(params.ID)`);
|
||||||
|
side.cases.forEach((v) => {f(v)});
|
||||||
|
f(`
|
||||||
|
default:
|
||||||
|
if r.IsNotify() {
|
||||||
|
conn.Reply(ctx, r, nil, jsonrpc2.NewErrorf(jsonrpc2.CodeMethodNotFound, "method %q not found", r.Method))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}`);
|
||||||
|
f(`
|
||||||
|
type ${side.name}Dispatcher struct {
|
||||||
|
*jsonrpc2.Conn
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
side.calls.forEach((v) => {f(v)});
|
||||||
|
if (side.name == 'server')
|
||||||
|
f(`
|
||||||
|
type CancelParams struct {
|
||||||
|
/**
|
||||||
|
* The request id to cancel.
|
||||||
|
*/
|
||||||
|
ID jsonrpc2.ID \`json:"id"\`
|
||||||
|
}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
interface side {
|
||||||
|
methods: string[];
|
||||||
|
cases: string[];
|
||||||
|
calls: 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 req = new Map<string, ts.NewExpression>(); // requests
|
||||||
|
let not = new Map<string, ts.NewExpression>(); // notifications
|
||||||
|
let receives = new Map<string, 'server'|'client'>(); // who receives it
|
||||||
|
|
||||||
|
function setReceives() {
|
||||||
|
// mark them all as server, then adjust the client ones.
|
||||||
|
// it would be nice to have some independent check
|
||||||
|
req.forEach((_, k) => {receives.set(k, 'server')});
|
||||||
|
not.forEach((_, k) => {receives.set(k, 'server')});
|
||||||
|
receives.set('window/logMessage', 'client');
|
||||||
|
receives.set('telemetry/event', 'client');
|
||||||
|
receives.set('client/registerCapability', 'client');
|
||||||
|
receives.set('client/unregisterCapability', 'client');
|
||||||
|
receives.set('window/showMessage', 'client');
|
||||||
|
receives.set('window/showMessageRequest', 'client');
|
||||||
|
receives.set('workspace/workspaceFolders', 'client');
|
||||||
|
receives.set('workspace/configuration', 'client');
|
||||||
|
receives.set('workspace/applyEdit', 'client');
|
||||||
|
receives.set('textDocument/publishDiagnostics', 'client');
|
||||||
|
// a small check
|
||||||
|
receives.forEach((_, k) => {
|
||||||
|
if (!req.get(k) && !not.get(k)) throw new Error(`missing ${k}}`);
|
||||||
|
if (req.get(k) && not.get(k)) throw new Error(`dup ${k}`);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function goType(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);
|
||||||
|
// special cases, before we get confused
|
||||||
|
switch (m) {
|
||||||
|
case 'textDocument/completion':
|
||||||
|
return 'CompletionList';
|
||||||
|
case 'textDocument/documentSymbol':
|
||||||
|
return '[]DocumentSymbol';
|
||||||
|
case 'textDocument/prepareRename':
|
||||||
|
return 'Range';
|
||||||
|
case 'textDocument/codeAction':
|
||||||
|
return '[]CodeAction';
|
||||||
|
}
|
||||||
|
if (ts.isUnionTypeNode(n)) {
|
||||||
|
let x: string[] = [];
|
||||||
|
n.types.forEach(
|
||||||
|
(v) => {v.kind != ts.SyntaxKind.NullKeyword && x.push(goType(m, v))});
|
||||||
|
if (x.length == 1) return x[0];
|
||||||
|
|
||||||
|
prb(`===========${m} ${x}`)
|
||||||
|
// Because we don't fully resolve types, we don't know that
|
||||||
|
// Definition is Location | Location[]
|
||||||
|
if (x[0] == 'Definition') return '[]Location';
|
||||||
|
if (x[1] == '[]' + x[0] + 'Link') return x[1];
|
||||||
|
throw new Error(`${m}, ${x} unexpected types`)
|
||||||
|
}
|
||||||
|
return '?';
|
||||||
|
}
|
||||||
|
|
||||||
|
// walk the AST finding Requests and Notifications
|
||||||
|
function genStuff(node: ts.Node) {
|
||||||
|
if (!ts.isNewExpression(node)) {
|
||||||
|
ts.forEachChild(node, genStuff)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// process the right kind of new expression
|
||||||
|
const wh = node.expression.getText();
|
||||||
|
if (wh != 'RequestType' && wh != 'RequestType0' && wh != 'NotificationType' &&
|
||||||
|
wh != 'NotificationType0')
|
||||||
|
return;
|
||||||
|
if (node.arguments === undefined || node.arguments.length != 1 ||
|
||||||
|
!ts.isStringLiteral(node.arguments[0])) {
|
||||||
|
throw new Error(`missing n.arguments ${loc(node)}`)
|
||||||
|
}
|
||||||
|
// RequestType<useful>=new RequestTYpe('foo')
|
||||||
|
if (node.typeArguments === undefined) {
|
||||||
|
node.typeArguments = lookUp(node);
|
||||||
|
}
|
||||||
|
// new RequestType<useful>
|
||||||
|
let s = node.arguments[0].getText();
|
||||||
|
// Request or Notification
|
||||||
|
const v = wh[0] == 'R' ? req : not;
|
||||||
|
s = s.substring(1, s.length - 1); // remove quoting
|
||||||
|
if (s == '$/cancelRequest') return; // special case in output
|
||||||
|
v.set(s, node);
|
||||||
|
}
|
||||||
|
|
||||||
|
function lookUp(n: ts.NewExpression): ts.NodeArray<ts.TypeNode> {
|
||||||
|
// parent should be VariableDeclaration. its children should be
|
||||||
|
// Identifier('type') ???
|
||||||
|
// TypeReference: [Identifier('RequestType1), ]
|
||||||
|
// NewExpression (us)
|
||||||
|
const p = n.parent;
|
||||||
|
if (!ts.isVariableDeclaration(p)) throw new Error(`not variable decl`);
|
||||||
|
const tr = p.type;
|
||||||
|
if (!ts.isTypeReferenceNode(tr)) throw new Error(`not TypeReference`);
|
||||||
|
return tr.typeArguments;
|
||||||
|
}
|
||||||
|
|
||||||
|
function dumpAST() {
|
||||||
|
// dump the ast, for debugging
|
||||||
|
for (const sourceFile of program.getSourceFiles()) {
|
||||||
|
if (!sourceFile.isDeclarationFile) {
|
||||||
|
// walk the tree to do stuff
|
||||||
|
ts.forEachChild(sourceFile, describe);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// some tokens have the wrong default name
|
||||||
|
function strKind(n: ts.Node): string {
|
||||||
|
const x = ts.SyntaxKind[n.kind];
|
||||||
|
switch (x) {
|
||||||
|
default:
|
||||||
|
return x;
|
||||||
|
case 'FirstAssignment':
|
||||||
|
return 'EqualsToken';
|
||||||
|
case 'FirstBinaryOperator':
|
||||||
|
return 'LessThanToken';
|
||||||
|
case 'FirstCompoundAssignment':
|
||||||
|
return 'PlusEqualsToken';
|
||||||
|
case 'FirstContextualKeyword':
|
||||||
|
return 'AbstractKeyword';
|
||||||
|
case 'FirstLiteralToken':
|
||||||
|
return 'NumericLiteral';
|
||||||
|
case 'FirstNode':
|
||||||
|
return 'QualifiedName';
|
||||||
|
case 'FirstTemplateToken':
|
||||||
|
return 'NoSubstitutionTemplateLiteral';
|
||||||
|
case 'LastTemplateToken':
|
||||||
|
return 'TemplateTail';
|
||||||
|
case 'FirstTypeNode':
|
||||||
|
return 'TypePredicate';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function describe(node: ts.Node) {
|
||||||
|
if (node === undefined) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let indent = '';
|
||||||
|
|
||||||
|
function f(n: ts.Node) {
|
||||||
|
if (ts.isIdentifier(n)) {
|
||||||
|
pra(`${indent} ${loc(n)} ${strKind(n)} ${n.text} \n`)
|
||||||
|
} else if (ts.isPropertySignature(n) || ts.isEnumMember(n)) {
|
||||||
|
pra(`${indent} ${loc(n)} ${strKind(n)} \n`)
|
||||||
|
} else if (ts.isTypeLiteralNode(n)) {
|
||||||
|
let m = n.members
|
||||||
|
pra(`${indent} ${loc(n)} ${strKind(n)} ${m.length} \n`)
|
||||||
|
} else {
|
||||||
|
pra(`${indent} ${loc(n)} ${strKind(n)} \n`)
|
||||||
|
};
|
||||||
|
indent += ' '
|
||||||
|
ts.forEachChild(n, f)
|
||||||
|
indent = indent.slice(0, indent.length - 2)
|
||||||
|
}
|
||||||
|
f(node)
|
||||||
|
}
|
||||||
|
|
||||||
|
// string version of the location in the source file
|
||||||
|
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})`
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 (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});
|
||||||
|
}
|
||||||
|
|
||||||
|
main()
|
Loading…
Reference in New Issue