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/protocol.ts`, // why isn't this main.ts? 14 `${dir}/${srcDir}/protocol/src/main.ts`, 15 `${dir}${srcDir}/types/src/main.ts`, `${dir}${srcDir}/jsonrpc/src/main.ts` 16]; 17export const gitHash = '7b90c29d0cb5cd7b9c41084f6cb3781a955adeba'; 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