Skip to main content

Extensible Python linter written in Rust

Project description

jg-lint

Extensible Python linter with a Rust core.

Installation

Requires Python 3.14+.

uv add jg-lint

or with pip:

pip install jg-lint

or with Poetry:

poetry add jg-lint

Development install

To build from source, you need Maturin and a Rust toolchain:

git clone https://github.com/jangia/jg-lint.git
cd jg-lint
uv sync
uv run maturin develop

Usage

jg-lint check <paths...>

Lint one or more files or directories:

jg-lint check src/
jg-lint check src/ lib/ main.py
jg-lint check --config path/to/project src/

The --config flag points to the directory containing pyproject.toml (defaults to .).

Built-in rules

Code Description
JG001 Imports must be at module top level
JG002 if statements are not allowed in test functions (any function or method whose name starts with test_)

All built-in rules are enabled by default. When select is empty (or omitted), every implemented built-in rule runs alongside any plugin rules. To narrow what runs, list rule codes explicitly in select (e.g. select = ["JG001"]) or silence individual rules via ignore / per-file-ignores.

If both select and ignore end up filtering out every rule, jg-lint prints a warning on stderr so you know nothing was actually checked.

Output looks like:

src/app.py:12:1: MY001 TODO comments should be tracked as issues
src/app.py:45:1: MY001 TODO comments should be tracked as issues

Found 2 violation(s)

Exit code is 1 if any violations are found, 0 otherwise.

Inline suppression

Some rules opt in to per-line suppression via # noqa:

x = something()  # noqa: MY001
y = another()  # noqa: MY001, MY002

By design, # noqa is not honored by default — rules must explicitly set allow_noqa = True to allow it. This keeps suppression intentional and reserved for cases where the rule author has decided it's acceptable.

Configuration

All configuration lives in pyproject.toml under [tool.jg-lint] (the legacy [tool.jg-linter] section is still read as a fallback for backwards compatibility):

[tool.jg-lint]
select = ["MY001", "JG*"]    # rules to enable (see "Selecting rules" below)
ignore = ["MY002"]           # skip these rules globally
exclude = [".venv/**", "build/**"]
rules_path = "./rules"       # directory containing your custom rule modules

[tool.jg-lint.per-file-ignores]
"tests/**" = ["MY001"]       # skip MY001 in test files

Selecting rules

Each entry in select, ignore, and per-file-ignores is matched against a rule's code with the following pattern semantics:

  • "*" — matches every code.
  • "JG*" — matches every code starting with JG (e.g. JG001, JG042).
  • "JG001" — exact match.

Defaults when select is empty:

  • Every implemented built-in rule (e.g. JG001, JG002) is enabled.
  • Every plugin rule loaded from rules_path is enabled.

Use ignore (or per-file-ignores) to turn rules off, or set select explicitly to opt into a narrower subset.

Writing custom rules

1. Create a rule module

Put it inside the directory you've configured as rules_path (e.g. ./rules/no_todo.py).

from jg_linter import Rule, Violation


class NoTodoComments(Rule):
    code = "MY001"
    message = "TODO comments should be tracked as issues"

    def check(self, file_path: str, content: str) -> list[Violation]:
        violations = []
        for i, line in enumerate(content.splitlines(), 1):
            if "# TODO" in line:
                violations.append(
                    Violation(file_path, i, 1, self.code, self.message)
                )
        return violations

Every Rule subclass found in the loaded modules is auto-instantiated — no get_rules() function required. If you need custom control (e.g. parameterized rules), define get_rules() and it will be used instead of auto-discovery.

Each rule needs:

  • code -- unique identifier (e.g. MY001)
  • message -- human-readable description
  • check(file_path, content) -- returns a list of Violation objects

Set test_only = True on a rule to run it only against test files (files named test_*.py, *_test.py, or inside a tests/ directory).

Set allow_noqa = True on a rule to allow inline # noqa: CODE suppression. Without this, the rule cannot be silenced per-line and can only be turned off project-wide via ignore or per-file-ignores.

2. Point jg-lint at the rules folder

[tool.jg-lint]
rules_path = "./rules"

rules_path can be either:

  • a flat directory of .py files (each file imported on its own), or
  • a Python package — i.e. the directory itself has __init__.py; submodules are imported under a synthetic _jg_lint_user_rules package so relative imports work.

In either layout, every Rule subclass found is auto-instantiated. Files and folders starting with _ are skipped. If a module defines get_rules(), that function is used verbatim (auto-discovery is bypassed for that module), which lets you parameterize or filter what gets registered.

3. Run

jg-lint check src/
src/app.py:3:1: MY001 TODO comments should be tracked as issues
src/utils.py:17:1: MY001 TODO comments should be tracked as issues

Found 2 violation(s)

Contributing

You need a Rust toolchain (stable), Python 3.14+, and uv.

