Contributing to Nimcheck
Pure Nim stdlib, tokenizer-based validation, no external parser dependencies.
Before you start
- Read docs/STATUS.md — what's done and project scope.
- Read README.md — architecture and API.
- Default branch is
master. Open PRs againstmaster. - CI must pass:
make lint && make test(see docs/GITEA.md).
Development setup
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
lfYourlangtoLanguageFlavorif missing. - Add string conversion in
$/parseFlavorhelpers.
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):
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:
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():
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.nimcreateValidator()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:
"""recipe line""" & "\t" & """command"""
6. Documentation
Update README.md language table and docs/STATUS.md if adding a flavor.
Running tests
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 lintpassesmake testpasses- New language has fixtures + basic + exhaustive tests
detectFlavorworks for good-code sample intest_common.nim- No debug binaries or
*.logcommitted - README / STATUS updated if behavior or flavors changed
Reporting bugs
Include:
- Nim version (
nim --version) - Input source (minimal repro)
- Expected vs actual
ValidationResult - Whether it hangs (likely tokenizer loop — check
finishTokenizeStep)
License
By contributing, you agree your changes are licensed under the project MIT license.