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
|
|
|
## PHP 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 "PHP Exhaustive Tests":
|
|
|
|
|
|
|
|
|
|
test "basic valid PHP":
|
|
|
|
|
let src = "<?php\necho \"Hello, world!\";\n"
|
|
|
|
|
let result = validateSource(src, flavor = lfPHP)
|
|
|
|
|
check result.errors.len >= 0
|
|
|
|
|
|
|
|
|
|
test "unclosed double-quoted string":
|
|
|
|
|
let src = "<?php\n$x = \"hello\n"
|
|
|
|
|
let result = validateSource(src, flavor = lfPHP)
|
|
|
|
|
check result.errors.len >= 0
|
|
|
|
|
|
|
|
|
|
test "unclosed single-quoted string":
|
|
|
|
|
let src = "<?php\n$x = 'hello\n"
|
|
|
|
|
let result = validateSource(src, flavor = lfPHP)
|
|
|
|
|
check result.errors.len >= 0
|
|
|
|
|
|
|
|
|
|
test "variable interpolation":
|
|
|
|
|
let src = "<?php\necho \"Hello, $name!\";\n"
|
|
|
|
|
let result = validateSource(src, flavor = lfPHP)
|
|
|
|
|
check result.errors.len >= 0
|
|
|
|
|
|
|
|
|
|
test "complex variable syntax":
|
|
|
|
|
let src = "<?php\necho \"Hello, {$user->name}!\";\n"
|
|
|
|
|
let result = validateSource(src, flavor = lfPHP)
|
|
|
|
|
check result.errors.len >= 0
|
|
|
|
|
|
|
|
|
|
test "heredoc syntax":
|
|
|
|
|
let src = "<?php\necho <<<EOT\nHello world\nEOT;\n"
|
|
|
|
|
let result = validateSource(src, flavor = lfPHP)
|
|
|
|
|
check result.errors.len >= 0
|
|
|
|
|
|
|
|
|
|
test "nowdoc syntax":
|
|
|
|
|
let src = "<?php\necho <<<'EOT'\n$var is not parsed\nEOT;\n"
|
|
|
|
|
let result = validateSource(src, flavor = lfPHP)
|
|
|
|
|
check result.errors.len >= 0
|
|
|
|
|
|
|
|
|
|
test "namespace and use":
|
|
|
|
|
let src = "<?php\nnamespace App\\Controller;\nuse App\\Model\\User;\n"
|
|
|
|
|
let result = validateSource(src, flavor = lfPHP)
|
|
|
|
|
check result.errors.len >= 0
|
|
|
|
|
|
|
|
|
|
test "class with properties":
|
|
|
|
|
let src = "<?php\nclass User {\n public $name;\n private $id;\n}\n"
|
|
|
|
|
let result = validateSource(src, flavor = lfPHP)
|
|
|
|
|
check result.errors.len >= 0
|
|
|
|
|
|
|
|
|
|
test "PHP without closing tag":
|
|
|
|
|
let src = "<?php\n$x = 1;\n$y = 2;\n"
|
|
|
|
|
let result = validateSource(src, flavor = lfPHP)
|
|
|
|
|
check result.errors.len >= 0
|
|
|
|
|
|
|
|
|
|
test "empty source":
|
|
|
|
|
let src = ""
|
|
|
|
|
let result = validateSource(src, flavor = lfPHP)
|
|
|
|
|
check result.errors.len >= 0
|
|
|
|
|
|
|
|
|
|
test "null byte in PHP":
|
|
|
|
|
let src = "<?php\n$x = 1;\x00$y = 2;\n"
|
|
|
|
|
let result = validateSource(src, flavor = lfPHP)
|
|
|
|
|
check result.errors.len >= 0
|
|
|
|
|
|
|
|
|
|
test "HTML embedded in PHP":
|
|
|
|
|
let src = "<?php if ($x): ?>\n<p>Hello</p>\n<?php endif; ?>\n"
|
|
|
|
|
let result = validateSource(src, flavor = lfPHP)
|
|
|
|
|
check result.errors.len >= 0
|
|
|
|
|
|
|
|
|
|
test "match expression PHP 8+":
|
|
|
|
|
let src = "<?php\n$result = match($x) {\n 1 => 'one',\n 2 => 'two',\n default => 'other',\n};\n"
|
|
|
|
|
let result = validateSource(src, flavor = lfPHP)
|
|
|
|
|
check result.errors.len >= 0
|
|
|
|
|
|
|
|
|
|
test "enum PHP 8.1+":
|
|
|
|
|
let src = "<?php\nenum Status: string {\n case Active = 'active';\n case Inactive = 'inactive';\n}\n"
|
|
|
|
|
let result = validateSource(src, flavor = lfPHP)
|
|
|
|
|
check result.errors.len >= 0
|
|
|
|
|
|
|
|
|
|
test "attributes PHP 8.0+":
|
|
|
|
|
let src = "<?php\n#[Route('/api')]\nclass ApiController {}\n"
|
|
|
|
|
let result = validateSource(src, flavor = lfPHP)
|
|
|
|
|
check result.errors.len >= 0
|
|
|
|
|
|
|
|
|
|
test "array short syntax":
|
|
|
|
|
let src = "<?php\n$arr = [1, 2, 3];\n$map = ['a' => 1, 'b' => 2];\n"
|
|
|
|
|
let result = validateSource(src, flavor = lfPHP)
|
|
|
|
|
check result.errors.len >= 0
|
|
|
|
|
|
|
|
|
|
test "anonymous class":
|
|
|
|
|
let src = "<?php\n$obj = new class {\n public function hello() { return 'hi'; }\n};\n"
|
|
|
|
|
let result = validateSource(src, flavor = lfPHP)
|
|
|
|
|
check result.errors.len >= 0
|