Skip to main content

Lint SDK

The Lint SDK provides a language-agnostic linting framework for RBS. Register linters for any language — Black, ESLint, Prettier, Ruff, Biome, or your own custom linter — and run them as part of your build pipeline. Linting failures cause builds to exit with code 1, making the SDK ideal for CI/CD enforcement.
Linters are not pre-installed. RBS downloads and manages them as external dependencies, following the hermetic build principle. No system-level tool installation required.

Quick start

Python projects

# WORKSPACE.rbs
load("@rbs//python/toolchain.rbs", "python_toolchain")
load("@rbs//python/lint.rbs", "register_python_linters")

# Auto-downloads and registers Python toolchain
python_toolchain(name = "python3", version = "3.12")

# Register linters (downloads Black and isort automatically)
register_python_linters(
    black_version = "24.1.1",
    isort_version = "5.13.2",
)
# BUILD.rbs
load("@rbs//python/lint.rbs", "py_lint")

py_lint(
    name = "lint",
    srcs = glob(["**/*.py"], exclude = [".rbs/**", "venv/**"]),
)
rbs build //:lint

Node.js / TypeScript projects

# WORKSPACE.rbs
load("@rbs//nodejs/toolchain.rbs", "nodejs_toolchain")
load("@rbs//nodejs/lint.rbs", "register_nodejs_linters")

# Auto-downloads and registers Node.js toolchain
nodejs_toolchain(name = "nodejs", version = "20.11.0")

# Register linters (downloads Prettier automatically)
register_nodejs_linters(
    prettier_version = "3.2.0",
    eslint_version = None,  # Skip ESLint if not needed
)
# BUILD.rbs
load("@rbs//nodejs/lint.rbs", "nodejs_lint")

nodejs_lint(
    name = "lint",
    srcs = glob(["src/**/*.ts"], exclude = ["node_modules/**", ".rbs/**"]),
    linters = ["prettier"],
)
rbs build //:lint

CI/CD integration

The Lint SDK causes builds to exit 1 when linting fails, making it a natural fit for CI pipelines:
# BUILD.rbs
py_lint(
    name = "lint",
    srcs = glob(["**/*.py"], exclude = [".rbs/**"]),
)
# In CI — exits with code 1 if any file fails linting
rbs build //:lint

Pre-defined lint rules

Python: py_lint

Runs Black and isort on Python files:
load("@rbs//python/lint.rbs", "py_lint")

py_lint(
    name = "lint",
    srcs = glob(["**/*.py"], exclude = [".rbs/**", "venv/**", "__pycache__/**"]),
)

Node.js: nodejs_lint

Runs Prettier (and optionally ESLint) on JavaScript/TypeScript files:
load("@rbs//nodejs/lint.rbs", "nodejs_lint")

nodejs_lint(
    name = "lint",
    srcs = glob(["src/**/*.ts"], exclude = ["node_modules/**", "dist/**", ".rbs/**"]),
    linters = ["prettier"],  # Or ["prettier", "eslint"]
)

Pre-defined linters

Python linters

Registered via register_python_linters():
LinterPurposeCheck modeFix mode
BlackCode formatter--check --diffFormats in-place
isortImport sorter--check-only --diffSorts in-place
flake8Style checker--show-source --statisticsNo auto-fix
RuffFast lintercheck --show-sourcecheck --fix

Node.js / TypeScript linters

Registered via register_nodejs_linters():
LinterPurposeCheck modeFix mode
PrettierCode formatter--check--write
ESLintJavaScript/TypeScript linter--format stylish--fix
tscTypeScript type checker--noEmit --prettyNo auto-fix
BiomeFast all-in-one lintercheck --diagnostic-level=warncheck --apply

Defining custom linters

Register any linter without modifying the build system — just define it in your configuration:
native.define_linter(
    name = "my_linter",
    language = "python",
    executable = "@python://my-lint:1.0.0:my-lint",
    check_args = ["--check", "--verbose"],
    fix_args = ["--fix"],
    file_patterns = ["*.py"],
    exclude_patterns = ["*_test.py", "__pycache__/*"],
    config_file = ".my-lintrc",
    environment = {
        "PYTHONPATH": "/custom/path",
    },
    success_exit_codes = [0, 1],
)

Configuration options

OptionTypeDescription
namestringUnique identifier for the linter.
languagestringTarget language (e.g., "python", "nodejs", "go").
executablestringLinter binary path, name, or protocol reference (see below).
check_argslistArguments for lint checking mode.
fix_argslistArguments for auto-fix mode.
file_patternslistGlob patterns for files to lint (default: ["*"]).
exclude_patternslistGlob patterns for files to exclude.
config_filestringOptional path to a linter configuration file.
environmentdictEnvironment variables to set when running the linter.
success_exit_codeslistExit codes that indicate success (default: [0]).

