Skip to content

Commit ac01cc0

Browse files
committed
Add severity ordering and filtering controls
1 parent ff24114 commit ac01cc0

4 files changed

Lines changed: 104 additions & 6 deletions

File tree

README.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ This project focuses on exactly that.
2929
- Validate multiple files or directories in one command
3030
- Emit SARIF output for CI pipelines, review bots, and code scanning tools
3131
- Audit large script corpora and generate JSON and Markdown reports
32+
- Control which severities are emitted with professional CLI toggles
3233

3334
## Best Fit Use Cases
3435

@@ -78,6 +79,18 @@ Emit SARIF for CI or review tooling:
7879
pine-validator path\to\pine-scripts --sarif
7980
```
8081

82+
Show only errors and warnings:
83+
84+
```powershell
85+
pine-validator path\to\script.pine --no-hints --no-information
86+
```
87+
88+
Show only hints during cleanup:
89+
90+
```powershell
91+
pine-validator path\to\script.pine --no-errors --no-warnings --hints
92+
```
93+
8194
Validate from stdin:
8295

8396
```powershell
@@ -154,6 +167,12 @@ Output shape:
154167
- `--agent-json` for richer debugging output intended for agents and automation
155168
- directory and multi-file batch validation
156169
- `--sarif` for CI, code scanning, and external review integrations
170+
- `--errors/--no-errors`
171+
- `--warnings/--no-warnings`
172+
- `--information/--no-information`
173+
- `--hints/--no-hints`
174+
175+
Diagnostics are ordered by severity first, then by source location. By default the CLI prints errors first, then warnings, then information messages, and finally hints.
157176

158177
Exit codes:
159178

src/pinescript_validator/cli.py

Lines changed: 59 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import sys
77
from pathlib import Path
88

9-
from .diagnostics import Severity
9+
from .diagnostics import Diagnostic, Severity
1010
from .sarif import build_sarif_run
1111
from .validator import PineScriptValidator
1212

@@ -25,6 +25,30 @@ def build_parser() -> argparse.ArgumentParser:
2525
action="store_true",
2626
help="Print SARIF output for CI, code scanning, and machine-readable review tooling.",
2727
)
28+
parser.add_argument(
29+
"--errors",
30+
action=argparse.BooleanOptionalAction,
31+
default=True,
32+
help="Include or exclude error diagnostics.",
33+
)
34+
parser.add_argument(
35+
"--warnings",
36+
action=argparse.BooleanOptionalAction,
37+
default=True,
38+
help="Include or exclude warning diagnostics.",
39+
)
40+
parser.add_argument(
41+
"--information",
42+
action=argparse.BooleanOptionalAction,
43+
default=True,
44+
help="Include or exclude information diagnostics.",
45+
)
46+
parser.add_argument(
47+
"--hints",
48+
action=argparse.BooleanOptionalAction,
49+
default=True,
50+
help="Include or exclude hint diagnostics.",
51+
)
2852
return parser
2953

3054

@@ -64,6 +88,33 @@ def _validate_file_targets(validator: PineScriptValidator, paths: list[Path]) ->
6488
return results
6589

6690

