Skip to content

Commit 987c877

Browse files
committed
Detect Pine shadowing and ATR-based consistency
1 parent 4f6925f commit 987c877

2 files changed

Lines changed: 39 additions & 2 deletions

File tree

src/pinescript_validator/ast_validator.py

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@
2222
}
2323
)
2424

25+
CONSISTENCY_PROPAGATION_BUILTINS = CONSISTENCY_SENSITIVE_BUILTINS | frozenset({"ta.atr"})
26+
2527
PLOT_COUNT_LIMIT = 64
2628

2729

@@ -154,6 +156,17 @@ def define_symbol(
154156
) -> None:
155157
if name == "_":
156158
return
159+
if kind in {"variable", "parameter", "iterator"} and name in self.builtins.standalone_variables:
160+
self.errors.append(
161+
Diagnostic(
162+
line=line,
163+
column=column,
164+
length=len(name),
165+
message=f'Shadowing built-in variable "{name}"',
166+
severity=Severity.WARNING,
167+
source="ast",
168+
)
169+
)
157170
existing = scope.symbols.get(name)
158171
if existing is None:
159172
scope.symbols[name] = Symbol(name=name, line=line, column=column, kind=kind, type_name=type_name)
@@ -586,9 +599,15 @@ def expression_uses_consistency_sensitive_state(self, expression: AST.Expression
586599
if isinstance(expression, AST.CallExpression):
587600
function_name = self.extract_function_name(expression.callee)
588601
resolved_function_name = self.resolve_generic_function_name(function_name) if function_name is not None else None
589-
if function_name in self.consistency_sensitive_functions:
602+
if function_name in self.consistency_sensitive_functions or function_name in CONSISTENCY_PROPAGATION_BUILTINS:
590603
return True
591-
if resolved_function_name is not None and resolved_function_name in self.consistency_sensitive_functions:
604+
if (
605+
resolved_function_name is not None
606+
and (
607+
resolved_function_name in self.consistency_sensitive_functions
608+
or resolved_function_name in CONSISTENCY_PROPAGATION_BUILTINS
609+
)
610+
):
592611
return True
593612
if self.expression_uses_consistency_sensitive_state(expression.callee):
594613
return True

tests/test_validator.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,10 @@ def test_unused_variable(self) -> None:
5050
diagnostics = self.validator.validate_text('indicator("Test")\nvalue = close')
5151
self.assertTrue(any("never used" in diagnostic.message for diagnostic in diagnostics))
5252

53+
def test_shadowing_builtin_variable_is_reported(self) -> None:
54+
diagnostics = self.validator.validate_text("foo(int bar_index) =>\n bar_index")
55+
self.assertTrue(any('Shadowing built-in variable "bar_index"' in diagnostic.message for diagnostic in diagnostics))
56+
5357
def test_untyped_variable_declaration_cannot_use_na(self) -> None:
5458
diagnostics = self.validator.validate_text("x = na")
5559
self.assertTrue(
@@ -348,6 +352,20 @@ def test_consistency_warning_for_user_function_wrapping_ema_and_stdev(self) -> N
348352
self.assertTrue(any('The function "f_zscore" should be called on each calculation for consistency.' in d.message for d in diagnostics))
349353
self.assertTrue(any("extract the call from the ternary operator or from the scope" in d.message for d in diagnostics))
350354

355+
def test_consistency_warning_for_user_function_wrapping_atr(self) -> None:
356+
code = """
357+
f_push_struct_label_draw(y) =>
358+
atrv = ta.atr(14)
359+
y + atrv
360+
361+
if cond
362+
value = f_push_struct_label_draw(close)
363+
"""
364+
diagnostics = self.validator.validate_text(code)
365+
self.assertTrue(any('The function "f_push_struct_label_draw" should be called on each calculation for consistency.' in d.message for d in diagnostics))
366+
self.assertTrue(any("extract the call from this scope" in d.message for d in diagnostics))
367+
self.assertFalse(any('The function "ta.atr" should be called on each calculation for consistency.' in d.message for d in diagnostics))
368+
351369
def test_consistency_warning_for_conditional_cross_functions_and_sensitive_wrapper(self) -> None:
352370
code = """
353371
f_detect_breakout_direction(level_up, level_down) =>

0 commit comments

Comments
 (0)