UNPKG

rawsql-ts

Version:

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

345 lines 16.4 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.CommentEditor = void 0; const SqlComponent_1 = require("../models/SqlComponent"); /** * Utility class for editing comments on SQL components. * Provides functions to add, edit, delete, and search comments in SQL AST. */ class CommentEditor { /** * Add a comment to a SQL component using positioned comments system * For SelectQuery components, adds to headerComments for query-level comments * For other components, adds as 'before' positioned comment * @param component The SQL component to add comment to * @param comment The comment text to add * @param position Optional position for comment ('before' | 'after'), defaults to 'before' */ static addComment(component, comment, position = 'before') { // Check if this is a SelectQuery component - add to headerComments for query-level comments if (this.isSelectQuery(component)) { const selectQuery = component; if (!selectQuery.headerComments) { selectQuery.headerComments = []; } selectQuery.headerComments.push(comment); } else { // For other components, add to positioned comments component.addPositionedComments(position, [comment]); } } /** * Check if a component implements SelectQuery interface * Uses discriminator property for robust type detection * @param component The component to check * @returns true if the component is a SelectQuery */ static isSelectQuery(component) { // Use discriminator property for robust type detection return '__selectQueryType' in component && component.__selectQueryType === 'SelectQuery'; } /** * Edit an existing comment by index * For SelectQuery components, edits headerComments * For other components, edits positioned comments * @param component The SQL component containing the comment * @param index The index of the comment to edit (0-based) * @param newComment The new comment text * @param position Position to edit ('before' | 'after'), defaults to 'before' for non-SelectQuery components * @throws Error if index is invalid */ static editComment(component, index, newComment, position = 'before') { var _a, _b; if (this.isSelectQuery(component)) { const selectQuery = component; if (!selectQuery.headerComments || index < 0 || index >= selectQuery.headerComments.length) { throw new Error(`Invalid comment index: ${index}. Component has ${((_a = selectQuery.headerComments) === null || _a === void 0 ? void 0 : _a.length) || 0} comments.`); } selectQuery.headerComments[index] = newComment; } else { const positionedComments = component.getPositionedComments(position); if (!positionedComments || index < 0 || index >= positionedComments.length) { throw new Error(`Invalid comment index: ${index}. Component has ${(positionedComments === null || positionedComments === void 0 ? void 0 : positionedComments.length) || 0} ${position} positioned comments.`); } // Update the comment in the positioned comments const positioned = (_b = component.positionedComments) === null || _b === void 0 ? void 0 : _b.find(pc => pc.position === position); if (positioned) { positioned.comments[index] = newComment; } } } /** * Delete a comment by index * For SelectQuery components, deletes from headerComments * For other components, deletes from positioned comments * @param component The SQL component containing the comment * @param index The index of the comment to delete (0-based) * @param position Position to delete from ('before' | 'after'), defaults to 'before' for non-SelectQuery components * @throws Error if index is invalid */ static deleteComment(component, index, position = 'before') { var _a, _b, _c, _d; if (this.isSelectQuery(component)) { const selectQuery = component; if (!selectQuery.headerComments || index < 0 || index >= selectQuery.headerComments.length) { throw new Error(`Invalid comment index: ${index}. Component has ${((_a = selectQuery.headerComments) === null || _a === void 0 ? void 0 : _a.length) || 0} comments.`); } selectQuery.headerComments.splice(index, 1); if (selectQuery.headerComments.length === 0) { selectQuery.headerComments = null; } } else { const positionedComments = component.getPositionedComments(position); if (!positionedComments || index < 0 || index >= positionedComments.length) { throw new Error(`Invalid comment index: ${index}. Component has ${(positionedComments === null || positionedComments === void 0 ? void 0 : positionedComments.length) || 0} ${position} positioned comments.`); } // Delete the comment from positioned comments const positioned = (_b = component.positionedComments) === null || _b === void 0 ? void 0 : _b.find(pc => pc.position === position); if (positioned) { positioned.comments.splice(index, 1); // Clean up empty positioned comment groups if (positioned.comments.length === 0) { component.positionedComments = ((_c = component.positionedComments) === null || _c === void 0 ? void 0 : _c.filter(pc => pc.position !== position)) || null; if (((_d = component.positionedComments) === null || _d === void 0 ? void 0 : _d.length) === 0) { component.positionedComments = null; } } } } } /** * Delete all comments from a component * @param component The SQL component to clear comments from */ static deleteAllComments(component) { if (this.isSelectQuery(component)) { const selectQuery = component; selectQuery.headerComments = null; } else { component.positionedComments = null; } } /** * Get all comments from a component * For SelectQuery components, returns headerComments * For other components, returns all positioned comments as a flat array * @param component The SQL component to get comments from * @returns Array of comment strings (empty array if no comments) */ static getComments(component) { if (this.isSelectQuery(component)) { const selectQuery = component; return selectQuery.headerComments || []; } return component.getAllPositionedComments(); } /** * Find all components in the AST that have comments containing the search text * @param root The root SQL component to search from * @param searchText The text to search for in comments * @param caseSensitive Whether the search should be case-sensitive (default: false) * @returns Array of components that have matching comments */ static findComponentsWithComment(root, searchText, caseSensitive = false) { const results = []; const searchTerm = caseSensitive ? searchText : searchText.toLowerCase(); const traverse = (component) => { if (component && component instanceof SqlComponent_1.SqlComponent) { let hasMatchingComment = false; // Check positioned comments const allPositionedComments = component.getAllPositionedComments(); if (allPositionedComments && allPositionedComments.some(c => { const commentText = caseSensitive ? c : c.toLowerCase(); return commentText.includes(searchTerm); })) { hasMatchingComment = true; } // Check headerComments for SelectQuery components if (this.isSelectQuery(component)) { const selectQuery = component; if (selectQuery.headerComments && selectQuery.headerComments.some(c => { const commentText = caseSensitive ? c : c.toLowerCase(); return commentText.includes(searchTerm); })) { hasMatchingComment = true; } } if (hasMatchingComment) { results.push(component); } } // Traverse all properties recursively for (const key in component) { if (component[key] && typeof component[key] === 'object') { if (Array.isArray(component[key])) { component[key].forEach(traverse); } else { traverse(component[key]); } } } }; traverse(root); return results; } /** * Replace all occurrences of a text in comments across the entire AST * @param root The root SQL component to search and replace in * @param searchText The text to search for * @param replaceText The text to replace with * @param caseSensitive Whether the search should be case-sensitive (default: false) * @returns Number of replacements made */ static replaceInComments(root, searchText, replaceText, caseSensitive = false) { let replacementCount = 0; const traverse = (component) => { if (component && component instanceof SqlComponent_1.SqlComponent) { // Handle regular comments if (component.comments) { for (let i = 0; i < component.comments.length; i++) { const originalComment = component.comments[i]; const flags = caseSensitive ? 'g' : 'gi'; const regex = new RegExp(searchText.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), flags); const newComment = originalComment.replace(regex, replaceText); if (newComment !== originalComment) { component.comments[i] = newComment; replacementCount++; } } } // Handle positioned comments if (component.positionedComments) { for (const posComment of component.positionedComments) { if (posComment.comments) { for (let i = 0; i < posComment.comments.length; i++) { const originalComment = posComment.comments[i]; const flags = caseSensitive ? 'g' : 'gi'; const regex = new RegExp(searchText.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), flags); const newComment = originalComment.replace(regex, replaceText); if (newComment !== originalComment) { posComment.comments[i] = newComment; replacementCount++; } } } } } // Handle headerComments for SelectQuery components if (this.isSelectQuery(component)) { const selectQuery = component; if (selectQuery.headerComments) { for (let i = 0; i < selectQuery.headerComments.length; i++) { const originalComment = selectQuery.headerComments[i]; const flags = caseSensitive ? 'g' : 'gi'; const regex = new RegExp(searchText.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), flags); const newComment = originalComment.replace(regex, replaceText); if (newComment !== originalComment) { selectQuery.headerComments[i] = newComment; replacementCount++; } } } } } // Traverse all properties recursively for (const key in component) { if (component[key] && typeof component[key] === 'object') { if (Array.isArray(component[key])) { component[key].forEach(traverse); } else { traverse(component[key]); } } } }; traverse(root); return replacementCount; } /** * Count total number of comments in the AST * @param root The root SQL component to count comments in * @returns Total number of comments */ static countComments(root) { let count = 0; const traverse = (component) => { if (component && component instanceof SqlComponent_1.SqlComponent) { // Count positioned comments const positionedComments = component.getAllPositionedComments(); if (positionedComments) { count += positionedComments.length; } // Count headerComments for SelectQuery components if (this.isSelectQuery(component)) { const selectQuery = component; if (selectQuery.headerComments) { count += selectQuery.headerComments.length; } } } // Traverse all properties recursively for (const key in component) { if (component[key] && typeof component[key] === 'object') { if (Array.isArray(component[key])) { component[key].forEach(traverse); } else { traverse(component[key]); } } } }; traverse(root); return count; } /** * Get all comments from the entire AST as a flat array with their source components * @param root The root SQL component to extract comments from * @returns Array of objects containing comment text and the component they belong to */ static getAllComments(root) { const results = []; const traverse = (component) => { if (component && component instanceof SqlComponent_1.SqlComponent) { // Add positioned comments const positionedComments = component.getAllPositionedComments(); if (positionedComments) { positionedComments.forEach((comment, index) => { results.push({ comment, component, index }); }); } // Add headerComments for SelectQuery components if (this.isSelectQuery(component)) { const selectQuery = component; if (selectQuery.headerComments) { selectQuery.headerComments.forEach((comment, index) => { results.push({ comment, component, index }); }); } } } // Traverse all properties recursively for (const key in component) { if (component[key] && typeof component[key] === 'object') { if (Array.isArray(component[key])) { component[key].forEach(traverse); } else { traverse(component[key]); } } } }; traverse(root); return results; } } exports.CommentEditor = CommentEditor; //# sourceMappingURL=CommentEditor.js.map