91+
def _selected_severities(args: argparse.Namespace) -> set[Severity]:
92+
selected: set[Severity] = set()
93+
if args.errors:
94+
selected.add(Severity.ERROR)
95+
if args.warnings:
96+
selected.add(Severity.WARNING)
97+
if args.information:
98+
selected.add(Severity.INFORMATION)
99+
if args.hints:
100+
selected.add(Severity.HINT)
101+
return selected
102+
103+
104+
def _filter_results(results: list[dict[str, object]], selected: set[Severity]) -> list[dict[str, object]]:
105+
filtered: list[dict[str, object]] = []
106+
for item in results:
107+
diagnostics = [diagnostic for diagnostic in item["diagnostics"] if diagnostic.severity in selected]
108+
filtered.append(
109+
{
110+
"path": item["path"],
111+
"text": item["text"],
112+
"diagnostics": diagnostics,
113+
}
114+
)
115+
return filtered
116+
117+
67118
def _summary(results: list[dict[str, object]]) -> dict[str, int]:
68119
diagnostics = [diagnostic for item in results for diagnostic in item["diagnostics"]]
69120
return {
@@ -81,6 +132,9 @@ def main(argv: list[str] | None = None) -> int:
81132
validator = PineScriptValidator()
82133
if sum(1 for flag in (args.json, args.agent_json, args.sarif) if flag) > 1:
83134
raise SystemExit("Choose only one of --json, --agent-json, or --sarif.")
135+
selected_severities = _selected_severities(args)
136+
if not selected_severities:
137+
raise SystemExit("At least one diagnostic severity must remain enabled.")
84138

85139
if "-" in args.paths:
86140
if len(args.paths) != 1:
@@ -94,9 +148,10 @@ def main(argv: list[str] | None = None) -> int:
94148
if not file_paths:
95149
raise SystemExit("No .pine files matched the provided targets.")
96150
results = _validate_file_targets(validator, file_paths)
97-
file_path = results[0]["path"] if len(results) == 1 else None
98-
text = results[0]["text"] if len(results) == 1 else ""
99-
diagnostics = results[0]["diagnostics"] if len(results) == 1 else []
151+
results = _filter_results(results, selected_severities)
152+
file_path = results[0]["path"] if len(results) == 1 else None
153+
text = results[0]["text"] if len(results) == 1 else ""
154+
diagnostics = results[0]["diagnostics"] if len(results) == 1 else []
100155

101156
if args.sarif:
102157
print(json.dumps(build_sarif_run(results), ensure_ascii=False, indent=2))

src/pinescript_validator/validator.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,5 +78,5 @@ def _dedupe_and_sort(diagnostics: list[Diagnostic]) -> list[Diagnostic]:
7878
continue
7979
seen.add(key)
8080
output.append(diagnostic)
81-
output.sort(key=lambda item: (item.line, item.column, item.severity, item.message))
81+
output.sort(key=lambda item: (item.severity, item.line, item.column, item.message))
8282
return output

tests/test_validator.py

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
from __future__ import annotations
22

33
import unittest
4+
from argparse import Namespace
45
from pathlib import Path
56
from tempfile import TemporaryDirectory
67

8+
from pinescript_validator.cli import _expand_paths, _filter_results, _selected_severities
79
from pinescript_validator.diagnostics import Severity
810
from pinescript_validator.sarif import build_sarif_run
911
from pinescript_validator.validator import PineScriptValidator
10-
from pinescript_validator.cli import _expand_paths
1112

1213

1314
class ValidatorTests(unittest.TestCase):
@@ -479,6 +480,29 @@ def test_build_sarif_run_emits_results(self) -> None:
479480
self.assertEqual(len(sarif["runs"]), 1)
480481
self.assertGreaterEqual(len(sarif["runs"][0]["results"]), 1)
481482

483+
def test_diagnostics_are_sorted_by_severity_before_location(self) -> None:
484+
diagnostics = self.validator.validate_text('indicator("X")\nvalue = close\nplot(close, invalid_param=true)')
485+
486+
severities = [diagnostic.severity for diagnostic in diagnostics]
487+
self.assertEqual(severities, sorted(severities))
488+
489+
def test_selected_severities_respects_cli_toggles(self) -> None:
490+
selected = _selected_severities(
491+
Namespace(errors=True, warnings=False, information=False, hints=True)
492+
)
493+
494+
self.assertEqual(selected, {Severity.ERROR, Severity.HINT})
495+
496+
def test_filter_results_removes_disabled_severities(self) -> None:
497+
diagnostics = self.validator.validate_text('indicator("X")\nvalue = close\nplot(close, invalid_param=true)')
498+
filtered = _filter_results(
499+
[{"path": Path("sample.pine"), "text": "", "diagnostics": diagnostics}],
500+
{Severity.ERROR},
501+
)
502+
503+
self.assertTrue(filtered[0]["diagnostics"])
504+
self.assertTrue(all(item.severity == Severity.ERROR for item in filtered[0]["diagnostics"]))
505+
482506

483507
if __name__ == "__main__":
484508
unittest.main()

0 commit comments

Comments
 (0)