1/* eslint-disable no-useless-return */
2// read files from vscode-languageserver-node, and generate Go rpc stubs
3// and data definitions. (and maybe someday unmarshaling code)
4
5// The output is 3 files, tsprotocol.go contains the type definitions
6// while tsclient.go and tsserver.go contain the LSP API and stub. An LSP server
7// uses both APIs. To read the code, start in this file's main() function.
8
9// The code is rich in heuristics and special cases, some of which are to avoid
10// extensive changes to gopls, and some of which are due to the mismatch between
11// typescript and Go types. In particular, there is no Go equivalent to union
12// types, so each case ought to be considered separately. The Go equivalent of A
13// & B could frequently be struct{A;B;}, or it could be the equivalent type
14// listing all the members of A and B. Typically the code uses the former, but
15// especially if A and B have elements with the same name, it does a version of
16// the latter. ClientCapabilities has to be expanded, and ServerCapabilities is
17// expanded to make the generated code easier to read.
18
19// for us typescript ignorati, having an import makes this file a module
20import * as fs from 'fs';
21import * as ts from 'typescript';
22import * as u from './util';
23import { constName, getComments, goName, loc, strKind } from './util';
24
25var program: ts.Program;
26
27function parse() {
28  // this won't complain if some fnames don't exist
29  program = ts.createProgram(
30    u.fnames,
31    { target: ts.ScriptTarget.ES2018, module: ts.ModuleKind.CommonJS });
32  program.getTypeChecker();  // finish type checking and assignment
33}
34
35// ----- collecting information for RPCs
36let req = new Map<string, ts.NewExpression>();               // requests
37let not = new Map<string, ts.NewExpression>();               // notifications
38let ptypes = new Map<string, [ts.TypeNode, ts.TypeNode]>();  // req, resp types
39let receives = new Map<string, 'server' | 'client'>();         // who receives it
40let rpcTypes = new Set<string>();  // types seen in the rpcs
41
42function findRPCs(node: ts.Node) {
43  if (!ts.isModuleDeclaration(node)) {
44    return;
45  }
46  if (!ts.isIdentifier(node.name)) {
47    throw new Error(
48      `expected Identifier, got ${strKind(node.name)} at ${loc(node)}`);
49  }
50  let reqnot = req;
51  let v = node.name.getText();
52  if (v.endsWith('Notification')) reqnot = not;
53  else if (!v.endsWith('Request')) return;
54
55  if (!ts.isModuleBlock(node.body)) {
56    throw new Error(
57      `expected ModuleBlock got ${strKind(node.body)} at ${loc(node)}`);
58  }
59  let x: ts.ModuleBlock = node.body;
60  // The story is to expect const method = 'textDocument/implementation'
61  // const type = new ProtocolRequestType<...>(method)
62  // but the method may be an explicit string
63  let rpc: string = '';
64  let newNode: ts.NewExpression;
65  for (let i = 0; i < x.statements.length; i++) {
66    const uu = x.statements[i];
67    if (!ts.isVariableStatement(uu)) continue;
68    const dl: ts.VariableDeclarationList = uu.declarationList;
69    if (dl.declarations.length != 1)
70      throw new Error(`expected a single decl at ${loc(dl)}`);
71    const decl: ts.VariableDeclaration = dl.declarations[0];
72    const name = decl.name.getText();
73    // we want the initializers
74    if (name == 'method') {  // mostly StringLiteral but NoSubstitutionTemplateLiteral in protocol.semanticTokens.ts
75      if (!ts.isStringLiteral(decl.initializer)) {
76        if (!ts.isNoSubstitutionTemplateLiteral(decl.initializer)) {
77          console.log(`81: ${decl.initializer.getText()}`);
78          throw new Error(`expect StringLiteral at ${loc(decl)} got ${strKind(decl.initializer)}`);
79        }
80      }
81      rpc = decl.initializer.getText();
82    }
83    else if (name == 'type') {  // NewExpression
84      if (!ts.isNewExpression(decl.initializer))
85        throw new Error(`89 expected new at ${loc(decl)}`);
86      const nn: ts.NewExpression = decl.initializer;
87      newNode = nn;
88      const mtd = nn.arguments[0];
89      if (ts.isStringLiteral(mtd)) rpc = mtd.getText();
90      switch (nn.typeArguments.length) {
91        case 1:  // exit
92          ptypes.set(rpc, [nn.typeArguments[0], null]);
93          break;
94        case 2:  // notifications
95          ptypes.set(rpc, [nn.typeArguments[0], null]);
96          break;
97        case 4:  // request with no parameters
98          ptypes.set(rpc, [null, nn.typeArguments[0]]);
99          break;
100        case 5:  // request req, resp, partial(?)
101          ptypes.set(rpc, [nn.typeArguments[0], nn.typeArguments[1]]);
102          break;
103        default:
104          throw new Error(`${nn.typeArguments?.length} at ${loc(nn)}`);
105      }
106    }
107  }
108  if (rpc == '') throw new Error(`112 no name found at ${loc(x)}`);
109  // remember the implied types
110  const [a, b] = ptypes.get(rpc);
111  const add = function (n: ts.Node) {
112    rpcTypes.add(goName(n.getText()));
113  };
114  underlying(a, add);
115  underlying(b, add);
116  rpc = rpc.substring(1, rpc.length - 1);  // 'exit'
117  reqnot.set(rpc, newNode);
118}
119
120function setReceives() {
121  // mark them all as server, then adjust the client ones.
122  // it would be nice to have some independent check on this
123  // (this logic fails if the server ever sends $/canceRequest
124  //  or $/progress)
125  req.forEach((_, k) => { receives.set(k, 'server'); });
126  not.forEach((_, k) => { receives.set(k, 'server'); });
127  receives.set('window/showMessage', 'client');
128  receives.set('window/showMessageRequest', 'client');
129  receives.set('window/logMessage', 'client');
130  receives.set('telemetry/event', 'client');
131  receives.set('client/registerCapability', 'client');
132  receives.set('client/unregisterCapability', 'client');
133  receives.set('workspace/workspaceFolders', 'client');
134  receives.set('workspace/configuration', 'client');
135  receives.set('workspace/applyEdit', 'client');
136  receives.set('textDocument/publishDiagnostics', 'client');
137  receives.set('window/workDoneProgress/create', 'client');
138  receives.set('window/showDocument', 'client');
139  receives.set('$/progress', 'client');
140  // a small check
141  receives.forEach((_, k) => {
142    if (!req.get(k) && !not.get(k)) throw new Error(`145 missing ${k}}`);
143    if (req.get(k) && not.get(k)) throw new Error(`146 dup ${k}`);
144  });
145}
146
147type DataKind = 'module' | 'interface' | 'alias' | 'enum' | 'class';
148
149interface Data {
150  kind: DataKind;
151  me: ts.Node;   // root node for this type
152  name: string;  // Go name
153  origname: string; // their name
154  generics: ts.NodeArray<ts.TypeParameterDeclaration>;
155  as: ts.NodeArray<ts.HeritageClause>;  // inheritance
156  // Interface
157  properties: ts.NodeArray<ts.PropertySignature>
158  alias: ts.TypeNode;                        // type alias
159  // module
160  statements: ts.NodeArray<ts.Statement>;
161  enums: ts.NodeArray<ts.EnumMember>;
162  // class
163  members: ts.NodeArray<ts.PropertyDeclaration>;
164}
165function newData(n: ts.Node, nm: string, k: DataKind, origname: string): Data {
166  return {
167    kind: k,
168    me: n, name: goName(nm), origname: origname,
169    generics: ts.factory.createNodeArray<ts.TypeParameterDeclaration>(),
170    as: ts.factory.createNodeArray<ts.HeritageClause>(),
171    properties: ts.factory.createNodeArray<ts.PropertySignature>(), alias: undefined,
172    statements: ts.factory.createNodeArray<ts.Statement>(),
173    enums: ts.factory.createNodeArray<ts.EnumMember>(),
174    members: ts.factory.createNodeArray<ts.PropertyDeclaration>(),
175  };
176}
177
178// for debugging, produce a skeleton description
179function strData(d: Data): string {
180  if (!d) { return 'nil'; }
181  const f = function (na: ts.NodeArray<any>): number {
182    return na.length;
183  };
184  const nm = d.name == d.origname ? `${d.name}` : `${d.name}/${d.origname}`;
185  return `g:${f(d.generics)} a:${f(d.as)} p:${f(d.properties)} s:${f(d.statements)} e:${f(d.enums)} m:${f(d.members)} a:${d.alias !== undefined} D(${nm}) k:${d.kind}`;
186}
187
188let data = new Map<string, Data>();            // parsed data types
189let seenTypes = new Map<string, Data>();       // type names we've seen
190let extraTypes = new Map<string, string[]>();  // to avoid struct params
191
192function setData(nm: string, d: Data) {
193  const v = data.get(nm);
194  if (!v) {
195    data.set(nm, d);
196    return;
197  }
198  // if there are multiple definitions of the same name, decide what to do.
199  // For now the choices are only aliases and modules
200  // alias is preferred unless the constant values are needed
201  if (nm === 'PrepareSupportDefaultBehavior') {
202    // want the alias, as we're going to change the type and can't afford a constant
203    if (d.kind === 'alias') data.set(nm, d);
204    else if (v.kind == 'alias') data.set(nm, v);
205    else throw new Error(`208 ${d.kind} ${v.kind}`);
206    return;
207  }
208  if (nm === 'CodeActionKind') {
209    // want the module, need the constants
210    if (d.kind === 'module') data.set(nm, d);
211    else if (v.kind === 'module') data.set(nm, v);
212    else throw new Error(`215 ${d.kind} ${v.kind}`);
213  }
214  if (v.kind === 'alias' && d.kind !== 'alias') return;
215  if (d.kind === 'alias' && v.kind !== 'alias') {
216    data.set(nm, d);
217    return;
218  }
219  if (v.kind === 'alias' && d.kind === 'alias') return;
220  // protocol/src/common/protocol.foldingRange.ts 44: 1 (39: 2) and
221  // types/src/main.ts 397: 1 (392: 2)
222  // for FoldingRangeKind
223  if (d.me.getText() === v.me.getText()) return;
224  // error messages for an unexpected case
225  console.log(`228 ${strData(v)} ${loc(v.me)} for`);
226  console.log(`229 ${v.me.getText().replace(/\n/g, '\\n')}`);
227  console.log(`230 ${strData(d)} ${loc(d.me)}`);
228  console.log(`231 ${d.me.getText().replace(/\n/g, '\\n')}`);
229  throw new Error(`232 setData found ${v.kind} for ${d.kind}`);
230}
231
232// look at top level data definitions
233function genTypes(node: ts.Node) {
234  // Ignore top-level items that can't produce output
235  if (ts.isExpressionStatement(node) || ts.isFunctionDeclaration(node) ||
236    ts.isImportDeclaration(node) || ts.isVariableStatement(node) ||
237    ts.isExportDeclaration(node) || ts.isEmptyStatement(node) ||
238    ts.isExportAssignment(node) || ts.isImportEqualsDeclaration(node) ||
239    ts.isBlock(node) || node.kind == ts.SyntaxKind.EndOfFileToken) {
240    return;
241  }
242  if (ts.isInterfaceDeclaration(node)) {
243    const v: ts.InterfaceDeclaration = node;
244    // need to check the members, many of which are disruptive
245    let mems: ts.PropertySignature[] = [];
246    const f = function (t: ts.TypeElement) {
247      if (ts.isPropertySignature(t)) {
248        mems.push(t);
249      } else if (ts.isMethodSignature(t) || ts.isCallSignatureDeclaration(t)) {
250        return;
251      } else if (ts.isIndexSignatureDeclaration(t)) {
252        // probably safe to ignore these
253        // [key: string]: boolean | number | string | undefined;
254        // and InitializeResult: [custom: string]: any;]
255      } else
256        throw new Error(`259 unexpected ${strKind(t)}`);
257    };
258    v.members.forEach(f);
259    if (mems.length == 0 && !v.heritageClauses &&
260      v.name.getText() != 'InitializedParams') {
261      return;  // Don't seem to need any of these [Logger, PipTransport, ...]
262    }
263    // Found one we want
264    let x = newData(v, goName(v.name.getText()), 'interface', v.name.getText());
265    x.properties = ts.factory.createNodeArray<ts.PropertySignature>(mems);
266    if (v.typeParameters) x.generics = v.typeParameters;
267    if (v.heritageClauses) x.as = v.heritageClauses;
268    if (x.generics.length > 1) {  // Unneeded
269      // Item interface Item<K, V>...
270      return;
271    }
272    if (data.has(x.name)) {  // modifying one we've seen
273      x = dataChoose(x, data.get(x.name));
274    }
275    setData(x.name, x);
276  } else if (ts.isTypeAliasDeclaration(node)) {
277    const v: ts.TypeAliasDeclaration = node;
278    let x = newData(v, v.name.getText(), 'alias', v.name.getText());
279    x.alias = v.type;
280    // if type is a union of constants, we (mostly) don't want it
281    // (at the top level)
282    // Unfortunately this is false for TraceValues
283    if (ts.isUnionTypeNode(v.type) &&
284      v.type.types.every((n: ts.TypeNode) => ts.isLiteralTypeNode(n))) {
285      if (x.name != 'TraceValues') return;
286    }
287    if (v.typeParameters) {
288      x.generics = v.typeParameters;
289    }
290    if (data.has(x.name)) x = dataChoose(x, data.get(x.name));
291    if (x.generics.length > 1) {
292      return;
293    }
294    setData(x.name, x);
295  } else if (ts.isModuleDeclaration(node)) {
296    const v: ts.ModuleDeclaration = node;
297    if (!ts.isModuleBlock(v.body)) {
298      throw new Error(`${loc(v)} not ModuleBlock, but ${strKind(v.body)}`);
299    }
300    const b: ts.ModuleBlock = v.body;
301    var s: ts.Statement[] = [];
302    // we don't want most of these
303    const fx = function (x: ts.Statement) {
304      if (ts.isFunctionDeclaration(x)) {
305        return;
306      }
307      if (ts.isTypeAliasDeclaration(x) || ts.isModuleDeclaration(x)) {
308        return;
309      }
310      if (!ts.isVariableStatement(x))
311        throw new Error(
312          `315 expected VariableStatment ${loc(x)} ${strKind(x)} ${x.getText()}`);
313      if (hasNewExpression(x)) {
314        return;
315      }
316      s.push(x);
317    };
318    b.statements.forEach(fx);
319    if (s.length == 0) {
320      return;
321    }
322    let m = newData(node, v.name.getText(), 'module', v.name.getText());
323    m.statements = ts.factory.createNodeArray<ts.Statement>(s);
324    if (data.has(m.name)) m = dataChoose(m, data.get(m.name));
325    setData(m.name, m);
326  } else if (ts.isEnumDeclaration(node)) {
327    const nm = node.name.getText();
328    let v = newData(node, nm, 'enum', node.name.getText());
329    v.enums = node.members;
330    if (data.has(nm)) {
331      v = dataChoose(v, data.get(nm));
332    }
333    setData(nm, v);
334  } else if (ts.isClassDeclaration(node)) {
335    const v: ts.ClassDeclaration = node;
336    var d: ts.PropertyDeclaration[] = [];
337    const wanted = function (c: ts.ClassElement): string {
338      if (ts.isConstructorDeclaration(c)) {
339        return '';
340      }
341      if (ts.isMethodDeclaration(c)) {
342        return '';
343      }
344      if (ts.isGetAccessor(c)) {
345        return '';
346      }
347      if (ts.isSetAccessor(c)) {
348        return '';
349      }
350      if (ts.isPropertyDeclaration(c)) {
351        d.push(c);
352        return strKind(c);
353      }
354      throw new Error(`Class decl ${strKind(c)} `);
355    };
356    v.members.forEach((c) => wanted(c));
357    if (d.length == 0) {
358      return;
359    }  // don't need it
360    let c = newData(v, v.name.getText(), 'class', v.name.getText());
361    c.members = ts.factory.createNodeArray<ts.PropertyDeclaration>(d);
362    if (v.typeParameters) {
363      c.generics = v.typeParameters;
364    }
365    if (c.generics.length > 1) {
366      return;
367    }
368    if (v.heritageClauses) {
369      c.as = v.heritageClauses;
370    }
371    if (data.has(c.name))
372      throw new Error(`Class dup ${loc(c.me)} and ${loc(data.get(c.name).me)}`);
373    setData(c.name, c);
374  } else {
375    throw new Error(`378 unexpected ${strKind(node)} ${loc(node)} `);
376  }
377}
378
379// Typescript can accumulate, but this chooses one or the other
380function dataChoose(a: Data, b: Data): Data {
381  // maybe they are textually identical? (e.g., FoldingRangeKind)
382  const [at, bt] = [a.me.getText(), b.me.getText()];
383  if (at == bt) {
384    return a;
385  }
386  switch (a.name) {
387    case 'InitializeError':
388    case 'CompletionItemTag':
389    case 'SymbolTag':
390    case 'CodeActionKind':
391    case 'Integer':
392    case 'Uinteger':
393    case 'Decimal':
394      // want the Module, if anything
395      return a.statements.length > 0 ? a : b;
396    case 'CancellationToken':
397    case 'CancellationStrategy':
398      // want the Interface
399      return a.properties.length > 0 ? a : b;
400    case 'TextDocumentContentChangeEvent':  // almost the same
401    case 'TokenFormat':
402    case 'PrepareSupportDefaultBehavior':
403      return a;
404  }
405  console.log(
406    `409 ${strKind(a.me)} ${strKind(b.me)} ${a.name} ${loc(a.me)} ${loc(b.me)}`);
407  throw new Error(`410 Fix dataChoose for ${a.name}`);
408}
409
410// is a node an ancestor of a NewExpression
411function hasNewExpression(n: ts.Node): boolean {
412  let ans = false;
413  n.forEachChild((n: ts.Node) => {
414    if (ts.isNewExpression(n)) ans = true;
415  });
416  return ans;
417}
418
419function checkOnce() {
420  // Data for all the rpc types?
421  rpcTypes.forEach(s => {
422    if (!data.has(s)) throw new Error(`checkOnce, ${s}?`);
423  });
424}
425
426// helper function to find underlying types
427// eslint-disable-next-line no-unused-vars
428function underlying(n: ts.Node | undefined, f: (n: ts.Node) => void) {
429  if (!n) return;
430  const ff = function (n: ts.Node) {
431    underlying(n, f);
432  };
433  if (ts.isIdentifier(n)) {
434    f(n);
435  } else if (
436    n.kind == ts.SyntaxKind.StringKeyword ||
437    n.kind == ts.SyntaxKind.NumberKeyword ||
438    n.kind == ts.SyntaxKind.AnyKeyword ||
439    n.kind == ts.SyntaxKind.UnknownKeyword ||
440    n.kind == ts.SyntaxKind.NullKeyword ||
441    n.kind == ts.SyntaxKind.BooleanKeyword ||
442    n.kind == ts.SyntaxKind.ObjectKeyword ||
443    n.kind == ts.SyntaxKind.VoidKeyword) {
444    // nothing to do
445  } else if (ts.isTypeReferenceNode(n)) {
446    f(n.typeName);
447  } else if (ts.isArrayTypeNode(n)) {
448    underlying(n.elementType, f);
449  } else if (ts.isHeritageClause(n)) {
450    n.types.forEach(ff);
451  } else if (ts.isExpressionWithTypeArguments(n)) {
452    underlying(n.expression, f);
453  } else if (ts.isPropertySignature(n)) {
454    underlying(n.type, f);
455  } else if (ts.isTypeLiteralNode(n)) {
456    n.members.forEach(ff);
457  } else if (ts.isUnionTypeNode(n) || ts.isIntersectionTypeNode(n)) {
458    n.types.forEach(ff);
459  } else if (ts.isIndexSignatureDeclaration(n)) {
460    underlying(n.type, f);
461  } else if (ts.isParenthesizedTypeNode(n)) {
462    underlying(n.type, f);
463  } else if (
464    ts.isLiteralTypeNode(n) || ts.isVariableStatement(n) ||
465    ts.isTupleTypeNode(n)) {
466    // we only see these in moreTypes, but they are handled elsewhere
467  } else if (ts.isEnumMember(n)) {
468    if (ts.isStringLiteral(n.initializer)) return;
469    throw new Error(`472 EnumMember ${strKind(n.initializer)} ${n.name.getText()}`);
470  } else {
471    throw new Error(`474 saw ${strKind(n)} in underlying. ${n.getText()} at ${loc(n)}`);
472  }
473}
474
475// find all the types implied by seenTypes.
476// Simplest way to the transitive closure is to stabilize the size of seenTypes
477// but it is slow
478function moreTypes() {
479  const extra = function (s: string) {
480    if (!data.has(s)) throw new Error(`moreTypes needs ${s}`);
481    seenTypes.set(s, data.get(s));
482  };
483  rpcTypes.forEach(extra);  // all the types needed by the rpcs
484  // needed in enums.go (or elsewhere)
485  extra('InitializeError');
486  extra('WatchKind');
487  extra('FoldingRangeKind');
488  // not sure why these weren't picked up
489  extra('DidChangeWatchedFilesRegistrationOptions');
490  extra('WorkDoneProgressBegin');
491  extra('WorkDoneProgressReport');
492  extra('WorkDoneProgressEnd');
493  let old = 0;
494  do {
495    old = seenTypes.size;
496
497    const m = new Map<string, Data>();
498    const add = function (n: ts.Node) {
499      const nm = goName(n.getText());
500      if (seenTypes.has(nm) || m.has(nm)) return;
501      if (data.get(nm)) {
502        m.set(nm, data.get(nm));
503      }
504    };
505    // expect all the heritage clauses have single Identifiers
506    const h = function (n: ts.Node) {
507      underlying(n, add);
508    };
509    const f = function (x: ts.NodeArray<ts.Node>) {
510      x.forEach(h);
511    };
512    seenTypes.forEach((d: Data) => d && f(d.as));
513    // find the types in the properties
514    seenTypes.forEach((d: Data) => d && f(d.properties));
515    // and in the alias and in the statements and in the enums
516    seenTypes.forEach((d: Data) => d && underlying(d.alias, add));
517    seenTypes.forEach((d: Data) => d && f(d.statements));
518    seenTypes.forEach((d: Data) => d && f(d.enums));
519    m.forEach((d, k) => seenTypes.set(k, d));
520  }
521  while (seenTypes.size != old)
522    ;
523}
524
525function cleanData() { // middle pass
526  // seenTypes contains all the top-level types.
527  seenTypes.forEach((d) => {
528    if (d.kind == 'alias') mergeAlias(d);
529  });
530}
531
532function sameType(a: ts.TypeNode, b: ts.TypeNode): boolean {
533  if (a.kind !== b.kind) return false;
534  if (a.kind === ts.SyntaxKind.BooleanKeyword) return true;
535  if (ts.isTypeReferenceNode(a) && ts.isTypeReferenceNode(b) &&
536    a.typeName.getText() === b.typeName.getText()) return true;
537  if (ts.isArrayTypeNode(a) && ts.isArrayTypeNode(b)) return sameType(a.elementType, b.elementType);
538  if (ts.isTypeLiteralNode(a) && ts.isTypeLiteralNode(b)) {
539    if (a.members.length !== b.members.length) return false;
540    if (a.members.length === 1) return a.members[0].name.getText() === b.members[0].name.getText();
541    if (loc(a) === loc(b)) return true;
542  }
543  throw new Error(`546 sameType? ${strKind(a)} ${strKind(b)}`);
544}
545type CreateMutable<Type> = {
546  -readonly [Property in keyof Type]: Type[Property];
547};
548type propMap = Map<string, ts.PropertySignature>;
549function propMapSet(pm: propMap, name: string, v: ts.PropertySignature) {
550  if (!pm.get(name)) {
551    try { getComments(v); } catch (e) { console.log(`552 ${name} ${e}`); }
552    pm.set(name, v);
553    return;
554  }
555  const a = pm.get(name).type;
556  const b = v.type;
557  if (sameType(a, b)) {
558    return;
559  }
560  if (ts.isTypeReferenceNode(a) && ts.isTypeLiteralNode(b)) {
561    const x = mergeTypeRefLit(a, b);
562    const fake: CreateMutable<ts.PropertySignature> = v;
563    fake['type'] = x;
564    check(fake as ts.PropertySignature, '565');
565    pm.set(name, fake as ts.PropertySignature);
566    return;
567  }
568  if (ts.isTypeLiteralNode(a) && ts.isTypeLiteralNode(b)) {
569    const x = mergeTypeLitLit(a, b);
570    const fake: CreateMutable<ts.PropertySignature> = v;
571    fake['type'] = x;
572    check(fake as ts.PropertySignature, '578');
573    pm.set(name, fake as ts.PropertySignature);
574    return;
575  }
576  console.log(`577 ${pm.get(name).getText()}\n${v.getText()}`);
577  throw new Error(`578 should merge ${strKind(a)} and ${strKind(b)} for ${name}`);
578}
579function addToProperties(pm: propMap, tn: ts.TypeNode | undefined, prefix = '') {
580  if (!tn) return;
581  if (ts.isTypeReferenceNode(tn)) {
582    const d = seenTypes.get(goName(tn.typeName.getText()));
583    if (tn.typeName.getText() === 'T') return;
584    if (!d) throw new Error(`584 ${tn.typeName.getText()} not found`);
585    if (d.properties.length === 0 && d.alias === undefined) return;
586    if (d.alias !== undefined) {
587      if (ts.isIntersectionTypeNode(d.alias)) {
588        d.alias.types.forEach((tn) => addToProperties(pm, tn, prefix)); // prefix?
589        return;
590      }
591    }
592    d.properties.forEach((ps) => {
593      const name = `${prefix}.${ps.name.getText()}`;
594      propMapSet(pm, name, ps);
595      addToProperties(pm, ps.type, name);
596    });
597  } else if (strKind(tn) === 'TypeLiteral') {
598    if (!ts.isTypeLiteralNode(tn)) new Error(`598 ${strKind(tn)}`);
599    tn.forEachChild((child: ts.Node) => {
600      if (!ts.isPropertySignature(child)) throw new Error(`600 ${strKind(child)}`);
601      const name = `${prefix}.${child.name.getText()}`;
602      propMapSet(pm, name, child);
603      addToProperties(pm, child.type, name);
604    });
605  }
606}
607function deepProperties(d: Data): propMap | undefined {
608  let properties: propMap = new Map<string, ts.PropertySignature>();
609  if (!d.alias || !ts.isIntersectionTypeNode(d.alias)) return undefined;
610  d.alias.types.forEach((ts) => addToProperties(properties, ts));
611  return properties;
612}
613
614function mergeAlias(d: Data) {
615  const props = deepProperties(d);
616  if (!props) return; // nothing merged
617  // now each element of props should have length 1
618  // change d to merged, toss its alias field, fill in its properties
619  const v: ts.PropertySignature[] = [];
620  props.forEach((ps, nm) => {
621    const xlen = nm.split('.').length;
622    if (xlen !== 2) return; // not top-level
623    v.push(ps);
624  });
625  d.kind = 'interface';
626  d.alias = undefined;
627  d.properties = ts.factory.createNodeArray(v);
628}
629
630function mergeTypeLitLit(a: ts.TypeLiteralNode, b: ts.TypeLiteralNode): ts.TypeLiteralNode {
631  const v = new Map<string, ts.TypeElement>(); // avoid duplicates
632  a.members.forEach((te) => v.set(te.name.getText(), te));
633  b.members.forEach((te) => v.set(te.name.getText(), te));
634  const x: ts.TypeElement[] = [];
635  v.forEach((te) => x.push(te));
636  const fake: CreateMutable<ts.TypeLiteralNode> = a;
637  fake['members'] = ts.factory.createNodeArray(x);
638  check(fake as ts.TypeLiteralNode, '643');
639  return fake as ts.TypeLiteralNode;
640}
641
642function mergeTypeRefLit(a: ts.TypeReferenceNode, b: ts.TypeLiteralNode): ts.TypeLiteralNode {
643  const d = seenTypes.get(goName(a.typeName.getText()));
644  if (!d) throw new Error(`644 name ${a.typeName.getText()} not found`);
645  const typ = d.me;
646  if (!ts.isInterfaceDeclaration(typ)) throw new Error(`646 got ${strKind(typ)} not InterfaceDecl`);
647  const v = new Map<string, ts.TypeElement>(); // avoid duplicates
648  typ.members.forEach((te) => v.set(te.name.getText(), te));
649  b.members.forEach((te) => v.set(te.name.getText(), te));
650  const x: ts.TypeElement[] = [];
651  v.forEach((te) => x.push(te));
652
653  const w = ts.factory.createNodeArray(x);
654  const fk: CreateMutable<ts.TypeLiteralNode> = b;
655  fk['members'] = w;
656  (fk['members'] as { pos: number })['pos'] = b.members.pos;
657  (fk['members'] as { end: number })['end'] = b.members.end;
658  check(fk as ts.TypeLiteralNode, '662');
659  return fk as ts.TypeLiteralNode;
660}
661
662// check that constructed nodes still have associated text
663function check(n: ts.Node, loc: string) {
664  try { getComments(n); } catch (e) { console.log(`check at ${loc} ${e}`); }
665  try { n.getText(); } catch (e) { console.log(`text check at ${loc}`); }
666}
667
668let typesOut = new Array<string>();
669let constsOut = new Array<string>();
670
671// generate Go types
672function toGo(d: Data, nm: string) {
673  if (!d) return;  // this is probably a generic T
674  if (d.name.startsWith('Inner') || d.name === 'WindowClientCapabilities') return; // removed by alias processing
675  if (d.name === 'Integer' || d.name === 'Uinteger') return; // unneeded
676  switch (d.kind) {
677    case 'alias':
678      goTypeAlias(d, nm); break;
679    case 'module': goModule(d, nm); break;
680    case 'enum': goEnum(d, nm); break;
681    case 'interface': goInterface(d, nm); break;
682    default:
683      throw new Error(
684        `672: more cases in toGo ${nm} ${d.kind}`);
685  }
686}
687
688// these fields need a * and are not covered by the code
689// that calls isStructType.
690var starred: [string, string][] = [
691  ['TextDocumentContentChangeEvent', 'range'], ['CodeAction', 'command'],
692  ['CodeAction', 'disabled'],
693  ['DidSaveTextDocumentParams', 'text'], ['CompletionItem', 'command'],
694  ['Diagnostic', 'codeDescription']
695];
696
697// generate Go code for an interface
698function goInterface(d: Data, nm: string) {
699  let ans = `type ${goName(nm)} struct {\n`;
700
701  // generate the code for each member
702  const g = function (n: ts.PropertySignature) {
703    if (!ts.isPropertySignature(n))
704      throw new Error(`expected PropertySignature got ${strKind(n)} `);
705    ans = ans.concat(getComments(n));
706    const json = u.JSON(n);
707    let gt = goType(n.type, n.name.getText());
708    if (gt == d.name) gt = '*' + gt; // avoid recursive types (SelectionRange)
709    // there are several cases where a * is needed
710    // (putting * in front of too many things breaks uses of CodeActionKind)
711    starred.forEach(([a, b]) => {
712      if (d.name == a && n.name.getText() == b) {
713        gt = '*' + gt;
714      }
715    });
716    ans = ans.concat(`${goName(n.name.getText())} ${gt}`, json, '\n');
717  };
718  d.properties.forEach(g);
719  // heritage clauses become embedded types
720  // check they are all Identifiers
721  const f = function (n: ts.ExpressionWithTypeArguments) {
722    if (!ts.isIdentifier(n.expression))
723      throw new Error(`Interface ${nm} heritage ${strKind(n.expression)} `);
724    ans = ans.concat(goName(n.expression.getText()), '\n');
725  };
726  d.as.forEach((n: ts.HeritageClause) => n.types.forEach(f));
727  ans = ans.concat('}\n');
728  typesOut.push(getComments(d.me));
729  typesOut.push(ans);
730}
731
732// generate Go code for a module (const declarations)
733// Generates type definitions, and named constants
734function goModule(d: Data, nm: string) {
735  if (d.generics.length > 0 || d.as.length > 0) {
736    throw new Error(`743 goModule: unexpected for ${nm}
737  `);
738  }
739  // all the statements should be export const <id>: value
740  //   or value = value
741  // They are VariableStatements with x.declarationList having a single
742  //   VariableDeclaration
743  let isNumeric = false;
744  const f = function (n: ts.Statement, i: number) {
745    if (!ts.isVariableStatement(n)) {
746      throw new Error(`753 ${nm} ${i} expected VariableStatement,
747      got ${strKind(n)}`);
748    }
749    const c = getComments(n);
750    const v = n.declarationList.declarations[0];  // only one
751
752    if (!v.initializer)
753      throw new Error(`760 no initializer ${nm} ${i} ${v.name.getText()}`);
754    isNumeric = strKind(v.initializer) == 'NumericLiteral';
755    if (c != '') constsOut.push(c);  // no point if there are no comments
756    // There are duplicates.
757    const cname = constName(goName(v.name.getText()), nm);
758    let val = v.initializer.getText();
759    val = val.split('\'').join('"');  // useless work for numbers
760    constsOut.push(`${cname} ${nm} = ${val}`);
761  };
762  d.statements.forEach(f);
763  typesOut.push(getComments(d.me));
764  // Or should they be type aliases?
765  typesOut.push(`type ${nm} ${isNumeric ? 'float64' : 'string'}`);
766}
767
768// generate Go code for an enum. Both types and named constants
769function goEnum(d: Data, nm: string) {
770  let isNumeric = false;
771  const f = function (v: ts.EnumMember, j: number) {  // same as goModule
772    if (!v.initializer)
773      throw new Error(`goEnum no initializer ${nm} ${j} ${v.name.getText()}`);
774    isNumeric = strKind(v.initializer) == 'NumericLiteral';
775    const c = getComments(v);
776    const cname = constName(goName(v.name.getText()), nm);
777    let val = v.initializer.getText();
778    val = val.split('\'').join('"');  // replace quotes. useless work for numbers
779    constsOut.push(`${c}${cname} ${nm} = ${val}`);
780  };
781  d.enums.forEach(f);
782  typesOut.push(getComments(d.me));
783  // Or should they be type aliases?
784  typesOut.push(`type ${nm} ${isNumeric ? 'float64' : 'string'}`);
785}
786
787// generate code for a type alias
788function goTypeAlias(d: Data, nm: string) {
789  if (d.as.length != 0 || d.generics.length != 0) {
790    if (nm != 'ServerCapabilities')
791      throw new Error(`${nm} has extra fields(${d.as.length},${d.generics.length}) ${d.me.getText()}`);
792  }
793  typesOut.push(getComments(d.me));
794  // d.alias doesn't seem to have comments
795  let aliasStr = goName(nm) == 'DocumentURI' ? ' ' : ' = ';
796  if (nm == 'PrepareSupportDefaultBehavior') {
797    // code-insiders is sending a bool, not a number. PJW: check this after Feb/2021
798    // (and gopls never looks at it anyway)
799    typesOut.push(`type ${goName(nm)}${aliasStr}interface{}\n`);
800    return;
801  }
802  typesOut.push(`type ${goName(nm)}${aliasStr}${goType(d.alias, nm)}\n`);
803}
804
805// return a go type and maybe an assocated javascript tag
806function goType(n: ts.TypeNode | undefined, nm: string): string {
807  if (!n) throw new Error(`goType undefined for ${nm}`);
808  if (n.getText() == 'T') return 'interface{}';  // should check it's generic
809  if (ts.isTypeReferenceNode(n)) {
810    // DocumentDiagnosticReportKind.unChanged (or .new) value is "new" or "unChanged"
811    if (n.getText().startsWith('DocumentDiagnostic')) return 'string';
812    switch (n.getText()) {
813      case 'integer': return 'int32';
814      case 'uinteger': return 'uint32';
815      default: return goName(n.typeName.getText());  // avoid <T>
816    }
817  } else if (ts.isUnionTypeNode(n)) {
818    return goUnionType(n, nm);
819  } else if (ts.isIntersectionTypeNode(n)) {
820    return goIntersectionType(n, nm);
821  } else if (strKind(n) == 'StringKeyword') {
822    return 'string';
823  } else if (strKind(n) == 'NumberKeyword') {
824    return 'float64';
825  } else if (strKind(n) == 'BooleanKeyword') {
826    return 'bool';
827  } else if (strKind(n) == 'AnyKeyword' || strKind(n) == 'UnknownKeyword') {
828    return 'interface{}';
829  } else if (strKind(n) == 'NullKeyword') {
830    return 'nil';
831  } else if (strKind(n) == 'VoidKeyword' || strKind(n) == 'NeverKeyword') {
832    return 'void';
833  } else if (strKind(n) == 'ObjectKeyword') {
834    return 'interface{}';
835  } else if (ts.isArrayTypeNode(n)) {
836    if (nm === 'arguments') {
837      // Command and ExecuteCommandParams
838      return '[]json.RawMessage';
839    }
840    return `[]${goType(n.elementType, nm)}`;
841  } else if (ts.isParenthesizedTypeNode(n)) {
842    return goType(n.type, nm);
843  } else if (ts.isLiteralTypeNode(n)) {
844    return strKind(n.literal) == 'StringLiteral' ? 'string' : 'float64';
845  } else if (ts.isTypeLiteralNode(n)) {
846    // these are anonymous structs
847    const v = goTypeLiteral(n, nm);
848    return v;
849  } else if (ts.isTupleTypeNode(n)) {
850    if (n.getText() == '[number, number]') return '[]float64';
851    throw new Error(`goType unexpected Tuple ${n.getText()}`);
852  }
853  throw new Error(`${strKind(n)} goType unexpected ${n.getText()} for ${nm}`);
854}
855
856// The choice is uniform interface{}, or some heuristically assigned choice,
857// or some better sytematic idea I haven't thought of. Using interface{}
858// is, in practice, impossibly complex in the existing code.
859function goUnionType(n: ts.UnionTypeNode, nm: string): string {
860  let help = `/*${n.getText()}*/`;  // show the original as a comment
861  // There are some bad cases with newlines:
862  // range?: boolean | {\n	};
863  // full?: boolean | {\n		/**\n		 * The server supports deltas for full documents.\n		 */\n		delta?: boolean;\n	}
864  // These are handled specially:
865  if (nm == 'range') help = help.replace(/\n/, '');
866  if (nm == 'full' && help.indexOf('\n') != -1) {
867    help = '/*boolean | <elided struct>*/';
868  }
869  // handle all the special cases
870  switch (n.types.length) {
871    case 2: {
872      const a = strKind(n.types[0]);
873      const b = strKind(n.types[1]);
874      if (a == 'NumberKeyword' && b == 'StringKeyword') {  // ID
875        return `interface{} ${help}`;
876      }
877      if (b == 'NullKeyword' || n.types[1].getText() === 'null') {
878        // PJW: fix this. it looks like 'null' is now being parsed as LiteralType
879        // and check the other keyword cases
880        if (nm == 'textDocument/codeAction') {
881          // (Command | CodeAction)[] | null
882          return `[]CodeAction ${help}`;
883        }
884        let v = goType(n.types[0], 'a');
885        return `${v} ${help}`;
886      }
887      if (a == 'BooleanKeyword') {  // usually want bool
888        if (nm == 'codeActionProvider') return `interface{} ${help}`;
889        if (nm == 'renameProvider') return `interface{} ${help}`;
890        if (nm == 'full') return `interface{} ${help}`; // there's a struct
891        if (nm == 'save') return `${goType(n.types[1], '680')} ${help}`;
892        return `${goType(n.types[0], 'b')} ${help}`;
893      }
894      if (b == 'ArrayType') return `${goType(n.types[1], 'c')} ${help}`;
895      if (help.includes('InsertReplaceEdit') && n.types[0].getText() == 'TextEdit') {
896        return `*TextEdit ${help}`;
897      }
898      if (a == 'TypeReference') {
899        if (nm == 'edits') return `${goType(n.types[0], '715')} ${help}`;
900        if (a == b) return `interface{} ${help}`;
901        if (nm == 'code') return `interface{} ${help}`;
902      }
903      if (a == 'StringKeyword') return `string ${help}`;
904      if (a == 'TypeLiteral' && nm == 'TextDocumentContentChangeEvent') {
905        return `${goType(n.types[0], nm)}`;
906      }
907      if (a == 'TypeLiteral' && b === 'TypeLiteral') {
908        // DocumentDiagnosticReport
909        // the first one includes the second one
910        return `${goType(n.types[0], '9d')}`;
911      }
912      throw new Error(`911 ${nm}: a:${a}/${goType(n.types[0], '9a')} b:${b}/${goType(n.types[1], '9b')} ${loc(n)}`);
913    }
914    case 3: {
915      const aa = strKind(n.types[0]);
916      const bb = strKind(n.types[1]);
917      const cc = strKind(n.types[2]);
918      if (nm == 'DocumentFilter') {
919        // not really a union. the first is enough, up to a missing
920        // omitempty but avoid repetitious comments
921        return `${goType(n.types[0], 'g')}`;
922      }
923      if (nm == 'textDocument/documentSymbol') {
924        return `[]interface{} ${help}`;
925      }
926      if (aa == 'TypeReference' && bb == 'ArrayType' && (cc == 'NullKeyword' || cc === 'LiteralType')) {
927        return `${goType(n.types[0], 'd')} ${help}`;
928      }
929      if (aa == 'TypeReference' && bb == aa && cc == 'ArrayType') {
930        // should check that this is Hover.Contents
931        return `${goType(n.types[0], 'e')} ${help}`;
932      }
933      if (aa == 'ArrayType' && bb == 'TypeReference' && (cc == 'NullKeyword' || cc === 'LiteralType')) {
934        // check this is nm == 'textDocument/completion'
935        return `${goType(n.types[1], 'f')} ${help}`;
936      }
937      if (aa == 'LiteralType' && bb == aa && cc == aa) return `string ${help}`;
938      // keep this for diagnosing unexpected interface{} results
939      // console.log(`931, interface{} for ${aa}/${goType(n.types[0], 'g')},${bb}/${goType(n.types[1], 'h')},${cc}/${goType(n.types[2], 'i')} ${nm}`);
940      break;
941    }
942    case 4:
943      if (nm == 'documentChanges') return `TextDocumentEdit ${help} `;
944      if (nm == 'textDocument/prepareRename') return `Range ${help} `;
945    // eslint-disable-next-line no-fallthrough
946    default:
947      throw new Error(`goUnionType len=${n.types.length} nm=${nm}`);
948  }
949
950  // Result will be interface{} with a comment
951  let isLiteral = true;
952  let literal = 'string';
953  let res = 'interface{} /* ';
954  n.types.forEach((v: ts.TypeNode, i: number) => {
955    // might get an interface inside:
956    //  (Command | CodeAction)[] | null
957    let m = goType(v, nm);
958    if (m.indexOf('interface') != -1) {
959      // avoid nested comments
960      m = m.split(' ')[0];
961    }
962    m = m.split('\n').join('; ');  // sloppy: struct{;
963    res = res.concat(`${i == 0 ? '' : ' | '}`, m);
964    if (!ts.isLiteralTypeNode(v)) isLiteral = false;
965    else literal = strKind(v.literal) == 'StringLiteral' ? 'string' : 'number';
966  });
967  if (!isLiteral) {
968    return res + '*/';
969  }
970  // I don't think we get here
971  // trace?: 'off' | 'messages' | 'verbose' should get string
972  return `${literal} /* ${n.getText()} */`;
973}
974
975// some of the intersection types A&B are ok as struct{A;B;} and some
976// could be expanded, and ClientCapabilites has to be expanded,
977// at least for workspace. It's possible to check algorithmically,
978// but much simpler just to check explicitly.
979function goIntersectionType(n: ts.IntersectionTypeNode, nm: string): string {
980  if (nm == 'ClientCapabilities') return expandIntersection(n);
981  //if (nm == 'ServerCapabilities') return expandIntersection(n); // save for later consideration
982  let inner = '';
983  n.types.forEach(
984    (t: ts.TypeNode) => { inner = inner.concat(goType(t, nm), '\n'); });
985  return `struct{ \n${inner}} `;
986}
987
988// for each of the intersected types, extract its components (each will
989// have a Data with properties) extract the properties, and keep track
990// of them by name. The names that occur once can be output. The names
991// that occur more than once need to be combined.
992function expandIntersection(n: ts.IntersectionTypeNode): string {
993  const bad = function (n: ts.Node, s: string) {
994    return new Error(`expandIntersection ${strKind(n)} ${s}`);
995  };
996  let props = new Map<string, ts.PropertySignature[]>();
997  for (const tp of n.types) {
998    if (!ts.isTypeReferenceNode(tp)) throw bad(tp, 'A');
999    const d = data.get(goName(tp.typeName.getText()));
1000    for (const p of d.properties) {
1001      if (!ts.isPropertySignature(p)) throw bad(p, 'B');
1002      let v = props.get(p.name.getText()) || [];
1003      v.push(p);
1004      props.set(p.name.getText(), v);
1005    }
1006  }
1007  let ans = 'struct {\n';
1008  for (const [k, v] of Array.from(props)) {
1009    if (v.length == 1) {
1010      const a = v[0];
1011      ans = ans.concat(getComments(a));
1012      ans = ans.concat(`${goName(k)} ${goType(a.type, k)} ${u.JSON(a)}\n`);
1013      continue;
1014    }
1015    ans = ans.concat(`${goName(k)} struct {\n`);
1016    for (let i = 0; i < v.length; i++) {
1017      const a = v[i];
1018      if (ts.isTypeReferenceNode(a.type)) {
1019        ans = ans.concat(getComments(a));
1020        ans = ans.concat(goName(a.type.typeName.getText()), '\n');
1021      } else if (ts.isTypeLiteralNode(a.type)) {
1022        if (a.type.members.length != 1) throw bad(a.type, 'C');
1023        const b = a.type.members[0];
1024        if (!ts.isPropertySignature(b)) throw bad(b, 'D');
1025        ans = ans.concat(getComments(b));
1026        ans = ans.concat(
1027          goName(b.name.getText()), ' ', goType(b.type, 'a'), u.JSON(b), '\n');
1028      } else {
1029        throw bad(a.type, `E ${a.getText()} in ${goName(k)} at ${loc(a)}`);
1030      }
1031    }
1032    ans = ans.concat('}\n');
1033  }
1034  ans = ans.concat('}\n');
1035  return ans;
1036}
1037
1038// Does it make sense to use a pointer?
1039function isStructType(te: ts.TypeNode): boolean {
1040  switch (strKind(te)) {
1041    case 'UnionType': // really need to know which type will be chosen
1042    case 'BooleanKeyword':
1043    case 'StringKeyword':
1044    case 'ArrayType':
1045      return false;
1046    case 'TypeLiteral': return false; // true makes for difficult compound constants
1047    // but think more carefully to understands why starred is needed.
1048    case 'TypeReference': {
1049      if (!ts.isTypeReferenceNode(te)) throw new Error(`1047 impossible ${strKind(te)}`);
1050      const d = seenTypes.get(goName(te.typeName.getText()));
1051      if (d === undefined) return false;
1052      if (d.properties.length > 1) return true;
1053      // alias or interface with a single property (The alias is Uinteger, which we ignore later)
1054      if (d.alias) return false;
1055      const x = d.properties[0].type;
1056      return isStructType(x);
1057    }
1058    default: throw new Error(`1055 indirectable> ${strKind(te)}`);
1059  }
1060}
1061
1062function goTypeLiteral(n: ts.TypeLiteralNode, nm: string): string {
1063  let ans: string[] = [];  // in case we generate a new extra type
1064  let res = 'struct{\n';   // the actual answer usually
1065  const g = function (nx: ts.TypeElement) {
1066    // add the json, as in goInterface(). Strange inside union types.
1067    if (ts.isPropertySignature(nx)) {
1068      let json = u.JSON(nx);
1069      let typ = goType(nx.type, nx.name.getText());
1070      const v = getComments(nx) || '';
1071      starred.forEach(([a, b]) => {
1072        if (a != nm || b != typ.toLowerCase()) return;
1073        typ = '*' + typ;
1074        json = json.substring(0, json.length - 2) + ',omitempty"`';
1075      });
1076      if (typ[0] !== '*' && isStructType(nx.type)) typ = '*' + typ;
1077      res = res.concat(`${v} ${goName(nx.name.getText())} ${typ}`, json, '\n');
1078      ans.push(`${v}${goName(nx.name.getText())} ${typ} ${json}\n`);
1079    } else if (ts.isIndexSignatureDeclaration(nx)) {
1080      const comment = nx.getText().replace(/[/]/g, '');
1081      if (nx.getText() == '[uri: string]: TextEdit[];') {
1082        res = 'map[string][]TextEdit';
1083      } else if (nx.getText() == '[id: string /* ChangeAnnotationIdentifier */]: ChangeAnnotation;') {
1084        res = 'map[string]ChangeAnnotationIdentifier';
1085      } else if (nx.getText().startsWith('[uri: string')) {
1086        res = 'map[string]interface{}';
1087      } else {
1088        throw new Error(`1088 handle ${nx.getText()} ${loc(nx)}`);
1089      }
1090      res += ` /*${comment}*/`;
1091      ans.push(res);
1092      return;
1093    } else
1094      throw new Error(`TypeLiteral had ${strKind(nx)}`);
1095  };
1096  n.members.forEach(g);
1097  // for some the generated type is wanted, for others it's not needed
1098  if (!nm.startsWith('workspace')) {
1099    if (res.startsWith('struct')) return res + '}';  // map[] is special
1100    return res;
1101  }
1102  // these names have to be made unique
1103  const genName = `${goName(nm)}${extraTypes.size}Gn`;
1104  extraTypes.set(genName, ans);
1105  return genName;
1106}
1107
1108// print all the types and constants and extra types
1109function outputTypes() {
1110  // generate go types alphabeticaly
1111  let v = Array.from(seenTypes.keys());
1112  v.sort();
1113  v.forEach((x) => toGo(seenTypes.get(x), x));
1114  u.prgo(u.computeHeader(true));
1115  u.prgo('import "encoding/json"\n\n');
1116  typesOut.forEach((s) => {
1117    u.prgo(s);
1118    // it's more convenient not to have to think about trailing newlines
1119    // when generating types, but doc comments can't have an extra \n
1120    if (s.indexOf('/**') < 0) u.prgo('\n');
1121  });
1122  u.prgo('\nconst (\n');
1123  constsOut.forEach((s) => {
1124    u.prgo(s);
1125    u.prgo('\n');
1126  });
1127  u.prgo(')\n');
1128  u.prgo('// Types created to name formal parameters and embedded structs\n');
1129  extraTypes.forEach((v, k) => {
1130    u.prgo(` type ${k} struct {\n`);
1131    v.forEach((s) => {
1132      u.prgo(s);
1133      u.prgo('\n');
1134    });
1135    u.prgo('}\n');
1136  });
1137}
1138
1139// client and server ------------------
1140
1141interface side {
1142  methods: string[];
1143  cases: string[];
1144  calls: string[];
1145  name: string;    // client or server
1146  goName: string;  // Client or Server
1147  outputFile?: string;
1148  fd?: number
1149}
1150let client: side = {
1151  methods: [],
1152  cases: [],
1153  calls: [],
1154  name: 'client',
1155  goName: 'Client',
1156};
1157let server: side = {
1158  methods: [],
1159  cases: [],
1160  calls: [],
1161  name: 'server',
1162  goName: 'Server',
1163};
1164
1165// commonly used output
1166const notNil = `if len(r.Params()) > 0 {
1167  return true, reply(ctx, nil, errors.Errorf("%w: expected no params", jsonrpc2.ErrInvalidParams))
1168}`;
1169
1170// Go code for notifications. Side is client or server, m is the request
1171// method
1172function goNot(side: side, m: string) {
1173  if (m == '$/cancelRequest') return;  // handled specially in protocol.go
1174  const n = not.get(m);
1175  const a = goType(n.typeArguments[0], m);
1176  const nm = methodName(m);
1177  side.methods.push(sig(nm, a, ''));
1178  const caseHdr = ` case "${m}":  // notif`;
1179  let case1 = notNil;
1180  if (a != '' && a != 'void') {
1181    case1 = `var params ${a}
1182    if err := json.Unmarshal(r.Params(), &params); err != nil {
1183      return true, sendParseError(ctx, reply, err)
1184    }
1185    err:= ${side.name}.${nm}(ctx, &params)
1186    return true, reply(ctx, nil, err)`;
1187  } else {
1188    case1 = `err := ${side.name}.${nm}(ctx)
1189    return true, reply(ctx, nil, err)`;
1190  }
1191  side.cases.push(`${caseHdr}\n${case1}`);
1192
1193  const arg3 = a == '' || a == 'void' ? 'nil' : 'params';
1194  side.calls.push(`
1195  func (s *${side.name}Dispatcher) ${sig(nm, a, '', true)} {
1196    return s.sender.Notify(ctx, "${m}", ${arg3})
1197  }`);
1198}
1199
1200// Go code for requests.
1201function goReq(side: side, m: string) {
1202  const n = req.get(m);
1203  const nm = methodName(m);
1204  let a = goType(n.typeArguments[0], m);
1205  let b = goType(n.typeArguments[1], m);
1206  if (n.getText().includes('Type0')) {
1207    b = a;
1208    a = '';  // workspace/workspaceFolders and shutdown
1209  }
1210  u.prb(`${side.name} req ${a != ''}, ${b != ''} ${nm} ${m} ${loc(n)} `);
1211  side.methods.push(sig(nm, a, b));
1212
1213  const caseHdr = `case "${m}": // req`;
1214  let case1 = notNil;
1215  if (a != '') {
1216    if (extraTypes.has('Param' + nm)) a = 'Param' + nm;
1217    case1 = `var params ${a}
1218    if err := json.Unmarshal(r.Params(), &params); err != nil {
1219      return true, sendParseError(ctx, reply, err)
1220    }`;
1221    if (a === 'ParamInitialize') {
1222      case1 = `var params ${a}
1223    if err := json.Unmarshal(r.Params(), &params); err != nil {
1224      if _, ok := err.(*json.UnmarshalTypeError); !ok {
1225        return true, sendParseError(ctx, reply, err)
1226      }
1227    }`;
1228    }
1229  }
1230  const arg2 = a == '' ? '' : ', &params';
1231  // if case2 is not explicitly typed string, typescript makes it a union of strings
1232  let case2: string = `if err := ${side.name}.${nm}(ctx${arg2}); err != nil {
1233    event.Error(ctx, "", err)
1234  }`;
1235  if (b != '' && b != 'void') {
1236    case2 = `resp, err := ${side.name}.${nm}(ctx${arg2})
1237    return true, reply(ctx, resp, err)`;
1238  } else {  // response is nil
1239    case2 = `err := ${side.name}.${nm}(ctx${arg2})
1240    return true, reply(ctx, nil, err)`;
1241  }
1242
1243  side.cases.push(`${caseHdr}\n${case1}\n${case2}`);
1244
1245  const callHdr = `func (s *${side.name}Dispatcher) ${sig(nm, a, b, true)} {`;
1246  let callBody = `return s.sender.Call(ctx, "${m}", nil, nil)\n}`;
1247  if (b != '' && b != 'void') {
1248    const p2 = a == '' ? 'nil' : 'params';
1249    const returnType = indirect(b) ? `*${b}` : b;
1250    callBody = `var result ${returnType}
1251			if err := s.sender.Call(ctx, "${m}", ${p2}, &result); err != nil {
1252				return nil, err
1253      }
1254      return result, nil
1255    }`;
1256  } else if (a != '') {
1257    callBody = `return s.sender.Call(ctx, "${m}", params, nil) // Call, not Notify
1258  }`;
1259  }
1260  side.calls.push(`${callHdr}\n${callBody}\n`);
1261}
1262
1263// make sure method names are unique
1264let seenNames = new Set<string>();
1265function methodName(m: string): string {
1266  let i = m.indexOf('/');
1267  let s = m.substring(i + 1);
1268  let x = s[0].toUpperCase() + s.substring(1);
1269  for (let j = x.indexOf('/'); j >= 0; j = x.indexOf('/')) {
1270    let suffix = x.substring(j + 1);
1271    suffix = suffix[0].toUpperCase() + suffix.substring(1);
1272    let prefix = x.substring(0, j);
1273    x = prefix + suffix;
1274  }
1275  if (seenNames.has(x)) {
1276    // various Resolve and Diagnostic
1277    x += m[0].toUpperCase() + m.substring(1, i);
1278  }
1279  seenNames.add(x);
1280  return x;
1281}
1282
1283// used in sig and in goReq
1284function indirect(s: string): boolean {
1285  if (s == '' || s == 'void') return false;
1286  const skip = (x: string) => s.startsWith(x);
1287  if (skip('[]') || skip('interface') || skip('Declaration') ||
1288    skip('Definition') || skip('DocumentSelector'))
1289    return false;
1290  return true;
1291}
1292
1293// Go signatures for methods.
1294function sig(nm: string, a: string, b: string, names?: boolean): string {
1295  if (a.indexOf('struct') != -1) {
1296    const v = a.split('\n');
1297    extraTypes.set(`Param${nm}`, v.slice(1, v.length - 1));
1298    a = 'Param' + nm;
1299  }
1300  if (a == 'void')
1301    a = '';
1302  else if (a != '') {
1303    if (names)
1304      a = ', params *' + a;
1305    else
1306      a = ', *' + a;
1307  }
1308  let ret = 'error';
1309  if (b != '' && b != 'void') {
1310    // avoid * when it is senseless
1311    if (indirect(b)) b = '*' + b;
1312    ret = `(${b}, error)`;
1313  }
1314  let start = `${nm}(`;
1315  if (names) {
1316    start = start + 'ctx ';
1317  }
1318  return `${start}context.Context${a}) ${ret}`;
1319}
1320
1321// write the request/notification code
1322function output(side: side) {
1323  // make sure the output file exists
1324  if (!side.outputFile) {
1325    side.outputFile = `ts${side.name}.go`;
1326    side.fd = fs.openSync(side.outputFile, 'w');
1327  }
1328  const f = function (s: string) {
1329    fs.writeSync(side.fd!, s);
1330    fs.writeSync(side.fd!, '\n');
1331  };
1332  f(u.computeHeader(false));
1333  f(`
1334        import (
1335          "context"
1336          "encoding/json"
1337
1338          "golang.org/x/tools/internal/jsonrpc2"
1339          errors "golang.org/x/xerrors"
1340        )
1341        `);
1342  const a = side.name[0].toUpperCase() + side.name.substring(1);
1343  f(`type ${a} interface {`);
1344  side.methods.forEach((v) => { f(v); });
1345  f('}\n');
1346  f(`func ${side.name}Dispatch(ctx context.Context, ${side.name} ${a}, reply jsonrpc2.Replier, r jsonrpc2.Request) (bool, error) {
1347          switch r.Method() {`);
1348  side.cases.forEach((v) => { f(v); });
1349  f(`
1350        default:
1351          return false, nil
1352        }
1353      }`);
1354  side.calls.forEach((v) => { f(v); });
1355}
1356
1357// Handling of non-standard requests, so we can add gopls-specific calls.
1358function nonstandardRequests() {
1359  server.methods.push(
1360    'NonstandardRequest(ctx context.Context, method string, params interface{}) (interface{}, error)');
1361  server.calls.push(
1362    `func (s *serverDispatcher) NonstandardRequest(ctx context.Context, method string, params interface{}) (interface{}, error) {
1363      var result interface{}
1364      if err := s.sender.Call(ctx, method, params, &result); err != nil {
1365        return nil, err
1366      }
1367      return result, nil
1368    }
1369  `);
1370}
1371
1372// ----- remember it's a scripting language
1373function main() {
1374  if (u.gitHash != u.git()) {
1375    throw new Error(
1376      `git hash mismatch, wanted\n${u.gitHash} but source is at\n${u.git()}`);
1377  }
1378  u.createOutputFiles();
1379  parse();
1380  u.printAST(program);
1381  // find the Requests and Nofificatations
1382  for (const sourceFile of program.getSourceFiles()) {
1383    if (!sourceFile.isDeclarationFile) {
1384      ts.forEachChild(sourceFile, findRPCs);
1385    }
1386  }
1387  // separate RPCs into client and server
1388  setReceives();
1389  // visit every sourceFile collecting top-level type definitions
1390  for (const sourceFile of program.getSourceFiles()) {
1391    if (!sourceFile.isDeclarationFile) {
1392      ts.forEachChild(sourceFile, genTypes);
1393    }
1394  }
1395  // check that each thing occurs exactly once, and put pointers into
1396  // seenTypes
1397  checkOnce();
1398  // for each of Client and Server there are 3 parts to the output:
1399  // 1. type X interface {methods}
1400  // 2. func (h *serverHandler) Deliver(...) { switch r.method }
1401  // 3. func (x *xDispatcher) Method(ctx, parm)
1402  not.forEach(  // notifications
1403    (v, k) => {
1404      receives.get(k) == 'client' ? goNot(client, k) : goNot(server, k);
1405    });
1406  req.forEach(  // requests
1407    (v, k) => {
1408      receives.get(k) == 'client' ? goReq(client, k) : goReq(server, k);
1409    });
1410  nonstandardRequests();
1411  // find all the types implied by seenTypes and rpcs to try to avoid
1412  // generating types that aren't used
1413  moreTypes();
1414  // do merging
1415  cleanData();
1416  // and print the Go code
1417  outputTypes();
1418  console.log(`seen ${seenTypes.size + extraTypes.size}`);
1419  output(client);
1420  output(server);
1421}
1422
1423main();
1424