UNPKG

@ordojs/core

Version:

Core compiler and runtime for OrdoJS framework

877 lines (795 loc) 30.3 kB
/** * @fileoverview Dead Code Eliminator Tests */ import { beforeEach, describe, expect, it } from 'vitest'; import { ExpressionType, StatementType, type ClientBlockNode, type ComponentAST, type FunctionNode, type MarkupBlockNode, type ReactiveVariableNode, type ServerBlockNode } from '../types/index.js'; import { DeadCodeEliminator } from './dead-code-eliminator.js'; describe('DeadCodeEliminator', () => { let eliminator: DeadCodeEliminator; beforeEach(() => { eliminator = new DeadCodeEliminator(); }); describe('constructor', () => { it('should initialize with default options', () => { expect(eliminator).toBeInstanceOf(DeadCodeEliminator); }); it('should accept custom options', () => { const customEliminator = new DeadCodeEliminator({ aggressiveTreeShaking: false, inlineSmallFunctions: false, maxInlineSize: 5 }); expect(customEliminator).toBeInstanceOf(DeadCodeEliminator); }); }); describe('optimize', () => { it('should return the same AST when no optimizations are possible', () => { const ast = createSimpleAST(); const result = eliminator.optimize(ast); expect(result).toBe(ast); }); it('should remove unused reactive variables', () => { const ast = createASTWithUnusedVariables(); const originalVariableCount = ast.component.clientBlock!.reactiveVariables.length; const result = eliminator.optimize(ast); expect(result.component.clientBlock!.reactiveVariables.length).toBeLessThan(originalVariableCount); expect(result.component.clientBlock!.reactiveVariables.every(v => v.name === 'usedVariable')).toBe(true); }); it('should remove unused functions', () => { const ast = createASTWithUnusedFunctions(); const originalFunctionCount = ast.component.clientBlock!.functions.length; const result = eliminator.optimize(ast); expect(result.component.clientBlock!.functions.length).toBeLessThan(originalFunctionCount); expect(result.component.clientBlock!.functions.every(f => f.name === 'usedFunction')).toBe(true); }); it('should remove unused event handlers', () => { const ast = createASTWithUnusedEventHandlers(); const originalHandlerCount = ast.component.clientBlock!.eventHandlers.length; const result = eliminator.optimize(ast); expect(result.component.clientBlock!.eventHandlers.length).toBeLessThan(originalHandlerCount); expect(result.component.clientBlock!.eventHandlers.every(h => h.eventName === 'click')).toBe(true); }); it('should remove unused server functions', () => { const ast = createASTWithUnusedServerFunctions(); const originalFunctionCount = ast.component.serverBlock!.functions.length; const result = eliminator.optimize(ast); expect(result.component.serverBlock!.functions.length).toBeLessThan(originalFunctionCount); expect(result.component.serverBlock!.functions.every(f => f.isPublic || f.name === 'usedFunction')).toBe(true); }); it('should perform constant folding', () => { const ast = createASTWithConstantExpressions(); const result = eliminator.optimize(ast); // Check that constant expressions were folded const warnings = eliminator.getWarnings(); expect(warnings.some(w => w.message.includes('constant folding'))).toBe(true); }); it('should remove empty blocks', () => { const ast = createASTWithEmptyBlocks(); const result = eliminator.optimize(ast); const warnings = eliminator.getWarnings(); expect(warnings.some(w => w.message.includes('empty blocks'))).toBe(true); }); it('should handle errors gracefully', () => { const invalidAST = createInvalidAST(); expect(() => eliminator.optimize(invalidAST)).toThrow(); const errors = eliminator.getErrors(); expect(errors.length).toBeGreaterThan(0); expect(errors[0]).toBeInstanceOf(Error); }); it('should generate warnings for optimizations', () => { const ast = createASTWithUnusedVariables(); eliminator.optimize(ast); const warnings = eliminator.getWarnings(); expect(warnings.length).toBeGreaterThan(0); expect(warnings.every(w => w instanceof Error)).toBe(true); }); it('should preserve used variables and functions', () => { const ast = createASTWithUsedAndUnused(); const result = eliminator.optimize(ast); // Check that used variables are preserved const usedVariables = result.component.clientBlock!.reactiveVariables.map(v => v.name); expect(usedVariables).toContain('usedVariable'); expect(usedVariables).not.toContain('unusedVariable'); // Check that used functions are preserved const usedFunctions = result.component.clientBlock!.functions.map(f => f.name); expect(usedFunctions).toContain('usedFunction'); expect(usedFunctions).not.toContain('unusedFunction'); }); it('should handle complex nested expressions', () => { const ast = createASTWithComplexExpressions(); const result = eliminator.optimize(ast); expect(result).toBeDefined(); expect(result.component).toBeDefined(); }); it('should handle components without client or server blocks', () => { const ast = createASTWithoutBlocks(); const result = eliminator.optimize(ast); expect(result).toBe(ast); }); }); describe('error handling', () => { it('should handle null AST gracefully', () => { expect(() => eliminator.optimize(null as any)).toThrow(); }); it('should handle undefined AST gracefully', () => { expect(() => eliminator.optimize(undefined as any)).toThrow(); }); it('should handle malformed AST gracefully', () => { const malformedAST = createMalformedAST(); expect(() => eliminator.optimize(malformedAST)).toThrow(); }); }); describe('options', () => { it('should respect aggressiveTreeShaking option', () => { const conservativeEliminator = new DeadCodeEliminator({ aggressiveTreeShaking: false }); const ast = createASTWithUnusedVariables(); const result = conservativeEliminator.optimize(ast); // Should be less aggressive in removal expect(result.component.clientBlock!.reactiveVariables.length).toBeGreaterThanOrEqual( ast.component.clientBlock!.reactiveVariables.length ); }); it('should respect inlineSmallFunctions option', () => { const noInlineEliminator = new DeadCodeEliminator({ inlineSmallFunctions: false }); const ast = createASTWithSmallFunctions(); const result = noInlineEliminator.optimize(ast); const warnings = noInlineEliminator.getWarnings(); expect(warnings.some(w => w.message.includes('inlining'))).toBe(false); }); it('should respect maxInlineSize option', () => { const smallInlineEliminator = new DeadCodeEliminator({ maxInlineSize: 2 }); const ast = createASTWithSmallFunctions(); const result = smallInlineEliminator.optimize(ast); // Should inline fewer functions with smaller maxInlineSize const warnings = smallInlineEliminator.getWarnings(); const inliningWarnings = warnings.filter(w => w.message.includes('inlining')); expect(inliningWarnings.length).toBeLessThanOrEqual(2); }); }); }); // Helper functions to create test ASTs function createSimpleAST(): ComponentAST { return { component: { type: 'Component', name: 'TestComponent', props: [], markupBlock: { type: 'MarkupBlock', elements: [], textNodes: [], interpolations: [], range: { start: { line: 1, column: 1, offset: 0 }, end: { line: 1, column: 1, offset: 0 } } }, range: { start: { line: 1, column: 1, offset: 0 }, end: { line: 1, column: 1, offset: 0 } } }, dependencies: [], exports: [], sourceMap: { version: 3, sources: [], names: [], mappings: '', sourcesContent: [] } }; } function createASTWithUnusedVariables(): ComponentAST { const usedVariable: ReactiveVariableNode = { type: 'ReactiveVariable', name: 'usedVariable', initialValue: { type: 'Expression', expressionType: ExpressionType.LITERAL, value: 42, range: { start: { line: 1, column: 1, offset: 0 }, end: { line: 1, column: 1, offset: 0 } } }, dataType: { name: 'number', isArray: false, isOptional: false, genericTypes: [] }, isConst: false, range: { start: { line: 1, column: 1, offset: 0 }, end: { line: 1, column: 1, offset: 0 } } }; const unusedVariable: ReactiveVariableNode = { type: 'ReactiveVariable', name: 'unusedVariable', initialValue: { type: 'Expression', expressionType: ExpressionType.LITERAL, value: 100, range: { start: { line: 1, column: 1, offset: 0 }, end: { line: 1, column: 1, offset: 0 } } }, dataType: { name: 'number', isArray: false, isOptional: false, genericTypes: [] }, isConst: false, range: { start: { line: 1, column: 1, offset: 0 }, end: { line: 1, column: 1, offset: 0 } } }; const clientBlock: ClientBlockNode = { type: 'ClientBlock', reactiveVariables: [usedVariable, unusedVariable], eventHandlers: [], functions: [], lifecycle: [], range: { start: { line: 1, column: 1, offset: 0 }, end: { line: 1, column: 1, offset: 0 } } }; const markupBlock: MarkupBlockNode = { type: 'MarkupBlock', elements: [], textNodes: [], interpolations: [{ type: 'Interpolation', expression: { type: 'Expression', expressionType: ExpressionType.IDENTIFIER, identifier: 'usedVariable', range: { start: { line: 1, column: 1, offset: 0 }, end: { line: 1, column: 1, offset: 0 } } }, range: { start: { line: 1, column: 1, offset: 0 }, end: { line: 1, column: 1, offset: 0 } } }], range: { start: { line: 1, column: 1, offset: 0 }, end: { line: 1, column: 1, offset: 0 } } }; return { component: { type: 'Component', name: 'TestComponent', props: [], clientBlock, markupBlock, range: { start: { line: 1, column: 1, offset: 0 }, end: { line: 1, column: 1, offset: 0 } } }, dependencies: [], exports: [], sourceMap: { version: 3, sources: [], names: [], mappings: '', sourcesContent: [] } }; } function createASTWithUnusedFunctions(): ComponentAST { const usedFunction: FunctionNode = { type: 'Function', name: 'usedFunction', parameters: [], body: [], returnType: { name: 'void', isArray: false, isOptional: false, genericTypes: [] }, isAsync: false, range: { start: { line: 1, column: 1, offset: 0 }, end: { line: 1, column: 1, offset: 0 } } }; const unusedFunction: FunctionNode = { type: 'Function', name: 'unusedFunction', parameters: [], body: [], returnType: { name: 'void', isArray: false, isOptional: false, genericTypes: [] }, isAsync: false, range: { start: { line: 1, column: 1, offset: 0 }, end: { line: 1, column: 1, offset: 0 } } }; const clientBlock: ClientBlockNode = { type: 'ClientBlock', reactiveVariables: [], eventHandlers: [], functions: [usedFunction, unusedFunction], lifecycle: [], range: { start: { line: 1, column: 1, offset: 0 }, end: { line: 1, column: 1, offset: 0 } } }; const markupBlock: MarkupBlockNode = { type: 'MarkupBlock', elements: [], textNodes: [], interpolations: [{ type: 'Interpolation', expression: { type: 'Expression', expressionType: ExpressionType.CALL, callee: { type: 'Expression', expressionType: ExpressionType.IDENTIFIER, identifier: 'usedFunction', range: { start: { line: 1, column: 1, offset: 0 }, end: { line: 1, column: 1, offset: 0 } } }, arguments: [], range: { start: { line: 1, column: 1, offset: 0 }, end: { line: 1, column: 1, offset: 0 } } }, range: { start: { line: 1, column: 1, offset: 0 }, end: { line: 1, column: 1, offset: 0 } } }], range: { start: { line: 1, column: 1, offset: 0 }, end: { line: 1, column: 1, offset: 0 } } }; return { component: { type: 'Component', name: 'TestComponent', props: [], clientBlock, markupBlock, range: { start: { line: 1, column: 1, offset: 0 }, end: { line: 1, column: 1, offset: 0 } } }, dependencies: [], exports: [], sourceMap: { version: 3, sources: [], names: [], mappings: '', sourcesContent: [] } }; } function createASTWithUnusedEventHandlers(): ComponentAST { const clientBlock: ClientBlockNode = { type: 'ClientBlock', reactiveVariables: [], eventHandlers: [ { type: 'EventHandler', eventName: 'click', handler: { type: 'Expression', expressionType: ExpressionType.LITERAL, value: 'handleClick', range: { start: { line: 1, column: 1, offset: 0 }, end: { line: 1, column: 1, offset: 0 } } }, modifiers: [], range: { start: { line: 1, column: 1, offset: 0 }, end: { line: 1, column: 1, offset: 0 } } }, { type: 'EventHandler', eventName: 'unused', handler: { type: 'Expression', expressionType: ExpressionType.LITERAL, value: 'handleUnused', range: { start: { line: 1, column: 1, offset: 0 }, end: { line: 1, column: 1, offset: 0 } } }, modifiers: [], range: { start: { line: 1, column: 1, offset: 0 }, end: { line: 1, column: 1, offset: 0 } } } ], functions: [], lifecycle: [], range: { start: { line: 1, column: 1, offset: 0 }, end: { line: 1, column: 1, offset: 0 } } }; const markupBlock: MarkupBlockNode = { type: 'MarkupBlock', elements: [{ type: 'HTMLElement', tagName: 'button', attributes: [{ type: 'Attribute', name: 'onclick', value: 'click', isDirective: false, range: { start: { line: 1, column: 1, offset: 0 }, end: { line: 1, column: 1, offset: 0 } } }], children: [], isSelfClosing: false, isVoidElement: false, range: { start: { line: 1, column: 1, offset: 0 }, end: { line: 1, column: 1, offset: 0 } } }], textNodes: [], interpolations: [], range: { start: { line: 1, column: 1, offset: 0 }, end: { line: 1, column: 1, offset: 0 } } }; return { component: { type: 'Component', name: 'TestComponent', props: [], clientBlock, markupBlock, range: { start: { line: 1, column: 1, offset: 0 }, end: { line: 1, column: 1, offset: 0 } } }, dependencies: [], exports: [], sourceMap: { version: 3, sources: [], names: [], mappings: '', sourcesContent: [] } }; } function createASTWithUnusedServerFunctions(): ComponentAST { const serverBlock: ServerBlockNode = { type: 'ServerBlock', functions: [ { type: 'ServerFunction', name: 'usedFunction', parameters: [], body: [], returnType: { name: 'void', isArray: false, isOptional: false, genericTypes: [] }, isAsync: false, isPublic: false, middleware: [], permissions: [], range: { start: { line: 1, column: 1, offset: 0 }, end: { line: 1, column: 1, offset: 0 } } }, { type: 'ServerFunction', name: 'unusedFunction', parameters: [], body: [], returnType: { name: 'void', isArray: false, isOptional: false, genericTypes: [] }, isAsync: false, isPublic: false, middleware: [], permissions: [], range: { start: { line: 1, column: 1, offset: 0 }, end: { line: 1, column: 1, offset: 0 } } } ], middleware: [], dataFetchers: [], imports: [], range: { start: { line: 1, column: 1, offset: 0 }, end: { line: 1, column: 1, offset: 0 } } }; return { component: { type: 'Component', name: 'TestComponent', props: [], serverBlock, markupBlock: { type: 'MarkupBlock', elements: [], textNodes: [], interpolations: [{ type: 'Interpolation', expression: { type: 'Expression', expressionType: ExpressionType.CALL, callee: { type: 'Expression', expressionType: ExpressionType.IDENTIFIER, identifier: 'usedFunction', range: { start: { line: 1, column: 1, offset: 0 }, end: { line: 1, column: 1, offset: 0 } } }, arguments: [], range: { start: { line: 1, column: 1, offset: 0 }, end: { line: 1, column: 1, offset: 0 } } }, range: { start: { line: 1, column: 1, offset: 0 }, end: { line: 1, column: 1, offset: 0 } } }], range: { start: { line: 1, column: 1, offset: 0 }, end: { line: 1, column: 1, offset: 0 } } }, range: { start: { line: 1, column: 1, offset: 0 }, end: { line: 1, column: 1, offset: 0 } } }, dependencies: [], exports: [], sourceMap: { version: 3, sources: [], names: [], mappings: '', sourcesContent: [] } }; } function createASTWithConstantExpressions(): ComponentAST { const clientBlock: ClientBlockNode = { type: 'ClientBlock', reactiveVariables: [], eventHandlers: [], functions: [{ type: 'Function', name: 'testFunction', parameters: [], body: [{ type: 'Statement', statementType: StatementType.EXPRESSION, expression: { type: 'Expression', expressionType: ExpressionType.BINARY, operator: '+', left: { type: 'Expression', expressionType: ExpressionType.LITERAL, value: 2, range: { start: { line: 1, column: 1, offset: 0 }, end: { line: 1, column: 1, offset: 0 } } }, right: { type: 'Expression', expressionType: ExpressionType.LITERAL, value: 3, range: { start: { line: 1, column: 1, offset: 0 }, end: { line: 1, column: 1, offset: 0 } } }, range: { start: { line: 1, column: 1, offset: 0 }, end: { line: 1, column: 1, offset: 0 } } }, range: { start: { line: 1, column: 1, offset: 0 }, end: { line: 1, column: 1, offset: 0 } } }], returnType: { name: 'number', isArray: false, isOptional: false, genericTypes: [] }, isAsync: false, range: { start: { line: 1, column: 1, offset: 0 }, end: { line: 1, column: 1, offset: 0 } } }], lifecycle: [], range: { start: { line: 1, column: 1, offset: 0 }, end: { line: 1, column: 1, offset: 0 } } }; return { component: { type: 'Component', name: 'TestComponent', props: [], clientBlock, markupBlock: { type: 'MarkupBlock', elements: [], textNodes: [], interpolations: [], range: { start: { line: 1, column: 1, offset: 0 }, end: { line: 1, column: 1, offset: 0 } } }, range: { start: { line: 1, column: 1, offset: 0 }, end: { line: 1, column: 1, offset: 0 } } }, dependencies: [], exports: [], sourceMap: { version: 3, sources: [], names: [], mappings: '', sourcesContent: [] } }; } function createASTWithEmptyBlocks(): ComponentAST { const clientBlock: ClientBlockNode = { type: 'ClientBlock', reactiveVariables: [], eventHandlers: [], functions: [{ type: 'Function', name: 'testFunction', parameters: [], body: [{ type: 'Statement', statementType: StatementType.BLOCK, body: [], range: { start: { line: 1, column: 1, offset: 0 }, end: { line: 1, column: 1, offset: 0 } } }], returnType: { name: 'void', isArray: false, isOptional: false, genericTypes: [] }, isAsync: false, range: { start: { line: 1, column: 1, offset: 0 }, end: { line: 1, column: 1, offset: 0 } } }], lifecycle: [], range: { start: { line: 1, column: 1, offset: 0 }, end: { line: 1, column: 1, offset: 0 } } }; return { component: { type: 'Component', name: 'TestComponent', props: [], clientBlock, markupBlock: { type: 'MarkupBlock', elements: [], textNodes: [], interpolations: [], range: { start: { line: 1, column: 1, offset: 0 }, end: { line: 1, column: 1, offset: 0 } } }, range: { start: { line: 1, column: 1, offset: 0 }, end: { line: 1, column: 1, offset: 0 } } }, dependencies: [], exports: [], sourceMap: { version: 3, sources: [], names: [], mappings: '', sourcesContent: [] } }; } function createASTWithUsedAndUnused(): ComponentAST { const usedVariable: ReactiveVariableNode = { type: 'ReactiveVariable', name: 'usedVariable', initialValue: { type: 'Expression', expressionType: ExpressionType.LITERAL, value: 42, range: { start: { line: 1, column: 1, offset: 0 }, end: { line: 1, column: 1, offset: 0 } } }, dataType: { name: 'number', isArray: false, isOptional: false, genericTypes: [] }, isConst: false, range: { start: { line: 1, column: 1, offset: 0 }, end: { line: 1, column: 1, offset: 0 } } }; const unusedVariable: ReactiveVariableNode = { type: 'ReactiveVariable', name: 'unusedVariable', initialValue: { type: 'Expression', expressionType: ExpressionType.LITERAL, value: 100, range: { start: { line: 1, column: 1, offset: 0 }, end: { line: 1, column: 1, offset: 0 } } }, dataType: { name: 'number', isArray: false, isOptional: false, genericTypes: [] }, isConst: false, range: { start: { line: 1, column: 1, offset: 0 }, end: { line: 1, column: 1, offset: 0 } } }; const usedFunction: FunctionNode = { type: 'Function', name: 'usedFunction', parameters: [], body: [], returnType: { name: 'void', isArray: false, isOptional: false, genericTypes: [] }, isAsync: false, range: { start: { line: 1, column: 1, offset: 0 }, end: { line: 1, column: 1, offset: 0 } } }; const unusedFunction: FunctionNode = { type: 'Function', name: 'unusedFunction', parameters: [], body: [], returnType: { name: 'void', isArray: false, isOptional: false, genericTypes: [] }, isAsync: false, range: { start: { line: 1, column: 1, offset: 0 }, end: { line: 1, column: 1, offset: 0 } } }; const clientBlock: ClientBlockNode = { type: 'ClientBlock', reactiveVariables: [usedVariable, unusedVariable], eventHandlers: [], functions: [usedFunction, unusedFunction], lifecycle: [], range: { start: { line: 1, column: 1, offset: 0 }, end: { line: 1, column: 1, offset: 0 } } }; const markupBlock: MarkupBlockNode = { type: 'MarkupBlock', elements: [], textNodes: [], interpolations: [ { type: 'Interpolation', expression: { type: 'Expression', expressionType: ExpressionType.IDENTIFIER, identifier: 'usedVariable', range: { start: { line: 1, column: 1, offset: 0 }, end: { line: 1, column: 1, offset: 0 } } }, range: { start: { line: 1, column: 1, offset: 0 }, end: { line: 1, column: 1, offset: 0 } } }, { type: 'Interpolation', expression: { type: 'Expression', expressionType: ExpressionType.CALL, callee: { type: 'Expression', expressionType: ExpressionType.IDENTIFIER, identifier: 'usedFunction', range: { start: { line: 1, column: 1, offset: 0 }, end: { line: 1, column: 1, offset: 0 } } }, arguments: [], range: { start: { line: 1, column: 1, offset: 0 }, end: { line: 1, column: 1, offset: 0 } } }, range: { start: { line: 1, column: 1, offset: 0 }, end: { line: 1, column: 1, offset: 0 } } } ], range: { start: { line: 1, column: 1, offset: 0 }, end: { line: 1, column: 1, offset: 0 } } }; return { component: { type: 'Component', name: 'TestComponent', props: [], clientBlock, markupBlock, range: { start: { line: 1, column: 1, offset: 0 }, end: { line: 1, column: 1, offset: 0 } } }, dependencies: [], exports: [], sourceMap: { version: 3, sources: [], names: [], mappings: '', sourcesContent: [] } }; } function createASTWithComplexExpressions(): ComponentAST { const clientBlock: ClientBlockNode = { type: 'ClientBlock', reactiveVariables: [], eventHandlers: [], functions: [{ type: 'Function', name: 'complexFunction', parameters: [], body: [{ type: 'Statement', statementType: StatementType.EXPRESSION, expression: { type: 'Expression', expressionType: ExpressionType.CALL, callee: { type: 'Expression', expressionType: ExpressionType.MEMBER, object: { type: 'Expression', expressionType: ExpressionType.IDENTIFIER, identifier: 'console', range: { start: { line: 1, column: 1, offset: 0 }, end: { line: 1, column: 1, offset: 0 } } }, property: { type: 'Expression', expressionType: ExpressionType.IDENTIFIER, identifier: 'log', range: { start: { line: 1, column: 1, offset: 0 }, end: { line: 1, column: 1, offset: 0 } } }, range: { start: { line: 1, column: 1, offset: 0 }, end: { line: 1, column: 1, offset: 0 } } }, arguments: [ { type: 'Expression', expressionType: ExpressionType.BINARY, operator: '+', left: { type: 'Expression', expressionType: ExpressionType.LITERAL, value: 'Hello', range: { start: { line: 1, column: 1, offset: 0 }, end: { line: 1, column: 1, offset: 0 } } }, right: { type: 'Expression', expressionType: ExpressionType.IDENTIFIER, identifier: 'name', range: { start: { line: 1, column: 1, offset: 0 }, end: { line: 1, column: 1, offset: 0 } } }, range: { start: { line: 1, column: 1, offset: 0 }, end: { line: 1, column: 1, offset: 0 } } } ], range: { start: { line: 1, column: 1, offset: 0 }, end: { line: 1, column: 1, offset: 0 } } }, range: { start: { line: 1, column: 1, offset: 0 }, end: { line: 1, column: 1, offset: 0 } } }], returnType: { name: 'void', isArray: false, isOptional: false, genericTypes: [] }, isAsync: false, range: { start: { line: 1, column: 1, offset: 0 }, end: { line: 1, column: 1, offset: 0 } } }], lifecycle: [], range: { start: { line: 1, column: 1, offset: 0 }, end: { line: 1, column: 1, offset: 0 } } }; return { component: { type: 'Component', name: 'TestComponent', props: [], clientBlock, markupBlock: { type: 'MarkupBlock', elements: [], textNodes: [], interpolations: [], range: { start: { line: 1, column: 1, offset: 0 }, end: { line: 1, column: 1, offset: 0 } } }, range: { start: { line: 1, column: 1, offset: 0 }, end: { line: 1, column: 1, offset: 0 } } }, dependencies: [], exports: [], sourceMap: { version: 3, sources: [], names: [], mappings: '', sourcesContent: [] } }; } function createASTWithoutBlocks(): ComponentAST { return { component: { type: 'Component', name: 'TestComponent', props: [], markupBlock: { type: 'MarkupBlock', elements: [], textNodes: [], interpolations: [], range: { start: { line: 1, column: 1, offset: 0 }, end: { line: 1, column: 1, offset: 0 } } }, range: { start: { line: 1, column: 1, offset: 0 }, end: { line: 1, column: 1, offset: 0 } } }, dependencies: [], exports: [], sourceMap: { version: 3, sources: [], names: [], mappings: '', sourcesContent: [] } }; } function createASTWithSmallFunctions(): ComponentAST { const clientBlock: ClientBlockNode = { type: 'ClientBlock', reactiveVariables: [], eventHandlers: [], functions: [{ type: 'Function', name: 'smallFunction', parameters: [], body: [{ type: 'Statement', statementType: StatementType.EXPRESSION, expression: { type: 'Expression', expressionType: ExpressionType.LITERAL, value: 42, range: { start: { line: 1, column: 1, offset: 0 }, end: { line: 1, column: 1, offset: 0 } } }, range: { start: { line: 1, column: 1, offset: 0 }, end: { line: 1, column: 1, offset: 0 } } }], returnType: { name: 'number', isArray: false, isOptional: false, genericTypes: [] }, isAsync: false, range: { start: { line: 1, column: 1, offset: 0 }, end: { line: 1, column: 1, offset: 0 } } }], lifecycle: [], range: { start: { line: 1, column: 1, offset: 0 }, end: { line: 1, column: 1, offset: 0 } } }; return { component: { type: 'Component', name: 'TestComponent', props: [], clientBlock, markupBlock: { type: 'MarkupBlock', elements: [], textNodes: [], interpolations: [], range: { start: { line: 1, column: 1, offset: 0 }, end: { line: 1, column: 1, offset: 0 } } }, range: { start: { line: 1, column: 1, offset: 0 }, end: { line: 1, column: 1, offset: 0 } } }, dependencies: [], exports: [], sourceMap: { version: 3, sources: [], names: [], mappings: '', sourcesContent: [] } }; } function createInvalidAST(): ComponentAST { return { component: { type: 'Component', name: 'TestComponent', props: [], markupBlock: { type: 'MarkupBlock', elements: [], textNodes: [], interpolations: [], range: { start: { line: 1, column: 1, offset: 0 }, end: { line: 1, column: 1, offset: 0 } } }, range: { start: { line: 1, column: 1, offset: 0 }, end: { line: 1, column: 1, offset: 0 } } } as any, // Force invalid AST dependencies: [], exports: [], sourceMap: { version: 3, sources: [], names: [], mappings: '', sourcesContent: [] } }; } function createMalformedAST(): ComponentAST { return { component: null as any, dependencies: [], exports: [], sourceMap: { version: 3, sources: [], names: [], mappings: '', sourcesContent: [] } }; }