1 // read files from vscode-languageserver-node, and generate Go rpc stubs
2 // and data definitions. (and maybe someday unmarshaling code)
4 // The output is 3 files, tsprotocol.go contains the type definitions
5 // while tsclient.go and tsserver.go contain the LSP API and stub. An LSP server
6 // uses both APIs. To read the code, start in this file's main() function.
8 // The code is rich in heuristics and special cases, some of which are to avoid
9 // extensive changes to gopls, and some of which are due to the mismatch between
10 // typescript and Go types. In particular, there is no Go equivalent to union
11 // types, so each case ought to be considered separately. The Go equivalent of A
12 // & B could frequently be struct{A;B;}, or it could be the equivalent type
13 // listing all the members of A and B. Typically the code uses the former, but
14 // especially if A and B have elements with the same name, it does a version of
15 // the latter. ClientCapabilities has to be expanded, and ServerCapabilities is
16 // expanded to make the generated code easier to read.
18 // for us typescript ignorati, having an import makes this file a module
19 import * as fs from 'fs';
20 import * as ts from 'typescript';
21 import * as u from './util';
22 import { constName, getComments, goName, loc, strKind } from './util';
24 var program: ts.Program;
27 // this won't complain if some fnames don't exist
28 program = ts.createProgram(
30 { target: ts.ScriptTarget.ES2018, module: ts.ModuleKind.CommonJS });
31 program.getTypeChecker(); // finish type checking and assignment
34 // ----- collecting information for RPCs
35 let req = new Map<string, ts.NewExpression>(); // requests
36 let not = new Map<string, ts.NewExpression>(); // notifications
37 let ptypes = new Map<string, [ts.TypeNode, ts.TypeNode]>(); // req, resp types
38 let receives = new Map<string, 'server' | 'client'>(); // who receives it
39 let rpcTypes = new Set<string>(); // types seen in the rpcs
41 function findRPCs(node: ts.Node) {
42 if (!ts.isModuleDeclaration(node)) {
45 if (!ts.isIdentifier(node.name)) {
47 `expected Identifier, got ${strKind(node.name)} at ${loc(node)}`)
50 let v = node.name.getText()
51 if (v.endsWith('Notification')) reqnot = not;
52 else if (!v.endsWith('Request')) return;
54 if (!ts.isModuleBlock(node.body)) {
56 `expected ModuleBody got ${strKind(node.body)} at ${loc(node)}`)
58 let x: ts.ModuleBlock = node.body
59 // The story is to expect const method = 'textDocument/implementation'
60 // const type = new ProtocolRequestType<...>(method)
61 // but the method may be an explicit string
63 let newNode: ts.NewExpression;
64 for (let i = 0; i < x.statements.length; i++) {
65 const uu = x.statements[i];
66 if (!ts.isVariableStatement(uu)) continue;
67 const dl: ts.VariableDeclarationList = uu.declarationList;
68 if (dl.declarations.length != 1)
69 throw new Error(`expected a single decl at ${loc(dl)}`);
70 const decl: ts.VariableDeclaration = dl.declarations[0];
71 const name = decl.name.getText()
72 // we want the initializers
73 if (name == 'method') { // mostly StringLiteral but NoSubstitutionTemplateLiteral in protocol.semanticTokens.ts
74 if (!ts.isStringLiteral(decl.initializer)) {
75 if (!ts.isNoSubstitutionTemplateLiteral(decl.initializer)) {
76 console.log(`${decl.initializer.getText()}`);
77 throw new Error(`expect StringLiteral at ${loc(decl)} got ${strKind(decl.initializer)}`);
80 rpc = decl.initializer.getText()
82 else if (name == 'type') { // NewExpression
83 if (!ts.isNewExpression(decl.initializer))
84 throw new Error(`expecte new at ${loc(decl)}`);
85 const nn: ts.NewExpression = decl.initializer
87 const mtd = nn.arguments[0];
88 if (ts.isStringLiteral(mtd)) rpc = mtd.getText();
89 switch (nn.typeArguments.length) {
91 ptypes.set(rpc, [nn.typeArguments[0], null])
93 case 2: // notifications
94 ptypes.set(rpc, [nn.typeArguments[0], null])
96 case 4: // request with no parameters
97 ptypes.set(rpc, [null, nn.typeArguments[0]])
99 case 5: // request req, resp, partial(?)
100 ptypes.set(rpc, [nn.typeArguments[0], nn.typeArguments[1]])
103 throw new Error(`${nn.typeArguments.length} at ${loc(nn)}`)
107 if (rpc == '') throw new Error(`no name found at ${loc(x)}`);
108 // remember the implied types
109 const [a, b] = ptypes.get(rpc);
110 const add = function (n: ts.Node) {
111 rpcTypes.add(goName(n.getText()))
115 rpc = rpc.substring(1, rpc.length - 1); // 'exit'
116 reqnot.set(rpc, newNode)
119 // handle missing typeArguments
120 function lookUp(n: ts.NewExpression): ts.NodeArray<ts.TypeNode> {
121 // parent should be VariableDeclaration. its children should be
122 // Identifier('type') ???
123 // TypeReference: [Identifier('RequestType1), ]
124 // NewExpression (us)
126 if (!ts.isVariableDeclaration(p)) throw new Error(`not variable decl`);
128 if (!ts.isTypeReferenceNode(tr)) throw new Error(`not TypeReference`);
129 return tr.typeArguments;
132 function setReceives() {
133 // mark them all as server, then adjust the client ones.
134 // it would be nice to have some independent check on this
135 // (this logic fails if the server ever sends $/canceRequest
137 req.forEach((_, k) => { receives.set(k, 'server') });
138 not.forEach((_, k) => { receives.set(k, 'server') });
139 receives.set('window/showMessage', 'client');
140 receives.set('window/showMessageRequest', 'client');
141 receives.set('window/logMessage', 'client');
142 receives.set('telemetry/event', 'client');
143 receives.set('client/registerCapability', 'client');
144 receives.set('client/unregisterCapability', 'client');
145 receives.set('workspace/workspaceFolders', 'client');
146 receives.set('workspace/configuration', 'client');
147 receives.set('workspace/applyEdit', 'client');
148 receives.set('textDocument/publishDiagnostics', 'client');
149 receives.set('window/workDoneProgress/create', 'client');
150 receives.set('$/progress', 'client');
152 receives.forEach((_, k) => {
153 if (!req.get(k) && !not.get(k)) throw new Error(`missing ${k}}`);
154 if (req.get(k) && not.get(k)) throw new Error(`dup ${k}`);
159 me: ts.Node; // root node for this type
160 name: string; // Go name
161 generics: ts.NodeArray<ts.TypeParameterDeclaration>;
162 as: ts.NodeArray<ts.HeritageClause>; // inheritance
164 properties: ts.NodeArray<ts.TypeElement>; // ts.PropertySignature
165 alias: ts.TypeNode; // type alias
167 statements: ts.NodeArray<ts.Statement>;
168 enums: ts.NodeArray<ts.EnumMember>;
170 members: ts.NodeArray<ts.PropertyDeclaration>;
172 function newData(n: ts.Node, nm: string): Data {
174 me: n, name: goName(nm),
175 generics: ts.createNodeArray<ts.TypeParameterDeclaration>(), as: ts.createNodeArray<ts.HeritageClause>(),
176 properties: ts.createNodeArray<ts.TypeElement>(), alias: undefined,
177 statements: ts.createNodeArray<ts.Statement>(),
178 enums: ts.createNodeArray<ts.EnumMember>(),
179 members: ts.createNodeArray<ts.PropertyDeclaration>(),
183 // for debugging, produce a skeleton description
184 function strData(d: Data): string {
185 const f = function (na: ts.NodeArray<any>): number {
188 return `D(${d.name}) g;${f(d.generics)} a:${f(d.as)} p:${f(d.properties)} s:${f(d.statements)} e:${f(d.enums)} m:${f(d.members)} ${d.alias != undefined}`
191 let data = new Map<string, Data>(); // parsed data types
192 let seenTypes = new Map<string, Data>(); // type names we've seen
193 let extraTypes = new Map<string, string[]>(); // to avoid struct params
195 // look at top level data definitions
196 function genTypes(node: ts.Node) {
197 // Ignore top-level items that can't produce output
198 if (ts.isExpressionStatement(node) || ts.isFunctionDeclaration(node) ||
199 ts.isImportDeclaration(node) || ts.isVariableStatement(node) ||
200 ts.isExportDeclaration(node) || ts.isEmptyStatement(node) ||
201 ts.isExportAssignment(node) || ts.isImportEqualsDeclaration(node) ||
202 ts.isBlock(node) || node.kind == ts.SyntaxKind.EndOfFileToken) {
205 if (ts.isInterfaceDeclaration(node)) {
206 const v: ts.InterfaceDeclaration = node;
207 // need to check the members, many of which are disruptive
208 let mems: ts.TypeElement[] = [];
209 const f = function (t: ts.TypeElement) {
210 if (ts.isPropertySignature(t)) {
212 } else if (ts.isMethodSignature(t) || ts.isCallSignatureDeclaration(t)) {
214 } else if (ts.isIndexSignatureDeclaration(t)) {
215 // probably safe to ignore these
216 // [key: string]: boolean | number | string | undefined;
217 // and InitializeResult: [custom: string]: any;]
220 throw new Error(`217 unexpected ${strKind(t)}`)
222 v.members.forEach(f);
223 if (mems.length == 0 && !v.heritageClauses &&
224 v.name.getText() != 'InitializedParams') {
225 return // really? (Don't seem to need any of these)
228 let x = newData(v, goName(v.name.getText()));
229 x.properties = ts.createNodeArray<ts.TypeElement>(mems);
230 if (v.typeParameters) x.generics = v.typeParameters;
231 if (v.heritageClauses) x.as = v.heritageClauses;
232 if (x.generics.length > 1) { // Unneeded
233 // Item interface Item<K, V>...
236 if (data.has(x.name)) { // modifying one we've seen
237 x = dataMerge(x, data.get(x.name));
240 } else if (ts.isTypeAliasDeclaration(node)) {
241 const v: ts.TypeAliasDeclaration = node;
242 let x = newData(v, v.name.getText());
244 // if type is a union of constants, we (mostly) don't want it
245 // (at the top level)
246 // Unfortunately this is false for TraceValues
247 if (ts.isUnionTypeNode(v.type) &&
248 v.type.types.every((n: ts.TypeNode) => ts.isLiteralTypeNode(n))) {
249 if (x.name != 'TraceValues') return;
251 if (v.typeParameters) {
252 x.generics = v.typeParameters;
254 if (data.has(x.name)) x = dataMerge(x, data.get(x.name));
255 if (x.generics.length > 1) {
259 } else if (ts.isModuleDeclaration(node)) {
260 const v: ts.ModuleDeclaration = node;
261 if (!ts.isModuleBlock(v.body)) {
262 throw new Error(`${loc(v)} not ModuleBlock, but ${strKind(v.body)}`)
264 const b: ts.ModuleBlock = v.body;
265 var s: ts.Statement[] = [];
266 // we don't want most of these
267 const fx = function (x: ts.Statement) {
268 if (ts.isFunctionDeclaration(x)) {
271 if (ts.isTypeAliasDeclaration(x) || ts.isModuleDeclaration(x)) {
274 if (!ts.isVariableStatement(x))
276 `expected VariableStatment ${loc(x)} ${strKind(x)} ${x.getText()}`);
277 if (hasNewExpression(x)) {
282 b.statements.forEach(fx)
286 let m = newData(node, v.name.getText());
287 m.statements = ts.createNodeArray<ts.Statement>(s);
288 if (data.has(m.name)) m = dataMerge(m, data.get(m.name));
290 } else if (ts.isEnumDeclaration(node)) {
291 const nm = node.name.getText();
292 let v = newData(node, nm);
293 v.enums = node.members;
295 v = dataMerge(v, data.get(nm));
298 } else if (ts.isClassDeclaration(node)) {
299 const v: ts.ClassDeclaration = node;
300 var d: ts.PropertyDeclaration[] = [];
301 // look harder at the PropertyDeclarations.
302 const wanted = function (c: ts.ClassElement): string {
303 if (ts.isConstructorDeclaration(c)) {
306 if (ts.isMethodDeclaration(c)) {
309 if (ts.isGetAccessor(c)) {
312 if (ts.isSetAccessor(c)) {
315 if (ts.isPropertyDeclaration(c)) {
319 throw new Error(`Class decl ${strKind(c)} `)
321 v.members.forEach((c, i) => wanted(c));
324 }; // don't need it, maybe
325 let c = newData(v, v.name.getText());
326 c.members = ts.createNodeArray<ts.PropertyDeclaration>(d);
327 if (v.typeParameters) {
328 c.generics = v.typeParameters
330 if (c.generics.length > 1) {
333 if (v.heritageClauses) {
334 c.as = v.heritageClauses
336 if (data.has(c.name))
337 throw new Error(`Class dup ${loc(c.me)} and ${loc(data.get(c.name).me)}`);
340 throw new Error(`338 unexpected ${strKind(node)} ${loc(node)} `)
344 // Typescript can accumulate
345 function dataMerge(a: Data, b: Data): Data {
346 // maybe they are textually identical? (it happens)
347 const [at, bt] = [a.me.getText(), b.me.getText()];
351 const ax = `(${a.statements.length},${a.properties.length})`
352 const bx = `(${b.statements.length},${b.properties.length})`
354 case 'InitializeError':
356 case 'CompletionItemTag':
358 case 'CodeActionKind':
360 return a.statements.length > 0 ? a : b;
361 case 'CancellationToken':
362 case 'CancellationStrategy':
363 // want the Interface
364 return a.properties.length > 0 ? a : b;
365 case 'TextDocumentContentChangeEvent': // almost the same
370 `367 ${strKind(a.me)} ${strKind(b.me)} ${a.name} ${loc(a.me)} ${loc(b.me)}`)
371 throw new Error(`Fix dataMerge for ${a.name}`)
374 // is a node an ancestor of a NewExpression
375 function hasNewExpression(n: ts.Node): boolean {
377 n.forEachChild((n: ts.Node) => {
378 if (ts.isNewExpression(n)) ans = true;
383 function checkOnce() {
384 // Data for all the rpc types?
385 rpcTypes.forEach(s => {
386 if (!data.has(s)) throw new Error(`checkOnce, ${s}?`)
390 // helper function to find underlying types
391 function underlying(n: ts.Node, f: (n: ts.Node) => void) {
393 const ff = function (n: ts.Node) {
396 if (ts.isIdentifier(n)) {
399 n.kind == ts.SyntaxKind.StringKeyword ||
400 n.kind == ts.SyntaxKind.NumberKeyword ||
401 n.kind == ts.SyntaxKind.AnyKeyword ||
402 n.kind == ts.SyntaxKind.UnknownKeyword ||
403 n.kind == ts.SyntaxKind.NullKeyword ||
404 n.kind == ts.SyntaxKind.BooleanKeyword ||
405 n.kind == ts.SyntaxKind.ObjectKeyword ||
406 n.kind == ts.SyntaxKind.VoidKeyword) {
408 } else if (ts.isTypeReferenceNode(n)) {
410 } else if (ts.isArrayTypeNode(n)) {
411 underlying(n.elementType, f)
412 } else if (ts.isHeritageClause(n)) {
414 } else if (ts.isExpressionWithTypeArguments(n)) {
415 underlying(n.expression, f)
416 } else if (ts.isPropertySignature(n)) {
417 underlying(n.type, f)
418 } else if (ts.isTypeLiteralNode(n)) {
419 n.members.forEach(ff)
420 } else if (ts.isUnionTypeNode(n) || ts.isIntersectionTypeNode(n)) {
422 } else if (ts.isIndexSignatureDeclaration(n)) {
423 underlying(n.type, f)
424 } else if (ts.isParenthesizedTypeNode(n)) {
425 underlying(n.type, f)
427 ts.isLiteralTypeNode(n) || ts.isVariableStatement(n) ||
428 ts.isTupleTypeNode(n)) {
429 // we only see these in moreTypes, but they are handled elsewhere
431 } else if (ts.isEnumMember(n)) {
432 if (ts.isStringLiteral(n.initializer)) return;
433 throw new Error(`EnumMember ${strKind(n.initializer)} ${n.name.getText()}`)
435 throw new Error(`saw ${strKind(n)} in underlying. ${n.getText()} at ${loc(n)}`)
439 // find all the types implied by seenTypes.
440 // Simplest way to the transitive closure is to stabilize the size of seenTypes
442 function moreTypes() {
443 const extra = function (s: string) {
444 if (!data.has(s)) throw new Error(`moreTypes needs ${s}`);
445 seenTypes.set(s, data.get(s))
447 rpcTypes.forEach(extra); // all the types needed by the rpcs
448 // needed in enums.go (or elsewhere)
449 extra('InitializeError')
451 extra('FoldingRangeKind')
452 // not sure why these weren't picked up
453 extra('FileSystemWatcher')
454 extra('DidChangeWatchedFilesRegistrationOptions')
455 extra('WorkDoneProgressBegin')
456 extra('WorkDoneProgressReport')
457 extra('WorkDoneProgressEnd')
462 const m = new Map<string, Data>();
463 const add = function (n: ts.Node) {
464 const nm = goName(n.getText());
465 if (seenTypes.has(nm) || m.has(nm)) return;
466 // For generic parameters, this might set it to undefined
467 m.set(nm, data.get(nm));
469 // expect all the heritage clauses have single Identifiers
470 const h = function (n: ts.Node) {
473 const f = function (x: ts.NodeArray<ts.Node>) {
476 seenTypes.forEach((d: Data) => d && f(d.as))
477 // find the types in the properties
478 seenTypes.forEach((d: Data) => d && f(d.properties))
479 // and in the alias and in the statements and in the enums
480 seenTypes.forEach((d: Data) => d && underlying(d.alias, add))
481 seenTypes.forEach((d: Data) => d && f(d.statements))
482 seenTypes.forEach((d: Data) => d && f(d.enums))
483 m.forEach((d, k) => seenTypes.set(k, d))
485 while (seenTypes.size != old)
489 let typesOut = new Array<string>();
490 let constsOut = new Array<string>();
493 function toGo(d: Data, nm: string) {
494 if (!d) return; // this is probably a generic T
497 } else if (d.statements.length > 0) {
499 } else if (d.enums.length > 0) {
502 d.properties.length > 0 || d.as.length > 0 || nm == 'InitializedParams') {
506 `more cases in toGo ${nm} ${d.as.length} ${d.generics.length} `)
509 // these fields need a *
510 var starred: [string, string][] = [
511 ['TextDocumentContentChangeEvent', 'range'], ['CodeAction', 'command'],
512 ['DidSaveTextDocumentParams', 'text'], ['CompletionItem', 'command'],
513 ['Diagnostic', 'codeDescription']
516 // generate Go code for an interface
517 function goInterface(d: Data, nm: string) {
518 let ans = `type ${goName(nm)} struct {\n`;
520 // generate the code for each member
521 const g = function (n: ts.TypeElement) {
522 if (!ts.isPropertySignature(n))
523 throw new Error(`expected PropertySignature got ${strKind(n)} `);
524 ans = ans.concat(getComments(n));
525 const json = u.JSON(n);
526 // SelectionRange is a recursive type
527 let gt = goType(n.type, n.name.getText(), nm);
528 if (gt == d.name) gt = '*' + gt; // avoid recursive types
529 // there are several cases where a * is needed
530 starred.forEach(([a, b]) => {
531 if (d.name == a && n.name.getText() == b) {
535 ans = ans.concat(`${goName(n.name.getText())} ${gt}`, json, '\n');
537 d.properties.forEach(g)
538 // heritage clauses become embedded types
539 // check they are all Identifiers
540 const f = function (n: ts.ExpressionWithTypeArguments) {
541 if (!ts.isIdentifier(n.expression))
542 throw new Error(`Interface ${nm} heritage ${strKind(n.expression)} `);
543 ans = ans.concat(goName(n.expression.getText()), '\n')
545 d.as.forEach((n: ts.HeritageClause) => n.types.forEach(f))
546 ans = ans.concat(`}\n`);
547 typesOut.push(getComments(d.me))
551 // generate Go code for a module (const declarations)
552 // Generates type definitions, and named constants
553 function goModule(d: Data, nm: string) {
554 if (d.generics.length > 0 || d.as.length > 0) {
555 throw new Error(`goModule: unexpected for ${nm}
558 // all the statements should be export const <id>: value
560 // They are VariableStatements with x.declarationList having a single
561 // VariableDeclaration
562 let isNumeric = false;
563 const f = function (n: ts.Statement, i: number) {
564 if (!ts.isVariableStatement(n)) {
565 throw new Error(` ${nm} ${i} expected VariableStatement,
568 const c = getComments(n)
569 const v = n.declarationList.declarations[0]; // only one
572 throw new Error(`no initializer ${nm} ${i} ${v.name.getText()}`);
573 isNumeric = strKind(v.initializer) == 'NumericLiteral';
574 if (c != '') constsOut.push(c); // no point if there are no comments
575 // There are duplicates.
576 const cname = constName(goName(v.name.getText()), nm);
577 let val = v.initializer.getText()
578 val = val.split('\'').join('"') // useless work for numbers
579 constsOut.push(`${cname} ${nm} = ${val}`)
581 d.statements.forEach(f)
582 typesOut.push(getComments(d.me))
583 // Or should they be type aliases?
584 typesOut.push(`type ${nm} ${isNumeric ? 'float64' : 'string'}`)
587 // generate Go code for an enum. Both types and named constants
588 function goEnum(d: Data, nm: string) {
589 let isNumeric = false
590 const f = function (v: ts.EnumMember, j: number) { // same as goModule
592 throw new Error(`goEnum no initializer ${nm} ${j} ${v.name.getText()}`);
593 isNumeric = strKind(v.initializer) == 'NumericLiteral';
594 const c = getComments(v);
595 const cname = constName(goName(v.name.getText()), nm);
596 let val = v.initializer.getText()
597 val = val.split('\'').join('"') // replace quotes. useless work for numbers
598 constsOut.push(`${c}${cname} ${nm} = ${val}`)
601 typesOut.push(getComments(d.me))
602 // Or should they be type aliases?
603 typesOut.push(`type ${nm} ${isNumeric ? 'float64' : 'string'}`)
606 // generate code for a type alias
607 function goTypeAlias(d: Data, nm: string) {
608 if (d.as.length != 0 || d.generics.length != 0) {
609 if (nm != 'ServerCapabilities')
610 throw new Error(`${nm} has extra fields(${d.as.length},${d.generics.length}) ${d.me.getText()}`);
612 typesOut.push(getComments(d.me))
613 // d.alias doesn't seem to have comments
614 let aliasStr = goName(nm) == 'DocumentURI' ? ' ' : ' = '
615 typesOut.push(`type ${goName(nm)}${aliasStr}${goType(d.alias, nm)}\n`)
618 // return a go type and maybe an assocated javascript tag
619 function goType(n: ts.TypeNode, nm: string, parent?: string): string {
620 if (n.getText() == 'T') return 'interface{}'; // should check it's generic
621 if (ts.isTypeReferenceNode(n)) {
622 return goName(n.typeName.getText()); // avoid <T>
623 } else if (ts.isUnionTypeNode(n)) {
624 return goUnionType(n, nm, parent);
625 } else if (ts.isIntersectionTypeNode(n)) {
626 return goIntersectionType(n, nm);
627 } else if (strKind(n) == 'StringKeyword') {
629 } else if (strKind(n) == 'NumberKeyword') {
631 } else if (strKind(n) == 'BooleanKeyword') {
633 } else if (strKind(n) == 'AnyKeyword' || strKind(n) == 'UnknownKeyword') {
634 return 'interface{}';
635 } else if (strKind(n) == 'NullKeyword') {
637 } else if (strKind(n) == 'VoidKeyword' || strKind(n) == 'NeverKeyword') {
639 } else if (strKind(n) == 'ObjectKeyword') {
641 } else if (ts.isArrayTypeNode(n)) {
642 if (nm === 'arguments') {
643 // Command and ExecuteCommandParams
644 return '[]json.RawMessage';
646 return `[]${goType(n.elementType, nm)}`
647 } else if (ts.isParenthesizedTypeNode(n)) {
648 return goType(n.type, nm)
649 } else if (ts.isLiteralTypeNode(n)) {
650 return strKind(n.literal) == 'StringLiteral' ? 'string' : 'float64';
651 } else if (ts.isTypeLiteralNode(n)) {
652 // these are anonymous structs
653 const v = goTypeLiteral(n, nm);
655 } else if (ts.isTupleTypeNode(n)) {
656 if (n.getText() == '[number, number]') return '[]float64';
657 throw new Error(`goType unexpected Tuple ${n.getText()}`)
659 throw new Error(`${strKind(n)} goType unexpected ${n.getText()} for ${nm}`)
662 // The choice is uniform interface{}, or some heuristically assigned choice,
663 // or some better sytematic idea I haven't thought of. Using interface{}
664 // is, in practice, impossibly complex in the existing code.
665 function goUnionType(n: ts.UnionTypeNode, nm: string, parent?: string): string {
666 let help = `/*${n.getText()}*/` // show the original as a comment
667 // There are some bad cases with newlines:
668 // range?: boolean | {\n };
669 // full?: boolean | {\n /**\n * The server supports deltas for full documents.\n */\n delta?: boolean;\n }
670 // These are handled specially:
671 if (nm == 'range') help = help.replace(/\n/, '');
672 if (nm == 'full' && help.indexOf('\n') != -1) {
673 help = '/*boolean | <elided struct>*/';
675 // handle all the special cases
676 switch (n.types.length) {
678 const a = strKind(n.types[0])
679 const b = strKind(n.types[1])
680 if (a == 'NumberKeyword' && b == 'StringKeyword') { // ID
681 return `interface{} ${help}`
683 if (b == 'NullKeyword') {
684 if (nm == 'textDocument/codeAction') {
685 // (Command | CodeAction)[] | null
686 return `[]CodeAction ${help}`
688 let v = goType(n.types[0], 'a')
689 if (v.startsWith(`[]interface`)) v = v.slice(2, v.length)
690 return `${v} ${help}`
692 if (a == 'BooleanKeyword') { // usually want bool
693 if (nm == 'codeActionProvider') return `interface{} ${help}`;
694 if (nm == 'renameProvider') return `interface{} ${help}`;
695 if (nm == 'full') return `interface{} ${help}`; // there's a struct
696 if (nm == 'save') return `${goType(n.types[1], '680')} ${help}`;
697 return `${goType(n.types[0], 'b')} ${help}`
699 if (b == 'ArrayType') return `${goType(n.types[1], 'c')} ${help}`;
700 if (help.includes('InsertReplaceEdit') && n.types[0].getText() == 'TextEdit') {
701 return `*TextEdit ${help}`
703 if (a == 'TypeReference' && a == b) return `interface{} ${help}`;
704 if (a == 'StringKeyword') return `string ${help}`;
705 if (a == 'TypeLiteral' && nm == 'TextDocumentContentChangeEvent') {
706 return `${goType(n.types[0], nm)}`
708 throw new Error(`691 ${a} ${b} ${n.getText()} ${loc(n)}`);
710 const aa = strKind(n.types[0])
711 const bb = strKind(n.types[1])
712 const cc = strKind(n.types[2])
713 if (nm == 'textDocument/prepareRename') {
714 // want Range, not interface{}
715 return `${goType(n.types[0], nm)} ${help}`
717 if (nm == 'DocumentFilter') {
718 // not really a union. the first is enough, up to a missing
719 // omitempty but avoid repetitious comments
720 return `${goType(n.types[0], 'g')}`
722 if (nm == 'textDocument/documentSymbol') {
723 return `[]interface{} ${help}`
725 if (aa == 'TypeReference' && bb == 'ArrayType' && cc == 'NullKeyword') {
726 return `${goType(n.types[0], 'd')} ${help}`
728 if (aa == 'TypeReference' && bb == aa && cc == 'ArrayType') {
729 // should check that this is Hover.Contents
730 return `${goType(n.types[0], 'e')} ${help}`
732 if (aa == 'ArrayType' && bb == 'TypeReference' && cc == 'NullKeyword') {
733 // check this is nm == 'textDocument/completion'
734 return `${goType(n.types[1], 'f')} ${help}`
736 if (aa == 'LiteralType' && bb == aa && cc == aa) return `string ${help}`;
739 if (nm == 'documentChanges') return `TextDocumentEdit ${help} `;
740 if (nm == 'textDocument/prepareRename') return `Range ${help} `;
742 throw new Error(`goUnionType len=${n.types.length} nm=${nm}`)
745 // Result will be interface{} with a comment
746 let isLiteral = true;
747 let literal = 'string';
748 let res = `interface{} /* `
749 n.types.forEach((v: ts.TypeNode, i: number) => {
750 // might get an interface inside:
751 // (Command | CodeAction)[] | null
752 let m = goType(v, nm);
753 if (m.indexOf('interface') != -1) {
754 // avoid nested comments
757 m = m.split('\n').join('; ') // sloppy: struct{;
758 res = res.concat(`${i == 0 ? '' : ' | '}`, m)
759 if (!ts.isLiteralTypeNode(v)) isLiteral = false;
760 else literal = strKind(v.literal) == 'StringLiteral' ? 'string' : 'number';
765 // trace?: 'off' | 'messages' | 'verbose' should get string
766 return `${literal} /* ${n.getText()} */`
769 // some of the intersection types A&B are ok as struct{A;B;} and some
770 // could be expanded, and ClientCapabilites has to be expanded,
771 // at least for workspace. It's possible to check algorithmically,
772 // but much simpler just to check explicity.
773 function goIntersectionType(n: ts.IntersectionTypeNode, nm: string): string {
774 if (nm == 'ClientCapabilities') return expandIntersection(n);
775 if (nm == 'ServerCapabilities') return expandIntersection(n);
778 (t: ts.TypeNode) => { inner = inner.concat(goType(t, nm), '\n') });
779 return `struct{ \n${inner}} `
782 // for each of the itersected types, extract its components (each will
783 // have a Data with properties) extract the properties, and keep track
784 // of them by name. The names that occur once can be output. The names
785 // that occur more than once need to be combined.
786 function expandIntersection(n: ts.IntersectionTypeNode): string {
787 const bad = function (n: ts.Node, s: string) {
788 return new Error(`expandIntersection ${strKind(n)} ${s}`)
790 let props = new Map<string, ts.PropertySignature[]>();
791 for (const tp of n.types) {
792 if (!ts.isTypeReferenceNode(tp)) throw bad(tp, 'A');
793 const d = data.get(goName(tp.typeName.getText()));
794 for (const p of d.properties) {
795 if (!ts.isPropertySignature(p)) throw bad(p, 'B');
796 let v = props.get(p.name.getText()) || [];
798 props.set(p.name.getText(), v);
801 let ans = 'struct {\n';
802 for (const [k, v] of Array.from(props)) {
805 ans = ans.concat(getComments(a));
806 ans = ans.concat(`${goName(k)} ${goType(a.type, k)} ${u.JSON(a)}\n`)
809 ans = ans.concat(`${goName(k)} struct {\n`)
810 for (let i = 0; i < v.length; i++) {
812 if (ts.isTypeReferenceNode(a.type)) {
813 ans = ans.concat(getComments(a))
814 ans = ans.concat(goName(a.type.typeName.getText()), '\n');
815 } else if (ts.isTypeLiteralNode(a.type)) {
816 if (a.type.members.length != 1) throw bad(a.type, 'C');
817 const b = a.type.members[0];
818 if (!ts.isPropertySignature(b)) throw bad(b, 'D');
819 ans = ans.concat(getComments(b));
821 goName(b.name.getText()), ' ', goType(b.type, 'a'), u.JSON(b), '\n')
822 } else if (a.type.kind == ts.SyntaxKind.ObjectKeyword) {
823 ans = ans.concat(getComments(a))
825 goName(a.name.getText()), ' ', 'interface{}', u.JSON(a), '\n')
827 throw bad(a.type, `E ${a.getText()} in ${goName(k)} at ${loc(a)}`)
830 ans = ans.concat('}\n');
832 ans = ans.concat('}\n');
836 function goTypeLiteral(n: ts.TypeLiteralNode, nm: string): string {
837 let ans: string[] = []; // in case we generate a new extra type
838 let res = 'struct{\n' // the actual answer usually
839 const g = function (nx: ts.TypeElement) {
840 // add the json, as in goInterface(). Strange inside union types.
841 if (ts.isPropertySignature(nx)) {
842 let json = u.JSON(nx);
843 let typ = goType(nx.type, nx.name.getText())
844 const v = getComments(nx) || '';
845 starred.forEach(([a, b]) => {
846 if (a != nm || b != typ.toLowerCase()) return;
848 json = json.substring(0, json.length - 2) + ',omitempty"`'
850 res = res.concat(`${v} ${goName(nx.name.getText())} ${typ}`, json, '\n')
851 ans.push(`${v}${goName(nx.name.getText())} ${typ} ${json}\n`)
852 } else if (ts.isIndexSignatureDeclaration(nx)) {
853 if (nx.getText() == '[uri: string]: TextEdit[];') {
854 res = 'map[string][]TextEdit';
855 ans.push(`map[string][]TextEdit`); // this is never used
858 throw new Error(` handle ${nx.getText()}`)
860 throw new Error(`TypeLiteral had ${strKind(nx)}`)
863 // for some the generated type is wanted, for others it's not needed
864 if (!nm.startsWith('workspace')) {
865 if (res.startsWith('struct')) return res + '}'; // map[] is special
868 extraTypes.set(goName(nm) + 'Gn', ans)
869 return goName(nm) + 'Gn'
872 // print all the types and constants and extra types
873 function outputTypes() {
874 // generate go types alphabeticaly
875 let v = Array.from(seenTypes.keys());
877 v.forEach((x) => toGo(seenTypes.get(x), x))
878 u.prgo(u.computeHeader(true))
879 u.prgo(`import "encoding/json"\n\n`);
880 typesOut.forEach((s) => {
882 // it's more convenient not to have to think about trailing newlines
883 // when generating types, but doc comments can't have an extra \n
884 if (s.indexOf('/**') < 0) u.prgo('\n');
886 u.prgo('\nconst (\n');
887 constsOut.forEach((s) => {
892 u.prgo('// Types created to name formal parameters and embedded structs\n')
893 extraTypes.forEach((v, k) => {
894 u.prgo(` type ${k} struct {\n`)
903 // client and server ------------------
909 name: string; // client or server
910 goName: string; // Client or Server
929 // commonly used output
930 const notNil = `if len(r.Params()) > 0 {
931 return true, reply(ctx, nil, errors.Errorf("%w: expected no params", jsonrpc2.ErrInvalidParams))
934 // Go code for notifications. Side is client or server, m is the request
936 function goNot(side: side, m: string) {
937 if (m == '$/cancelRequest') return; // handled specially in protocol.go
938 const n = not.get(m);
939 const a = goType(n.typeArguments[0], m);
940 const nm = methodName(m);
941 side.methods.push(sig(nm, a, ''));
942 const caseHdr = ` case "${m}": // notif`;
944 if (a != '' && a != 'void') {
945 case1 = `var params ${a}
946 if err := json.Unmarshal(r.Params(), ¶ms); err != nil {
947 return true, sendParseError(ctx, reply, err)
949 err:= ${side.name}.${nm}(ctx, ¶ms)
950 return true, reply(ctx, nil, err)`
952 case1 = `err := ${side.name}.${nm}(ctx)
953 return true, reply(ctx, nil, err)`;
955 side.cases.push(`${caseHdr}\n${case1}`);
957 const arg3 = a == '' || a == 'void' ? 'nil' : 'params';
959 func (s *${side.name}Dispatcher) ${sig(nm, a, '', true)} {
960 return s.Conn.Notify(ctx, "${m}", ${arg3})
964 // Go code for requests.
965 function goReq(side: side, m: string) {
966 const n = req.get(m);
967 const nm = methodName(m);
968 let a = goType(n.typeArguments[0], m);
969 let b = goType(n.typeArguments[1], m);
970 if (n.getText().includes('Type0')) {
972 a = ''; // workspace/workspaceFolders and shutdown
974 u.prb(`${side.name} req ${a != ''}, ${b != ''} ${nm} ${m} ${loc(n)} `)
975 side.methods.push(sig(nm, a, b));
977 const caseHdr = `case "${m}": // req`;
980 if (extraTypes.has('Param' + nm)) a = 'Param' + nm
981 case1 = `var params ${a}
982 if err := json.Unmarshal(r.Params(), ¶ms); err != nil {
983 return true, sendParseError(ctx, reply, err)
986 const arg2 = a == '' ? '' : ', ¶ms';
987 let case2 = `if err := ${side.name}.${nm}(ctx${arg2}); err != nil {
988 event.Error(ctx, "", err)
990 if (b != '' && b != 'void') {
991 case2 = `resp, err := ${side.name}.${nm}(ctx${arg2})
992 return true, reply(ctx, resp, err)`;
993 } else { // response is nil
994 case2 = `err := ${side.name}.${nm}(ctx${arg2})
995 return true, reply(ctx, nil, err)`
998 side.cases.push(`${caseHdr}\n${case1}\n${case2}`);
1000 const callHdr = `func (s *${side.name}Dispatcher) ${sig(nm, a, b, true)} {`;
1001 let callBody = `return Call(ctx, s.Conn, "${m}", nil, nil)\n}`;
1002 if (b != '' && b != 'void') {
1003 const p2 = a == '' ? 'nil' : 'params';
1004 const returnType = indirect(b) ? `*${b}` : b;
1005 callBody = `var result ${returnType}
1006 if err := Call(ctx, s.Conn, "${m}", ${p2}, &result); err != nil {
1011 } else if (a != '') {
1012 callBody = `return Call(ctx, s.Conn, "${m}", params, nil) // Call, not Notify
1015 side.calls.push(`${callHdr}\n${callBody}\n`);
1018 // make sure method names are unique
1019 let seenNames = new Set<string>();
1020 function methodName(m: string): string {
1021 let i = m.indexOf('/');
1022 let s = m.substring(i + 1);
1023 let x = s[0].toUpperCase() + s.substring(1);
1024 for (let j = x.indexOf('/'); j >= 0; j = x.indexOf('/')) {
1025 let suffix = x.substring(j + 1)
1026 suffix = suffix[0].toUpperCase() + suffix.substring(1)
1027 let prefix = x.substring(0, j)
1030 if (seenNames.has(x)) {
1031 // Resolve, ResolveCodeLens, ResolveDocumentLink
1032 if (!x.startsWith('Resolve')) throw new Error(`expected Resolve, not ${x}`)
1033 x += m[0].toUpperCase() + m.substring(1, i)
1039 // used in sig and in goReq
1040 function indirect(s: string): boolean {
1041 if (s == '' || s == 'void') return false;
1042 const skip = (x: string) => s.startsWith(x);
1043 if (skip('[]') || skip('interface') || skip('Declaration') ||
1044 skip('Definition') || skip('DocumentSelector'))
1049 // Go signatures for methods.
1050 function sig(nm: string, a: string, b: string, names?: boolean): string {
1051 if (a.indexOf('struct') != -1) {
1052 const v = a.split('\n')
1053 extraTypes.set(`Param${nm}`, v.slice(1, v.length - 1))
1060 a = ', params *' + a;
1065 if (b != '' && b != 'void') {
1066 // avoid * when it is senseless
1067 if (indirect(b)) b = '*' + b;
1068 ret = `(${b}, error)`;
1070 let start = `${nm}(`;
1072 start = start + 'ctx ';
1074 return `${start}context.Context${a}) ${ret}`;
1077 // write the request/notification code
1078 function output(side: side) {
1079 // make sure the output file exists
1080 if (!side.outputFile) {
1081 side.outputFile = `ts${side.name}.go`;
1082 side.fd = fs.openSync(side.outputFile, 'w');
1084 const f = function (s: string) {
1085 fs.writeSync(side.fd, s);
1086 fs.writeSync(side.fd, '\n');
1088 f(u.computeHeader(false));
1094 "golang.org/x/tools/internal/jsonrpc2"
1095 errors "golang.org/x/xerrors"
1098 const a = side.name[0].toUpperCase() + side.name.substring(1)
1099 f(`type ${a} interface {`);
1100 side.methods.forEach((v) => { f(v) });
1102 f(`func ${side.name}Dispatch(ctx context.Context, ${side.name} ${a}, reply jsonrpc2.Replier, r jsonrpc2.Request) (bool, error) {
1103 switch r.Method() {`);
1104 side.cases.forEach((v) => { f(v) });
1110 side.calls.forEach((v) => { f(v) });
1113 // Handling of non-standard requests, so we can add gopls-specific calls.
1114 function nonstandardRequests() {
1115 server.methods.push(
1116 'NonstandardRequest(ctx context.Context, method string, params interface{}) (interface{}, error)')
1118 `func (s *serverDispatcher) NonstandardRequest(ctx context.Context, method string, params interface{}) (interface{}, error) {
1119 var result interface{}
1120 if err := Call(ctx, s.Conn, method, params, &result); err != nil {
1128 // ----- remember it's a scripting language
1130 if (u.gitHash != u.git()) {
1132 `git hash mismatch, wanted\n${u.gitHash} but source is at\n${u.git()}`);
1134 u.createOutputFiles()
1137 // find the Requests and Nofificatations
1138 for (const sourceFile of program.getSourceFiles()) {
1139 if (!sourceFile.isDeclarationFile) {
1140 ts.forEachChild(sourceFile, findRPCs)
1143 // separate RPCs into client and server
1145 // visit every sourceFile collecting top-level type definitions
1146 for (const sourceFile of program.getSourceFiles()) {
1147 if (!sourceFile.isDeclarationFile) {
1148 ts.forEachChild(sourceFile, genTypes)
1151 // check that each thing occurs exactly once, and put pointers into
1154 // for each of Client and Server there are 3 parts to the output:
1155 // 1. type X interface {methods}
1156 // 2. func (h *serverHandler) Deliver(...) { switch r.method }
1157 // 3. func (x *xDispatcher) Method(ctx, parm)
1158 not.forEach( // notifications
1160 receives.get(k) == 'client' ? goNot(client, k) : goNot(server, k)
1162 req.forEach( // requests
1164 receives.get(k) == 'client' ? goReq(client, k) : goReq(server, k)
1166 nonstandardRequests();
1167 // find all the types implied by seenTypes and rpcs to try to avoid
1168 // generating types that aren't used
1170 // and print the Go code
1172 console.log(`seen ${seenTypes.size + extraTypes.size}`)