This commit is contained in:
retoor 2026-07-08 12:10:42 +02:00
parent 3dacbb3ab0
commit 608974021d
7 changed files with 44 additions and 44 deletions

22
.gitignore vendored
View File

@ -4,7 +4,7 @@ tests/test_json_exhaustive
tests/test_toml_exhaustive tests/test_toml_exhaustive
tests/test_yaml_exhaustive tests/test_yaml_exhaustive
*.bak* *.bak*
# ── Nim ────────────────────────────────────────────────────────────────── # -- Nim ------------------------------------------------------------------
nimcache/ nimcache/
nimblecache/ nimblecache/
*.exe *.exe
@ -15,10 +15,10 @@ nimblecache/
*.so *.so
*.dylib *.dylib
# ── Binaries (compiled output) ─────────────────────────────────────────── # -- Binaries (compiled output) -------------------------------------------
bin/ bin/
# ── Test compiled binaries (source is .nim, binaries have no extension) ── # -- Test compiled binaries (source is .nim, binaries have no extension) --
tests/test_all tests/test_all
tests/test_bash tests/test_bash
tests/test_nim tests/test_nim
@ -35,16 +35,16 @@ tests/test_mixed
tests/test_config tests/test_config
tests/test_fuzz tests/test_fuzz
# ── Logs ───────────────────────────────────────────────────────────────── # -- Logs -----------------------------------------------------------------
*.log *.log
# ── Python cache ───────────────────────────────────────────────────────── # -- Python cache ---------------------------------------------------------
__pycache__/ __pycache__/
*.pyc *.pyc
*.pyo *.pyo
*.pyd *.pyd
# ── OS files ───────────────────────────────────────────────────────────── # -- OS files -------------------------------------------------------------
.DS_Store .DS_Store
.DS_Store? .DS_Store?
._* ._*
@ -54,7 +54,7 @@ ehthumbs.db
Thumbs.db Thumbs.db
desktop.ini desktop.ini
# ── Editor files ───────────────────────────────────────────────────────── # -- Editor files ---------------------------------------------------------
*.swp *.swp
*.swo *.swo
*~ *~
@ -65,23 +65,23 @@ desktop.ini
*.sublime-project *.sublime-project
*.sublime-workspace *.sublime-workspace
# ── Environment / secrets ──────────────────────────────────────────────── # -- Environment / secrets ------------------------------------------------
.env .env
.env.local .env.local
.env.*.local .env.*.local
*.env *.env
!.env.example !.env.example
# ── Temporary / backup files ───────────────────────────────────────────── # -- Temporary / backup files ---------------------------------------------
/tmp/ /tmp/
*.tmp *.tmp
*.temp *.temp
*.pid *.pid
# ── dpc agent logs ─────────────────────────────────────────────────────── # -- dpc agent logs -------------------------------------------------------
dpc.log dpc.log
# ── Coverage reports ───────────────────────────────────────────────────── # -- Coverage reports -----------------------------------------------------
*.gcda *.gcda
*.gcno *.gcno
*.gcov *.gcov

View File

