From c3dda73d3aa4044bf978f5bb93aefb1a49dfd712 Mon Sep 17 00:00:00 2001 From: Michal Date: Sun, 18 Jan 2026 20:27:12 +0100 Subject: [PATCH] improve control flow transformer robustness and remove debug logs --- cli/helpers/control-flow-transformer.ts | 141 +++++++++++++++--- cli/scripts/serve.ts | 4 +- core/module/injector.ts | 40 ----- core/module/template-renderer.ts | 2 - core/module/web-component-factory.ts | 3 - platform-browser/browser.ts | 3 - router/directives/router-link.directive.ts | 2 - tests/unit/test-functionality.ts | 161 +++++++++++++++++++++ 8 files changed, 287 insertions(+), 69 deletions(-) diff --git a/cli/helpers/control-flow-transformer.ts b/cli/helpers/control-flow-transformer.ts index a1920b5..65bea33 100644 --- a/cli/helpers/control-flow-transformer.ts +++ b/cli/helpers/control-flow-transformer.ts @@ -56,10 +56,20 @@ export class ControlFlowTransformer { closeParenIndex--; const openBraceIndex = content.indexOf('{', closeParenIndex); - if (openBraceIndex === -1) return null; + if (openBraceIndex === -1) { + // Handle case where there's no opening brace - create a simple block + return { + match: content.substring(ifIndex, closeParenIndex + 1) + '{ }', + startIndex: ifIndex, + endIndex: closeParenIndex + 1 + }; + } let endIndex = this.findIfBlockEnd(content, openBraceIndex); - if (endIndex === -1) return null; + if (endIndex === -1) { + // For incomplete blocks, try to process what we have + endIndex = content.length; + } return { match: content.substring(ifIndex, endIndex), @@ -79,7 +89,21 @@ export class ControlFlowTransformer { index++; } - if (braceCount !== 0) return -1; + // If we couldn't find matching closing brace, try to handle incomplete blocks + if (braceCount !== 0) { + // For incomplete blocks, find the end of the current line or next @if/@for statement + const remainingContent = content.substring(startBraceIndex); + const nextIfIndex = remainingContent.indexOf('@if', 1); + const nextForIndex = remainingContent.indexOf('@for', 1); + const lineEndIndex = remainingContent.indexOf('\n'); + + let endIndex = content.length; + if (nextIfIndex !== -1) endIndex = Math.min(endIndex, startBraceIndex + nextIfIndex); + if (nextForIndex !== -1) endIndex = Math.min(endIndex, startBraceIndex + nextForIndex); + if (lineEndIndex !== -1) endIndex = Math.min(endIndex, startBraceIndex + lineEndIndex); + + return endIndex; + } while (index < content.length) { const remaining = content.substring(index); @@ -165,7 +189,21 @@ export class ControlFlowTransformer { if (forIndex === -1) return null; const openParenIndex = content.indexOf('(', forIndex); - const closeParenIndex = content.indexOf(')', openParenIndex); + if (openParenIndex === -1) return null; + + // Properly count nested parentheses + let parenCount = 1; + let closeParenIndex = openParenIndex + 1; + while (closeParenIndex < content.length && parenCount > 0) { + const char = content[closeParenIndex]; + if (char === '(') parenCount++; + else if (char === ')') parenCount--; + closeParenIndex++; + } + + if (parenCount !== 0) return null; + closeParenIndex--; // Move back to the closing paren + const openBraceIndex = content.indexOf('{', closeParenIndex); if (openBraceIndex === -1) return null; @@ -194,7 +232,21 @@ export class ControlFlowTransformer { if (startIndex === -1) return null; const openParenIndex = match.indexOf('(', startIndex); - const closeParenIndex = match.indexOf(')', openParenIndex); + if (openParenIndex === -1) return null; + + // Properly count nested parentheses + let parenCount = 1; + let closeParenIndex = openParenIndex + 1; + while (closeParenIndex < match.length && parenCount > 0) { + const char = match[closeParenIndex]; + if (char === '(') parenCount++; + else if (char === ')') parenCount--; + closeParenIndex++; + } + + if (parenCount !== 0) return null; + closeParenIndex--; // Move back to the closing paren + const openBraceIndex = match.indexOf('{', closeParenIndex); if (openBraceIndex === -1) return null; @@ -219,7 +271,8 @@ export class ControlFlowTransformer { const forPart = parts[0].trim(); const trackPart = parts[1]?.trim(); - const forMatch = forPart.match(/^\s*([^\s]+)\s+of\s+([^\s]+)\s*$/); + // Match: variable of iterable (iterable can contain parentheses, dots, etc.) + const forMatch = forPart.match(/^\s*(\w+)\s+of\s+(.+)\s*$/); if (!forMatch) return null; const variable = forMatch[1].trim(); @@ -248,7 +301,10 @@ export class ControlFlowTransformer { ngForExpression += `; trackBy: ${forBlock.trackBy}`; } - return `${forBlock.content}`; + // Recursively transform nested @if and @for blocks in content + const transformedContent = this.transform(forBlock.content); + + return `${transformedContent}`; } private parseBlocks(match: string): ControlFlowBlock[] { @@ -273,6 +329,12 @@ export class ControlFlowTransformer { const { condition, aliasVariable } = this.parseConditionWithAlias(conditionStr.trim()); const openBraceIndex = match.indexOf('{', closeParenIndex); + if (openBraceIndex === -1) { + // Incomplete @if block - no opening brace found + blocks.push({ condition, content: '', aliasVariable }); + return blocks; + } + let braceCount = 1; let closeBraceIndex = openBraceIndex + 1; @@ -282,11 +344,22 @@ export class ControlFlowTransformer { else if (char === '}') braceCount--; closeBraceIndex++; } - closeBraceIndex--; - const content = match.substring(openBraceIndex + 1, closeBraceIndex); + let content = ''; + if (braceCount === 0) { + // Complete block found + closeBraceIndex--; + content = match.substring(openBraceIndex + 1, closeBraceIndex); + index = closeBraceIndex + 1; + } else { + // Incomplete block - take everything after opening brace + content = match.substring(openBraceIndex + 1); + index = match.length; + } + + // Trim leading whitespace from content + content = content.trimStart(); blocks.push({ condition, content, aliasVariable }); - index = closeBraceIndex + 1; } while (index < match.length) { @@ -311,6 +384,13 @@ export class ControlFlowTransformer { const { condition, aliasVariable } = this.parseConditionWithAlias(conditionStr.trim()); const openBraceIndex = match.indexOf('{', closeParenIndex); + if (openBraceIndex === -1) { + // Incomplete @else if block - no opening brace found + blocks.push({ condition, content: '', aliasVariable }); + index = match.length; + break; + } + let braceCount = 1; let closeBraceIndex = openBraceIndex + 1; @@ -320,11 +400,22 @@ export class ControlFlowTransformer { else if (char === '}') braceCount--; closeBraceIndex++; } - closeBraceIndex--; - const content = match.substring(openBraceIndex + 1, closeBraceIndex); + let content = ''; + if (braceCount === 0) { + // Complete block found + closeBraceIndex--; + content = match.substring(openBraceIndex + 1, closeBraceIndex); + index = closeBraceIndex + 1; + } else { + // Incomplete block - take everything after opening brace + content = match.substring(openBraceIndex + 1); + index = match.length; + } + + // Trim leading whitespace from content + content = content.trimStart(); blocks.push({ condition, content, aliasVariable }); - index = closeBraceIndex + 1; } else if (elseMatch) { const openBraceIndex = index + elseMatch[0].length - 1; let braceCount = 1; @@ -336,11 +427,22 @@ export class ControlFlowTransformer { else if (char === '}') braceCount--; closeBraceIndex++; } - closeBraceIndex--; - const content = match.substring(openBraceIndex + 1, closeBraceIndex); + let content = ''; + if (braceCount === 0) { + // Complete block found + closeBraceIndex--; + content = match.substring(openBraceIndex + 1, closeBraceIndex); + index = closeBraceIndex + 1; + } else { + // Incomplete block - take everything after opening brace + content = match.substring(openBraceIndex + 1); + index = match.length; + } + + // Trim leading whitespace from content + content = content.trimStart(); blocks.push({ condition: null, content }); - index = closeBraceIndex + 1; } else { break; } @@ -368,10 +470,13 @@ export class ControlFlowTransformer { const block = blocks[i]; const condition = this.buildCondition(block.condition, negated); + // Recursively transform nested @if and @for blocks in content + const transformedContent = this.transform(block.content); + if (block.aliasVariable) { - result += `${block.content}`; + result += `${transformedContent}`; } else { - result += `${block.content}`; + result += `${transformedContent}`; } if (i < blocks.length - 1) { diff --git a/cli/scripts/serve.ts b/cli/scripts/serve.ts index 609074f..3585416 100644 --- a/cli/scripts/serve.ts +++ b/cli/scripts/serve.ts @@ -160,6 +160,7 @@ class Server extends BaseBuilder { } private proxyRequest(targetUrl: string, req: http.IncomingMessage, res: http.ServerResponse): void { + console.log(`[Proxy] ${req.method} ${req.url} -> ${targetUrl}`); const parsedUrl = new URL(targetUrl); const protocol = parsedUrl.protocol === 'https:' ? https : http; @@ -173,13 +174,14 @@ class Server extends BaseBuilder { }, }, (proxyRes) => { + console.log(`[Proxy] Response: ${proxyRes.statusCode} for ${req.url}`); res.writeHead(proxyRes.statusCode || 500, proxyRes.headers); proxyRes.pipe(res); }, ); proxyReq.on('error', (err) => { - console.error('Proxy error:', err.message); + console.error(`[Proxy] Error for ${req.url}:`, err.message); res.writeHead(502); res.end('Bad Gateway'); }); diff --git a/core/module/injector.ts b/core/module/injector.ts index b04010e..be9ea21 100644 --- a/core/module/injector.ts +++ b/core/module/injector.ts @@ -61,27 +61,11 @@ export class Injector { } public createInstanceWithProviders(classType: Type, providers: Provider[]): T { - console.log('[DI] createInstanceWithProviders START', { - classType, - typeofClassType: typeof classType, - isFunction: typeof classType === 'function', - hasName: classType?.name, - originalName: (classType as any)?.__quarc_original_name__, - providers: providers.map(p => ({ - provide: typeof p.provide === 'string' ? p.provide : p.provide?.name, - type: 'useValue' in p ? 'useValue' : 'useClass' in p ? 'useClass' : 'useFactory' in p ? 'useFactory' : 'useExisting' - })) - }); - if (!classType) { throw new Error(`[DI] createInstanceWithProviders called with undefined classType`); } try { - console.log({ - className: (classType as any).__quarc_original_name__ || classType.name, - classType, - }); const dependencies = this.resolveDependenciesWithProviders(classType, providers); /** / console.log({ @@ -127,11 +111,6 @@ export class Injector { return `${metadata.selector} (class)`; } - console.log({ - classType, - metadata, - }); - return 'Unknown class'; } @@ -187,44 +166,27 @@ export class Injector { } private resolveDependency(token: any, providers: Provider[]): any { - console.log('[DI] resolveDependency', { - token, - typeofToken: typeof token, - tokenName: typeof token === 'string' ? token : (token as any).__quarc_original_name__ || token?.name, - isFunction: typeof token === 'function', - }); - const tokenName = typeof token === 'string' ? token : (token as any).__quarc_original_name__ || token.name; const provider = this.findProvider(token, providers); if (provider) { - console.log('[DI] Found provider for token', tokenName); return this.resolveProviderValue(provider, providers); } if (this.sharedInstances[tokenName]) { - console.log('[DI] Found in sharedInstances', tokenName); return this.sharedInstances[tokenName]; } if (this.instanceCache[tokenName]) { - console.log('[DI] Found in instanceCache', tokenName); return this.instanceCache[tokenName]; } - console.log('[DI] Creating new instance for token', tokenName); return this.createInstanceWithProviders(token, providers); } private getConstructorParameterTypes(classType: Type): any[] { const className = classType?.name || 'Unknown'; - console.log({ - className, - classType, - diParams: (classType as any).__di_params__, - }); - if (!classType) { throw new Error(`[DI] Cannot resolve dependencies: classType is undefined`); } @@ -256,12 +218,10 @@ export class Injector { public register(classType: Type, instance: T | Type): void { const key = (classType as any).__quarc_original_name__ || classType.name; this.instanceCache[key] = instance; - console.log('injector register', classType, key, instance); } public registerShared(classType: Type, instance: T | Type): void { const key = (classType as any).__quarc_original_name__ || classType.name; - console.log('injector registerShared', classType, key, instance); this.sharedInstances[key] = instance; } diff --git a/core/module/template-renderer.ts b/core/module/template-renderer.ts index a53fda7..aad6cd2 100644 --- a/core/module/template-renderer.ts +++ b/core/module/template-renderer.ts @@ -235,7 +235,6 @@ export class TemplateFragment { const isForIn = !!forInMatch; if (!match) { - console.warn('Invalid ngFor expression:', ngForExpression); parent.insertBefore(endMarker, ngContainer); ngContainer.remove(); return; @@ -376,7 +375,6 @@ export class TemplateFragment { */ rerenderFragment(markerIndex: number): void { if (markerIndex < 0 || markerIndex >= this.ngContainerMarkers.length) { - console.warn('Invalid marker index:', markerIndex); return; } diff --git a/core/module/web-component-factory.ts b/core/module/web-component-factory.ts index 4543965..38ae63a 100644 --- a/core/module/web-component-factory.ts +++ b/core/module/web-component-factory.ts @@ -24,7 +24,6 @@ export class WebComponentFactory { const componentMeta = componentType._quarcComponent?.[0]; if (!componentMeta) { - console.warn(`Component ${componentType.name} has no _quarcComponent metadata`); return false; } @@ -69,7 +68,6 @@ export class WebComponentFactory { this.componentTypes.set(tagName, componentType); return true; } catch (error) { - console.warn(`Failed to register component ${tagName}:`, error); return false; } } @@ -108,7 +106,6 @@ export class WebComponentFactory { } } - console.log({localProviders}); return injector.createInstanceWithProviders(componentType, localProviders); } diff --git a/platform-browser/browser.ts b/platform-browser/browser.ts index f9d97b8..2fc42ef 100644 --- a/platform-browser/browser.ts +++ b/platform-browser/browser.ts @@ -35,11 +35,8 @@ async function tryLoadExternalScripts(urls: string | string[]): Promise { await loadExternalScript(url); return; } catch { - console.warn(`[External] Could not load from: ${url}`); } } - - console.info("[External] No external scripts loaded - app continues without enhancements"); } export async function bootstrapApplication( diff --git a/router/directives/router-link.directive.ts b/router/directives/router-link.directive.ts index 81044cc..fc19311 100644 --- a/router/directives/router-link.directive.ts +++ b/router/directives/router-link.directive.ts @@ -15,7 +15,6 @@ export class RouterLink implements IDirective { public _nativeElement: HTMLElement, private activatedRoute?: ActivatedRoute, ) { - console.log({ routerLink: this.routerLink() }); this._nativeElement.addEventListener('click', (event) => { this.onClick(event); }); @@ -44,7 +43,6 @@ export class RouterLink implements IDirective { this.router.navigate(commands, extras).then(success => { }).catch(error => { - console.error('RouterLink CLICK - Navigation failed:', error); }); } diff --git a/tests/unit/test-functionality.ts b/tests/unit/test-functionality.ts index 9c1896e..23b4441 100644 --- a/tests/unit/test-functionality.ts +++ b/tests/unit/test-functionality.ts @@ -261,6 +261,167 @@ test('ControlFlowTransformer: @if @else if oba z aliasem', () => { result.includes('*ngIf="!(primary()) && !(secondary())"'); }); +// Test 25: ControlFlowTransformer - niekompletny @if (bez zamknięcia) +test('ControlFlowTransformer: niekompletny @if', () => { + const transformer = new ControlFlowTransformer(); + const input = '@if (range.name) { content'; + const result = transformer.transform(input); + return result.includes('content'); +}); + +// Test 26: ControlFlowTransformer - niekompletny @if na końcu +test('ControlFlowTransformer: niekompletny @if na końcu', () => { + const transformer = new ControlFlowTransformer(); + const input = '} @if (prepared.sensor.loading) { '; + const result = transformer.transform(input); + return result.includes(''); +}); + +// Test 27: ControlFlowTransformer - niekompletny @if bez nawiasu klamrowego +test('ControlFlowTransformer: niekompletny @if bez nawiasu klamrowego', () => { + const transformer = new ControlFlowTransformer(); + const input = '@if (condition) '; + const result = transformer.transform(input); + return result.includes(''); +}); + +// Test 28: ControlFlowTransformer - zagnieżdżony @if wewnątrz @for +test('ControlFlowTransformer: zagnieżdżony @if wewnątrz @for', () => { + const transformer = new ControlFlowTransformer(); + const input = `@for (item of items) { + @if (item.active) { + {{ item.name }} + } + }`; + const result = transformer.transform(input); + return result.includes('') && + result.includes('') && + !result.includes('@if') && + !result.includes('@for'); +}); + +// Test 29: ControlFlowTransformer - zagnieżdżony @for wewnątrz @if +test('ControlFlowTransformer: zagnieżdżony @for wewnątrz @if', () => { + const transformer = new ControlFlowTransformer(); + const input = `@if (hasItems) { + @for (item of items; track item.id) { +
{{ item }}
+ } + }`; + const result = transformer.transform(input); + return result.includes('') && + result.includes('') && + !result.includes('@if') && + !result.includes('@for'); +}); + +// Test 30: ControlFlowTransformer - wielokrotnie zagnieżdżone @if wewnątrz @for +test('ControlFlowTransformer: wielokrotnie zagnieżdżone @if wewnątrz @for', () => { + const transformer = new ControlFlowTransformer(); + const input = `@for (range of ranges; track $index) { +
+ @if (range.name) { + {{ range.name }} + } + {{ range.min }} +
+ }`; + const result = transformer.transform(input); + return result.includes('') && + result.includes('') && + !result.includes('@if') && + !result.includes('@for'); +}); + +// Test 31: ControlFlowTransformer - kompleksowy przypadek z user template +test('ControlFlowTransformer: kompleksowy przypadek użytkownika', () => { + const transformer = new ControlFlowTransformer(); + const input = `@if (prepared.sensor.ranges) { + + @for (range of prepared.sensor.ranges; track $index) { +
+ @if (range.name) { + {{ range.name }} + } + {{ range.min }} + {{ range.max }} +
+ } +
+}`; + const result = transformer.transform(input); + return result.includes('') && + result.includes('') && + result.includes('') && + !result.includes('@if') && + !result.includes('@for'); +}); + +// Test 32: ControlFlowTransformer - głęboko zagnieżdżone @if/@for/@if +test('ControlFlowTransformer: głęboko zagnieżdżone @if/@for/@if', () => { + const transformer = new ControlFlowTransformer(); + const input = `@if (level1) { + @for (item of items; track item.id) { + @if (item.visible) { +
{{ item.name }}
+ } + } + }`; + const result = transformer.transform(input); + return result.includes('') && + result.includes('') && + result.includes('') && + !result.includes('@if') && + !result.includes('@for'); +}); + +// Test 33: ControlFlowTransformer - @for z funkcją w iterable +test('ControlFlowTransformer: @for z funkcją w iterable', () => { + const transformer = new ControlFlowTransformer(); + const input = '@for (item of getItems(); track item.id) {
{{ item }}
}'; + const result = transformer.transform(input); + return result.includes('') && + !result.includes('@for'); +}); + +// Test 34: ControlFlowTransformer - @for z zagnieżdżonymi nawiasami w funkcji +test('ControlFlowTransformer: @for z zagnieżdżonymi nawiasami w funkcji', () => { + const transformer = new ControlFlowTransformer(); + const input = '@for (prepared of preparedSensors(); track prepared.sensor.id) {
{{ prepared.sensor.name }}
}'; + const result = transformer.transform(input); + return result.includes('') && + result.includes('prepared.sensor.name') && + !result.includes('@for'); +}); + +// Test 35: ControlFlowTransformer - @for z metodą obiektu w iterable +test('ControlFlowTransformer: @for z metodą obiektu w iterable', () => { + const transformer = new ControlFlowTransformer(); + const input = '@for (item of data.getItems(); track $index) { {{ item }} }'; + const result = transformer.transform(input); + return result.includes('') && + !result.includes('@for'); +}); + +// Test 36: ControlFlowTransformer - @for z wieloma zagnieżdżonymi nawiasami +test('ControlFlowTransformer: @for z wieloma zagnieżdżonymi nawiasami', () => { + const transformer = new ControlFlowTransformer(); + const input = '@for (item of service.getData(filter(value())); track item.id) {
{{ item }}
}'; + const result = transformer.transform(input); + return result.includes('') && + !result.includes('@for'); +}); + +// Test 37: ControlFlowTransformer - @for z funkcją i złożonym trackBy +test('ControlFlowTransformer: @for z funkcją i złożonym trackBy', () => { + const transformer = new ControlFlowTransformer(); + const input = '@for (range of prepared.sensor.ranges; track $index) {
{{ range.name }}
}'; + const result = transformer.transform(input); + return result.includes('') && + result.includes('range.name') && + !result.includes('@for'); +}); + console.log('\n=== PODSUMOWANIE ==='); console.log(`✅ Testy zaliczone: ${passedTests}`); console.log(`❌ Testy niezaliczone: ${failedTests}`);