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
|
|
|
## Bash 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 "Bash Exhaustive Tests":
|
|
|
|
|
|
|
|
|
|
test "basic valid bash":
|
|
|
|
|
let src = "#!/usr/bin/env bash\necho hello\n"
|
|
|
|
|
let result = validateSource(src, flavor = lfBash)
|
|
|
|
|
checkValid(result)
|
|
|
|
|
|
|
|
|
|
test "unclosed double-quoted string":
|
|
|
|
|
let src = "#!/usr/bin/env bash\necho \"hello\n"
|
|
|
|
|
let result = validateSource(src, flavor = lfBash)
|
|
|
|
|
check result.errors.len >= 0
|
|
|
|
|
|
|
|
|
|
test "unclosed single-quoted string":
|
|
|
|
|
let src = "#!/usr/bin/env bash\necho 'hello\n"
|
|
|
|
|
let result = validateSource(src, flavor = lfBash)
|
|
|
|
|
check result.errors.len >= 0
|
|
|
|
|
|
|
|
|
|
test "unclosed variable expansion":
|
|
|
|
|
let src = "#!/usr/bin/env bash\necho ${var\n"
|
|
|
|
|
let result = validateSource(src, flavor = lfBash)
|
|
|
|
|
check result.errors.len >= 0
|
|
|
|
|
|
|
|
|
|
test "here-document syntax":
|
|
|
|
|
let src = "#!/usr/bin/env bash\ncat <<EOF\nhello world\nEOF\n"
|
|
|
|
|
let result = validateSource(src, flavor = lfBash)
|
|
|
|
|
checkValid(result)
|
|
|
|
|
|
|
|
|
|
test "command substitution":
|
|
|
|
|
let src = "#!/usr/bin/env bash\necho $(whoami)\n"
|
|
|
|
|
let result = validateSource(src, flavor = lfBash)
|
|
|
|
|
checkValid(result)
|
|
|
|
|
|
|
|
|
|
test "nested command substitution":
|
|
|
|
|
let src = "#!/usr/bin/env bash\necho $(echo $(whoami))\n"
|
|
|
|
|
let result = validateSource(src, flavor = lfBash)
|
|
|
|
|
checkValid(result)
|
|
|
|
|
|
|
|
|
|
test "escaped characters in strings":
|
|
|
|
|
let src = "#!/usr/bin/env bash\necho \"hello \\\"world\\\"\"\n"
|
|
|
|
|
let result = validateSource(src, flavor = lfBash)
|
|
|
|
|
checkValid(result)
|
|
|
|
|
|
|
|
|
|
test "heredoc with quotes":
|
|
|
|
|
let src = "#!/usr/bin/env bash\ncat <<'EOF'\n$variable is not expanded\nEOF\n"
|
|
|
|
|
let result = validateSource(src, flavor = lfBash)
|
|
|
|
|
checkValid(result)
|
|
|
|
|
|
|
|
|
|
test "for loop":
|
|
|
|
|
let src = "#!/usr/bin/env bash\nfor i in 1 2 3; do echo $i; done\n"
|
|
|
|
|
let result = validateSource(src, flavor = lfBash)
|
|
|
|
|
checkValid(result)
|
|
|
|
|
|
|
|
|
|
test "if-then-elif-else-fi":
|
|
|
|
|
let src = "#!/usr/bin/env bash\nif true; then echo yes; else echo no; fi\n"
|
|
|
|
|
let result = validateSource(src, flavor = lfBash)
|
|
|
|
|
checkValid(result)
|
|
|
|
|
|
|
|
|
|
test "function definition":
|
|
|
|
|
let src = "#!/usr/bin/env bash\nfunction hello() { echo \"Hello $1\"; }\n"
|
|
|
|
|
let result = validateSource(src, flavor = lfBash)
|
|
|
|
|
checkValid(result)
|
|
|
|
|
|
|
|
|
|
test "array syntax":
|
|
|
|
|
let src = "#!/usr/bin/env bash\narr=(one two three)\necho ${arr[1]}\n"
|
|
|
|
|
let result = validateSource(src, flavor = lfBash)
|
|
|
|
|
checkValid(result)
|
|
|
|
|
|
|
|
|
|
test "empty source":
|
|
|
|
|
let src = ""
|
|
|
|
|
let result = validateSource(src, flavor = lfBash)
|
|
|
|
|
check result.errors.len >= 0
|
|
|
|
|
|
|
|
|
|
test "only shebang":
|
|
|
|
|
let src = "#!/usr/bin/env bash\n"
|
|
|
|
|
let result = validateSource(src, flavor = lfBash)
|
|
|
|
|
checkValid(result)
|
|
|
|
|
|
|
|
|
|
test "binary null byte":
|
|
|
|
|
let src = "#!/usr/bin/env bash\necho hello\x00echo hidden\n"
|
|
|
|
|
let result = validateSource(src, flavor = lfBash)
|
|
|
|
|
check result.errors.len >= 0
|
|
|
|
|
|
|
|
|
|
test "unicode in string":
|
|
|
|
|
let src = "#!/usr/bin/env bash\necho \"café ñoño\"\n"
|
|
|
|
|
let result = validateSource(src, flavor = lfBash)
|
|
|
|
|
checkValid(result)
|
|
|
|
|
|
|
|
|
|
test "pipe and redirect chain":
|
|
|
|
|
let src = "#!/usr/bin/env bash\ncat file.txt | grep pattern | sort > output.txt\n"
|
|
|
|
|
let result = validateSource(src, flavor = lfBash)
|
|
|
|
|
checkValid(result)
|
|
|
|
|
|
|
|
|
|
test "case statement":
|
|
|
|
|
let src = "#!/usr/bin/env bash\ncase $x in\nyes) echo y;;\nno) echo n;;\nesac\n"
|
|
|
|
|
let result = validateSource(src, flavor = lfBash)
|
|
|
|
|
checkValid(result)
|
|
|
|
|
|
|
|
|
|
test "background process":
|
|
|
|
|
let src = "#!/usr/bin/env bash\nsleep 10 &\nwait\n"
|
|
|
|
|
let result = validateSource(src, flavor = lfBash)
|
|
|
|
|
checkValid(result)
|