Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion .vscodeignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,11 @@
.gitattributes
.claude/**

# Dependencies
# Dependencies.
# node_modules is excluded wholesale, but RUNTIME dependencies must ship or the
# extension dies at activation with "Cannot find module". This was caught by
# extracting the VSIX and executing the packaged code — the file listing looked
# perfectly fine.
node_modules/**

# Coverage
Expand Down
42 changes: 42 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,48 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

---

## [0.6.0] - 2026-08-07

### ✨ Semantic checks — defects that compile and are still wrong

Until now this extension caught code TradingView would reject. These catch code it
**accepts** and then behaves unexpectedly — the category that costs money rather
than time.

| ID | Detects | Severity |
|---|---|---|
| **S1** | `request.security()` reading the current, still-forming bar — repainting | Warning |
| **S2** | `ta.*` called inside a ternary or block — its history develops gaps | Warning |
| **S5** | More than 64 plot calls — TradingView rejects the script | Error |
| **S6** | More than 40 `request.*()` calls | Error |
| **S7** | `plot` / `bgcolor` / `fill` outside global scope — a v6 scope error | Error |
| **S8** | A function defined inside a block — Pine has no nested functions | Error |
| **S9** | `strategy.entry` with no exit anywhere — unbounded risk | Warning |

Suppress a finding you have considered:

```pine
d = request.security(t, "D", close) // pine-ignore: S1
```

Syntactic diagnostics are never suppressible — a compile error is a fact, not a
judgement.

### 🔧 Architecture

The validation engine is now published as
[`pinescript-v6-validator`](https://www.npmjs.com/package/pinescript-v6-validator)
and consumed by this extension rather than duplicated. A check is written once;
the editor renders it and agent tooling returns it. Two copies would drift, and a
drifted rule means your agent and your editor disagree about the same file.

### 🐛 Found by the new checks

`examples/indicator.2.3.pine` called `bgcolor()` inside an `if` — a genuine v6
scope error, twelve lines above code in the same file doing it correctly.

---

## [0.5.1] - 2026-08-07

Packaging release. **No validator behaviour changes from 0.5.0** — the version is
Expand Down
17 changes: 13 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ Search for **"Pine Script v6 IDE Tools"** in VS Code Extensions or [install dire
### Or Install from VSIX
Download the latest `.vsix` from [Releases](https://github.com/jpantsjoha/pinescript-vscode-extension/releases) and install:
```bash
code --install-extension pinescript-v6-extension-0.5.1.vsix
code --install-extension pinescript-v6-extension-0.6.0.vsix
```

---
Expand All @@ -38,6 +38,15 @@ code --install-extension pinescript-v6-extension-0.5.1.vsix
- **22 function namespaces** with full parameter validation
- **32 strategy.* variables** (position_size, equity, netprofit, etc.)

### 🧠 **Semantic checks — catches code that compiles and is still wrong**
- **Repainting** — `request.security()` reading the current, forming bar
- **`ta.*` in a conditional** — silently corrupts the indicator's own history
- **Scope errors** — `plot`/`bgcolor` inside `if`, functions defined in a block
- **Platform limits** — 64 plots, 40 `request.*()` calls, counted before TradingView rejects you
- **Unbounded risk** — `strategy.entry` with no exit anywhere

Suppress one you have considered: `// pine-ignore: S1`

### 🔍 **Real-Time Validation**
- Catches undefined functions and variables
- Detects missing/extra parameters
Expand Down Expand Up @@ -141,7 +150,7 @@ See [CHANGELOG](./CHANGELOG.md) for complete version history.

## 🧪 Testing

- **112/112 tests passing** (100%)
- **169/169 tests passing** (100%)
- **Golden corpus**: 13 real scripts that compile on TradingView, asserted to
produce zero errors — any error against them is a false positive by definition
- **Paired regression tests**: every false-positive fix ships with a "must still
Expand Down Expand Up @@ -213,5 +222,5 @@ Special thanks to:
---

**Full Language Coverage**: 6,665 Pine Script v6 constructs
**Test Coverage**: 112 tests
**Current Version**: 0.5.1
**Test Coverage**: 169 tests
**Current Version**: 0.6.0
16 changes: 13 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 4 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"name": "Jaroslav Pantsjoha",
"url": "https://jpantsjoha.com"
},
"version": "0.5.1",
"version": "0.6.0",
"icon": "images/pinescript-extension.png",
"repository": {
"type": "git",
Expand Down Expand Up @@ -116,7 +116,7 @@
},
"scripts": {
"clean": "rm -rf dist && rm -f build/*.vsix",
"build": "tsc -p . && tsc -p packages/validator",
"build": "tsc -p . && tsc -p packages/validator && rm -rf dist/engine && mkdir -p dist/engine && cp -R node_modules/pinescript-v6-validator/dist/* dist/engine/",
"watch": "tsc -w -p .",
"test": "npm run build && node --test test/*.test.js",
"test:validation": "npm run build && node --test test/validation.test.js",
Expand All @@ -142,6 +142,7 @@
"typescript": "^5.4.5"
},
"dependencies": {
"glob": "^11.0.3"
"glob": "^11.0.3",
"pinescript-v6-validator": "^0.2.0"
}
}
23 changes: 23 additions & 0 deletions packages/validator/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,29 @@ PINE_FUNCTIONS_MERGED['line.new'].overloads;
// Both official call forms: (first_point, second_point, …) and (x1, y1, x2, y2, …)
```

## Semantic checks — defects that compile

Most Pine tooling catches code TradingView will reject. These catch code it
**accepts** and then behaves unexpectedly:

| ID | Detects |
|---|---|
| S1 | `request.security()` reading the current, still-forming bar — repainting |
| S2 | `ta.*` called inside a conditional — its history develops gaps |
| S5 / S6 | More than 64 plots or 40 `request.*()` calls — TradingView rejects the script |
| S7 | `plot` / `bgcolor` / `fill` outside global scope — a v6 scope error |
| S8 | A function defined inside a block — Pine has no nested functions |
| S9 | `strategy.entry` with no exit anywhere — unbounded risk |

Suppress a specific finding when you have considered it:

```pine
d = request.security(t, "D", close) // pine-ignore: S1
```

Syntactic diagnostics are never suppressible — a compile error is a fact, not a
judgement.

## What it catches

- **Overloaded constructors.** `line.new`, `label.new` and `box.new` each accept a
Expand Down
44 changes: 35 additions & 9 deletions packages/validator/package.json
Original file line number Diff line number Diff line change
@@ -1,20 +1,46 @@
{
"name": "pinescript-v6-validator",
"version": "0.1.0",
"description": "TradingView Pine Script v6 validator and reference dataset — 457 function signatures with explicit overload modelling. The engine behind the Pine Script v6 IDE Tools VS Code extension.",
"keywords": ["pinescript", "pine-script", "pine-v6", "tradingview", "validator", "linter", "trading", "indicators"],
"author": { "name": "Jaroslav Pantsjoha", "url": "https://jpantsjoha.com" },
"version": "0.2.0",
"description": "TradingView Pine Script v6 validator and reference dataset \u2014 457 function signatures with explicit overload modelling. The engine behind the Pine Script v6 IDE Tools VS Code extension.",
"keywords": [
"pinescript",
"pine-script",
"pine-v6",
"tradingview",
"validator",
"linter",
"trading",
"indicators"
],
"author": {
"name": "Jaroslav Pantsjoha",
"url": "https://jpantsjoha.com"
},
"license": "MIT",
"homepage": "https://github.com/jpantsjoha/pinescript-vscode-extension",
"repository": { "type": "git", "url": "git+https://github.com/jpantsjoha/pinescript-vscode-extension.git", "directory": "packages/validator" },
"bugs": { "url": "https://github.com/jpantsjoha/pinescript-vscode-extension/issues" },
"repository": {
"type": "git",
"url": "git+https://github.com/jpantsjoha/pinescript-vscode-extension.git",
"directory": "packages/validator"
},
"bugs": {
"url": "https://github.com/jpantsjoha/pinescript-vscode-extension/issues"
},
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"files": ["dist", "README.md", "LICENSE"],
"engines": { "node": ">=18" },
"files": [
"dist",
"README.md",
"LICENSE"
],
"engines": {
"node": ">=18"
},
"scripts": {
"build": "tsc -p .",
"prepublishOnly": "npm run build && node -e \"require('./dist/index.js')\""
},
"devDependencies": { "typescript": "^5.4.5" }
"devDependencies": {
"typescript": "^5.4.5"
}
}
6 changes: 6 additions & 0 deletions scripts/audit.js
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,12 @@ function auditDiagnosticCoverage() {
.map(f => f.replace(/\.ts$/, ''))
.filter(name => new RegExp(`from '\\./parser/${name}'`).test(extension));

// Since ADR-0001 the semantic checks live in the published engine rather than
// src/parser/, so they are named by their package import instead of a local file.
if (/from 'pinescript-v6-validator'/.test(extension)) {
sources.push('runSemanticChecks');
}

if (!sources.length) {
warn('diagnostics', 'could not identify any diagnostic source imported by extension.ts');
return;
Expand Down
20 changes: 20 additions & 0 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@ import { createSignatureHelpProvider } from './signatureHelp';
// without also being covered by validate-cli.js and the golden corpus. See STATUS.md.
import { AccurateValidator } from './parser/accurateValidator';
import { runDocumentChecks } from './parser/documentChecks';
// Semantic checks come from the published engine rather than a local copy.
// ADR-0001: a check is written once, in the engine. Two copies drift, and a
// drifted rule means the editor and the agent disagree about the same file.
// Resolved at runtime from dist/engine, which the build copies from the pinned
// npm package. Same single source; avoids shipping node_modules in the VSIX.
const engine = require('../engine/index.js');

export function activate(context: vscode.ExtensionContext) {
// Optional: ensure files.associations maps *.pine -> pine
Expand Down Expand Up @@ -190,6 +196,20 @@ export function activate(context: vscode.ExtensionContext) {
console.error('[Pine Validator] Validation error:', e);
}

// Semantic checks — defects that compile and are still wrong (repainting,
// ta.* history gaps, scope violations). Suppressions are read from the RAW
// text, before any pass blanks the comments the directives live in.
try {
const suppressions = engine.extractSuppressions(text);
for (const check of engine.applySuppressions(engine.runSemanticChecks(text), suppressions)) {
const pos = new vscode.Position(check.line - 1, check.column);
const endPos = pos.translate(0, check.length);
diags.push(new vscode.Diagnostic(new vscode.Range(pos, endPos), check.message, check.severity));
}
} catch (e) {
console.error('[Pine Validator] Semantic check error:', e);
}

// Whole-document heuristic checks. Extracted to src/parser/documentChecks.ts so
// they are testable and runnable from validate-cli.js — inline here they were
// invisible to every test, and shipped 28 false alertcondition errors across the
Expand Down
16 changes: 14 additions & 2 deletions test/golden-corpus.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,14 @@ const { runDocumentChecks } = require('../dist/src/parser/documentChecks.js');
* first is how 28 false `alertcondition` errors survived a "0 errors" corpus run.
*/
function allDiagnostics(source) {
return [...new AccurateValidator().validate(source), ...runDocumentChecks(source)];
// All THREE sources. validatePineScript from the engine aggregates the semantic
// checks and applies suppressions; the two extension-local modules are added
// here so the gate reflects exactly what a user sees in the editor.
return [
...new AccurateValidator().validate(source),
...runDocumentChecks(source),
...validatePineScript(source).filter(d => d.checkId)
];
}

