Files
2024-02-15 17:01:50 +00:00

99 lines
2.9 KiB
TypeScript

import {StringAccumulator} from 'sentry/views/starfish/utils/sqlish/formatters/stringAccumulator';
import type {Token} from 'sentry/views/starfish/utils/sqlish/types';
interface Options {
maxLineLength?: number;
}
export function string(tokens: Token[], options: Options = {}): string {
const accumulator = new StringAccumulator();
let precedingNonWhitespaceToken: Token | undefined = undefined;
let parenthesisLevel: number = 0; // Tracks the current parenthesis nesting level
const indentationLevels: number[] = []; // Tracks the parenthesis nesting levels at which we've incremented the indentation
function contentize(token: Token): void {
if (Array.isArray(token.content)) {
token.content.forEach(contentize);
return;
}
if (token.type === 'LeftParenthesis') {
parenthesisLevel += 1;
accumulator.add('(');
// If the previous legible token is a meaningful keyword that triggers a
// newline, increase the current indentation level and note the parenthesis level where this happened
if (
typeof precedingNonWhitespaceToken?.content === 'string' &&
PARENTHESIS_NEWLINE_KEYWORDS.has(precedingNonWhitespaceToken.content)
) {
accumulator.break();
accumulator.indent();
indentationLevels.push(parenthesisLevel);
}
}
if (token.type === 'RightParenthesis') {
// If this right parenthesis closes a left parenthesis at a level where
// we incremented the indentation, decrement the indentation
if (indentationLevels.at(-1) === parenthesisLevel) {
accumulator.break();
accumulator.unindent();
indentationLevels.pop();
}
parenthesisLevel -= 1;
accumulator.add(')');
}
if (typeof token.content === 'string') {
if (token.type === 'Keyword' && NEWLINE_KEYWORDS.has(token.content)) {
if (!accumulator.lastLine.isEmpty) {
accumulator.break();
}
accumulator.add(token.content);
} else if (token.type === 'Whitespace') {
// Convert all whitespace to single spaces
accumulator.space();
} else if (['LeftParenthesis', 'RightParenthesis'].includes(token.type)) {
// Parenthesis contents are appended above, so we can skip them here
} else {
accumulator.add(token.content);
}
}
if (token.type !== 'Whitespace') {
precedingNonWhitespaceToken = token;
}
return;
}
tokens.forEach(contentize);
return accumulator.toString(options.maxLineLength);
}
// Keywords that always trigger a newline
const NEWLINE_KEYWORDS = new Set([
'DELETE',
'FROM',
'GROUP',
'INNER',
'INSERT',
'LEFT',
'LIMIT',
'OFFSET',
'ORDER',
'RETURNING',
'RIGHT',
'SELECT',
'VALUES',
'WHERE',
]);
// Keywords that may or may not trigger a newline, but they always trigger a newlines if followed by a parenthesis
const PARENTHESIS_NEWLINE_KEYWORDS = new Set([...NEWLINE_KEYWORDS, ...['IN']]);