UNPKG

@ordojs/core

Version:

Core compiler and runtime for OrdoJS framework

580 lines 22.7 kB
/** * @fileoverview OrdoJS Dead Code Eliminator - Comprehensive dead code elimination and optimization */ import { ExpressionType, OptimizationError, OptimizationType, StatementType } from '../types/index.js'; /** * Default dead code elimination options */ const DEFAULT_OPTIONS = { aggressiveTreeShaking: true, inlineSmallFunctions: true, maxInlineSize: 10, removeUnusedVariables: true, removeUnreachableCode: true, removeUnusedCSS: true, removeUnusedEventHandlers: true, removeUnusedServerFunctions: true, constantFolding: true, removeEmptyBlocks: true }; /** * Comprehensive dead code eliminator for OrdoJS components */ export class DeadCodeEliminator { options; usageAnalysis; errors = []; warnings = []; constructor(options = {}) { this.options = { ...DEFAULT_OPTIONS, ...options }; this.reset(); } /** * Optimize a component AST by removing dead code */ optimize(ast) { this.reset(); try { // Step 1: Analyze usage patterns this.analyzeUsage(ast); // Step 2: Perform constant folding if enabled if (this.options.constantFolding) { this.performConstantFolding(ast); } // Step 3: Remove unused variables if (this.options.removeUnusedVariables) { this.removeUnusedVariables(ast); } // Step 4: Remove unused functions this.removeUnusedFunctions(ast); // Step 5: Remove unused event handlers if (this.options.removeUnusedEventHandlers) { this.removeUnusedEventHandlers(ast); } // Step 6: Remove unused server functions if (this.options.removeUnusedServerFunctions) { this.removeUnusedServerFunctions(ast); } // Step 7: Remove unreachable code if (this.options.removeUnreachableCode) { this.removeUnreachableCode(ast); } // Step 8: Inline small functions if (this.options.inlineSmallFunctions) { this.inlineSmallFunctions(ast); } // Step 9: Remove empty blocks if (this.options.removeEmptyBlocks) { this.removeEmptyBlocks(ast); } // Step 10: Remove unused CSS rules if (this.options.removeUnusedCSS) { this.removeUnusedCSSRules(ast); } return ast; } catch (error) { const optimizationError = new OptimizationError(`Dead code elimination failed: ${error instanceof Error ? error.message : String(error)}`, { start: { line: 1, column: 1, offset: 0 }, end: { line: 1, column: 1, offset: 0 } }, OptimizationType.DEAD_CODE_ELIMINATION); this.errors.push(optimizationError); throw optimizationError; } } /** * Get optimization errors */ getErrors() { return [...this.errors]; } /** * Get optimization warnings */ getWarnings() { return [...this.warnings]; } /** * Reset internal state */ reset() { this.usageAnalysis = { usedVariables: new Set(), usedFunctions: new Set(), usedEventHandlers: new Set(), usedCSSSelectors: new Set(), usedServerFunctions: new Set(), reachableStatements: new Set(), constantExpressions: new Map() }; this.errors = []; this.warnings = []; } /** * Analyze usage patterns in the component */ analyzeUsage(ast) { const component = ast.component; // Analyze client block if (component.clientBlock) { this.analyzeClientBlockUsage(component.clientBlock); } // Analyze server block if (component.serverBlock) { this.analyzeServerBlockUsage(component.serverBlock); } // Analyze markup block this.analyzeMarkupBlockUsage(component.markupBlock); // Analyze style block if (component.styleBlock) { this.analyzeStyleBlockUsage(component.styleBlock); } } /** * Analyze usage in client block */ analyzeClientBlockUsage(clientBlock) { // Analyze reactive variables usage for (const variable of clientBlock.reactiveVariables) { this.usageAnalysis.usedVariables.add(variable.name); } // Analyze functions usage for (const func of clientBlock.functions) { this.usageAnalysis.usedFunctions.add(func.name); this.analyzeStatementUsage(func.body); } // Analyze event handlers usage for (const handler of clientBlock.eventHandlers) { this.usageAnalysis.usedEventHandlers.add(handler.eventName); if (typeof handler.handler === 'object' && 'expressionType' in handler.handler) { this.analyzeExpressionUsage(handler.handler); } } } /** * Analyze usage in server block */ analyzeServerBlockUsage(serverBlock) { // Analyze server functions usage for (const func of serverBlock.functions) { if (func.isPublic) { this.usageAnalysis.usedServerFunctions.add(func.name); } this.analyzeStatementUsage(func.body); } } /** * Analyze usage in markup block */ analyzeMarkupBlockUsage(markupBlock) { // Analyze interpolations for (const interpolation of markupBlock.interpolations) { this.analyzeExpressionUsage(interpolation.expression); } // Analyze HTML elements for (const element of markupBlock.elements) { this.analyzeHTMLElementUsage(element); } } /** * Analyze usage in HTML element */ analyzeHTMLElementUsage(element) { // Analyze attributes for (const attr of element.attributes) { if (typeof attr.value === 'object') { this.analyzeExpressionUsage(attr.value); } } // Analyze children for (const child of element.children) { if (child.type === 'HTMLElement') { this.analyzeHTMLElementUsage(child); } else if (child.type === 'Interpolation') { this.analyzeExpressionUsage(child.expression); } } } /** * Analyze usage in style block */ analyzeStyleBlockUsage(styleBlock) { for (const rule of styleBlock.rules) { this.usageAnalysis.usedCSSSelectors.add(rule.selector); } } /** * Analyze expression usage */ analyzeExpressionUsage(expr) { if (!expr) return; switch (expr.expressionType) { case ExpressionType.IDENTIFIER: if (expr.identifier) { this.usageAnalysis.usedVariables.add(expr.identifier); } break; case ExpressionType.CALL: if (expr.callee && expr.callee.identifier) { this.usageAnalysis.usedFunctions.add(expr.callee.identifier); } if (expr.arguments) { for (const arg of expr.arguments) { this.analyzeExpressionUsage(arg); } } break; case ExpressionType.BINARY: if (expr.left) this.analyzeExpressionUsage(expr.left); if (expr.right) this.analyzeExpressionUsage(expr.right); break; case ExpressionType.UNARY: if (expr.right) this.analyzeExpressionUsage(expr.right); break; case ExpressionType.MEMBER: if (expr.object) this.analyzeExpressionUsage(expr.object); if (expr.property) this.analyzeExpressionUsage(expr.property); break; case ExpressionType.ASSIGNMENT: if (expr.left) this.analyzeExpressionUsage(expr.left); if (expr.right) this.analyzeExpressionUsage(expr.right); break; } } /** * Analyze statement usage */ analyzeStatementUsage(statements) { for (const stmt of statements) { this.usageAnalysis.reachableStatements.add(stmt); switch (stmt.statementType) { case StatementType.EXPRESSION: if (stmt.expression) { this.analyzeExpressionUsage(stmt.expression); } break; case StatementType.BLOCK: if (stmt.body) { this.analyzeStatementUsage(stmt.body); } break; case StatementType.IF: if (stmt.condition) { this.analyzeExpressionUsage(stmt.condition); } if (stmt.body) { this.analyzeStatementUsage(stmt.body); } break; case StatementType.FOR: case StatementType.WHILE: if (stmt.init) this.analyzeStatementUsage([stmt.init]); if (stmt.condition) this.analyzeExpressionUsage(stmt.condition); if (stmt.update) this.analyzeExpressionUsage(stmt.update); if (stmt.body) this.analyzeStatementUsage(stmt.body); break; case StatementType.RETURN: if (stmt.expression) { this.analyzeExpressionUsage(stmt.expression); } break; case StatementType.VARIABLE_DECLARATION: if (stmt.expression) { this.analyzeExpressionUsage(stmt.expression); } break; } } } /** * Perform constant folding */ performConstantFolding(ast) { // This would implement constant folding logic // For now, we'll add a placeholder for the implementation let foldedConstants = 0; const foldExpression = (expr) => { if (!expr) return expr; switch (expr.expressionType) { case ExpressionType.BINARY: if (expr.left && expr.right && expr.left.expressionType === ExpressionType.LITERAL && expr.right.expressionType === ExpressionType.LITERAL) { // Fold binary operations on literals const left = expr.left.value; const right = expr.right.value; const operator = expr.operator; let result; switch (operator) { case '+': result = left + right; break; case '-': result = left - right; break; case '*': result = left * right; break; case '/': result = left / right; break; default: return expr; } foldedConstants++; return { ...expr, expressionType: ExpressionType.LITERAL, value: result }; } break; } return expr; }; // Apply constant folding to the entire AST this.applyExpressionTransformation(ast, foldExpression); } /** * Remove unused variables */ removeUnusedVariables(ast) { if (!ast.component.clientBlock) return; const originalCount = ast.component.clientBlock.reactiveVariables.length; ast.component.clientBlock.reactiveVariables = ast.component.clientBlock.reactiveVariables.filter(variable => this.usageAnalysis.usedVariables.has(variable.name)); const removedCount = originalCount - ast.component.clientBlock.reactiveVariables.length; if (removedCount > 0) { this.warnings.push(new OptimizationError(`Removed ${removedCount} unused reactive variables`, { start: { line: 1, column: 1, offset: 0 }, end: { line: 1, column: 1, offset: 0 } }, OptimizationType.DEAD_CODE_ELIMINATION)); } } /** * Remove unused functions */ removeUnusedFunctions(ast) { if (!ast.component.clientBlock) return; const originalCount = ast.component.clientBlock.functions.length; ast.component.clientBlock.functions = ast.component.clientBlock.functions.filter(func => this.usageAnalysis.usedFunctions.has(func.name)); const removedCount = originalCount - ast.component.clientBlock.functions.length; if (removedCount > 0) { this.warnings.push(new OptimizationError(`Removed ${removedCount} unused functions`, { start: { line: 1, column: 1, offset: 0 }, end: { line: 1, column: 1, offset: 0 } }, OptimizationType.DEAD_CODE_ELIMINATION)); } } /** * Remove unused event handlers */ removeUnusedEventHandlers(ast) { if (!ast.component.clientBlock) return; const originalCount = ast.component.clientBlock.eventHandlers.length; ast.component.clientBlock.eventHandlers = ast.component.clientBlock.eventHandlers.filter(handler => this.usageAnalysis.usedEventHandlers.has(handler.eventName)); const removedCount = originalCount - ast.component.clientBlock.eventHandlers.length; if (removedCount > 0) { this.warnings.push(new OptimizationError(`Removed ${removedCount} unused event handlers`, { start: { line: 1, column: 1, offset: 0 }, end: { line: 1, column: 1, offset: 0 } }, OptimizationType.DEAD_CODE_ELIMINATION)); } } /** * Remove unused server functions */ removeUnusedServerFunctions(ast) { if (!ast.component.serverBlock) return; const originalCount = ast.component.serverBlock.functions.length; ast.component.serverBlock.functions = ast.component.serverBlock.functions.filter(func => func.isPublic || this.usageAnalysis.usedServerFunctions.has(func.name)); const removedCount = originalCount - ast.component.serverBlock.functions.length; if (removedCount > 0) { this.warnings.push(new OptimizationError(`Removed ${removedCount} unused server functions`, { start: { line: 1, column: 1, offset: 0 }, end: { line: 1, column: 1, offset: 0 } }, OptimizationType.DEAD_CODE_ELIMINATION)); } } /** * Remove unreachable code */ removeUnreachableCode(ast) { // This would implement unreachable code detection and removal // For now, we'll add a placeholder for the implementation const removeUnreachableStatements = (statements) => { return statements.filter(stmt => this.usageAnalysis.reachableStatements.has(stmt)); }; // Apply to client block functions if (ast.component.clientBlock) { for (const func of ast.component.clientBlock.functions) { func.body = removeUnreachableStatements(func.body); } } // Apply to server block functions if (ast.component.serverBlock) { for (const func of ast.component.serverBlock.functions) { func.body = removeUnreachableStatements(func.body); } } } /** * Inline small functions */ inlineSmallFunctions(ast) { if (!ast.component.clientBlock) return; const inlinedFunctions = []; for (const func of ast.component.clientBlock.functions) { if (this.shouldInlineFunction(func)) { // This would implement the actual inlining logic // For now, we'll mark it for inlining inlinedFunctions.push(func.name); } } if (inlinedFunctions.length > 0) { this.warnings.push(new OptimizationError(`Marked ${inlinedFunctions.length} functions for inlining`, { start: { line: 1, column: 1, offset: 0 }, end: { line: 1, column: 1, offset: 0 } }, OptimizationType.INLINE_EXPANSION)); } } /** * Check if a function should be inlined */ shouldInlineFunction(func) { if (!this.options.inlineSmallFunctions) return false; if (!this.options.maxInlineSize) return false; // Count AST nodes in function body let nodeCount = 0; const countNodes = (statements) => { for (const stmt of statements) { nodeCount++; if (stmt.body) { countNodes(stmt.body); } if (stmt.expression) { nodeCount++; } } }; countNodes(func.body); return nodeCount <= this.options.maxInlineSize; } /** * Remove empty blocks */ removeEmptyBlocks(ast) { let removedBlocks = 0; const removeEmptyBlocksFromStatements = (statements) => { return statements.filter(stmt => { if (stmt.statementType === StatementType.BLOCK) { if (!stmt.body || stmt.body.length === 0) { removedBlocks++; return false; } stmt.body = removeEmptyBlocksFromStatements(stmt.body); } return true; }); }; // Apply to client block functions if (ast.component.clientBlock) { for (const func of ast.component.clientBlock.functions) { func.body = removeEmptyBlocksFromStatements(func.body); } } // Apply to server block functions if (ast.component.serverBlock) { for (const func of ast.component.serverBlock.functions) { func.body = removeEmptyBlocksFromStatements(func.body); } } if (removedBlocks > 0) { this.warnings.push(new OptimizationError(`Removed ${removedBlocks} empty blocks`, { start: { line: 1, column: 1, offset: 0 }, end: { line: 1, column: 1, offset: 0 } }, OptimizationType.DEAD_CODE_ELIMINATION)); } } /** * Remove unused CSS rules */ removeUnusedCSSRules(ast) { if (!ast.component.styleBlock) return; const originalCount = ast.component.styleBlock.rules.length; ast.component.styleBlock.rules = ast.component.styleBlock.rules.filter(rule => this.usageAnalysis.usedCSSSelectors.has(rule.selector)); const removedCount = originalCount - ast.component.styleBlock.rules.length; if (removedCount > 0) { this.warnings.push(new OptimizationError(`Removed ${removedCount} unused CSS rules`, { start: { line: 1, column: 1, offset: 0 }, end: { line: 1, column: 1, offset: 0 } }, OptimizationType.DEAD_CODE_ELIMINATION)); } } /** * Apply transformation to all expressions in the AST */ applyExpressionTransformation(ast, transform) { const transformExpression = (expr) => { if (!expr) return expr; // Transform children first if (expr.left) expr.left = transformExpression(expr.left); if (expr.right) expr.right = transformExpression(expr.right); if (expr.callee) expr.callee = transformExpression(expr.callee); if (expr.object) expr.object = transformExpression(expr.object); if (expr.property) expr.property = transformExpression(expr.property); if (expr.arguments) { expr.arguments = expr.arguments.map(transformExpression); } // Apply transformation return transform(expr); }; // Apply to client block if (ast.component.clientBlock) { for (const func of ast.component.clientBlock.functions) { for (const stmt of func.body) { if (stmt.expression) { stmt.expression = transformExpression(stmt.expression); } } } } // Apply to server block if (ast.component.serverBlock) { for (const func of ast.component.serverBlock.functions) { for (const stmt of func.body) { if (stmt.expression) { stmt.expression = transformExpression(stmt.expression); } } } } // Apply to markup block for (const interpolation of ast.component.markupBlock.interpolations) { interpolation.expression = transformExpression(interpolation.expression); } // Apply to HTML elements const transformHTMLElement = (element) => { for (const attr of element.attributes) { if (typeof attr.value === 'object') { attr.value = transformExpression(attr.value); } } for (const child of element.children) { if (child.type === 'HTMLElement') { transformHTMLElement(child); } else if (child.type === 'Interpolation') { child.expression = transformExpression(child.expression); } } }; for (const element of ast.component.markupBlock.elements) { transformHTMLElement(element); } } } //# sourceMappingURL=dead-code-eliminator.js.map