# Contributing to Nimcheck
Pure Nim stdlib, tokenizer-based validation, no external parser dependencies.
---
## Before you start
1. Read [docs/STATUS.md](docs/STATUS.md) — what's done and project scope.
2. Read [README.md](README.md) — architecture and API.
3. Default branch is **`master`**. Open PRs against `master`.
4. CI must pass: `make lint && make test` (see [docs/GITEA.md](docs/GITEA.md)).
---
## Development setup
```bash
git clone https://retoor.molodetz.nl/retoor/nimcheck.git
cd nimcheck
nimble build
nimble test
```
Requires **Nim >= 2.0.0**.
---
## Code style
- Match existing naming and module layout.
- Keep validators simple: tokenize → analyze → report.
- **Never throw from validation paths** — catch and return `E9999`.
- No new external dependencies without discussion.
- Minimal comments; code should be self-explanatory.
---
## Adding a new language
### 1. Types and detection
In `src/nimcheck/core/types.nim`:
- Add `lfYourlang` to `LanguageFlavor` if missing.
- Add string conversion in `$` / `parseFlavor` helpers.
In `src/nimcheck/core/detector.nim`:
- Add extension mappings to `extensionMap`.
- Add shebang entries if applicable.
- Add content-scoring keywords (avoid false positives — see entropy/margin rules).
### 2. Tokenizer
Create `src/nimcheck/tokenizers/<lang>_tokenizer.nim` (or extend `extended_tokenizers.nim` / `cfamily_tokenizers.nim` for CLike langs):
```nim
type MyLangTokenizer* = ref object of TokenizerBase
method tokenize*(self: MyLangTokenizer) =
while self.hasMore():
let posBefore = self.pos
# ... lex next token ...
self.finishTokenizeStep(posBefore) # REQUIRED — prevents infinite loops
self.emitToken(tkEndOfFile, "", self.currentPos())
```
Rules:
- Always call `finishTokenizeStep(posBefore)` at end of each loop iteration.
- Use `closeBracket()` for `)`, `]`, `}` — it advances position.
- Record errors via `recordError()`, don't raise.
### 3. Validator
Create `src/nimcheck/languages/<lang>_validator.nim`:
```nim
type MyLangValidator* = ref object of ValidatorBase
method createTokenizer*(self: MyLangValidator): TokenizerBase = ...
method analyzeTokens*(self: MyLangValidator) =
procCall ValidatorBase(self).analyzeTokens() # bracket validation
# language-specific block/string/structure checks
```
Register in `init()`:
```nim
registerValidator("mylang", proc(...) = result = newMyLangValidator(...))
```
### 4. Wire up
- Import validator in `src/nimcheck.nim` (inside the validator import block).
- Add factory case in `src/nimcheck/core/validatorbase.nim` `createValidator()` if needed.
### 5. Tests
For each language add:
```
tests/fixtures/<lang>/valid.*
tests/fixtures/<lang>/invalid.*
tests/fixtures/<lang>/edge_cases.*
tests/test_<lang>.nim # basic: good code, bad code, files, detectFlavor
tests/test_<lang>_exhaustive.nim
```
Add shared snippets to `tests/test_common.nim` if other tests need them.
Import in `tests/test_all.nim`.
**Makefile tab pitfall:** In Nim `"""` strings, a line starting with `\t` is often backslash+t (ord 92+116), not TAB (ord 9). Use:
```nim
"""recipe line""" & "\t" & """command"""
```
### 6. Documentation
Update `README.md` language table and `docs/STATUS.md` if adding a flavor.
---
## Running tests
```bash
make test # everything
nim c -r tests/test_go.nim # single language
make test-nim # via Makefile shortcut
nimble test # via nimble task
```
---
## Pull request checklist
- [ ] `make lint` passes
- [ ] `make test` passes
- [ ] New language has fixtures + basic + exhaustive tests
- [ ] `detectFlavor` works for good-code sample in `test_common.nim`
- [ ] No debug binaries or `*.log` committed
- [ ] README / STATUS updated if behavior or flavors changed
---
## Reporting bugs
Include:
1. Nim version (`nim --version`)
2. Input source (minimal repro)
3. Expected vs actual `ValidationResult`
4. Whether it hangs (likely tokenizer loop — check `finishTokenizeStep`)
---
## License
By contributing, you agree your changes are licensed under the project MIT license.