| |
| // for us typescript ignorati, having an import makes this file a module |
| import * as fs from 'fs'; |
| import * as process from 'process'; |
| import * as ts from 'typescript'; |
| |
| // This file contains various utilities having to do with producing strings |
| // and managing output |
| |
| // ------ create files |
| let dir = process.env['HOME']; |
| const srcDir = '/vscode-languageserver-node'; |
| export const fnames = [ |
| `${dir}${srcDir}/protocol/src/common/protocol.ts`, |
| `${dir}/${srcDir}/protocol/src/browser/main.ts`, `${dir}${srcDir}/types/src/main.ts`, |
| `${dir}${srcDir}/jsonrpc/src/node/main.ts` |
| ]; |
| export const gitHash = '0cb3812e7d540ef3a904e96df795bc37a21de9b0'; |
| let outFname = 'tsprotocol.go'; |
| let fda: number, fdb: number, fde: number; // file descriptors |
| |
| export function createOutputFiles() { |
| fda = fs.openSync('/tmp/ts-a', 'w'); // dump of AST |
| fdb = fs.openSync('/tmp/ts-b', 'w'); // unused, for debugging |
| fde = fs.openSync(outFname, 'w'); // generated Go |
| } |
| export function pra(s: string) { |
| return (fs.writeSync(fda, s)); |
| } |
| export function prb(s: string) { |
| return (fs.writeSync(fdb, s)); |
| } |
| export function prgo(s: string) { |
| return (fs.writeSync(fde, s)); |
| } |
| |
| // Get the hash value of the git commit |
| export function git(): string { |
| let a = fs.readFileSync(`${dir}${srcDir}/.git/HEAD`).toString(); |
| // ref: refs/heads/foo, or a hash like |
| // cc12d1a1c7df935012cdef5d085cdba04a7c8ebe |
| if (a.charAt(a.length - 1) == '\n') { |
| a = a.substring(0, a.length - 1); |
| } |
| if (a.length == 40) { |
| return a; // a hash |
| } |
| if (a.substring(0, 5) == 'ref: ') { |
| const fname = `${dir}${srcDir}/.git/` + a.substring(5); |
| let b = fs.readFileSync(fname).toString(); |
| if (b.length == 41) { |
| return b.substring(0, 40); |
| } |
| } |
| throw new Error('failed to find the git commit hash'); |
| } |
| |
| // Produce a header for Go output files |
| export function computeHeader(pkgDoc: boolean): string { |
| let lastMod = 0; |
| let lastDate = new Date(); |
| for (const f of fnames) { |
| const st = fs.statSync(f); |
| if (st.mtimeMs > lastMod) { |
| lastMod = st.mtimeMs; |
| lastDate = st.mtime; |
| } |
| } |
| const cp = `// Copyright 2019 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| `; |
| const a = |
| '// Package protocol contains data types and code for LSP jsonrpcs\n' + |
| '// generated automatically from vscode-languageserver-node\n' + |
| `// commit: ${gitHash}\n` + |
| `// last fetched ${lastDate}\n`; |
| const b = 'package protocol\n'; |
| const c = '\n// Code generated (see typescript/README.md) DO NOT EDIT.\n\n'; |
| if (pkgDoc) { |
| return cp + a + b + c; |
| } |
| else { |
| return cp + b + a + c; |
| } |
| } |
| |
| // Turn a typescript name into an exportable Go name, and appease lint |
| export function goName(s: string): string { |
| let ans = s; |
| if (s.charAt(0) == '_') { |
| // in the end, none of these are emitted. |
| ans = 'Inner' + 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; |
| } |
| |
| // Generate JSON tag for a struct field |
| export function JSON(n: ts.PropertySignature): string { |
| const json = `\`json:"${n.name.getText()}${n.questionToken !== undefined ? ',omitempty' : ''}"\``; |
| return json; |
| } |
| |
| // 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. |
| export function constName(nm: string, type: string): string { |
| let pref = new Map<string, string>([ |
| ['DiagnosticSeverity', 'Severity'], ['WatchKind', 'Watch'], |
| ['SignatureHelpTriggerKind', 'Sig'], ['CompletionItemTag', 'Compl'], |
| ['Integer', 'INT_'], ['Uinteger', 'UINT_'] |
| ]); // typeName->prefix |
| let suff = new Map<string, string>([ |
| ['CompletionItemKind', 'Completion'], ['InsertTextFormat', 'TextFormat'], |
| ['SymbolTag', 'Symbol'], ['FileOperationPatternKind', 'Op'], |
| ]); |
| let ans = nm; |
| if (pref.get(type)) ans = pref.get(type) + ans; |
| if (suff.has(type)) ans = ans + suff.get(type); |
| return ans; |
| } |
| |
| // Find the comments associated with an AST node |
| export function getComments(node: ts.Node): string { |
| const sf = node.getSourceFile(); |
| const start = node.getStart(sf, false); |
| const starta = node.getStart(sf, true); |
| const x = sf.text.substring(starta, start); |
| return x; |
| } |
| |
| |
| // --------- printing the AST, for debugging |
| |
| export function printAST(program: ts.Program) { |
| // dump the ast, for debugging |
| const f = function (n: ts.Node) { |
| describe(n, pra); |
| }; |
| for (const sourceFile of program.getSourceFiles()) { |
| if (!sourceFile.isDeclarationFile) { |
| // walk the tree to do stuff |
| ts.forEachChild(sourceFile, f); |
| } |
| } |
| pra('\n'); |
| for (const key of Object.keys(seenThings).sort()) { |
| pra(`${key}: ${seenThings.get(key)} \n`); |
| } |
| } |
| |
| // Used in printing the AST |
| let seenThings = new Map<string, number>(); |
| function seenAdd(x: string) { |
| const u = seenThings.get(x); |
| seenThings.set(x, u === undefined ? 1 : u + 1); |
| } |
| |
| // eslint-disable-next-line no-unused-vars |
| function describe(node: ts.Node, pr: (_: string) => any) { |
| if (node === undefined) { |
| return; |
| } |
| let indent = ''; |
| |
| function f(n: ts.Node) { |
| seenAdd(kinds(n)); |
| if (ts.isIdentifier(n)) { |
| pr(`${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; |
| pr(`${indent} ${loc(n)} ${strKind(n)} ${m.length} \n`); |
| } |
| else if (ts.isStringLiteral(n)) { |
| pr(`${indent} ${loc(n)} ${strKind(n)} ${n.text} \n`); |
| } |
| else { pr(`${indent} ${loc(n)} ${strKind(n)} \n`); } |
| indent += ' .'; |
| ts.forEachChild(n, f); |
| indent = indent.slice(0, indent.length - 2); |
| } |
| f(node); |
| } |
| |
| |
| // For debugging, say where an AST node is in a file |
| export function loc(node: ts.Node | undefined): string { |
| if (!node) throw new Error('loc called with undefined (cannot happen!)'); |
| 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})`; |
| } |
| |
| // --- various string stuff |
| |
| // return a string of the kinds of the immediate descendants |
| // as part of printing the AST tree |
| function kinds(n: ts.Node): string { |
| let res = 'Seen ' + strKind(n); |
| function f(n: ts.Node): void { res += ' ' + strKind(n); } |
| ts.forEachChild(n, f); |
| return res; |
| } |
| |
| // What kind of AST node is it? This would just be typescript's |
| // SyntaxKind[n.kind] except that the default names for some nodes |
| // are misleading |
| export function strKind(n: ts.Node | undefined): string { |
| if (n == null || n == undefined) { |
| return 'null'; |
| } |
| return kindToStr(n.kind); |
| } |
| |
| function kindToStr(k: ts.SyntaxKind): string { |
| const x = ts.SyntaxKind[k]; |
| // 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'; |
| } |
| } |