git clone https://github.com/jangia/jg-lint.git
cd jg-lint
uv sync
uv run maturin develop

Tests

cargo test                 # Rust unit tests
uv run pytest              # Python tests (rebuild with `maturin develop` if Rust changed)
bash scripts/e2e.sh        # CLI smoke test against fixtures in examples/

examples/ contains one folder per built-in rule. Each folder has its own pyproject.toml that selects only that rule, plus bad*.py fixtures that must be flagged and good*.py fixtures that must not. scripts/e2e.sh greps the CLI output to confirm both halves; CI runs it on every push.

Linting and formatting

Before opening a PR, run the same checks CI does:

# Rust
cargo fmt --all -- --check          # formatting
cargo clippy --all-targets -- -D warnings   # lints (fails on any warning)
cargo audit                          # CVEs in dependencies (install with `cargo install cargo-audit`)

# Python
uv run ruff check .                  # lint
uv run ruff format --check .         # format check

To auto-fix:

cargo fmt --all
cargo clippy --fix --all-targets
uv run ruff check --fix .
uv run ruff format .

Releasing

Releases are driven by git tags of the form vX.Y.Z. To cut one:

  1. Bump version in pyproject.toml, commit, and merge to main.
  2. Tag main and push: git tag vX.Y.Z && git push origin vX.Y.Z.

Pushing the tag triggers .github/workflows/release.yml, which verifies the tag matches the pyproject.toml version, builds the sdist + wheels for all platforms, publishes to PyPI, and creates a GitHub Release with the artifacts and auto-generated notes attached. If the tag/version check fails the run aborts before anything is published.

License

MIT — see LICENSE.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

jg_lint-0.7.0.tar.gz (29.3 kB view details)

Uploaded Source

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

jg_lint-0.7.0-cp315-cp315t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (1.3 MB view details)

Uploaded CPython 3.15tmanylinux: glibc 2.17+ x86-64

jg_lint-0.7.0-cp315-cp315-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (1.3 MB view details)

Uploaded CPython 3.15manylinux: glibc 2.17+ x86-64

jg_lint-0.7.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (1.3 MB view details)

Uploaded CPython 3.14tmanylinux: glibc 2.17+ x86-64

jg_lint-0.7.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl (1.3 MB view details)

Uploaded CPython 3.14tmanylinux: glibc 2.17+ ARM64

jg_lint-0.7.0-cp314-cp314-win_amd64.whl (1.0 MB view details)

Uploaded CPython 3.14Windows x86-64

jg_lint-0.7.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (1.3 MB view details)

Uploaded CPython 3.14manylinux: glibc 2.17+ x86-64

jg_lint-0.7.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl (1.3 MB view details)

Uploaded CPython 3.14manylinux: glibc 2.17+ ARM64

jg_lint-0.7.0-cp314-cp314-macosx_11_0_arm64.whl (1.2 MB view details)

Uploaded CPython 3.14macOS 11.0+ ARM64

jg_lint-0.7.0-cp314-cp314-macosx_10_12_x86_64.whl (1.2 MB view details)

Uploaded CPython 3.14macOS 10.12+ x86-64

File details

Details for the file jg_lint-0.7.0.tar.gz.

File metadata

  • Download URL: jg_lint-0.7.0.tar.gz
  • Upload date:
  • Size: 29.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for jg_lint-0.7.0.tar.gz
Algorithm Hash digest
SHA256 72ee1cad382e603fdb6af96c12800f336988af2152ab36629b5cdec55be3f416
MD5 599746a826f196a01759439ad1c38447
BLAKE2b-256 62c079a92209199ea210b983d9c973825ca48d3de759a791334d3cc56c2bd48d

See more details on using hashes here.

Provenance

The following attestation bundles were made for jg_lint-0.7.0.tar.gz:

Publisher: release.yml on jangia/jg-lint

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file jg_lint-0.7.0-cp315-cp315t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for jg_lint-0.7.0-cp315-cp315t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 d92b773121eab4fa419057fad30aa6fb64f61ab050b8a9666dce755c9b42653b
MD5 c720041ff7925f7c753cd8628859c736
BLAKE2b-256 3dc0a2ff2ea4f23900b0ebe3d12389636bc7781cef6290ee985743e801f2ac3f

See more details on using hashes here.

Provenance

The following attestation bundles were made for jg_lint-0.7.0-cp315-cp315t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl:

Publisher: release.yml on jangia/jg-lint

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file jg_lint-0.7.0-cp315-cp315-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for jg_lint-0.7.0-cp315-cp315-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 72c04f7acfff96b149235141c5d7f6cc4ade7369b380db830f8172549ec4a0c8
MD5 3633c08242190b8f2177de36ecd05f3a
BLAKE2b-256 5a2611f5fa26e0381bf29bbfe55f481c60d7b52fa7b0ce08b73b05eb8a793ad6