Executable protocol format

Reference linter binaries from managed external dependencies using the protocol format:
@{ecosystem}://{package}:{version}:{binary}
Examples:
  • @python://black:24.1.1:black — Python’s Black formatter
  • @nodejs://prettier:3.2.0:prettier — Prettier
  • @nodejs://typescript:5.3.3:tsc — TypeScript compiler
RBS automatically locates the binary in the managed dependency cache, resolving the correct platform and version.

ctx.lint API reference

The ctx.lint module is available inside any rule implementation.

ctx.lint.run()

Run a linter in check mode:
result = ctx.lint.run(
    linter = "black",
    files = ["main.py", "utils.py"],
    working_dir = ".",
    extra_args = ["--verbose"],
)

# result.success  → bool (whether linting passed)
# result.output   → string (stdout/stderr from linter)
# result.exit_code → int
# result.files_linted → int

ctx.lint.fix()

Run a linter in auto-fix mode:
result = ctx.lint.fix(
    linter = "prettier",
    files = ["app.ts", "utils.ts"],
)

ctx.lint.run_all()

Run all registered linters for a language:
result = ctx.lint.run_all(
    language = "python",
    files = ["main.py", "lib.py"],
)

# result.success → True if ALL linters passed
# result.results → list of individual linter results

ctx.lint.fix_all()

Run all linters for a language in fix mode:
result = ctx.lint.fix_all(
    language = "nodejs",
    files = glob(["**/*.ts"]),
)

ctx.lint.list()

List all registered linters:
# List all linters
all_linters = ctx.lint.list()

# List only Python linters
python_linters = ctx.lint.list(language = "python")

ctx.lint.get_linter()

Get a specific linter’s configuration:
linter = ctx.lint.get_linter(name = "black")
if linter:
    print("Executable:", linter.executable)
    print("Check args:", linter.check_args)

Complete examples

Django project

# WORKSPACE.rbs
load("@rbs//python/toolchain.rbs", "python_toolchain")
load("@rbs//python/lint.rbs", "register_python_linters")
load("@rbs//python/dependencies.rbs", "py_repository")

python_toolchain(name = "python3", version = "3.12")

register_python_linters(
    black_version = "24.1.1",
    isort_version = "5.13.2",
)

py_repository(name = "django_repo", package = "django", version = "5.0.1")
# BUILD.rbs
load("@rbs//python/rules.rbs", "py_binary")
load("@rbs//python/lint.rbs", "py_lint")

py_binary(
    name = "server",
    srcs = glob(["**/*.py"]),
    main = "manage.py",
    deps = [":django_repo"],
)

py_lint(
    name = "lint",
    srcs = glob(["**/*.py"], exclude = [".rbs/**", "venv/**", "__pycache__/**"]),
)

TypeScript project

# WORKSPACE.rbs
load("@rbs//nodejs/toolchain.rbs", "nodejs_toolchain")
load("@rbs//nodejs/lint.rbs", "register_nodejs_linters")
load("@rbs//nodejs/dependencies.rbs", "nodejs_repository")

nodejs_toolchain(name = "nodejs", version = "20.11.0")

register_nodejs_linters(prettier_version = "3.2.0")

nodejs_repository(name = "express_repo", package = "express", version = "4.18.2")
nodejs_repository(name = "typescript_repo", package = "typescript", version = "5.3.3", host = True)
# BUILD.rbs
load("@rbs//nodejs/rules.rbs", "nodejs_binary")
load("@rbs//nodejs/lint.rbs", "nodejs_lint")

nodejs_binary(
    name = "server",
    srcs = glob(["src/**/*.ts"]),
    main = "src/server.ts",
    deps = [":express_repo", ":typescript_repo"],
)

nodejs_lint(
    name = "lint",
    srcs = glob(["src/**/*.ts"], exclude = ["node_modules/**", "dist/**", ".rbs/**"]),
)

Adding support for a new ecosystem

The SDK-first design means you can add linter support for any language without modifying the build system:
# Register a new linter for any language
native.define_linter(
    name = "golangci-lint",
    language = "go",
    executable = "golangci-lint",
    check_args = ["run"],
    fix_args = ["run", "--fix"],
    file_patterns = ["*.go"],
    success_exit_codes = [0],
)

Troubleshooting

Linter not found

If you see “executable file not found”:
  1. Check that the external dependency is declared in WORKSPACE.rbs.
  2. Verify the linter is registered with register_*_linters().
  3. Confirm the package was downloaded (check .rbs/external-deps/).

Wrong files being linted

Use glob() with exclude patterns to skip generated files and dependencies:
srcs = glob(
    ["**/*.py"],
    exclude = [".rbs/**", "venv/**", ".venv/**", "__pycache__/**"],
)