@ordojs/core
Version:
Core compiler and runtime for OrdoJS framework
580 lines • 22.7 kB
JavaScript
/**
* @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