Skip to content

False positive "Undefined namespace or variable" on function parameters #16

Description

@pash7ka

Summary

The validator's collectDeclaredVariables extracts only the function NAME from funcName(params) => declarations, ignoring the parameter names. As a result, any reference to a parameter inside the function body via param.field (typed parameter or UDT instance) triggers a false Undefined namespace or variable 'param'.

Environment

  • Extension: jpantsjoha.pinescript-v6-extension v0.4.4
  • VS Code: any recent
  • OS: platform-independent

Minimal repro

//@version=6
indicator("param repro", overlay=true)

doStuff(int x, float y) =>
    plot(x + y)

doStuff(1, 2.0)

A more realistic case (where the bug is most painful) with user-defined types (UDT):

//@version=6
indicator("param repro", overlay=true)

type State
    float a

update(State st, float v) =>
    st.a := v
    st

var State s = State.new(na)
update(s, close)

Note, UDT itself triggers the error, I've filled separate issue #15.

Expected

No diagnostics. Both scripts compile and run correctly in the TradingView Pine Editor.

Actual

In the second sample, every reference to st.a inside update triggers:

Undefined namespace or variable 'st'

On a real codebase with many UDT-receiving functions (e.g. setters/updaters/computes), this produces dozens of red squiggles inside otherwise-correct code.

Root cause

In accurateValidator.ts, collectDeclaredVariables:

const paramDeclarations = line.matchAll(/([a-zA-Z_][a-zA-Z0-9_]*)\s*\([^)]*\)\s*=>/g);
for (const match of paramDeclarations) {
    const funcName = match[1];
    if (funcName) {
        this.declaredVariables.add(funcName);
    }
}

The regex skips the parenthesized parameter list ([^)]*) instead of capturing it, so parameter names are never registered. When the function body later references paramName.field, the namespace check fails to find paramName in declaredVariables.

Suggested minimal fix

Capture the parameter list as a second group, then split by , and extract the last identifier before any = from each part — that's the parameter name (handling name, type name, name = default, and type name = default cases uniformly).

const paramDeclarations = line.matchAll(/([a-zA-Z_][a-zA-Z0-9_]*)\s*\(([^)]*)\)\s*=>/g);
for (const match of paramDeclarations) {
    const funcName = match[1];
    const paramsStr = match[2];
    if (funcName) {
        this.declaredVariables.add(funcName);
    }
    if (paramsStr && paramsStr.trim()) {
        for (const part of paramsStr.split(',')) {
            const beforeEq = part.split('=')[0];
            const ids = beforeEq.match(/[A-Za-z_][A-Za-z0-9_]*/g);
            if (ids && ids.length > 0) {
                const paramName = ids[ids.length - 1];
                if (!this.isReservedKeyword(paramName)) {
                    this.declaredVariables.add(paramName);
                }
            }
        }
    }
}

This handles single-line declarations. Multi-line function headers (parameters wrapping across lines) would need additional work, but single-line covers the vast majority of real code.

I've been running this locally against v0.4.4 — confirmed it eliminates the 28 false-positive errors on a real strategy file without over-suppressing (genuine NoSuchThing.foo() references still flag correctly).

Happy to send a PR with the fix + a test fixture if useful.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions