rawsql-ts
Version:
High-performance SQL parser and AST analyzer written in TypeScript. Provides fast parsing and advanced transformation capabilities.
221 lines • 8.63 kB
JavaScript
import { BinarySelectQuery, SimpleSelectQuery, ValuesQuery } from "../models/SelectQuery";
import { CommonTable, SelectClause, SelectItem, SourceAliasExpression, WithClause } from "../models/Clause";
import { SqlComponent } from "../models/SqlComponent";
/**
* Extracts comment attachment facts already represented in the SQL AST.
*
* @remarks This API does not infer product-specific meanings or rewrite
* comments. It returns a deterministic flat list without mutating the input AST.
*/
export class AstCommentAttachmentExtractor {
constructor() {
this.attachments = [];
this.visited = new Set();
this.suppressedDetachedCommentArrays = new WeakSet();
}
/**
* Extract comment attachment facts from a SQL AST component.
*/
static extract(input) {
if (!input) {
return [];
}
const extractor = new AstCommentAttachmentExtractor();
extractor.visit(input);
return extractor.attachments;
}
visit(value) {
if (!value || typeof value !== "object") {
return;
}
if (this.visited.has(value)) {
return;
}
this.visited.add(value);
if (Array.isArray(value)) {
for (const item of value) {
this.visit(item);
}
return;
}
if (value instanceof SqlComponent) {
this.visitSqlComponent(value);
return;
}
}
visitSqlComponent(component) {
this.emitHeaderComments(component);
this.emitPositionedComments(component, "before");
if (!(component instanceof SimpleSelectQuery)) {
this.emitLegacyComments(component);
}
if (component instanceof SelectItem) {
this.emitPositionedComments(component, "after");
this.emitSelectItemKeywordComments(component);
this.visit(component.value);
this.visit(component.identifier);
return;
}
if (component instanceof CommonTable) {
this.visit(component.aliasExpression);
this.visit(component.query);
this.emitPositionedComments(component, "after");
return;
}
if (component instanceof WithClause) {
this.visit(component.tables);
this.emitDetachedComments(component.trailingComments);
this.emitDetachedComments(component.globalComments);
this.emitPositionedComments(component, "after");
return;
}
if (component instanceof SimpleSelectQuery) {
this.suppressWithClauseTrailingCommentsAlreadyCopiedToQuery(component);
this.visit(component.withClause);
this.emitLegacyComments(component);
this.visit(component.selectClause);
this.visit(component.fromClause);
this.visit(component.whereClause);
this.visit(component.groupByClause);
this.visit(component.havingClause);
this.visit(component.windowClause);
this.visit(component.orderByClause);
this.visit(component.limitClause);
this.visit(component.offsetClause);
this.visit(component.fetchClause);
this.visit(component.forClause);
this.emitPositionedComments(component, "after");
return;
}
if (component instanceof BinarySelectQuery) {
this.visit(component.left);
this.visit(component.right);
this.emitPositionedComments(component, "after");
return;
}
if (component instanceof ValuesQuery) {
this.visit(component.withClause);
this.visit(component.tuples);
this.emitPositionedComments(component, "after");
return;
}
if (component instanceof SelectClause) {
this.visit(component.distinct);
this.visit(component.hints);
this.visit(component.items);
this.emitPositionedComments(component, "after");
return;
}
if (component instanceof SourceAliasExpression) {
this.visit(component.table);
this.visit(component.columns);
this.emitPositionedComments(component, "after");
return;
}
this.visitFallbackProperties(component);
this.emitPositionedComments(component, "after");
}
visitFallbackProperties(component) {
for (const key of Object.keys(component)) {
if (this.shouldSkipProperty(key)) {
continue;
}
this.visit(component[key]);
}
}
shouldSkipProperty(key) {
return key === "comments" ||
key === "positionedComments" ||
key === "headerComments" ||
key === "trailingComments" ||
key === "globalComments" ||
key === "asKeywordPositionedComments" ||
key === "asKeywordComments" ||
key === "aliasPositionedComments" ||
key === "aliasComments" ||
key === "cteNameCache";
}
emitHeaderComments(component) {
if (!this.isSelectQuery(component)) {
return;
}
this.emitComments(component.headerComments, "leading", component);
}
emitLegacyComments(component) {
this.emitComments(component.comments, "detached");
}
emitPositionedComments(component, position) {
if (!component.positionedComments) {
return;
}
for (const positionedComment of component.positionedComments) {
if (positionedComment.position !== position) {
continue;
}
this.emitComments(positionedComment.comments, position === "before" ? "leading" : "trailing", component);
}
}
emitSelectItemKeywordComments(selectItem) {
const itemWithCommentFields = selectItem;
this.emitKeywordPositionedComments(itemWithCommentFields.asKeywordPositionedComments, selectItem, "inner");
this.emitComments(itemWithCommentFields.asKeywordComments, "inner", selectItem);
this.emitKeywordPositionedComments(itemWithCommentFields.aliasPositionedComments, selectItem);
this.emitComments(itemWithCommentFields.aliasComments, "trailing", selectItem);
}
emitKeywordPositionedComments(positionedComments, targetNode, placementOverride) {
if (!positionedComments) {
return;
}
for (const positionedComment of positionedComments) {
const placement = placementOverride !== null && placementOverride !== void 0 ? placementOverride : (positionedComment.position === "before" ? "leading" : "trailing");
this.emitComments(positionedComment.comments, placement, targetNode);
}
}
emitDetachedComments(comments) {
if (comments && this.suppressedDetachedCommentArrays.has(comments)) {
return;
}
this.emitComments(comments, "detached");
}
emitComments(comments, placement, targetNode) {
if (!comments) {
return;
}
for (const text of comments) {
this.attachments.push(Object.assign({ text, sourceOrder: this.attachments.length, placement }, (targetNode ? { targetNode } : {})));
}
}
isSelectQuery(component) {
return "__selectQueryType" in component &&
component.__selectQueryType === "SelectQuery";
}
suppressWithClauseTrailingCommentsAlreadyCopiedToQuery(query) {
var _a;
const trailingComments = (_a = query.withClause) === null || _a === void 0 ? void 0 : _a.trailingComments;
if (!trailingComments || !query.comments) {
return;
}
if (this.containsCommentSequence(query.comments, trailingComments)) {
this.suppressedDetachedCommentArrays.add(trailingComments);
}
}
containsCommentSequence(source, candidate) {
if (candidate.length === 0) {
return true;
}
for (let start = 0; start <= source.length - candidate.length; start++) {
const matches = candidate.every((comment, index) => source[start + index] === comment);
if (matches) {
return true;
}
}
return false;
}
}
/**
* Extract comment attachment facts from a SQL AST component.
*/
export function extractAstCommentAttachments(input) {
return AstCommentAttachmentExtractor.extract(input);
}
//# sourceMappingURL=AstCommentAttachmentExtractor.js.map