1import * as fs from 'fs';
2import * as ts from 'typescript';
3
4// Need a general strategy for union types. This code tries (heuristically)
5// to choose one, but sometimes defaults (heuristically) to interface{}
6interface Const {
7  typeName: string  // repeated in each const
8  goType: string
9  me: ts.Node
10  name: string   // constant's name
11  value: string  // constant's value
12}
13let Consts: Const[] = [];
14let seenConstTypes = new Map<string, boolean>();
15
16interface Struct {
17  me: ts.Node
18  name: string
19  embeds?: string[]
20  fields?: Field[];
21  extends?: string[]
22}
23let Structs: Struct[] = [];
24
25interface Field {
26  me: ts.Node
27  id: ts.Identifier
28  goName: string
29  optional: boolean
30  goType: string
31  json: string
32  gostuff?: string
33  substruct?: Field[]  // embedded struct from TypeLiteral
34}
35
36interface Type {
37  me: ts.Node
38  goName: string
39  goType: string
40  stuff: string
41}
42let Types: Type[] = [];
43
44// Used in printing the AST
45let seenThings = new Map<string, number>();
46function seenAdd(x: string) {
47  seenThings[x] = (seenThings[x] === undefined ? 1 : seenThings[x] + 1)
48}
49
50let dir = process.env['HOME'];
51const srcDir = '/vscode-languageserver-node'
52let fnames = [
53  `${srcDir}/protocol/src/protocol.ts`, `${srcDir}/types/src/main.ts`,
54  `${srcDir}/jsonrpc/src/main.ts`
55];
56let gitHash = 'fda16d6b63ba0fbdbd21d437ea810685528a0018';
57let outFname = 'tsprotocol.go';
58let fda: number, fdb: number, fde: number;  // file descriptors
59
60function createOutputFiles() {
61  fda = fs.openSync('/tmp/ts-a', 'w')  // dump of AST
62  fdb = fs.openSync('/tmp/ts-b', 'w')  // unused, for debugging
63  fde = fs.openSync(outFname, 'w')     // generated Go
64}
65function pra(s: string) {
66  return (fs.writeSync(fda, s))
67}
68function prb(s: string) {
69  return (fs.writeSync(fdb, s))
70}
71function prgo(s: string) {
72  return (fs.writeSync(fde, s))
73}
74
75// struct names that don't need to go in the output
76let dontEmit = new Map<string, boolean>();
77
78function generate(files: string[], options: ts.CompilerOptions): void {
79  let program = ts.createProgram(files, options);
80  program.getTypeChecker();  // used for side-effects
81
82  // dump the ast, for debugging
83  for (const sourceFile of program.getSourceFiles()) {
84    if (!sourceFile.isDeclarationFile) {
85      // walk the tree to do stuff
86      ts.forEachChild(sourceFile, describe);
87    }
88  }
89  pra('\n')
90  for (const key of Object.keys(seenThings).sort()) {
91    pra(`${key}: ${seenThings[key]}\n`)
92  }
93
94  // visit every sourceFile in the program, generating types
95  for (const sourceFile of program.getSourceFiles()) {
96    if (!sourceFile.isDeclarationFile) {
97      ts.forEachChild(sourceFile, genTypes)
98    }
99  }
100  return;
101
102  function genTypes(node: ts.Node) {
103    // Ignore top-level items that produce no output
104    if (ts.isExpressionStatement(node) || ts.isFunctionDeclaration(node) ||
105      ts.isImportDeclaration(node) || ts.isVariableStatement(node) ||
106      ts.isExportDeclaration(node) || ts.isEmptyStatement(node) ||
107      node.kind == ts.SyntaxKind.EndOfFileToken) {
108      return;
109    }
110    if (ts.isInterfaceDeclaration(node)) {
111      doInterface(node)
112      return;
113    } else if (ts.isTypeAliasDeclaration(node)) {
114      doTypeAlias(node)
115    } else if (ts.isModuleDeclaration(node)) {
116      doModuleDeclaration(node)
117    } else if (ts.isEnumDeclaration(node)) {
118      doEnumDecl(node)
119    } else if (ts.isClassDeclaration(node)) {
120      doClassDeclaration(node)
121    } else {
122      throw new Error(`unexpected ${strKind(node)} ${loc(node)}`)
123    }
124  }
125
126  function doClassDeclaration(node: ts.ClassDeclaration) {
127    let id: ts.Identifier = node.name;
128    let props = new Array<ts.PropertyDeclaration>()
129    let extend: ts.HeritageClause;
130    let bad = false
131    node.forEachChild((n: ts.Node) => {
132      if (ts.isIdentifier(n)) {
133        return
134      }
135      if (ts.isPropertyDeclaration(n)) {
136        props.push(n);
137        return
138      }
139      if (n.kind == ts.SyntaxKind.ExportKeyword) {
140        return
141      }
142      if (n.kind == ts.SyntaxKind.Constructor || ts.isMethodDeclaration(n) ||
143        ts.isGetAccessor(n) || ts.isSetAccessor(n) ||
144        ts.isTypeParameterDeclaration(n)) {
145        bad = true;
146        return
147      }
148      if (ts.isHeritageClause(n)) {
149        return
150      }
151      if (n.kind == ts.SyntaxKind.AbstractKeyword) {
152        bad = true;  // we think all of these are useless, but are unsure
153        return;
154      }
155      throw new Error(`doClass ${loc(n)} ${kinds(n)}`)
156    })
157    if (bad) {
158      // the class is not useful for Go.
159      return
160    }
161    let fields: Field[] = [];
162    for (const pr of props) {
163      fields.push(fromPropDecl(pr))
164    }
165    let ans = {
166      me: node,
167      name: toGoName(getText(id)),
168      extends: heritageStrs(extend),
169      fields: fields
170    };
171    Structs.push(ans)
172  }
173
174  // called only from doClassDeclaration
175  function fromPropDecl(node: ts.PropertyDeclaration): Field {
176    let id: ts.Identifier = (ts.isIdentifier(node.name) && node.name);
177    let opt = node.questionToken != undefined;
178    let typ: ts.Node = node.type;
179    const computed = computeType(typ);
180    let goType = computed.goType
181    let ans = {
182      me: node,
183      id: id,
184      goName: toGoName(getText(id)),
185      optional: opt,
186      goType: goType,
187      json: `\`json:"${id.text}${opt ? ',omitempty' : ''}"\``,
188      substruct: computed.fields
189    };
190    return ans
191  }
192
193  function doInterface(node: ts.InterfaceDeclaration) {
194    // name: Identifier;
195    // typeParameters?: NodeArray<TypeParameterDeclaration>;
196    // heritageClauses?: NodeArray<HeritageClause>;
197    // members: NodeArray<TypeElement>;
198
199    // find the Identifier from children
200    // process the PropertySignature children
201    // the members might have generic info, but so do the children
202    let id: ts.Identifier = node.name
203    let extend: ts.HeritageClause
204    let generid: ts.Identifier
205    let properties = new Array<ts.PropertySignature>()
206    let index: ts.IndexSignatureDeclaration  // generate some sort of map
207    let bad = false;  // maybe we don't care about this one at all
208    node.forEachChild((n: ts.Node) => {
209      if (n.kind == ts.SyntaxKind.ExportKeyword || ts.isMethodSignature(n)) {
210        // ignore
211      } else if (ts.isIdentifier(n)) {
212      } else if (ts.isHeritageClause(n)) {
213        extend = n;
214      } else if (ts.isTypeParameterDeclaration(n)) {
215        // Act as if this is <T = any>
216        generid = n.name;
217      } else if (ts.isPropertySignature(n)) {
218        properties.push(n);
219      } else if (ts.isIndexSignatureDeclaration(n)) {
220        if (index !== undefined) {
221          throw new Error(`${loc(n)} multiple index expressions`)
222        }
223        index = n
224      } else if (n.kind == ts.SyntaxKind.CallSignature) {
225        bad = true;
226      } else {
227        throw new Error(`${loc(n)} doInterface ${strKind(n)} `)
228      }
229    })
230    if (bad) return;
231    let fields: Field[] = [];
232    for (const p of properties) {
233      fields.push(genProp(p, generid))
234    }
235    if (index != undefined) {
236      fields.push(fromIndexSignature(index))
237    }
238    const ans = {
239      me: node,
240      name: toGoName(getText(id)),
241      extends: heritageStrs(extend),
242      fields: fields
243    };
244
245    Structs.push(ans)
246  }
247
248  function heritageStrs(node: ts.HeritageClause): string[] {
249    // ExpressionWithTypeArguments+, and each is an Identifier
250    let ans: string[] = [];
251    if (node == undefined) {
252      return ans
253    }
254    let x: ts.ExpressionWithTypeArguments[] = []
255    node.forEachChild((n: ts.Node) => {
256      if (ts.isExpressionWithTypeArguments(n)) x.push(n)
257    })
258    for (const p of x) {
259      p.forEachChild((n: ts.Node) => {
260        if (ts.isIdentifier(n)) {
261          ans.push(toGoName(getText(n)));
262          return;
263        }
264        if (ts.isTypeReferenceNode(n)) {
265          // don't want these, ignore them
266          return;
267        }
268        throw new Error(`expected Identifier ${loc(n)} ${kinds(p)} `)
269      })
270    }
271    return ans
272  }
273
274  // optional gen is the contents of <T>
275  function genProp(node: ts.PropertySignature, gen: ts.Identifier): Field {
276    let id: ts.Identifier
277    let thing: ts.Node
278    let opt = false
279    node.forEachChild((n: ts.Node) => {
280      if (ts.isIdentifier(n)) {
281        id = n
282      } else if (n.kind == ts.SyntaxKind.QuestionToken) {
283        opt = true
284      } else if (n.kind == ts.SyntaxKind.ReadonlyKeyword) {
285        return
286      } else {
287        if (thing !== undefined) {
288          throw new Error(`${loc(n)} weird`)
289        }
290        thing = n
291      }
292    })
293    let goName = toGoName(id.text)
294    let { goType, gostuff, optional, fields } = computeType(thing)
295    // Generics
296    if (gen && gen.text == goType) goType = 'interface{}';
297    opt = opt || optional;
298    let ans = {
299      me: node,
300      id: id,
301      goName: goName,
302      optional: opt,
303      goType: goType,
304      gostuff: gostuff,
305      substruct: fields,
306      json: `\`json:"${id.text}${opt ? ',omitempty' : ''}"\``
307    };
308    // These are not structs, so '*' would be wrong.
309    switch (goType) {
310      case 'CompletionItemKind':
311      case 'TextDocumentSyncKind':
312      case 'CodeActionKind':
313      case 'FailureHandlingKind':  // string
314      case 'InsertTextFormat':     // float64
315      case 'DiagnosticSeverity':
316        ans.optional = false
317    }
318    return ans
319  }
320
321  function doModuleDeclaration(node: ts.ModuleDeclaration) {
322    // Export Identifier ModuleBlock
323    let id: ts.Identifier = (ts.isIdentifier(node.name) && node.name);
324    // Don't want FunctionDeclarations
325    // some of the VariableStatement are consts, and want their comments
326    // and each VariableStatement is Export, VariableDeclarationList
327    // and each VariableDeclarationList is a single VariableDeclaration
328    let v: ts.VariableDeclaration[] = [];
329    function f(n: ts.Node) {
330      if (ts.isVariableDeclaration(n)) {
331        v.push(n);
332        return
333      }
334      if (ts.isFunctionDeclaration(n)) {
335        return
336      }
337      n.forEachChild(f)
338    }
339    f(node)
340    for (const vx of v) {
341      if (hasNewExpression(vx)) {
342        return
343      }
344      buildConst(getText(id), vx)
345    }
346  }
347
348  function buildConst(tname: string, node: ts.VariableDeclaration): Const {
349    // node is Identifier, optional-goo, (FirstLiteralToken|StringLiteral)
350    let id: ts.Identifier = (ts.isIdentifier(node.name) && node.name);
351    let str: string
352    let first: string
353    node.forEachChild((n: ts.Node) => {
354      if (ts.isStringLiteral(n)) {
355        str = getText(n)
356      } else if (n.kind == ts.SyntaxKind.NumericLiteral) {
357        first = getText(n)
358      }
359    })
360    if (str == undefined && first == undefined) {
361      return
362    }  // various
363    const ty = (str != undefined) ? 'string' : 'float64'
364    const val = (str != undefined) ? str.replace(/'/g, '"') : first
365    const name = toGoName(getText(id))
366    const c = {
367      typeName: tname,
368      goType: ty,
369      me: node.parent.parent,
370      name: name,
371      value: val
372    };
373    Consts.push(c)
374    return c
375  }
376
377  // is node an ancestor of a NewExpression
378  function hasNewExpression(n: ts.Node): boolean {
379    let ans = false;
380    n.forEachChild((n: ts.Node) => {
381      if (ts.isNewExpression(n)) ans = true;
382    })
383    return ans
384  }
385
386  function doEnumDecl(node: ts.EnumDeclaration) {
387    // Generates Consts. Identifier EnumMember+
388    // EnumMember: Identifier StringLiteral
389    let id: ts.Identifier = node.name
390    let mems = node.members
391    let theType = 'string';
392    for (const m of mems) {
393      let name: string
394      let value: string
395      m.forEachChild((n: ts.Node) => {
396        if (ts.isIdentifier(n)) {
397          name = getText(n)
398        } else if (ts.isStringLiteral(n)) {
399          value = getText(n).replace(/'/g, '"')
400        } else if (ts.isNumericLiteral(n)) {
401          value = getText(n);
402          theType = 'float64';
403        } else {
404          throw new Error(`in doEnumDecl ${strKind(n)} ${loc(n)}`)
405        }
406      })
407      let ans = {
408        typeName: getText(id),
409        goType: theType,
410        me: m,
411        name: name,
412        value: value
413      };
414      Consts.push(ans)
415    }
416  }
417
418  // top-level TypeAlias
419  function doTypeAlias(node: ts.TypeAliasDeclaration) {
420    // these are all Export Identifier alias
421    let id: ts.Identifier = node.name;
422    let alias: ts.TypeNode = node.type;
423    let ans = {
424      me: node,
425      id: id,
426      goName: toGoName(getText(id)),
427      goType: '?',  // filled in later in this function
428      stuff: ''
429    };
430    if (ts.isUnionTypeNode(alias)) {
431      ans.goType = weirdUnionType(alias)
432      if (ans.goType == undefined) {
433        // these are mostly redundant; maybe sort them out later
434        return
435      }
436      if (ans.goType == 'interface{}') {
437        // we didn't know what to do, so explain the choice
438        ans.stuff = `// ` + getText(alias)
439      }
440      Types.push(ans)
441      return
442    }
443    if (ts.isIntersectionTypeNode(alias)) {  // a Struct, not a Type
444      let embeds: string[] = []
445      alias.forEachChild((n: ts.Node) => {
446        if (ts.isTypeReferenceNode(n)) {
447          const s = toGoName(computeType(n).goType)
448          embeds.push(s)
449          // It's here just for embedding, and not used independently, maybe
450          // PJW!
451          // dontEmit.set(s, true); // PJW: do we need this?
452        } else
453          throw new Error(`expected TypeRef ${strKind(n)} ${loc(n)}`)
454      })
455      let ans = { me: node, name: toGoName(getText(id)), embeds: embeds };
456      Structs.push(ans)
457      return
458    }
459    if (ts.isArrayTypeNode(alias)) {  // []DocumentFilter
460      ans.goType = '[]DocumentFilter';
461      Types.push(ans)
462      return
463    }
464    if (ts.isLiteralTypeNode(alias)) {
465      return  // type A = 1, so nope
466    }
467    if (ts.isTypeLiteralNode(alias)) {
468      return;  // type A = {...}
469    }
470    if (ts.isTypeReferenceNode(alias)) {
471      ans.goType = computeType(alias).goType
472      if (ans.goType.match(/und/) != null) throw new Error('396')
473      Types.push(ans)  // type A B
474      return
475    }
476    if (alias.kind == ts.SyntaxKind.StringKeyword) {  // type A string
477      ans.goType = 'string';
478      Types.push(ans);
479      return
480    }
481    throw new Error(
482      `in doTypeAlias ${loc(alias)} ${kinds(node)}: ${strKind(alias)}\n`)
483  }
484
485  // string, or number, or DocumentFilter
486  function weirdUnionType(node: ts.UnionTypeNode): string {
487    let bad = false;
488    let aNumber = false;
489    let aString = false;
490    let tl: ts.TypeLiteralNode[] = []
491    node.forEachChild((n: ts.Node) => {
492      if (ts.isTypeLiteralNode(n)) {
493        tl.push(n);
494        return;
495      }
496      if (ts.isLiteralTypeNode(n)) {
497        n.literal.kind == ts.SyntaxKind.NumericLiteral ? aNumber = true :
498          aString = true;
499        return;
500      }
501      if (n.kind == ts.SyntaxKind.NumberKeyword ||
502        n.kind == ts.SyntaxKind.StringKeyword) {
503        n.kind == ts.SyntaxKind.NumberKeyword ? aNumber = true : aString = true;
504        return
505      }
506      bad = true
507    })
508    if (bad) return;  // none of these are useful (so far)
509    if (aNumber) {
510      if (aString) return 'interface{}';
511      return 'float64';
512    }
513    if (aString) return 'string';
514    let x = computeType(tl[0])
515    x.fields[0].json = x.fields[0].json.replace(/"`/, ',omitempty"`')
516    let out: string[] = [];
517    for (const f of x.fields) {
518      out.push(strField(f))
519    }
520    out.push('}\n')
521    let ans = 'struct {\n'.concat(...out);
522    return ans
523  }
524
525  // complex and filled with heuristics
526  function computeType(node: ts.Node): { goType: string, gostuff?: string, optional?: boolean, fields?: Field[] } {
527    switch (node.kind) {
528      case ts.SyntaxKind.AnyKeyword:
529      case ts.SyntaxKind.ObjectKeyword:
530        return { goType: 'interface{}' };
531      case ts.SyntaxKind.BooleanKeyword:
532        return { goType: 'bool' };
533      case ts.SyntaxKind.NumberKeyword:
534        return { goType: 'float64' };
535      case ts.SyntaxKind.StringKeyword:
536        return { goType: 'string' };
537      case ts.SyntaxKind.NullKeyword:
538      case ts.SyntaxKind.UndefinedKeyword:
539        return { goType: 'nil' };
540    }
541    if (ts.isArrayTypeNode(node)) {
542      let { goType, gostuff, optional } = computeType(node.elementType)
543      return ({ goType: '[]' + goType, gostuff: gostuff, optional: optional })
544    } else if (ts.isTypeReferenceNode(node)) {
545      // typeArguments?: NodeArray<TypeNode>;typeName: EntityName;
546      // typeArguments won't show up in the generated Go
547      // EntityName: Identifier|QualifiedName
548      let tn: ts.EntityName = node.typeName;
549      if (ts.isQualifiedName(tn)) {
550        throw new Error(`qualified name at ${loc(node)}`);
551      } else if (ts.isIdentifier(tn)) {
552        return { goType: toGoName(tn.text) };
553      } else {
554        throw new Error(
555          `expected identifier got ${strKind(node.typeName)} at ${loc(tn)}`)
556      }
557    } else if (ts.isLiteralTypeNode(node)) {
558      // string|float64 (are there other possibilities?)
559      // as of 20190908: only see string
560      const txt = getText(node);
561      let typ = 'float64'
562      if (txt.charAt(0) == '\'') {
563        typ = 'string'
564      }
565      return { goType: typ, gostuff: getText(node) };
566    } else if (ts.isTypeLiteralNode(node)) {
567      // {[uri:string]: TextEdit[];} -> map[string][]TextEdit
568      let x: Field[] = [];
569      let indexCnt = 0
570      node.forEachChild((n: ts.Node) => {
571        if (ts.isPropertySignature(n)) {
572          x.push(genProp(n, undefined))
573          return
574        } else if (ts.isIndexSignatureDeclaration(n)) {
575          indexCnt++
576          x.push(fromIndexSignature(n))
577          return
578        }
579        throw new Error(`${loc(n)} gotype ${strKind(n)}, not expected`)
580      });
581      if (indexCnt > 0) {
582        if (indexCnt != 1 || x.length != 1)
583          throw new Error(`undexpected Index ${loc(x[0].me)}`)
584        // instead of {map...} just the map
585        return ({ goType: x[0].goType, gostuff: x[0].gostuff })
586      }
587      return ({ goType: 'embedded!', fields: x })
588    } else if (ts.isUnionTypeNode(node)) {
589      // The major heuristics
590      let x = new Array<{ goType: string, gostuff?: string, optiona?: boolean }>()
591      node.forEachChild((n: ts.Node) => { x.push(computeType(n)) })
592      if (x.length == 2 && x[1].goType == 'nil') {
593        // Foo | null, or Foo | undefined
594        return x[0]  // make it optional somehow? TODO
595      }
596      if (x[0].goType == 'bool') {  // take it, mostly
597        if (x[1].goType == 'RenameOptions' ||
598          x[1].goType == 'CodeActionOptions') {
599          return ({ goType: 'interface{}', gostuff: getText(node) })
600        }
601        return ({ goType: 'bool', gostuff: getText(node) })
602      }
603      // these are special cases from looking at the source
604      let gostuff = getText(node);
605      if (x[0].goType == `"off"` || x[0].goType == 'string') {
606        return ({ goType: 'string', gostuff: gostuff })
607      }
608      if (x[0].goType == 'TextDocumentSyncOptions') {
609        // TextDocumentSyncOptions | TextDocumentSyncKind
610        return ({ goType: 'interface{}', gostuff: gostuff })
611      }
612      if (x[0].goType == 'float64' && x[1].goType == 'string') {
613        return {
614          goType: 'interface{}', gostuff: gostuff
615        }
616      }
617      if (x[0].goType == 'MarkupContent' && x[1].goType == 'MarkedString') {
618        return {
619          goType: 'MarkupContent', gostuff: gostuff
620        }
621      }
622      if (x[0].goType == 'RequestMessage' && x[1].goType == 'ResponseMessage') {
623        return {
624          goType: 'interface{}', gostuff: gostuff
625        }
626      }
627      // Fail loudly
628      console.log(`UnionType ${loc(node)}`)
629      for (const v of x) {
630        console.log(`${v.goType}`)
631      }
632      throw new Error('in UnionType, weird')
633    } else if (ts.isParenthesizedTypeNode(node)) {
634      // check that this is (TextDocumentEdit | CreateFile | RenameFile |
635      // DeleteFile) TODO(pjw) IT IS NOT! FIX THIS! ALSO:
636      // (variousOptions & StaticFegistrationOptions)
637      return {
638        goType: 'TextDocumentEdit', gostuff: getText(node)
639      }
640    } else if (ts.isTupleTypeNode(node)) {
641      // in string | [number, number]. TODO(pjw): check it really is
642      return {
643        goType: 'string', gostuff: getText(node)
644      }
645    } else if (ts.isFunctionTypeNode(node)) {
646      // we don't want these members; mark them
647      return {
648        goType: 'bad', gostuff: getText(node)
649      }
650    }
651    throw new Error(`computeType unknown ${strKind(node)} at ${loc(node)}`)
652  }
653
654  function fromIndexSignature(node: ts.IndexSignatureDeclaration): Field {
655    let parm: ts.ParameterDeclaration
656    let at: ts.Node
657    node.forEachChild((n: ts.Node) => {
658      if (ts.isParameter(n)) {
659        parm = n
660      } else if (
661        ts.isArrayTypeNode(n) || n.kind == ts.SyntaxKind.AnyKeyword ||
662        ts.isUnionTypeNode(n)) {
663        at = n
664      } else
665        throw new Error(`fromIndexSig ${strKind(n)} ${loc(n)}`)
666    })
667    let goType = computeType(at).goType
668    let id: ts.Identifier
669    parm.forEachChild((n: ts.Node) => {
670      if (ts.isIdentifier(n)) {
671        id = n
672      } else if (n.kind != ts.SyntaxKind.StringKeyword) {
673        throw new Error(`fromIndexSig expected string, ${strKind(n)} ${loc(n)}`)
674      }
675    })
676    goType = `map[string]${goType}`
677    return {
678      me: node, goName: toGoName(id.text), id: null, goType: goType,
679      optional: false, json: `\`json:"${id.text}"\``,
680      gostuff: `${getText(node)}`
681    }
682  }
683
684  function toGoName(s: string): string {
685    let ans = s
686    if (s.charAt(0) == '_') {
687      ans = 'Inner' + s.substring(1)
688    }
689    else { ans = s.substring(0, 1).toUpperCase() + s.substring(1) };
690    ans = ans.replace(/Uri$/, 'URI')
691    ans = ans.replace(/Id$/, 'ID')
692    return ans
693  }
694
695  // find the text of a node
696  function getText(node: ts.Node): string {
697    let sf = node.getSourceFile();
698    let start = node.getStart(sf)
699    let end = node.getEnd()
700    return sf.text.substring(start, end)
701  }
702  // return a string of the kinds of the immediate descendants
703  function kinds(n: ts.Node): string {
704    let res = 'Seen ' + strKind(n);
705    function f(n: ts.Node): void { res += ' ' + strKind(n) };
706    ts.forEachChild(n, f)
707    return res
708  }
709
710  function strKind(n: ts.Node): string {
711    const x = ts.SyntaxKind[n.kind];
712    // some of these have two names
713    switch (x) {
714      default:
715        return x;
716      case 'FirstAssignment':
717        return 'EqualsToken';
718      case 'FirstBinaryOperator':
719        return 'LessThanToken';
720      case 'FirstCompoundAssignment':
721        return 'PlusEqualsToken';
722      case 'FirstContextualKeyword':
723        return 'AbstractKeyword';
724      case 'FirstLiteralToken':
725        return 'NumericLiteral';
726      case 'FirstNode':
727        return 'QualifiedName';
728      case 'FirstTemplateToken':
729        return 'NoSubstitutionTemplateLiteral';
730      case 'LastTemplateToken':
731        return 'TemplateTail';
732      case 'FirstTypeNode':
733        return 'TypePredicate';
734    }
735  }
736
737  function describe(node: ts.Node) {
738    if (node === undefined) {
739      return
740    }
741    let indent = '';
742
743    function f(n: ts.Node) {
744      seenAdd(kinds(n))
745      if (ts.isIdentifier(n)) {
746        pra(`${indent} ${loc(n)} ${strKind(n)} ${n.text}\n`)
747      }
748      else if (ts.isPropertySignature(n) || ts.isEnumMember(n)) {
749        pra(`${indent} ${loc(n)} ${strKind(n)}\n`)
750      }
751      else if (ts.isTypeLiteralNode(n)) {
752        let m = n.members
753        pra(`${indent} ${loc(n)} ${strKind(n)} ${m.length}\n`)
754      }
755      else { pra(`${indent} ${loc(n)} ${strKind(n)}\n`) };
756      indent += '  '
757      ts.forEachChild(n, f)
758      indent = indent.slice(0, indent.length - 2)
759    }
760    f(node)
761  }
762}
763
764function getComments(node: ts.Node): string {
765  const sf = node.getSourceFile();
766  const start = node.getStart(sf, false)
767  const starta = node.getStart(sf, true)
768  const x = sf.text.substring(starta, start)
769  return x
770}
771
772function loc(node: ts.Node): string {
773  const sf = node.getSourceFile();
774  const start = node.getStart()
775  const x = sf.getLineAndCharacterOfPosition(start)
776  const full = node.getFullStart()
777  const y = sf.getLineAndCharacterOfPosition(full)
778  let fn = sf.fileName
779  const n = fn.search(/-node./)
780  fn = fn.substring(n + 6)
781  return `${fn} ${x.line + 1}:${x.character + 1} (${y.line + 1}:${
782    y.character + 1})`
783}
784
785function emitTypes() {
786  seenConstTypes.set('MessageQueue', true);  // skip
787  for (const t of Types) {
788    if (seenConstTypes.get(t.goName)) continue;
789    if (t.goName == 'CodeActionKind') continue;  // consts better choice
790    if (t.goType === undefined) continue;
791    let stuff = (t.stuff == undefined) ? '' : t.stuff;
792    prgo(`// ${t.goName} is a type\n`)
793    prgo(`${getComments(t.me)}`)
794    prgo(`type ${t.goName} = ${t.goType}${stuff}\n`)
795    seenConstTypes.set(t.goName, true);
796  }
797}
798
799let byName = new Map<string, Struct>();
800function emitStructs() {
801  dontEmit.set('Thenable', true);
802  dontEmit.set('EmitterOptions', true);
803  dontEmit.set('MessageReader', true);
804  dontEmit.set('MessageWriter', true);
805  dontEmit.set('CancellationToken', true);
806  dontEmit.set('PipeTransport', true);
807  dontEmit.set('SocketTransport', true);
808  dontEmit.set('Item', true);
809  dontEmit.set('Event', true);
810  dontEmit.set('Logger', true);
811  dontEmit.set('Disposable', true);
812  dontEmit.set('PartialMessageInfo', true);
813  dontEmit.set('MessageConnection', true);
814  dontEmit.set('ResponsePromise', true);
815  dontEmit.set('ResponseMessage', true);
816  dontEmit.set('ErrorMessage', true);
817  dontEmit.set('NotificationMessage', true);
818  dontEmit.set('RequestHandlerElement', true);
819  dontEmit.set('RequestMessage', true);
820  dontEmit.set('NotificationHandlerElement', true);
821  dontEmit.set('Message', true);  // duplicate of jsonrpc2:wire.go
822  dontEmit.set('LSPLogMessage', true);
823  dontEmit.set('InnerEM', true);
824  dontEmit.set('ResponseErrorLiteral', true);
825  dontEmit.set('TraceOptions', true);
826  dontEmit.set('MessageType', true);  // want the enum
827  // backwards compatibility, done in requests.ts:
828  dontEmit.set('CancelParams', true);
829
830  for (const str of Structs) {
831    byName.set(str.name, str)
832  }
833  let seenName = new Map<string, boolean>()
834  for (const str of Structs) {
835    if (str.name == 'InitializeError') {
836      // only want its consts, not the struct
837      continue
838    }
839    if (seenName.get(str.name) || dontEmit.get(str.name)) {
840      continue
841    }
842    let noopt = false;
843    seenName.set(str.name, true)
844    prgo(genComments(str.name, getComments(str.me)))
845    prgo(`type ${str.name} struct {\n`)
846    // if it has fields, generate them
847    if (str.fields != undefined) {
848      for (const f of str.fields) {
849        prgo(strField(f, noopt))
850      }
851    }
852    if (str.extends) {
853      // ResourceOperation just repeats the Kind field
854      for (const s of str.extends) {
855        if (s != 'ResourceOperation')
856          prgo(`\t${s}\n`)  // what this type extends.
857      }
858    } else if (str.embeds) {
859      prb(`embeds: ${str.name}\n`);
860      noopt = (str.name == 'ClientCapabilities');
861      // embedded struct. the hard case is from intersection types,
862      // where fields with the same name have to be combined into
863      // a single struct
864      let fields = new Map<string, Field[]>();
865      for (const e of str.embeds) {
866        const nm = byName.get(e);
867        if (nm.embeds) throw new Error(`${nm.name} is an embedded embed`);
868        // each of these fields might be a something that needs amalgamating
869        for (const f of nm.fields) {
870          let x = fields.get(f.goName);
871          if (x === undefined) x = [];
872          x.push(f);
873          fields.set(f.goName, x);
874        }
875      }
876      fields.forEach((val, key) => {
877        if (val.length > 1) {
878          // merge the fields with the same name
879          prgo(strField(val[0], noopt, val));
880        } else {
881          prgo(strField(val[0], noopt));
882        }
883      });
884    }
885    prgo(`}\n`);
886  }
887}
888
889function genComments(name: string, maybe: string): string {
890  if (maybe == '') return `\n\t// ${name} is\n`;
891  if (maybe.indexOf('/**') == 0) {
892    return maybe.replace('/**', `\n/*${name} defined:`)
893  }
894  throw new Error(`weird comment ${maybe.indexOf('/**')}`)
895}
896
897// Turn a Field into an output string
898// flds is for merging
899function strField(f: Field, noopt?: boolean, flds?: Field[]): string {
900  let ans: string[] = [];
901  let opt = (!noopt && f.optional) ? '*' : ''
902  switch (f.goType.charAt(0)) {
903    case 's':  // string
904    case 'b':  // bool
905    case 'f':  // float64
906    case 'i':  // interface{}
907    case '[':  // []foo
908      opt = ''
909  }
910  let stuff = (f.gostuff == undefined) ? '' : ` // ${f.gostuff}`
911  ans.push(genComments(f.goName, getComments(f.me)))
912  if (flds === undefined && f.substruct == undefined) {
913    ans.push(`\t${f.goName} ${opt}${f.goType} ${f.json}${stuff}\n`)
914  }
915  else if (flds !== undefined) {
916    // The logic that got us here is imprecise, so it is possible that
917    // the fields are really all the same, and don't need to be
918    // combined into a struct.
919    let simple = true;
920    for (const ff of flds) {
921      if (ff.substruct !== undefined || byName.get(ff.goType) !== undefined) {
922        simple = false
923        break
924      }
925    }
926    if (simple) {
927      // should check that the ffs are really all the same
928      return strField(flds[0], noopt)
929    }
930    ans.push(`\t${f.goName} ${opt}struct{\n`);
931    for (const ff of flds) {
932      if (ff.substruct !== undefined) {
933        for (const x of ff.substruct) {
934          ans.push(strField(x, noopt))
935        }
936      } else if (byName.get(ff.goType) !== undefined) {
937        const st = byName.get(ff.goType);
938        for (let i = 0; i < st.fields.length; i++) {
939          ans.push(strField(st.fields[i], noopt))
940        }
941      } else {
942        ans.push(strField(ff, noopt));
943      }
944    }
945    ans.push(`\t} ${f.json}${stuff}\n`);
946  }
947  else {
948    ans.push(`\t${f.goName} ${opt}struct {\n`)
949    for (const x of f.substruct) {
950      ans.push(strField(x, noopt))
951    }
952    ans.push(`\t} ${f.json}${stuff}\n`)
953  }
954  return (''.concat(...ans))
955}
956
957function emitConsts() {
958  // need the consts too! Generate modifying prefixes and suffixes to ensure
959  // consts are unique. (Go consts are package-level, but Typescript's are
960  // not.) Use suffixes to minimize changes to gopls.
961  let pref = new Map<string, string>([
962    ['DiagnosticSeverity', 'Severity'], ['WatchKind', 'Watch']
963  ])  // typeName->prefix
964  let suff = new Map<string, string>([
965    ['CompletionItemKind', 'Completion'], ['InsertTextFormat', 'TextFormat']
966  ])
967  for (const c of Consts) {
968    if (seenConstTypes.get(c.typeName)) {
969      continue
970    }
971    seenConstTypes.set(c.typeName, true);
972    if (pref.get(c.typeName) == undefined) {
973      pref.set(c.typeName, '')  // initialize to empty value
974    }
975    if (suff.get(c.typeName) == undefined) {
976      suff.set(c.typeName, '')
977    }
978    prgo(`// ${c.typeName} defines constants\n`)
979    prgo(`type ${c.typeName} ${c.goType}\n`)
980  }
981  prgo('const (\n')
982  let seenConsts = new Map<string, boolean>()  // to avoid duplicates
983  for (const c of Consts) {
984    const x = `${pref.get(c.typeName)}${c.name}${suff.get(c.typeName)}`
985    if (seenConsts.get(x)) {
986      continue
987    }
988    seenConsts.set(x, true)
989    if (c.value === undefined) continue;      // didn't figure it out
990    if (x.startsWith('undefined')) continue;  // what's going on here?
991    prgo(genComments(x, getComments(c.me)))
992    prgo(`\t${x} ${c.typeName} = ${c.value}\n`)
993  }
994  prgo(')\n')
995}
996
997function emitHeader(files: string[]) {
998  let lastMod = 0
999  let lastDate: Date
1000  for (const f of files) {
1001    const st = fs.statSync(f)
1002    if (st.mtimeMs > lastMod) {
1003      lastMod = st.mtimeMs
1004      lastDate = st.mtime
1005    }
1006  }
1007  let a = fs.readFileSync(`${dir}${srcDir}/.git/refs/heads/master`);
1008  prgo(`// Package protocol contains data types and code for LSP jsonrpcs\n`)
1009  prgo(`// generated automatically from vscode-languageserver-node\n`)
1010  prgo(`// commit: ${gitHash}\n`)
1011  prgo(`// last fetched ${lastDate}\n`)
1012  prgo('package protocol\n\n')
1013  prgo(`// Code generated (see typescript/README.md) DO NOT EDIT.\n`);
1014};
1015
1016function git(): string {
1017  let a = fs.readFileSync(`${dir}${srcDir}/.git/HEAD`).toString();
1018  // ref: refs/heads/foo, or a hash like cc12d1a1c7df935012cdef5d085cdba04a7c8ebe
1019  if (a.charAt(a.length - 1) == '\n') {
1020    a = a.substring(0, a.length - 1);
1021  }
1022  if (a.length == 40) {
1023    return a // a hash
1024  }
1025  if (a.substring(0, 5) == 'ref: ') {
1026    const fname = `${dir}${srcDir}/.git/` + a.substring(5);
1027    let b = fs.readFileSync(fname).toString()
1028    if (b.length == 41) {
1029      return b.substring(0, 40);
1030    }
1031  }
1032  throw new Error("failed to find the git commit hash")
1033}
1034
1035// ad hoc argument parsing: [-d dir] [-o outputfile], and order matters
1036function main() {
1037  if (gitHash != git()) {
1038    throw new Error(`git hash mismatch, wanted\n${gitHash} but source is at\n${git()}`)
1039  }
1040  let args = process.argv.slice(2)  // effective command line
1041  if (args.length > 0) {
1042    let j = 0;
1043    if (args[j] == '-d') {
1044      dir = args[j + 1]
1045      j += 2
1046    }
1047    if (args[j] == '-o') {
1048      outFname = args[j + 1]
1049      j += 2
1050    }
1051    if (j != args.length) throw new Error(`incomprehensible args ${args}`)
1052  }
1053  let files: string[] = [];
1054  for (let i = 0; i < fnames.length; i++) {
1055    files.push(`${dir}${fnames[i]}`)
1056  }
1057  createOutputFiles()
1058  generate(
1059    files, { target: ts.ScriptTarget.ES5, module: ts.ModuleKind.CommonJS });
1060  emitHeader(files)
1061  emitStructs()
1062  emitConsts()
1063  emitTypes()
1064}
1065
1066main()
1067