diff --git a/packages/pine2ts/src/transpiler/semantic/SemanticAnalyzer.ts b/packages/pine2ts/src/transpiler/semantic/SemanticAnalyzer.ts index 3c57db8..b100d62 100644 --- a/packages/pine2ts/src/transpiler/semantic/SemanticAnalyzer.ts +++ b/packages/pine2ts/src/transpiler/semantic/SemanticAnalyzer.ts @@ -666,10 +666,16 @@ export class SemanticAnalyzer { } } - // Visit arguments + // Visit arguments - but handle named parameters specially if (node.children) { for (const arg of node.children) { - this.visitExpression(arg); + // Named parameter: Assignment node where left side is the param name + if (arg.type === 'Assignment' && arg.children && arg.children.length === 2) { + // Only visit the right side (the value), not the left side (param name) + this.visitExpression(arg.children[1]!); + } else { + this.visitExpression(arg); + } } } break; diff --git a/packages/pine2ts/tests/semantic.test.ts b/packages/pine2ts/tests/semantic.test.ts index 3004c71..57d09f2 100644 --- a/packages/pine2ts/tests/semantic.test.ts +++ b/packages/pine2ts/tests/semantic.test.ts @@ -348,4 +348,94 @@ describe('SemanticAnalyzer', () => { expect(result.errors.some(e => e.kind === 'BREAK_OUTSIDE_LOOP')).toBe(true); }); }); + + describe('named parameters', () => { + it('should not error on named parameters in indicator()', () => { + const parser = new PineParser(); + const { ast } = parser.parse(` + indicator("Test", shorttitle="T", overlay=true) + `); + + const analyzer = new SemanticAnalyzer(); + const result = analyzer.analyze(ast); + + expect(result.valid).toBe(true); + expect(result.errors).toHaveLength(0); + }); + + it('should not error on named parameters in input functions', () => { + const parser = new PineParser(); + const { ast } = parser.parse(` + indicator("Test") + len = input.int(14, title="Length", minval=1, maxval=100) + `); + + const analyzer = new SemanticAnalyzer(); + const result = analyzer.analyze(ast); + + expect(result.valid).toBe(true); + expect(result.errors).toHaveLength(0); + }); + + it('should not error on named parameters in plot()', () => { + const parser = new PineParser(); + const { ast } = parser.parse(` + indicator("Test") + plot(close, title="Close", color=color.red, linewidth=2) + `); + + const analyzer = new SemanticAnalyzer(); + const result = analyzer.analyze(ast); + + expect(result.valid).toBe(true); + expect(result.errors).toHaveLength(0); + }); + + it('should still error on actual undefined variables', () => { + const parser = new PineParser(); + const { ast } = parser.parse(` + indicator("Test") + x = undefinedVar + 1 + `); + + const analyzer = new SemanticAnalyzer(); + const result = analyzer.analyze(ast); + + expect(result.valid).toBe(false); + expect(result.errors.length).toBeGreaterThan(0); + expect(result.errors[0]!.kind).toBe('UNDEFINED_VARIABLE'); + expect(result.errors[0]!.message).toContain('undefinedVar'); + }); + + it('should handle complex mixed positional and named parameters', () => { + const parser = new PineParser(); + const { ast } = parser.parse(` + indicator("Average Day Range", shorttitle="ADR", timeframe="", timeframe_gaps=true) + lengthInput = input.int(14, title="Length") + plot(close, title="ADR", color=color.blue) + `); + + const analyzer = new SemanticAnalyzer(); + const result = analyzer.analyze(ast); + + expect(result.valid).toBe(true); + expect(result.errors).toHaveLength(0); + }); + + it('should validate expressions in named parameter values', () => { + const parser = new PineParser(); + const { ast } = parser.parse(` + indicator("Test") + plot(close, title="Close", color=undefinedColor) + `); + + const analyzer = new SemanticAnalyzer(); + const result = analyzer.analyze(ast); + + expect(result.valid).toBe(false); + expect(result.errors.length).toBeGreaterThan(0); + expect(result.errors[0]!.kind).toBe('UNDEFINED_VARIABLE'); + expect(result.errors[0]!.message).toContain('undefinedColor'); + }); + }); });