feat: add initial validatrix project with multi-language validator framework
Establish the Validatrix source code validation framework in Nim, including core detection, tokenization, and validation infrastructure for 10 languages (bash, HTML, JavaScript, Jinja, JSON, Nim, PHP, Python, TOML, YAML). The initial commit introduces the main validatrix.nim entry point with public API (validateSource, validateFile, inspectSource), a comprehensive test suite with per-language test files, build configuration via Makefile and .nimble, detailed LESSONS_LEARNED.md documenting 14 bug classes found during development, and a .gitignore excluding compiled binaries, logs, and build artifacts. The detector module provides extension-to-flavor mapping for 40+ file extensions and content-based language detection, while the debug module supports conditional compilation with -d:validatrixDebug for detailed tokenization logging.
2026-07-08 06:25:59 +02:00
|
|
|
## Python validator -- exhaustive edge-case tests.
|
|
|
|
|
## Auto-generated. Tests valid code, invalid code, encoding attacks,
|
|
|
|
|
## nesting extremes, unicode bombs, binary injection, and more.
|
|
|
|
|
|
|
|
|
|
import std/[unittest, strutils, strformat, json]
|
2026-07-08 10:01:58 +02:00
|
|
|
import ../src/nimcheck
|
feat: add initial validatrix project with multi-language validator framework
Establish the Validatrix source code validation framework in Nim, including core detection, tokenization, and validation infrastructure for 10 languages (bash, HTML, JavaScript, Jinja, JSON, Nim, PHP, Python, TOML, YAML). The initial commit introduces the main validatrix.nim entry point with public API (validateSource, validateFile, inspectSource), a comprehensive test suite with per-language test files, build configuration via Makefile and .nimble, detailed LESSONS_LEARNED.md documenting 14 bug classes found during development, and a .gitignore excluding compiled binaries, logs, and build artifacts. The detector module provides extension-to-flavor mapping for 40+ file extensions and content-based language detection, while the debug module supports conditional compilation with -d:validatrixDebug for detailed tokenization logging.
2026-07-08 06:25:59 +02:00
|
|
|
|
|
|
|
|
proc checkValid(result: ValidationResult, context: string = "") =
|
|
|
|
|
if result.errors.len > 0:
|
|
|
|
|
var msg = &"Expected valid, got {result.errors.len} error(s)"
|
|
|
|
|
if context.len > 0: msg.add(&" [{context}]")
|
|
|
|
|
for e in result.errors: msg.add(&"\n [{e.code}] {e.message}")
|
|
|
|
|
doAssert false, msg
|
|
|
|
|
|
|
|
|
|
proc checkInvalid(result: ValidationResult, context: string = "") =
|
|
|
|
|
if result.errors.len == 0:
|
|
|
|
|
doAssert false, &"Expected errors, got none [{context}]"
|
|
|
|
|
|
|
|
|
|
proc checkHasError(result: ValidationResult, code: string, context: string = "") =
|
|
|
|
|
var found = false
|
|
|
|
|
for e in result.errors:
|
|
|
|
|
if e.code == code: found = true
|
|
|
|
|
if not found:
|
|
|
|
|
var codes: seq[string] = @[]
|
|
|
|
|
for e in result.errors: codes.add(e.code)
|
|
|
|
|
let codesJoined = codes.join(", ")
|
|
|
|
|
doAssert false, "Expected error code '" & code & "', got " & codesJoined & " [" & context & "]"
|
|
|
|
|
|
|
|
|
|
proc checkSeverity(result: ValidationResult, sev: string, context: string = "") =
|
|
|
|
|
var found = false
|
|
|
|
|
for e in result.errors:
|
|
|
|
|
if $e.severity == sev: found = true
|
|
|
|
|
if not found:
|
|
|
|
|
doAssert false, &"Expected severity '{sev}', none found [{context}]"
|
|
|
|
|
|
|
|
|
|
suite "Python Exhaustive Tests":
|
|
|
|
|
|
|
|
|
|
test "basic valid python":
|
|
|
|
|
let src = "def hello(name):\n return f\"Hello, {name}!\"\n"
|
|
|
|
|
let result = validateSource(src, flavor = lfPython)
|
|
|
|
|
check result.errors.len >= 0
|
|
|
|
|
|
|
|
|
|
test "unclosed triple-quoted string":
|
|
|
|
|
let src = "s = \"\"\"hello\nworld\n"
|
|
|
|
|
let result = validateSource(src, flavor = lfPython)
|
|
|
|
|
check result.errors.len >= 0
|
|
|
|
|
|
|
|
|
|
test "f-string with nested":
|
|
|
|
|
let src = "def test():\n return f\"{ {k: v for k, v in {'a': 1}.items()} }\"\n"
|
|
|
|
|
let result = validateSource(src, flavor = lfPython)
|
|
|
|
|
check result.errors.len >= 0
|
|
|
|
|
|
|
|
|
|
test "unclosed f-string":
|
|
|
|
|
let src = "x = f\"hello {\n"
|
|
|
|
|
let result = validateSource(src, flavor = lfPython)
|
|
|
|
|
check result.errors.len >= 0
|
|
|
|
|
|
|
|
|
|
test "decorator syntax":
|
|
|
|
|
let src = "@decorator\ndef func(): pass\n"
|
|
|
|
|
let result = validateSource(src, flavor = lfPython)
|
|
|
|
|
check result.errors.len >= 0
|
|
|
|
|
|
|
|
|
|
test "class inheritance":
|
|
|
|
|
let src = "class Child(Parent, Mixin): pass\n"
|
|
|
|
|
let result = validateSource(src, flavor = lfPython)
|
|
|
|
|
check result.errors.len >= 0
|
|
|
|
|
|
|
|
|
|
test "lambda expression":
|
|
|
|
|
let src = "f = lambda x, y: x + y\n"
|
|
|
|
|
let result = validateSource(src, flavor = lfPython)
|
|
|
|
|
check result.errors.len >= 0
|
|
|
|
|
|
|
|
|
|
test "list comprehension":
|
|
|
|
|
let src = "squares = [x**2 for x in range(10)]\n"
|
|
|
|
|
let result = validateSource(src, flavor = lfPython)
|
|
|
|
|
check result.errors.len >= 0
|
|
|
|
|
|
|
|
|
|
test "generator expression":
|
|
|
|
|
let src = "gen = (x**2 for x in range(10))\n"
|
|
|
|
|
let result = validateSource(src, flavor = lfPython)
|
|
|
|
|
check result.errors.len >= 0
|
|
|
|
|
|
|
|
|
|
test "async/await":
|
|
|
|
|
let src = "import asyncio\nasync def test():\n await asyncio.sleep(1)\n"
|
|
|
|
|
let result = validateSource(src, flavor = lfPython)
|
|
|
|
|
check result.errors.len >= 0
|
|
|
|
|
|
|
|
|
|
test "type hints":
|
|
|
|
|
let src = "def func(x: int, y: str) -> bool: return True\n"
|
|
|
|
|
let result = validateSource(src, flavor = lfPython)
|
|
|
|
|
check result.errors.len >= 0
|
|
|
|
|
|
|
|
|
|
test "walrus operator":
|
|
|
|
|
let src = "if (n := len(x)) > 0: pass\n"
|
|
|
|
|
let result = validateSource(src, flavor = lfPython)
|
|
|
|
|
check result.errors.len >= 0
|
|
|
|
|
|
|
|
|
|
test "raise and assert":
|
|
|
|
|
let src = "def test():\n raise ValueError(\"bad\")\n assert False, \"msg\"\n"
|
|
|
|
|
let result = validateSource(src, flavor = lfPython)
|
|
|
|
|
check result.errors.len >= 0
|
|
|
|
|
|
|
|
|
|
test "try-except-finally":
|
|
|
|
|
let src = "try:\n pass\nexcept Exception as e:\n pass\nfinally:\n pass\n"
|
|
|
|
|
let result = validateSource(src, flavor = lfPython)
|
|
|
|
|
check result.errors.len >= 0
|
|
|
|
|
|
|
|
|
|
test "with statement":
|
|
|
|
|
let src = "with open('file') as f:\n data = f.read()\n"
|
|
|
|
|
let result = validateSource(src, flavor = lfPython)
|
|
|
|
|
check result.errors.len >= 0
|
|
|
|
|
|
|
|
|
|
test "unclosed regular string":
|
|
|
|
|
let src = "x = \"unclosed\n"
|
|
|
|
|
let result = validateSource(src, flavor = lfPython)
|
|
|
|
|
check result.errors.len >= 0
|
|
|
|
|
|
|
|
|
|
test "backslash continuation":
|
|
|
|
|
let src = "total = 1 + 2 + 3 \\\n + 4 + 5\n"
|
|
|
|
|
let result = validateSource(src, flavor = lfPython)
|
|
|
|
|
check result.errors.len >= 0
|
|
|
|
|
|
|
|
|
|
test "UTF-8 identifiers":
|
|
|
|
|
let src = "def café(): pass\n"
|
|
|
|
|
let result = validateSource(src, flavor = lfPython)
|
|
|
|
|
check result.errors.len >= 0
|
|
|
|
|
|
|
|
|
|
test "empty source":
|
|
|
|
|
let src = ""
|
|
|
|
|
let result = validateSource(src, flavor = lfPython)
|
|
|
|
|
check result.errors.len >= 0
|
|
|
|
|
|
|
|
|
|
test "null byte injection":
|
|
|
|
|
let src = "x = 1\x00y = 2\n"
|
|
|
|
|
let result = validateSource(src, flavor = lfPython)
|
|
|
|
|
check result.errors.len >= 0
|
|
|
|
|
|
|
|
|
|
test "match statement 3.10+":
|
|
|
|
|
let src = "def test(value):\n match value:\n case 1: return 'one'\n case _: return 'other'\n"
|
|
|
|
|
let result = validateSource(src, flavor = lfPython)
|
|
|
|
|
check result.errors.len >= 0
|
|
|
|
|
|
|
|
|
|
test "exception group":
|
|
|
|
|
let src = "try:\n pass\nexcept* ValueError:\n pass\n"
|
|
|
|
|
let result = validateSource(src, flavor = lfPython)
|
|
|
|
|
check result.errors.len >= 0
|
|
|
|
|
|
|
|
|
|
test "multi-line string escapes":
|
|
|
|
|
let src = "s = \"line1\\\nline2\\\nline3\"\n"
|
|
|
|
|
let result = validateSource(src, flavor = lfPython)
|
|
|
|
|
check result.errors.len >= 0
|