Contributing to Nimcheck

Pure Nim stdlib, tokenizer-based validation, no external parser dependencies.


Before you start

  1. Read docs/STATUS.md — what's done and project scope.
  2. Read 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).

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 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):

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.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:

"""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 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.