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 or ./rules/no_todo/__init__.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


def get_rules() -> list[Rule]:
    return [NoTodoComments()]

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"

Every top-level .py file and package inside rules_path is imported automatically; any module exposing get_rules() contributes rules. Files and folders starting with _ are skipped. The rules directory is prepended to sys.path during loading, so no PYTHONPATH setup is needed.

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.6.0.tar.gz (28.4 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.6.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.6.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.6.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.6.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.6.0-cp314-cp314-win_amd64.whl (1.0 MB view details)

Uploaded CPython 3.14Windows x86-64

jg_lint-0.6.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.6.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.6.0-cp314-cp314-macosx_11_0_arm64.whl (1.1 MB view details)

Uploaded CPython 3.14macOS 11.0+ ARM64

jg_lint-0.6.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.6.0.tar.gz.

File metadata

  • Download URL: jg_lint-0.6.0.tar.gz
  • Upload date:
  • Size: 28.4 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.6.0.tar.gz
Algorithm Hash digest
SHA256 7d58b319bbf4ca54a7e233d625fcc68a9fa30b7f240d2eb2dacb5ca01cf0b9a0
MD5 15b01dbd9c2c3631a9361a2cc2269e73
BLAKE2b-256 08040a1db5c18d739da37fba66e7675fd84f2ec4ec776ebe49ec1d77afd65ca6

See more details on using hashes here.

Provenance

The following attestation bundles were made for jg_lint-0.6.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.6.0-cp315-cp315t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for jg_lint-0.6.0-cp315-cp315t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 266a8623dc0ac8f6eac47d5cf4b33111202dd8d5343cdf7e5330c4267d4967a6
MD5 5265bb9955fba8944d98e39f61670478
BLAKE2b-256 456f2240d0dcdebe42f32f05aeda39044639f18f233ea2eea98d4938fa14573d

See more details on using hashes here.

Provenance

The following attestation bundles were made for jg_lint-0.6.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.6.0-cp315-cp315-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for jg_lint-0.6.0-cp315-cp315-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 d922a48893934543ef158ecce83a71699d59611bf9d72deacd4b65cc72f788dc
MD5 635ee7273bdc7d6d7b35b57c96e99a6a
BLAKE2b-256 8da244928df744f41cb7a7b661624f50a2a86de6d3672bf17102967b0abc1ae9

See more details on using hashes here.

Provenance

The following attestation bundles were made for jg_lint-0.6.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.6.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for jg_lint-0.6.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 88f02f9f3063ab60632a867c02396fc87f00dd7dfd28c037013386062849876f
MD5 787dc51ace1a48f4f89d419f38a30f72
BLAKE2b-256 e03a793e5895accc60d1fa75489659454da4252bf87b150aa09e11f23618b75a

See more details on using hashes here.

Provenance

The following attestation bundles were made for jg_lint-0.6.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.6.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl.

File metadata

File hashes

Hashes for jg_lint-0.6.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm Hash digest
SHA256 032b3dffbebd4e0bd83601491917b07167c72d217029f13776b7c5b1a763bbe8
MD5 0d10f234857a27f98338111e12b2b06a
BLAKE2b-256 29705e46ce2af0981222b21bc944006d3754161093c35112a63a28ecf24fd900

See more details on using hashes here.

Provenance

The following attestation bundles were made for jg_lint-0.6.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.6.0-cp314-cp314-win_amd64.whl.

File metadata

  • Download URL: jg_lint-0.6.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.6.0-cp314-cp314-win_amd64.whl
Algorithm Hash digest
SHA256 7a417e3064802c7373bf2ed52d9f337c665e27a788974afd14922593fcacc205
MD5 5c9ace7c0dfded2ef821b6a7c144a9eb
BLAKE2b-256 e738b321ff8bfe875bd98b988cca00fbc7cc9d8fdefc23543c647b6694b74ea4

See more details on using hashes here.

Provenance

The following attestation bundles were made for jg_lint-0.6.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.6.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for jg_lint-0.6.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 2e3c03148ab5246bfda386fb790490576a26f1ec7f4531001c2e2f6ed2cb980b
MD5 141aa2ca1eb429b39f84318d85fdff46
BLAKE2b-256 c5648736e309f0be3a17d05dedcd1a073bc0031def980086be67eaab9619d8dc

See more details on using hashes here.

Provenance

The following attestation bundles were made for jg_lint-0.6.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.6.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl.

File metadata

File hashes

Hashes for jg_lint-0.6.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm Hash digest
SHA256 f1fdea9376fe93286db552f2b0eb105f67a4a59b3225c129fbeb18fbcd45ca62
MD5 ca06bb90a3857ab96488a3b5891a0d4f
BLAKE2b-256 7394f260a76e5192bc382f943afded4a9eb1f39553e77205c942143192379a1c

See more details on using hashes here.

Provenance

The following attestation bundles were made for jg_lint-0.6.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.6.0-cp314-cp314-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for jg_lint-0.6.0-cp314-cp314-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 f3a7a68e0a5c8545d957e7ec4f5953ab56eb4827e6463a3f0ef82334d2a06fe8
MD5 741ed1b83865cfb1a2c5564e19927c20
BLAKE2b-256 214a5389a23644e26983dd4d22be0552983d7f830a09313d778ed801db7b4501

See more details on using hashes here.

Provenance

The following attestation bundles were made for jg_lint-0.6.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.6.0-cp314-cp314-macosx_10_12_x86_64.whl.

File metadata

File hashes

Hashes for jg_lint-0.6.0-cp314-cp314-macosx_10_12_x86_64.whl
Algorithm Hash digest
SHA256 7b8b2ec961dec0ff9ae984554ed1f52cc1a67e1dc70e5f1db995f52187a35f60
MD5 62c895ce89b5701a77c0bd55418a8602
BLAKE2b-256 2313f61b10dc50e1c473aaa3066fa99e232918a62b44f998dd7036cb7f6ac8cc

See more details on using hashes here.

Provenance

The following attestation bundles were made for jg_lint-0.6.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