@ordojs/core
Version:
Core compiler and runtime for OrdoJS framework
787 lines (682 loc) • 22.8 kB
text/typescript
/**
* @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);
}
}
}