See more details on using hashes here.

Provenance

The following attestation bundles were made for jg_lint-0.7.0-cp315-cp315-manylinux_2_17_x86_64.manylinux2014_x86_64.whl:

Publisher: release.yml on jangia/jg-lint

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file jg_lint-0.7.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for jg_lint-0.7.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 2ef2d5dd028471e3958f2d571492d1705d0f51579e56b0dbf465b092e5d57388
MD5 7d14857ad2fc07cf1bab69937c2096e5
BLAKE2b-256 b03070a5973636ecc98a84dc95f07f681ca8568350d9b45c7bb1213ba102f228

See more details on using hashes here.

Provenance

The following attestation bundles were made for jg_lint-0.7.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl:

Publisher: release.yml on jangia/jg-lint

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file jg_lint-0.7.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl.

File metadata

File hashes

Hashes for jg_lint-0.7.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm Hash digest
SHA256 812fd4f23b0f1457905df9f062823c2d4c0f2304ec0b5a183950c5cc89a44217
MD5 c5bff011957037efe3bbca288b13d745
BLAKE2b-256 6d765f382ed5c90d7e8e35fccec76f97cf750463a2a20c0f340b2c66832a868f

See more details on using hashes here.

Provenance

The following attestation bundles were made for jg_lint-0.7.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl:

Publisher: release.yml on jangia/jg-lint

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file jg_lint-0.7.0-cp314-cp314-win_amd64.whl.

File metadata

  • Download URL: jg_lint-0.7.0-cp314-cp314-win_amd64.whl
  • Upload date:
  • Size: 1.0 MB
  • Tags: CPython 3.14, Windows x86-64
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for jg_lint-0.7.0-cp314-cp314-win_amd64.whl
Algorithm Hash digest
SHA256 4e90326b7b7293fbe3339de8fc0c6777a24d1eafac67b7e2dca4ef28342e5c48
MD5 6887f7cecebc974d162563550f590aa3
BLAKE2b-256 cde8f187d891850ff1267d6d981f5bb9797b675fb1f10c1fd7f84ceec12f4dee

See more details on using hashes here.

Provenance

The following attestation bundles were made for jg_lint-0.7.0-cp314-cp314-win_amd64.whl:

Publisher: release.yml on jangia/jg-lint

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file jg_lint-0.7.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for jg_lint-0.7.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 d4c3ccbb46416d98e3166ebbf92079494bb0945d9e1d2a70348c5f542478ecfb
MD5 1a466e9e650f258bf6823f6d1ab948b1
BLAKE2b-256 7195928a5e5dcc85eb7b4c1c33cac7e9749d36644be36e873192bf4b47029976

See more details on using hashes here.

Provenance

The following attestation bundles were made for jg_lint-0.7.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl:

Publisher: release.yml on jangia/jg-lint

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file jg_lint-0.7.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl.

File metadata

File hashes

Hashes for jg_lint-0.7.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm Hash digest
SHA256 f8bb2d11fddc4090ca2c4f1e7fceb079af4c7d0a39d8f1349016f02b01155673
MD5 1e6c51bcde58a3b9af7ae9879dcb21c3
BLAKE2b-256 4bb21b24b3a2bc24b4c3904f5932cc7fd315868c4a0e083a54a9a0bd75c5e007

See more details on using hashes here.

Provenance

The following attestation bundles were made for jg_lint-0.7.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl:

Publisher: release.yml on jangia/jg-lint

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file jg_lint-0.7.0-cp314-cp314-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for jg_lint-0.7.0-cp314-cp314-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 b5a07d78fb5e27223337f72d40abe1cc065aca525920e1cff288d0e0c6185edf
MD5 088ec3fdcdfdd7a1cb6f675b2f5f7a03
BLAKE2b-256 b85e2851c5907a23f1fb7fa1ae84f3fe67211ef36ba03fdd0e6005b1a471ef69

See more details on using hashes here.

Provenance

The following attestation bundles were made for jg_lint-0.7.0-cp314-cp314-macosx_11_0_arm64.whl:

Publisher: release.yml on jangia/jg-lint

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file jg_lint-0.7.0-cp314-cp314-macosx_10_12_x86_64.whl.

File metadata

File hashes

Hashes for jg_lint-0.7.0-cp314-cp314-macosx_10_12_x86_64.whl
Algorithm Hash digest
SHA256 22a4117be198e1445fd9725b1bba934e3f67fbc2d971358872ba3ee41e86562f
MD5 5d66c2f1e54755c4220947d49125242b
BLAKE2b-256 5a313eb27877bd84fd299b6443c511a52a4bcd281da4e669a989018daa2c34a1

See more details on using hashes here.

Provenance

The following attestation bundles were made for jg_lint-0.7.0-cp314-cp314-macosx_10_12_x86_64.whl:

Publisher: release.yml on jangia/jg-lint

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page