1
2// for us typescript ignorati, having an import makes this file a module
3import * as fs from 'fs';
4import * as ts from 'typescript';
5
6// This file contains various utilities having to do with producing strings
7// and managing output
8
9// ------ create files
10let dir = process.env['HOME'];
11const srcDir = '/vscode-languageserver-node'
12export const fnames = [
13  `${dir}${srcDir}/protocol/src/common/protocol.ts`,
14  `${dir}/${srcDir}/protocol/src/browser/main.ts`, `${dir}${srcDir}/types/src/main.ts`,
15  `${dir}${srcDir}/jsonrpc/src/node/main.ts`
16];
17export const gitHash = '901fd40345060d159f07d234bbc967966a929a34'
18let outFname = 'tsprotocol.go';
19let fda: number, fdb: number, fde: number;  // file descriptors
20
21export function createOutputFiles() {
22  fda = fs.openSync('/tmp/ts-a', 'w')  // dump of AST
23  fdb = fs.openSync('/tmp/ts-b', 'w')  // unused, for debugging
24  fde = fs.openSync(outFname, 'w')     // generated Go
25}
26export function pra(s: string) {
27  return (fs.writeSync(fda, s))
28}
29export function prb(s: string) {
30  return (fs.writeSync(fdb, s))
31}
32export function prgo(s: string) {
33  return (fs.writeSync(fde, s))
34}
35
36// Get the hash value of the git commit
37export function git(): string {
38  let a = fs.readFileSync(`${dir}${srcDir}/.git/HEAD`).toString();
39  // ref: refs/heads/foo, or a hash like
40  // cc12d1a1c7df935012cdef5d085cdba04a7c8ebe
41  if (a.charAt(a.length - 1) == '\n') {
42    a = a.substring(0, a.length - 1);
43  }
44  if (a.length == 40) {
45    return a  // a hash
46  }
47  if (a.substring(0, 5) == 'ref: ') {
48    const fname = `${dir}${srcDir}/.git/` + a.substring(5);
49    let b = fs.readFileSync(fname).toString()
50    if (b.length == 41) {
51      return b.substring(0, 40);
52    }
53  }
54  throw new Error('failed to find the git commit hash')
55}
56
57// Produce a header for Go output files
58export function computeHeader(pkgDoc: boolean): string {
59  let lastMod = 0
60  let lastDate: Date
61  for (const f of fnames) {
62    const st = fs.statSync(f)
63    if (st.mtimeMs > lastMod) {
64      lastMod = st.mtimeMs
65      lastDate = st.mtime
66    }
67  }
68  const a =
69    `// Package protocol contains data types and code for LSP jsonrpcs\n` +
70    `// generated automatically from vscode-languageserver-node\n` +
71    `// commit: ${gitHash}\n` +
72    `// last fetched ${lastDate}\n`
73  const b = 'package protocol\n'
74  const c = `\n// Code generated (see typescript/README.md) DO NOT EDIT.\n\n`
75  if (pkgDoc) {
76    return a + b + c
77  }
78  else {
79    return b + a + c
80  }
81};
82
83// Turn a typescript name into an exportable Go name, and appease lint
84export function goName(s: string): string {
85  let ans = s
86  if (s.charAt(0) == '_') {
87    ans = 'Inner' + s.substring(1)
88  }
89  else { ans = s.substring(0, 1).toUpperCase() + s.substring(1) };
90  ans = ans.replace(/Uri$/, 'URI')
91  ans = ans.replace(/Id$/, 'ID')
92  return ans
93}
94
95// Generate JSON tag for a struct field
96export function JSON(n: ts.PropertySignature): string {
97  const json = `\`json:"${n.name.getText()}${
98    n.questionToken != undefined ? ',omitempty' : ''}"\``;
99  return json
100}
101
102// Generate modifying prefixes and suffixes to ensure
103// consts are unique. (Go consts are package-level, but Typescript's are
104// not.) Use suffixes to minimize changes to gopls.
105export function constName(nm: string, type: string): string {
106  let pref = new Map<string, string>([
107    ['DiagnosticSeverity', 'Severity'], ['WatchKind', 'Watch'],
108    ['SignatureHelpTriggerKind', 'Sig'], ['CompletionItemTag', 'Compl']
109  ])  // typeName->prefix
110  let suff = new Map<string, string>([
111    ['CompletionItemKind', 'Completion'], ['InsertTextFormat', 'TextFormat'],
112    ['SymbolTag', 'Symbol']
113  ])
114  let ans = nm;
115  if (pref.get(type)) ans = pref.get(type) + ans;
116  if (suff.has(type)) ans = ans + suff.get(type)
117  return ans
118}
119
120// Find the comments associated with an AST node
121export function getComments(node: ts.Node): string {
122  const sf = node.getSourceFile();
123  const start = node.getStart(sf, false)
124  const starta = node.getStart(sf, true)
125  const x = sf.text.substring(starta, start)
126  return x
127}
128
129
130// --------- printing the AST, for debugging
131
132export function printAST(program: ts.Program) {
133  // dump the ast, for debugging
134  const f = function (n: ts.Node) {
135    describe(n, pra)
136  };
137  for (const sourceFile of program.getSourceFiles()) {
138    if (!sourceFile.isDeclarationFile) {
139      // walk the tree to do stuff
140      ts.forEachChild(sourceFile, f);
141    }
142  }
143  pra('\n')
144  for (const key of Object.keys(seenThings).sort()) {
145    pra(`${key}: ${seenThings[key]} \n`)
146  }
147}
148
149// Used in printing the AST
150let seenThings = new Map<string, number>();
151function seenAdd(x: string) {
152  seenThings[x] = (seenThings[x] === undefined ? 1 : seenThings[x] + 1)
153}
154
155function describe(node: ts.Node, pr: (s: string) => any) {
156  if (node === undefined) {
157    return
158  }
159  let indent = '';
160
161  function f(n: ts.Node) {
162    seenAdd(kinds(n))
163    if (ts.isIdentifier(n)) {
164      pr(`${indent} ${loc(n)} ${strKind(n)} ${n.text} \n`)
165    }
166    else if (ts.isPropertySignature(n) || ts.isEnumMember(n)) {
167      pra(`${indent} ${loc(n)} ${strKind(n)} \n`)
168    }
169    else if (ts.isTypeLiteralNode(n)) {
170      let m = n.members
171      pr(`${indent} ${loc(n)} ${strKind(n)} ${m.length} \n`)
172    }
173    else if (ts.isStringLiteral(n)) {
174      pr(`${indent} ${loc(n)} ${strKind(n)} ${n.text} \n`)
175    }
176    else { pr(`${indent} ${loc(n)} ${strKind(n)} \n`) };
177    indent += ' .'
178    ts.forEachChild(n, f)
179    indent = indent.slice(0, indent.length - 2)
180  }
181  f(node)
182}
183
184
185// For debugging, say where an AST node is in a file
186export function loc(node: ts.Node): string {
187  const sf = node.getSourceFile();
188  const start = node.getStart()
189  const x = sf.getLineAndCharacterOfPosition(start)
190  const full = node.getFullStart()
191  const y = sf.getLineAndCharacterOfPosition(full)
192  let fn = sf.fileName
193  const n = fn.search(/-node./)
194  fn = fn.substring(n + 6)
195  return `${fn} ${x.line + 1}: ${x.character + 1} (${y.line + 1}: ${
196    y.character + 1})`
197}
198// --- various string stuff
199
200// return a string of the kinds of the immediate descendants
201// as part of printing the AST tree
202function kinds(n: ts.Node): string {
203  let res = 'Seen ' + strKind(n);
204  function f(n: ts.Node): void { res += ' ' + strKind(n) };
205  ts.forEachChild(n, f)
206  return res
207}
208
209// What kind of AST node is it? This would just be typescript's
210// SyntaxKind[n.kind] except that the default names for some nodes
211// are misleading
212export function strKind(n: ts.Node): string {
213  if (n == null || n == undefined) {
214    return 'null'
215  }
216  const x = ts.SyntaxKind[n.kind];
217  // some of these have two names
218  switch (x) {
219    default:
220      return x;
221    case 'FirstAssignment':
222      return 'EqualsToken';
223    case 'FirstBinaryOperator':
224      return 'LessThanToken';
225    case 'FirstCompoundAssignment':
226      return 'PlusEqualsToken';
227    case 'FirstContextualKeyword':
228      return 'AbstractKeyword';
229    case 'FirstLiteralToken':
230      return 'NumericLiteral';
231    case 'FirstNode':
232      return 'QualifiedName';
233    case 'FirstTemplateToken':
234      return 'NoSubstitutionTemplateLiteral';
235    case 'LastTemplateToken':
236      return 'TemplateTail';
237    case 'FirstTypeNode':
238      return 'TypePredicate';
239  }
240}
241