@ -88,8 +88,8 @@ Each validator:
1. Creates its tokenizer and runs `tokenize()` 1. Creates its tokenizer and runs `tokenize()`
2. Collects tokenizer errors 2. Collects tokenizer errors
3. Runs `analyzeTokens()` language-specific structural analysis 3. Runs `analyzeTokens()` - language-specific structural analysis
4. Runs `buildModuleInfo()` populates `ModuleInfo` with imports, functions, classes 4. Runs `buildModuleInfo()` - populates `ModuleInfo` with imports, functions, classes
5. Computes validity, warning/info counts, duration 5. Computes validity, warning/info counts, duration
Validators register themselves via a factory pattern at import time. The registry maps flavor strings (e.g., `"python"`, `"bash"`) to constructors. Validators register themselves via a factory pattern at import time. The registry maps flavor strings (e.g., `"python"`, `"bash"`) to constructors.
@ -674,7 +674,7 @@ Every validation function follows the same contract:
| `sourcePath: string = ""` (optional) | `ValidationResult.durationMs: float` | | `sourcePath: string = ""` (optional) | `ValidationResult.durationMs: float` |
**Guarantees**: **Guarantees**:
- Never throws all exceptions are caught and returned as `E9999` errors - Never throws - all exceptions are caught and returned as `E9999` errors
- Empty source returns `E0009` (or `valid: true` for YAML/TOML where empty is valid) - Empty source returns `E0009` (or `valid: true` for YAML/TOML where empty is valid)
- File not found returns `E0011` - File not found returns `E0011`
- Unknown flavor with no detection possible returns an `E0007` error - Unknown flavor with no detection possible returns an `E0007` error
@ -686,8 +686,8 @@ Every validation function follows the same contract:
| Aspect | Details | | Aspect | Details |
|--------|---------| |--------|---------|
| Tokenizer | `nim_tokenizer.nim` `NimTokenizer` | | Tokenizer | `nim_tokenizer.nim` - `NimTokenizer` |
| Validator | `nim_validator.nim` `NimValidator` | | Validator | `nim_validator.nim` - `NimValidator` |
| Keywords | 80+ (addr, and, as, block, break, case, const, converter, defer, discard, elif, else, enum, except, export, finally, for, from, func, if, import, in, include, iterator, let, macro, method, mixin, mod, nil, not, object, of, or, proc, ptr, raise, ref, return, template, try, tuple, type, using, var, when, while, with, yield, etc.) | | Keywords | 80+ (addr, and, as, block, break, case, const, converter, defer, discard, elif, else, enum, except, export, finally, for, from, func, if, import, in, include, iterator, let, macro, method, mixin, mod, nil, not, object, of, or, proc, ptr, raise, ref, return, template, try, tuple, type, using, var, when, while, with, yield, etc.) |
| Error Code | Condition | Severity | Hint | | Error Code | Condition | Severity | Hint |
@ -707,8 +707,8 @@ Structural extraction: imports (`from X import Y`), functions (`proc`, `func`, `
| Aspect | Details | | Aspect | Details |
|--------|---------| |--------|---------|
| Tokenizer | `bash_tokenizer.nim` `BashTokenizer` | | Tokenizer | `bash_tokenizer.nim` - `BashTokenizer` |
| Validator | `bash_validator.nim` `BashValidator` | | Validator | `bash_validator.nim` - `BashValidator` |
| Keywords | 38+ (if, then, else, elif, fi, case, esac, for, while, until, do, done, in, function, select, time, return, break, continue, exit, declare, local, export, readonly, unset, eval, exec, let, source, shift, trap, type, typeset, ulimit, umask, alias, read, echo) | | Keywords | 38+ (if, then, else, elif, fi, case, esac, for, while, until, do, done, in, function, select, time, return, break, continue, exit, declare, local, export, readonly, unset, eval, exec, let, source, shift, trap, type, typeset, ulimit, umask, alias, read, echo) |
| Error Code | Condition | Severity | Hint | | Error Code | Condition | Severity | Hint |
@ -728,8 +728,8 @@ Special features: shebang detection (`#!`), heredoc detection (`<<EOF`), variabl
| Aspect | Details | | Aspect | Details |
|--------|---------| |--------|---------|
| Tokenizer | `python_tokenizer.nim` `PythonTokenizer` | | Tokenizer | `python_tokenizer.nim` - `PythonTokenizer` |
| Validator | `python_validator.nim` `PythonValidator` | | Validator | `python_validator.nim` - `PythonValidator` |
| Keywords | 33 (False, None, True, and, as, assert, async, await, break, class, continue, def, del, elif, else, except, finally, for, from, global, if, import, in, is, lambda, nonlocal, not, or, pass, raise, return, try, while, with, yield) | | Keywords | 33 (False, None, True, and, as, assert, async, await, break, class, continue, def, del, elif, else, except, finally, for, from, global, if, import, in, is, lambda, nonlocal, not, or, pass, raise, return, try, while, with, yield) |
| Error Code | Condition | Severity | Hint | | Error Code | Condition | Severity | Hint |
@ -749,8 +749,8 @@ Structural extraction: imports (`import X`, `from X import Y`), functions (`def`
| Aspect | Details | | Aspect | Details |
|--------|---------| |--------|---------|
| Tokenizer | `javascript_tokenizer.nim` `JavaScriptTokenizer` | | Tokenizer | `javascript_tokenizer.nim` - `JavaScriptTokenizer` |
| Validator | `javascript_validator.nim` `JavaScriptValidator` | | Validator | `javascript_validator.nim` - `JavaScriptValidator` |
| Keywords | 60+ (async, await, break, case, catch, class, const, continue, debugger, default, delete, do, else, export, extends, false, finally, for, function, if, import, in, instanceof, let, new, null, of, return, super, switch, static, this, throw, true, try, typeof, var, void, while, with, yield + TypeScript: interface, type, declare, module, namespace, abstract, readonly, as, any, boolean, number, string, never, keyof, infer, unknown) | | Keywords | 60+ (async, await, break, case, catch, class, const, continue, debugger, default, delete, do, else, export, extends, false, finally, for, function, if, import, in, instanceof, let, new, null, of, return, super, switch, static, this, throw, true, try, typeof, var, void, while, with, yield + TypeScript: interface, type, declare, module, namespace, abstract, readonly, as, any, boolean, number, string, never, keyof, infer, unknown) |
| Error Code | Condition | Severity | Hint | | Error Code | Condition | Severity | Hint |
@ -768,9 +768,9 @@ Structural extraction: imports/exports, functions (`function`, `async`), classes
| Aspect | Details | | Aspect | Details |
|--------|---------| |--------|---------|
| Tokenizer | `php_tokenizer.nim` `PHPTokenizer` | | Tokenizer | `php_tokenizer.nim` - `PHPTokenizer` |
| Validator | `php_validator.nim` `PHPValidator` | | Validator | `php_validator.nim` - `PHPValidator` |
| Mode switching | Tracks `inPhpMode` flag toggles on `<?php`/`<?` and `?>` | | Mode switching | Tracks `inPhpMode` flag - toggles on `<?php`/`<?` and `?>` |
| Error Code | Condition | Severity | Hint | | Error Code | Condition | Severity | Hint |
|------------|-----------|----------|------| |------------|-----------|----------|------|
@ -785,8 +785,8 @@ Structural extraction: functions (`function`, `fn`), classes (`class`), imports/
| Aspect | Details | | Aspect | Details |
|--------|---------| |--------|---------|
| Tokenizer | `html_tokenizer.nim` `HTMLTokenizer` | | Tokenizer | `html_tokenizer.nim` - `HTMLTokenizer` |
| Validator | `html_validator.nim` `HTMLValidator` | | Validator | `html_validator.nim` - `HTMLValidator` |
| Void elements | 14: area, base, br, col, embed, hr, img, input, link, meta, param, source, track, wbr | | Void elements | 14: area, base, br, col, embed, hr, img, input, link, meta, param, source, track, wbr |
| Error Code | Condition | Severity | Hint | | Error Code | Condition | Severity | Hint |
@ -801,8 +801,8 @@ Special features: tag stack balancing, void element awareness (no closing tag ne
| Aspect | Details | | Aspect | Details |
|--------|---------| |--------|---------|
| Tokenizer | `jinja_tokenizer.nim` `JinjaTokenizer` | | Tokenizer | `jinja_tokenizer.nim` - `JinjaTokenizer` |
| Validator | `jinja_validator.nim` `JinjaValidator` | | Validator | `jinja_validator.nim` - `JinjaValidator` |
| Error Code | Condition | Severity | Hint | | Error Code | Condition | Severity | Hint |
|------------|-----------|----------|------| |------------|-----------|----------|------|
@ -820,7 +820,7 @@ Special features: tracks 12 block tag types (`for`, `if`, `block`, `macro`, `fil
| Aspect | Details | | Aspect | Details |
|--------|---------| |--------|---------|
| Validator | `config_validators.nim` `JSONValidator` | | Validator | `config_validators.nim` - `JSONValidator` |
| Method | Uses Nim's built-in `parseJson()` for full validation | | Method | Uses Nim's built-in `parseJson()` for full validation |
| Error Code | Condition | Severity | Hint | | Error Code | Condition | Severity | Hint |
@ -833,7 +833,7 @@ Special features: tracks 12 block tag types (`for`, `if`, `block`, `macro`, `fil
| Aspect | Details | | Aspect | Details |
|--------|---------| |--------|---------|
| Validator | `config_validators.nim` `YAMLValidator` | | Validator | `config_validators.nim` - `YAMLValidator` |
| Method | Line-by-line structural analysis | | Method | Line-by-line structural analysis |
| Error Code | Condition | Severity | Hint | | Error Code | Condition | Severity | Hint |
@ -849,7 +849,7 @@ Note: Empty YAML (only whitespace/comments) is `valid: true`.
| Aspect | Details | | Aspect | Details |
|--------|---------| |--------|---------|
| Validator | `config_validators.nim` `TOMLValidator` | | Validator | `config_validators.nim` - `TOMLValidator` |
| Method | Line-by-line structural analysis | | Method | Line-by-line structural analysis |
| Error Code | Condition | Severity | Hint | | Error Code | Condition | Severity | Hint |
@ -912,7 +912,7 @@ Supported flavors:
| 0 | Validation successful (or help/version requested) | | 0 | Validation successful (or help/version requested) |
| Non-zero (Nim default) | Runtime error | | Non-zero (Nim default) | Runtime error |
Note: The `validate` command always exits 0 on the Nim level; the `valid` field in the output indicates pass/fail. This is by design Nimcheck never crashes. Note: The `validate` command always exits 0 on the Nim level; the `valid` field in the output indicates pass/fail. This is by design - Nimcheck never crashes.
--- ---
@ -1084,7 +1084,7 @@ See `LESSONS_LEARNED.md` for the complete checklist (Section 14). The minimum st
### Design Principles ### Design Principles
- **KISS**: Keep validators simple tokenize, analyze brackets, report - **KISS**: Keep validators simple - tokenize, analyze brackets, report
- **Never crash**: Every validation path in a try/except - **Never crash**: Every validation path in a try/except
- **No external deps**: Pure Nim standard library - **No external deps**: Pure Nim standard library
- **DRY**: Common logic in `tokenizerbase.nim` and `validatorbase.nim` - **DRY**: Common logic in `tokenizerbase.nim` and `validatorbase.nim`

View File

@ -17,7 +17,7 @@
import std/[json, strutils, os, strformat, sequtils, algorithm] import std/[json, strutils, os, strformat, sequtils, algorithm]
# Core types and base classes imported and re-exported for public API consumers # Core types and base classes - imported and re-exported for public API consumers
import nimcheck/core/types import nimcheck/core/types
import nimcheck/core/tokenizerbase import nimcheck/core/tokenizerbase
import nimcheck/core/validatorbase import nimcheck/core/validatorbase

View File

@ -1,4 +1,4 @@
## Validator registration called at module load time. ## Validator registration - called at module load time.
## ##
## Each language validator registers itself here. ## Each language validator registers itself here.
## This file is imported by the main nimcheck.nim module. ## This file is imported by the main nimcheck.nim module.

View File

@ -202,7 +202,7 @@ proc validateBrackets*(self: TokenizerBase): seq[ValidationError] =
let hintStr = &"Add a closing '{pairStr}' before this point" let hintStr = &"Add a closing '{pairStr}' before this point"
let err = newValidationError( let err = newValidationError(
esError, esError,
&"Unclosed '{ch}' expected matching '{pairStr}'", &"Unclosed '{ch}' - expected matching '{pairStr}'",
ErrUnclosedBracket, ErrUnclosedBracket,
pos, pos,
SourceRange(startPos: pos, endPos: pos), SourceRange(startPos: pos, endPos: pos),
@ -215,12 +215,12 @@ proc validateBrackets*(self: TokenizerBase): seq[ValidationError] =
self.errors.add(result) self.errors.add(result)
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# Abstract interface subclasses must implement # Abstract interface - subclasses must implement
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
method tokenize*(self: TokenizerBase) {.base, gcsafe.} = method tokenize*(self: TokenizerBase) {.base, gcsafe.} =
## Tokenize the entire source. Must be overridden. ## Tokenize the entire source. Must be overridden.
raise newException(ValueError, "tokenize() not implemented subclass must override") raise newException(ValueError, "tokenize() not implemented - subclass must override")
proc getAllTokens*(self: TokenizerBase): seq[Token] = proc getAllTokens*(self: TokenizerBase): seq[Token] =
## Get all tokens after tokenization. ## Get all tokens after tokenization.

View File

@ -36,7 +36,7 @@ proc newValidatorBase*(
result.result.flavor = options.flavor result.result.flavor = options.flavor
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# Template methods subclasses override # Template methods - subclasses override
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
method createTokenizer*(self: ValidatorBase): TokenizerBase {.base, gcsafe.} = method createTokenizer*(self: ValidatorBase): TokenizerBase {.base, gcsafe.} =
@ -121,7 +121,7 @@ method validate*(self: ValidatorBase): ValidationResult {.base, gcsafe.} =
self.result.moduleInfo.linesOfCode = self.source.count('\n') + 1 self.result.moduleInfo.linesOfCode = self.source.count('\n') + 1
except Exception as e: except Exception as e:
# Never crash always report useful error # Never crash - always report useful error
self.result.valid = false self.result.valid = false
self.result.errors.add(newValidationError( self.result.errors.add(newValidationError(
esCritical, esCritical,
@ -129,7 +129,7 @@ method validate*(self: ValidatorBase): ValidationResult {.base, gcsafe.} =
"E9999", "E9999",
newSourcePosition(0, 0, 0), newSourcePosition(0, 0, 0),
newSourceRange(newSourcePosition(0, 0, 0), newSourcePosition(0, 0, 0)), newSourceRange(newSourcePosition(0, 0, 0), newSourcePosition(0, 0, 0)),
hint = "This is a bug in the validator please report it" hint = "This is a bug in the validator - please report it"
)) ))
when defined(nimcheckDebug): when defined(nimcheckDebug):
if self.options.debugMode: if self.options.debugMode:
@ -162,7 +162,7 @@ proc isValid*(self: ValidatorBase): bool =
self.result.valid self.result.valid
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# Factory create correct validator for a flavor # Factory - create correct validator for a flavor
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
type type

View File

@ -77,7 +77,7 @@ method tokenize*(self: PHPTokenizer) =
self.emitToken(tkNewline, "\n", startPos) self.emitToken(tkNewline, "\n", startPos)
continue continue
# Inside PHP mode check for exit # Inside PHP mode - check for exit
if self.peekString(2) == "?>" and self.inPhpMode: if self.peekString(2) == "?>" and self.inPhpMode:
for _ in 1..2: discard self.advance() for _ in 1..2: discard self.advance()
self.emitToken(tkDirective, "?>", startPos) self.emitToken(tkDirective, "?>", startPos)