UNPKG

@ordojs/core

Version:

Core compiler and runtime for OrdoJS framework

787 lines (682 loc) 22.8 kB
/** * @fileoverview OrdoJS Dead Code Eliminator - Comprehensive dead code elimination and optimization */ import { ExpressionType, OptimizationError, OptimizationType, StatementType, type ClientBlockNode, type ComponentAST, type ExpressionNode, type FunctionNode, type HTMLElementNode, type InterpolationNode, type MarkupBlockNode, type ServerBlockNode, type StatementNode } from '../types/index.js'; /** * Dead code elimination options */ export interface DeadCodeEliminatorOptions { /** * Whether to perform aggressive tree shaking */ aggressiveTreeShaking?: boolean; /** * Whether to inline small functions */ inlineSmallFunctions?: boolean; /** * Maximum size (in AST nodes) for function inlining */ maxInlineSize?: number; /** * Whether to remove unused variables */ removeUnusedVariables?: boolean; /** * Whether to remove unreachable code */ removeUnreachableCode?: boolean; /** * Whether to remove unused CSS rules */ removeUnusedCSS?: boolean; /** * Whether to remove unused event handlers */ removeUnusedEventHandlers?: boolean; /** * Whether to remove unused server functions */ removeUnusedServerFunctions?: boolean; /** * Whether to perform constant folding */ constantFolding?: boolean; /** * Whether to remove empty blocks */ removeEmptyBlocks?: boolean; } /** * Default dead code elimination options */ const DEFAULT_OPTIONS: DeadCodeEliminatorOptions = { aggressiveTreeShaking: true, inlineSmallFunctions: true, maxInlineSize: 10, removeUnusedVariables: true, removeUnreachableCode: true, removeUnusedCSS: true, removeUnusedEventHandlers: true, removeUnusedServerFunctions: true, constantFolding: true, removeEmptyBlocks: true }; /** * Usage analysis result */ export interface UsageAnalysis { usedVariables: Set<string>; usedFunctions: Set<string>; usedEventHandlers: Set<string>; usedCSSSelectors: Set<string>; usedServerFunctions: Set<string>; reachableStatements: Set<StatementNode>; constantExpressions: Map<ExpressionNode, any>; } /** * Dead code elimination result */ export interface DeadCodeEliminationResult { optimizedAST: ComponentAST; removedVariables: string[]; removedFunctions: string[]; removedEventHandlers: string[]; removedCSSRules: string[]; removedServerFunctions: string[]; inlinedFunctions: string[]; foldedConstants: number; removedEmptyBlocks: number; optimizationRatio: number; errors: OptimizationError[]; warnings: OptimizationError[]; } /** * Comprehensive dead code eliminator for OrdoJS components */ export class DeadCodeEliminator { private options: DeadCodeEliminatorOptions; private usageAnalysis!: UsageAnalysis; private errors: OptimizationError[] = []; private warnings: OptimizationError[] = []; constructor(options: Partial<DeadCodeEliminatorOptions> = {}) { this.options = { ...DEFAULT_OPTIONS, ...options }; this.reset(); } /** * Optimize a component AST by removing dead code */ optimize(ast: ComponentAST): ComponentAST { 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(): OptimizationError[] { return [...this.errors]; } /** * Get optimization warnings */ getWarnings(): OptimizationError[] { return [...this.warnings]; } /** * Reset internal state */ private reset(): void { 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 */ private analyzeUsage(ast: ComponentAST): void { 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 */ private analyzeClientBlockUsage(clientBlock: ClientBlockNode): void { // 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 as ExpressionNode); } } } /** * Analyze usage in server block */ private analyzeServerBlockUsage(serverBlock: ServerBlockNode): void { // 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 */ private analyzeMarkupBlockUsage(markupBlock: MarkupBlockNode): void { // 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 */ private analyzeHTMLElementUsage(element: HTMLElementNode): void { // Analyze attributes for (const attr of element.attributes) { if (typeof attr.value === 'object') { this.analyzeExpressionUsage(attr.value as ExpressionNode); } } // Analyze children for (const child of element.children) { if (child.type === 'HTMLElement') { this.analyzeHTMLElementUsage(child as HTMLElementNode); } else if (child.type === 'Interpolation') { this.analyzeExpressionUsage((child as InterpolationNode).expression); } } } /** * Analyze usage in style block */ private analyzeStyleBlockUsage(styleBlock: any): void { for (const rule of styleBlock.rules) { this.usageAnalysis.usedCSSSelectors.add(rule.selector); } } /** * Analyze expression usage */ private analyzeExpressionUsage(expr: ExpressionNode): void { 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 */ private analyzeStatementUsage(statements: StatementNode[]): void { 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 */ private performConstantFolding(ast: ComponentAST): void { // This would implement constant folding logic // For now, we'll add a placeholder for the implementation let foldedConstants = 0; const foldExpression = (expr: ExpressionNode): ExpressionNode => { 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: any; 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 */ private removeUnusedVariables(ast: ComponentAST): void { 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 */ private removeUnusedFunctions(ast: ComponentAST): void { 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 */ private removeUnusedEventHandlers(ast: ComponentAST): void { 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 */ private removeUnusedServerFunctions(ast: ComponentAST): void { 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 */ private removeUnreachableCode(ast: ComponentAST): void { // This would implement unreachable code detection and removal // For now, we'll add a placeholder for the implementation const removeUnreachableStatements = (statements: StatementNode[]): StatementNode[] => { 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 */ private inlineSmallFunctions(ast: ComponentAST): void { if (!ast.component.clientBlock) return; const inlinedFunctions: string[] = []; 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 */ private shouldInlineFunction(func: FunctionNode): boolean { if (!this.options.inlineSmallFunctions) return false; if (!this.options.maxInlineSize) return false; // Count AST nodes in function body let nodeCount = 0; const countNodes = (statements: StatementNode[]): void => { 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 */ private removeEmptyBlocks(ast: ComponentAST): void { let removedBlocks = 0; const removeEmptyBlocksFromStatements = (statements: StatementNode[]): StatementNode[] => { 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 */ private removeUnusedCSSRules(ast: ComponentAST): void { 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 */ private applyExpressionTransformation( ast: ComponentAST, transform: (expr: ExpressionNode) => ExpressionNode ): void { const transformExpression = (expr: ExpressionNode): ExpressionNode => { 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: HTMLElementNode): void => { for (const attr of element.attributes) { if (typeof attr.value === 'object') { attr.value = transformExpression(attr.value as ExpressionNode); } } for (const child of element.children) { if (child.type === 'HTMLElement') { transformHTMLElement(child as HTMLElementNode); } else if (child.type === 'Interpolation') { (child as InterpolationNode).expression = transformExpression((child as InterpolationNode).expression); } } }; for (const element of ast.component.markupBlock.elements) { transformHTMLElement(element); } } }