internal/lsp/protocol: bring the code generating programs up to date

This is the typescript code that generates the current versions of
tsprotocol.go, tsserver.go, and tsclient.go.

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