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/**"]),
)
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"],
)
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():
| Linter | Purpose | Check mode | Fix mode |
|---|
| Black | Code formatter | --check --diff | Formats in-place |
| isort | Import sorter | --check-only --diff | Sorts in-place |
| flake8 | Style checker | --show-source --statistics | No auto-fix |
| Ruff | Fast linter | check --show-source | check --fix |
Node.js / TypeScript linters
Registered via register_nodejs_linters():
| Linter | Purpose | Check mode | Fix mode |
|---|
| Prettier | Code formatter | --check | --write |
| ESLint | JavaScript/TypeScript linter | --format stylish | --fix |
| tsc | TypeScript type checker | --noEmit --pretty | No auto-fix |
| Biome | Fast all-in-one linter | check --diagnostic-level=warn | check --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
| Option | Type | Description |
|---|
name | string | Unique identifier for the linter. |
language | string | Target language (e.g., "python", "nodejs", "go"). |
executable | string | Linter binary path, name, or protocol reference (see below). |
check_args | list | Arguments for lint checking mode. |
fix_args | list | Arguments for auto-fix mode. |
file_patterns | list | Glob patterns for files to lint (default: ["*"]). |
exclude_patterns | list | Glob patterns for files to exclude. |
config_file | string | Optional path to a linter configuration file. |
environment | dict | Environment variables to set when running the linter. |
success_exit_codes | list | Exit codes that indicate success (default: [0]). |
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”:
- Check that the external dependency is declared in
WORKSPACE.rbs.
- Verify the linter is registered with
register_*_linters().
- 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__/**"],
)