github.com/jd-ly/tools@v0.5.7/internal/lsp/protocol/typescript/code.ts (about)

     1  // read files from vscode-languageserver-node, and generate Go rpc stubs
     2  // and data definitions. (and maybe someday unmarshaling code)
     3  
     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.
     7  
     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.
    17  
    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';
    23  
    24  var program: ts.Program;
    25  
    26  function parse() {
    27    // this won't complain if some fnames don't exist
    28    program = ts.createProgram(
    29      u.fnames,
    30      { target: ts.ScriptTarget.ES2018, module: ts.ModuleKind.CommonJS });
    31    program.getTypeChecker();  // finish type checking and assignment
    32  }
    33  
    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
    40  
    41  function findRPCs(node: ts.Node) {
    42    if (!ts.isModuleDeclaration(node)) {
    43      return
    44    }
    45    if (!ts.isIdentifier(node.name)) {
    46      throw new Error(
    47        `expected Identifier, got ${strKind(node.name)} at ${loc(node)}`)
    48    }
    49    let reqnot = req
    50    let v = node.name.getText()
    51    if (v.endsWith('Notification')) reqnot = not;
    52    else if (!v.endsWith('Request')) return;
    53  
    54    if (!ts.isModuleBlock(node.body)) {
    55      throw new Error(
    56        `expected ModuleBody got ${strKind(node.body)} at ${loc(node)}`)
    57    }
    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
    62    let rpc: 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)}`);
    78          }
    79        }
    80        rpc = decl.initializer.getText()
    81      }
    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
    86        newNode = nn
    87        const mtd = nn.arguments[0];
    88        if (ts.isStringLiteral(mtd)) rpc = mtd.getText();
    89        switch (nn.typeArguments.length) {
    90          case 1:  // exit
    91            ptypes.set(rpc, [nn.typeArguments[0], null])
    92            break;
    93          case 2:  // notifications
    94            ptypes.set(rpc, [nn.typeArguments[0], null])
    95            break;
    96          case 4:  // request with no parameters
    97            ptypes.set(rpc, [null, nn.typeArguments[0]])
    98            break;
    99          case 5:  // request req, resp, partial(?)
   100            ptypes.set(rpc, [nn.typeArguments[0], nn.typeArguments[1]])
   101            break;
   102          default:
   103            throw new Error(`${nn.typeArguments.length} at ${loc(nn)}`)
   104        }
   105      }
   106    }
   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()))
   112    };
   113    underlying(a, add);
   114    underlying(b, add);
   115    rpc = rpc.substring(1, rpc.length - 1);  // 'exit'
   116    reqnot.set(rpc, newNode)
   117  }
   118  
   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)
   125    const p = n.parent;
   126    if (!ts.isVariableDeclaration(p)) throw new Error(`not variable decl`);
   127    const tr = p.type;
   128    if (!ts.isTypeReferenceNode(tr)) throw new Error(`not TypeReference`);
   129    return tr.typeArguments;
   130  }
   131  
   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
   136    //  or $/progress)
   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');
   151    // a small check
   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}`);
   155    })
   156  }
   157  
   158  interface Data {
   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
   163    // Interface
   164    properties: ts.NodeArray<ts.TypeElement>;  // ts.PropertySignature
   165    alias: ts.TypeNode;                        // type alias
   166    // module
   167    statements: ts.NodeArray<ts.Statement>;
   168    enums: ts.NodeArray<ts.EnumMember>;
   169    // class
   170    members: ts.NodeArray<ts.PropertyDeclaration>;
   171  }
   172  function newData(n: ts.Node, nm: string): Data {
   173    return {
   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>(),
   180    }
   181  }
   182  
   183  // for debugging, produce a skeleton description
   184  function strData(d: Data): string {
   185    const f = function (na: ts.NodeArray<any>): number {
   186      return na.length
   187    };
   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}`
   189  }
   190  
   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
   194  
   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) {
   203      return;
   204    }
   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)) {
   211          mems.push(t);
   212        } else if (ts.isMethodSignature(t) || ts.isCallSignatureDeclaration(t)) {
   213          return;
   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;]
   218          return
   219        } else
   220          throw new Error(`217 unexpected ${strKind(t)}`)
   221      };
   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)
   226      };
   227      // Found one we want
   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>...
   234        return
   235      };
   236      if (data.has(x.name)) {  // modifying one we've seen
   237        x = dataMerge(x, data.get(x.name));
   238      }
   239      data.set(x.name, x);
   240    } else if (ts.isTypeAliasDeclaration(node)) {
   241      const v: ts.TypeAliasDeclaration = node;
   242      let x = newData(v, v.name.getText());
   243      x.alias = v.type;
   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;
   250      }
   251      if (v.typeParameters) {
   252        x.generics = v.typeParameters;
   253      }
   254      if (data.has(x.name)) x = dataMerge(x, data.get(x.name));
   255      if (x.generics.length > 1) {
   256        return
   257      };
   258      data.set(x.name, x);
   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)}`)
   263      }
   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)) {
   269          return
   270        };
   271        if (ts.isTypeAliasDeclaration(x) || ts.isModuleDeclaration(x)) {
   272          return
   273        };
   274        if (!ts.isVariableStatement(x))
   275          throw new Error(
   276            `expected VariableStatment ${loc(x)} ${strKind(x)} ${x.getText()}`);
   277        if (hasNewExpression(x)) {
   278          return
   279        };
   280        s.push(x);
   281      };
   282      b.statements.forEach(fx)
   283      if (s.length == 0) {
   284        return
   285      };
   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));
   289      data.set(m.name, m);
   290    } else if (ts.isEnumDeclaration(node)) {
   291      const nm = node.name.getText();
   292      let v = newData(node, nm);
   293      v.enums = node.members;
   294      if (data.has(nm)) {
   295        v = dataMerge(v, data.get(nm));
   296      }
   297      data.set(nm, v);
   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)) {
   304          return ''
   305        };
   306        if (ts.isMethodDeclaration(c)) {
   307          return ''
   308        };
   309        if (ts.isGetAccessor(c)) {
   310          return ''
   311        };
   312        if (ts.isSetAccessor(c)) {
   313          return ''
   314        };
   315        if (ts.isPropertyDeclaration(c)) {
   316          d.push(c);
   317          return strKind(c)
   318        };
   319        throw new Error(`Class decl ${strKind(c)} `)
   320      };
   321      v.members.forEach((c, i) => wanted(c));
   322      if (d.length == 0) {
   323        return
   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
   329      }
   330      if (c.generics.length > 1) {
   331        return
   332      }
   333      if (v.heritageClauses) {
   334        c.as = v.heritageClauses
   335      }
   336      if (data.has(c.name))
   337        throw new Error(`Class dup ${loc(c.me)} and ${loc(data.get(c.name).me)}`);
   338      data.set(c.name, c);
   339    } else {
   340      throw new Error(`338 unexpected ${strKind(node)} ${loc(node)} `)
   341    }
   342  }
   343  
   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()];
   348    if (at == bt) {
   349      return a;
   350    }
   351    const ax = `(${a.statements.length},${a.properties.length})`
   352    const bx = `(${b.statements.length},${b.properties.length})`
   353    switch (a.name) {
   354      case 'InitializeError':
   355      case 'MessageType':
   356      case 'CompletionItemTag':
   357      case 'SymbolTag':
   358      case 'CodeActionKind':
   359        // want the Module
   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
   366      case 'TokenFormat':
   367        return a;
   368    }
   369    console.log(
   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}`)
   372  }
   373  
   374  // is a node an ancestor of a NewExpression
   375  function hasNewExpression(n: ts.Node): boolean {
   376    let ans = false;
   377    n.forEachChild((n: ts.Node) => {
   378      if (ts.isNewExpression(n)) ans = true;
   379    })
   380    return ans
   381  }
   382  
   383  function checkOnce() {
   384    // Data for all the rpc types?
   385    rpcTypes.forEach(s => {
   386      if (!data.has(s)) throw new Error(`checkOnce, ${s}?`)
   387    });
   388  }
   389  
   390  // helper function to find underlying types
   391  function underlying(n: ts.Node, f: (n: ts.Node) => void) {
   392    if (!n) return;
   393    const ff = function (n: ts.Node) {
   394      underlying(n, f)
   395    };
   396    if (ts.isIdentifier(n)) {
   397      f(n)
   398    } else if (
   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) {
   407      // nothing to do
   408    } else if (ts.isTypeReferenceNode(n)) {
   409      f(n.typeName)
   410    } else if (ts.isArrayTypeNode(n)) {
   411      underlying(n.elementType, f)
   412    } else if (ts.isHeritageClause(n)) {
   413      n.types.forEach(ff);
   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)) {
   421      n.types.forEach(ff)
   422    } else if (ts.isIndexSignatureDeclaration(n)) {
   423      underlying(n.type, f)
   424    } else if (ts.isParenthesizedTypeNode(n)) {
   425      underlying(n.type, f)
   426    } else if (
   427      ts.isLiteralTypeNode(n) || ts.isVariableStatement(n) ||
   428      ts.isTupleTypeNode(n)) {
   429      // we only see these in moreTypes, but they are handled elsewhere
   430      return;
   431    } else if (ts.isEnumMember(n)) {
   432      if (ts.isStringLiteral(n.initializer)) return;
   433      throw new Error(`EnumMember ${strKind(n.initializer)} ${n.name.getText()}`)
   434    } else {
   435      throw new Error(`saw ${strKind(n)} in underlying. ${n.getText()} at ${loc(n)}`)
   436    }
   437  }
   438  
   439  // find all the types implied by seenTypes.
   440  // Simplest way to the transitive closure is to stabilize the size of seenTypes
   441  // but it is slow
   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))
   446    };
   447    rpcTypes.forEach(extra);  // all the types needed by the rpcs
   448    // needed in enums.go (or elsewhere)
   449    extra('InitializeError')
   450    extra('WatchKind')
   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')
   458    let old = 0
   459    do {
   460      old = seenTypes.size
   461  
   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));
   468      };
   469      // expect all the heritage clauses have single Identifiers
   470      const h = function (n: ts.Node) {
   471        underlying(n, add);
   472      };
   473      const f = function (x: ts.NodeArray<ts.Node>) {
   474        x.forEach(h)
   475      };
   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))
   484    }
   485    while (seenTypes.size != old)
   486      ;
   487  }
   488  
   489  let typesOut = new Array<string>();
   490  let constsOut = new Array<string>();
   491  
   492  // generate Go types
   493  function toGo(d: Data, nm: string) {
   494    if (!d) return;  // this is probably a generic T
   495    if (d.alias) {
   496      goTypeAlias(d, nm);
   497    } else if (d.statements.length > 0) {
   498      goModule(d, nm);
   499    } else if (d.enums.length > 0) {
   500      goEnum(d, nm);
   501    } else if (
   502      d.properties.length > 0 || d.as.length > 0 || nm == 'InitializedParams') {
   503      goInterface(d, nm);
   504    } else
   505      throw new Error(
   506        `more cases in toGo ${nm} ${d.as.length} ${d.generics.length} `)
   507  }
   508  
   509  // these fields need a *
   510  var starred: [string, string][] = [
   511    ['TextDocumentContentChangeEvent', 'range'], ['CodeAction', 'command'],
   512    ['DidSaveTextDocumentParams', 'text'], ['CompletionItem', 'command'],
   513    ['Diagnostic', 'codeDescription']
   514  ];
   515  
   516  // generate Go code for an interface
   517  function goInterface(d: Data, nm: string) {
   518    let ans = `type ${goName(nm)} struct {\n`;
   519  
   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) {
   532          gt = '*' + gt;
   533        };
   534      })
   535      ans = ans.concat(`${goName(n.name.getText())} ${gt}`, json, '\n');
   536    };
   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')
   544    };
   545    d.as.forEach((n: ts.HeritageClause) => n.types.forEach(f))
   546    ans = ans.concat(`}\n`);
   547    typesOut.push(getComments(d.me))
   548    typesOut.push(ans)
   549  }
   550  
   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}
   556    `)
   557    }
   558    // all the statements should be export const <id>: value
   559    //   or value = 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,
   566        got ${strKind(n)}`);
   567      }
   568      const c = getComments(n)
   569      const v = n.declarationList.declarations[0];  // only one
   570  
   571      if (!v.initializer)
   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}`)
   580    };
   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'}`)
   585  }
   586  
   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
   591      if (!v.initializer)
   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}`)
   599    };
   600    d.enums.forEach(f)
   601    typesOut.push(getComments(d.me))
   602    // Or should they be type aliases?
   603    typesOut.push(`type ${nm} ${isNumeric ? 'float64' : 'string'}`)
   604  }
   605  
   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()}`);
   611    }
   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`)
   616  }
   617  
   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') {
   628      return 'string';
   629    } else if (strKind(n) == 'NumberKeyword') {
   630      return 'float64';
   631    } else if (strKind(n) == 'BooleanKeyword') {
   632      return 'bool';
   633    } else if (strKind(n) == 'AnyKeyword' || strKind(n) == 'UnknownKeyword') {
   634      return 'interface{}';
   635    } else if (strKind(n) == 'NullKeyword') {
   636      return 'nil'
   637    } else if (strKind(n) == 'VoidKeyword' || strKind(n) == 'NeverKeyword') {
   638      return 'void'
   639    } else if (strKind(n) == 'ObjectKeyword') {
   640      return 'interface{}'
   641    } else if (ts.isArrayTypeNode(n)) {
   642      if (nm === 'arguments') {
   643        // Command and ExecuteCommandParams
   644        return '[]json.RawMessage';
   645      }
   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);
   654      return v
   655    } else if (ts.isTupleTypeNode(n)) {
   656      if (n.getText() == '[number, number]') return '[]float64';
   657      throw new Error(`goType unexpected Tuple ${n.getText()}`)
   658    }
   659    throw new Error(`${strKind(n)} goType unexpected ${n.getText()} for ${nm}`)
   660  }
   661  
   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>*/';
   674    }
   675    // handle all the special cases
   676    switch (n.types.length) {
   677      case 2:
   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}`
   682        }
   683        if (b == 'NullKeyword') {
   684          if (nm == 'textDocument/codeAction') {
   685            // (Command | CodeAction)[] | null
   686            return `[]CodeAction ${help}`
   687          }
   688          let v = goType(n.types[0], 'a')
   689          if (v.startsWith(`[]interface`)) v = v.slice(2, v.length)
   690          return `${v} ${help}`
   691        }
   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}`
   698        }
   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}`
   702        }
   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)}`
   707        }
   708        throw new Error(`691 ${a} ${b} ${n.getText()} ${loc(n)}`);
   709      case 3:
   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}`
   716        }
   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')}`
   721        }
   722        if (nm == 'textDocument/documentSymbol') {
   723          return `[]interface{} ${help}`
   724        }
   725        if (aa == 'TypeReference' && bb == 'ArrayType' && cc == 'NullKeyword') {
   726          return `${goType(n.types[0], 'd')} ${help}`
   727        }
   728        if (aa == 'TypeReference' && bb == aa && cc == 'ArrayType') {
   729          // should check that this is Hover.Contents
   730          return `${goType(n.types[0], 'e')} ${help}`
   731        }
   732        if (aa == 'ArrayType' && bb == 'TypeReference' && cc == 'NullKeyword') {
   733          // check this is nm == 'textDocument/completion'
   734          return `${goType(n.types[1], 'f')} ${help}`
   735        }
   736        if (aa == 'LiteralType' && bb == aa && cc == aa) return `string ${help}`;
   737        break;
   738      case 4:
   739        if (nm == 'documentChanges') return `TextDocumentEdit ${help} `;
   740        if (nm == 'textDocument/prepareRename') return `Range ${help} `;
   741      default:
   742        throw new Error(`goUnionType len=${n.types.length} nm=${nm}`)
   743    }
   744  
   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
   755        m = m.split(' ')[0];
   756      }
   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';
   761    });
   762    if (!isLiteral) {
   763      return res + '*/';
   764    }
   765    // trace?: 'off' | 'messages' | 'verbose' should get string
   766    return `${literal} /* ${n.getText()} */`
   767  }
   768  
   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 explicitly.
   773  function goIntersectionType(n: ts.IntersectionTypeNode, nm: string): string {
   774    if (nm == 'ClientCapabilities') return expandIntersection(n);
   775    if (nm == 'ServerCapabilities') return expandIntersection(n);
   776    let inner = '';
   777    n.types.forEach(
   778      (t: ts.TypeNode) => { inner = inner.concat(goType(t, nm), '\n') });
   779    return `struct{ \n${inner}} `
   780  }
   781  
   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}`)
   789    };
   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()) || [];
   797        v.push(p);
   798        props.set(p.name.getText(), v);
   799      }
   800    }
   801    let ans = 'struct {\n';
   802    for (const [k, v] of Array.from(props)) {
   803      if (v.length == 1) {
   804        const a = v[0];
   805        ans = ans.concat(getComments(a));
   806        ans = ans.concat(`${goName(k)} ${goType(a.type, k)} ${u.JSON(a)}\n`)
   807        continue
   808      }
   809      ans = ans.concat(`${goName(k)} struct {\n`)
   810      for (let i = 0; i < v.length; i++) {
   811        const a = v[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));
   820          ans = ans.concat(
   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))
   824          ans = ans.concat(
   825            goName(a.name.getText()), ' ', 'interface{}', u.JSON(a), '\n')
   826        } else {
   827          throw bad(a.type, `E ${a.getText()} in ${goName(k)} at ${loc(a)}`)
   828        }
   829      }
   830      ans = ans.concat('}\n');
   831    }
   832    ans = ans.concat('}\n');
   833    return ans
   834  }
   835  
   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;
   847          typ = '*' + typ;
   848          json = json.substring(0, json.length - 2) + ',omitempty"`'
   849        })
   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
   856          return
   857        }
   858        throw new Error(` handle ${nx.getText()}`)
   859      } else
   860        throw new Error(`TypeLiteral had ${strKind(nx)}`)
   861    };
   862    n.members.forEach(g)
   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
   866      return res
   867    }
   868    extraTypes.set(goName(nm) + 'Gn', ans)
   869    return goName(nm) + 'Gn'
   870  }
   871  
   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());
   876    v.sort();
   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) => {
   881      u.prgo(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');
   885    })
   886    u.prgo('\nconst (\n');
   887    constsOut.forEach((s) => {
   888      u.prgo(s);
   889      u.prgo('\n')
   890    })
   891    u.prgo(')\n');
   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`)
   895      v.forEach((s) => {
   896        u.prgo(s);
   897        u.prgo('\n')
   898      });
   899      u.prgo('}\n')
   900    });
   901  }
   902  
   903  // client and server ------------------
   904  
   905  interface side {
   906    methods: string[];
   907    cases: string[];
   908    calls: string[];
   909    name: string;    // client or server
   910    goName: string;  // Client or Server
   911    outputFile?: string;
   912    fd?: number
   913  }
   914  let client: side = {
   915    methods: [],
   916    cases: [],
   917    calls: [],
   918    name: 'client',
   919    goName: 'Client',
   920  };
   921  let server: side = {
   922    methods: [],
   923    cases: [],
   924    calls: [],
   925    name: 'server',
   926    goName: 'Server',
   927  };
   928  
   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))
   932  }`;
   933  
   934  // Go code for notifications. Side is client or server, m is the request
   935  // method
   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`;
   943    let case1 = notNil;
   944    if (a != '' && a != 'void') {
   945      case1 = `var params ${a}
   946      if err := json.Unmarshal(r.Params(), &params); err != nil {
   947        return true, sendParseError(ctx, reply, err)
   948      }
   949      err:= ${side.name}.${nm}(ctx, &params)
   950      return true, reply(ctx, nil, err)`
   951    } else {
   952      case1 = `err := ${side.name}.${nm}(ctx)
   953      return true, reply(ctx, nil, err)`;
   954    }
   955    side.cases.push(`${caseHdr}\n${case1}`);
   956  
   957    const arg3 = a == '' || a == 'void' ? 'nil' : 'params';
   958    side.calls.push(`
   959    func (s *${side.name}Dispatcher) ${sig(nm, a, '', true)} {
   960      return s.Conn.Notify(ctx, "${m}", ${arg3})
   961    }`);
   962  }
   963  
   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')) {
   971      b = a;
   972      a = '';  // workspace/workspaceFolders and shutdown
   973    }
   974    u.prb(`${side.name} req ${a != ''}, ${b != ''} ${nm} ${m} ${loc(n)} `)
   975    side.methods.push(sig(nm, a, b));
   976  
   977    const caseHdr = `case "${m}": // req`;
   978    let case1 = notNil;
   979    if (a != '') {
   980      if (extraTypes.has('Param' + nm)) a = 'Param' + nm
   981      case1 = `var params ${a}
   982      if err := json.Unmarshal(r.Params(), &params); err != nil {
   983        return true, sendParseError(ctx, reply, err)
   984      }`;
   985    }
   986    const arg2 = a == '' ? '' : ', &params';
   987    let case2 = `if err := ${side.name}.${nm}(ctx${arg2}); err != nil {
   988      event.Error(ctx, "", err)
   989    }`;
   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)`
   996    }
   997  
   998    side.cases.push(`${caseHdr}\n${case1}\n${case2}`);
   999  
  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 {
  1007  				return nil, err
  1008        }
  1009        return result, nil
  1010      }`;
  1011    } else if (a != '') {
  1012      callBody = `return Call(ctx, s.Conn, "${m}", params, nil) // Call, not Notify
  1013    }`
  1014    }
  1015    side.calls.push(`${callHdr}\n${callBody}\n`);
  1016  }
  1017  
  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)
  1028      x = prefix + suffix
  1029    }
  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)
  1034    }
  1035    seenNames.add(x);
  1036    return x;
  1037  }
  1038  
  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'))
  1045      return false;
  1046    return true
  1047  }
  1048  
  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))
  1054      a = 'Param' + nm
  1055    }
  1056    if (a == 'void')
  1057      a = '';
  1058    else if (a != '') {
  1059      if (names)
  1060        a = ', params *' + a;
  1061      else
  1062        a = ', *' + a;
  1063    }
  1064    let ret = 'error';
  1065    if (b != '' && b != 'void') {
  1066      // avoid * when it is senseless
  1067      if (indirect(b)) b = '*' + b;
  1068      ret = `(${b}, error)`;
  1069    }
  1070    let start = `${nm}(`;
  1071    if (names) {
  1072      start = start + 'ctx ';
  1073    }
  1074    return `${start}context.Context${a}) ${ret}`;
  1075  }
  1076  
  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');
  1083    }
  1084    const f = function (s: string) {
  1085      fs.writeSync(side.fd, s);
  1086      fs.writeSync(side.fd, '\n');
  1087    };
  1088    f(u.computeHeader(false));
  1089    f(`
  1090          import (
  1091            "context"
  1092            "encoding/json"
  1093  
  1094            "github.com/jd-ly/tools/internal/jsonrpc2"
  1095            errors "golang.org/x/xerrors"
  1096          )
  1097          `);
  1098    const a = side.name[0].toUpperCase() + side.name.substring(1)
  1099    f(`type ${a} interface {`);
  1100    side.methods.forEach((v) => { f(v) });
  1101    f('}\n');
  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) });
  1105    f(`
  1106          default:
  1107            return false, nil
  1108          }
  1109        }`);
  1110    side.calls.forEach((v) => { f(v) });
  1111  }
  1112  
  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)')
  1117    server.calls.push(
  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 {
  1121          return nil, err
  1122        }
  1123        return result, nil
  1124      }
  1125    `)
  1126  }
  1127  
  1128  // ----- remember it's a scripting language
  1129  function main() {
  1130    if (u.gitHash != u.git()) {
  1131      throw new Error(
  1132        `git hash mismatch, wanted\n${u.gitHash} but source is at\n${u.git()}`);
  1133    }
  1134    u.createOutputFiles()
  1135    parse()
  1136    u.printAST(program)
  1137    // find the Requests and Nofificatations
  1138    for (const sourceFile of program.getSourceFiles()) {
  1139      if (!sourceFile.isDeclarationFile) {
  1140        ts.forEachChild(sourceFile, findRPCs)
  1141      }
  1142    }
  1143    // separate RPCs into client and server
  1144    setReceives();
  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)
  1149      }
  1150    }
  1151    // check that each thing occurs exactly once, and put pointers into
  1152    // seenTypes
  1153    checkOnce();
  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
  1159      (v, k) => {
  1160        receives.get(k) == 'client' ? goNot(client, k) : goNot(server, k)
  1161      });
  1162    req.forEach(  // requests
  1163      (v, k) => {
  1164        receives.get(k) == 'client' ? goReq(client, k) : goReq(server, k)
  1165      });
  1166    nonstandardRequests();
  1167    // find all the types implied by seenTypes and rpcs to try to avoid
  1168    // generating types that aren't used
  1169    moreTypes();
  1170    // and print the Go code
  1171    outputTypes()
  1172    console.log(`seen ${seenTypes.size + extraTypes.size}`)
  1173    output(client);
  1174    output(server);
  1175  }
  1176  
  1177  main()