UNPKG

rawsql-ts

Version:

High-performance SQL parser and AST analyzer written in TypeScript. Provides fast parsing and advanced transformation capabilities.

219 lines 7.69 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.PrintLine = exports.LinePrinter = void 0; /** * SqlPrintHelper provides utility methods for SQL pretty printing. */ class LinePrinter { /** * @param indentChar Character used for indentation (default: ' ') // Accepts logical names like 'space'/'tab' * @param indentSize Number of indentChar per level (default: 0) * @param newline Newline string (default: '\r\n') // Accepts logical names like 'lf'/'crlf'/'cr' * @param commaBreak Comma break style (default: 'none') */ constructor(indentChar = ' ', indentSize = 0, newline = '\r\n', commaBreak = 'none') { this.indentChar = indentChar; this.indentSize = indentSize; this.newline = newline; this.commaBreak = commaBreak; this.lines = []; this.appendNewline(0); } print() { let result = ''; for (const line of this.lines) { if (line.text !== '') { // append indent and text result += this.indent(line.level) + line.text; } } return result.trimEnd(); } /** * Returns the indent string for a given level. * @param level Indentation level */ indent(level) { return this.indentChar.repeat(this.indentSize * level); } /** * Appends a newline token to the given tokens array if newline is set, or adds an empty line if tokens is empty. * @param tokens Array of token objects with 'level' and 'text' property * @param level Indentation level */ appendNewline(level) { if (this.lines.length > 0) { const current = this.lines[this.lines.length - 1]; if (current.text !== '') { const lineText = this.endsWithAsciiWhitespace(current.text) ? current.text.trimEnd() : current.text; current.text = lineText + this.newline; } } this.lines.push(new PrintLine(level, '')); } /** * Appends text to the last element of tokens array. * @param tokens Array of token objects with 'text' property * @param text Text to append */ appendText(text) { // Handle special comma cleanup only when a comma token is appended. if (text === ',' && this.cleanupLine()) { // If cleanup was performed, add comma to previous line const previousLine = this.lines[this.lines.length - 1]; const lineText = this.endsWithAsciiWhitespace(previousLine.text) ? previousLine.text.trimEnd() : previousLine.text; previousLine.text = lineText + text; return; } const workLine = this.getCurrentLine(); // Leading space is not needed if (!(text === ' ' && workLine.text === '')) { workLine.text += text; } } endsWithAsciiWhitespace(text) { if (text.length === 0) { return false; } const tail = text.charCodeAt(text.length - 1); return tail === 32 || tail === 9 || tail === 10 || tail === 13; } trimTrailingWhitespaceFromPreviousLine() { if (this.lines.length < 2) { return; } const previousLine = this.lines[this.lines.length - 2]; const text = previousLine.text; const lineEnd = this.findLineEndIndex(text); let contentEnd = lineEnd; // Trim only trailing spaces/tabs before a preserved newline suffix. while (contentEnd > 0) { const code = text.charCodeAt(contentEnd - 1); if (code !== 32 && code !== 9) { break; } contentEnd--; } if (contentEnd === lineEnd) { return; } previousLine.text = text.slice(0, contentEnd) + text.slice(lineEnd); } findLineEndIndex(text) { if (text.endsWith('\r\n')) { return text.length - 2; } if (text.endsWith('\n')) { return text.length - 1; } return text.length; } /** * Cleans up the current line for comma formatting. * For 'after' and 'none' comma styles, removes empty line when a comma is being added. * @returns true if cleanup was performed, false otherwise */ cleanupLine() { const workLine = this.getCurrentLine(); if (this.isAsciiWhitespaceOnly(workLine.text) && this.lines.length > 1 && (this.commaBreak === 'after' || this.commaBreak === 'none')) { let previousIndex = this.lines.length - 2; while (previousIndex >= 0 && this.isAsciiWhitespaceOnly(this.lines[previousIndex].text)) { this.lines.splice(previousIndex, 1); previousIndex--; } if (previousIndex < 0) { return false; } const previousLine = this.lines[previousIndex]; // Avoid pulling commas onto a line comment to keep the comma executable if (this.lineHasTrailingComment(previousLine.text)) { return false; } this.lines.pop(); // Safe: we checked lines.length > 1 return true; // Cleanup performed } return false; // No cleanup needed } isAsciiWhitespaceOnly(text) { for (let i = 0; i < text.length; i++) { const code = text.charCodeAt(i); if (code !== 32 && code !== 9 && code !== 10 && code !== 13) { return false; } } return true; } lineHasTrailingComment(text) { if (!text.includes('--')) { return false; } if (!text.includes("'") && !text.includes('"')) { return true; } let quote = null; for (let i = 0; i < text.length - 1; i++) { const current = text.charCodeAt(i); const next = text.charCodeAt(i + 1); if (quote === 39) { if (current === 39) { if (next === 39) { i++; continue; } quote = null; } continue; } if (quote === 34) { if (current === 34) { if (next === 34) { i++; continue; } quote = null; } continue; } if (current === 39) { quote = 39; continue; } if (current === 34) { quote = 34; continue; } if (current === 45 && next === 45) { return true; } } return false; } getCurrentLine() { if (this.lines.length > 0) { return this.lines[this.lines.length - 1]; } else { throw new Error('No tokens to get current line from.'); } } /** * Checks if the current line is empty (has no text content) * @returns true if current line is empty, false otherwise */ isCurrentLineEmpty() { if (this.lines.length > 0) { const currentLine = this.lines[this.lines.length - 1]; return this.isAsciiWhitespaceOnly(currentLine.text); } return true; } } exports.LinePrinter = LinePrinter; class PrintLine { constructor(level, text) { this.level = level; this.text = text; } } exports.PrintLine = PrintLine; //# sourceMappingURL=LinePrinter.js.map