rawsql-ts
Version:
[beta]High-performance SQL parser and AST analyzer written in TypeScript. Provides fast parsing and advanced transformation capabilities.
148 lines • 5.64 kB
JavaScript
;
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 !== '') {
current.text = current.text.trimEnd() + 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 first
if (this.cleanupLine(text)) {
// If cleanup was performed, add comma to previous line
const previousLine = this.lines[this.lines.length - 1];
previousLine.text = previousLine.text.trimEnd() + text;
return;
}
const workLine = this.getCurrentLine();
// Leading space is not needed
if (!(text === ' ' && workLine.text === '')) {
workLine.text += text;
}
}
trimTrailingWhitespaceFromPreviousLine() {
if (this.lines.length < 2) {
return;
}
const previousLine = this.lines[this.lines.length - 2];
const newlineMatch = previousLine.text.match(/(\r?\n)$/);
const trailingNewline = newlineMatch ? newlineMatch[1] : '';
const content = trailingNewline
? previousLine.text.slice(0, -trailingNewline.length)
: previousLine.text;
previousLine.text = content.replace(/[ \t]+$/, '') + trailingNewline;
}
/**
* Cleans up the current line for comma formatting.
* For 'after' and 'none' comma styles, removes empty line when a comma is being added.
* @param text The text being processed
* @returns true if cleanup was performed, false otherwise
*/
cleanupLine(text) {
const workLine = this.getCurrentLine();
if (text === ',' && workLine.text.trim() === '' && this.lines.length > 1 && (this.commaBreak === 'after' || this.commaBreak === 'none')) {
let previousIndex = this.lines.length - 2;
while (previousIndex >= 0 && this.lines[previousIndex].text.trim() === '') {
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
}
lineHasTrailingComment(text) {
// Strip simple quoted sections so comment markers inside literals are ignored.
const withoutStrings = text
.replace(/'([^']|'')*'/g, '')
.replace(/"([^"]|"")*"/g, '')
.trim();
// Treat any remaining '--' as a line comment marker so we never pull commas onto commented lines.
return withoutStrings.includes('--');
}
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 currentLine.text.trim() === '';
}
return true;
}
}
exports.LinePrinter = LinePrinter;
class PrintLine {
constructor(level, text) {
this.level = level;
this.text = text;
}
}
exports.PrintLine = PrintLine;
//# sourceMappingURL=LinePrinter.js.map