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