const REPO_ROOT = path.join(__dirname, '..');
Expand Down Expand Up @@ -145,6 +152,8 @@ test('Golden corpus: validation stays within the 100ms performance budget', () =
const {
SEMANTIC_CHECKS,
extractSuppressions,
applySuppressions,
runSemanticChecks,
validatePineScript
} = require('../packages/validator/dist/index.js');

Expand All @@ -167,7 +176,10 @@ test('Semantic gate: no check fires on any committed fixture', () => {
// validator directly. Semantic checks live in the package; calling the
// extension's modules bypasses them entirely and the gate silently passes
// whatever it is supposed to be catching.
const semantic = validatePineScript(source).filter(d => d.checkId);
// runSemanticChecks directly, with suppressions applied exactly as the editor
// does — naming the source explicitly rather than relying on the aggregate, so
// scripts/audit.js can see this source is gated.
const semantic = applySuppressions(runSemanticChecks(source), extractSuppressions(source));

for (const finding of semantic) {
offenders.push(`${relativePath}:${finding.line} [${finding.checkId}] ${finding.message}`);
Expand Down
9 changes: 9 additions & 0 deletions validate-cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,14 @@ function loadValidators() {
// on files the editor covered in squiggles, so both run here by default.
try { out.documentChecks = { validate: require('./dist/src/parser/documentChecks').runDocumentChecks }; }
catch (e) { out.documentChecksErr = e.message; }
// Semantic checks — the third diagnostic source, from the published engine.
// ADR-0001: written once, consumed here and by the extension.
try {
const eng = require('pinescript-v6-validator');
out.semanticChecks = {
validate: (code) => eng.applySuppressions(eng.runSemanticChecks(code), eng.extractSuppressions(code))
};
} catch (e) { out.semanticChecksErr = e.message; }
return out;
}

Expand Down Expand Up @@ -85,6 +93,7 @@ function main() {
console.log(`\n${paint('▸ ' + file, c.bold)} ${paint('(' + code.split('\n').length + ' lines)', c.dim)}`);
if (mode === 'accurate' || mode === 'both') totalErrors += printErrors('AccurateValidator', run(v.accurate, code));
if ((mode === 'accurate' || mode === 'both') && v.documentChecks) totalErrors += printErrors('DocumentChecks', run(v.documentChecks, code));
if ((mode === 'accurate' || mode === 'both') && v.semanticChecks) totalErrors += printErrors('SemanticChecks', run(v.semanticChecks, code));
if (mode === 'comprehensive' || mode === 'both') totalErrors += printErrors('ComprehensiveValidator', run(v.comprehensive, code));
}
console.log('');
Expand Down
Loading