Skip to content

Conversation

@cgrindel
Copy link
Contributor

@cgrindel cgrindel commented Oct 14, 2025

Summary

Adds support for RuboCop linter for Ruby code in rules_lint, following the established patterns for language-specific linters (similar to flake8 for Python and eslint for JavaScript).

Implementation

Core Features

  • RuboCop linter aspect (lint/rubocop.bzl) that visits rb_binary, rb_library, and rb_test rules
  • Bundler integration with rules_ruby for hermetic gem management
  • SARIF report generation for machine-readable output
  • Fix mode support with --autocorrect-all via patcher helper
  • Color output support with RUBOCOP_FORCE_COLOR environment variable

Architecture

  • Uses rules_ruby's bundle_fetch() to manage RuboCop gem dependencies
  • Pinned to RuboCop 1.81.1 via Gemfile.lock for reproducible builds
  • Properly registers Ruby toolchains for Bazel integration
  • Handles sandbox permissions with XDG_CACHE_HOME workaround

Java Runtime Requirements

  • JRuby 10.0.2.0 requires Java 21 or higher
  • Example includes Java 21 configuration (example/tools/java21.bazelrc)
  • Gemfile.lock generated with Java platform support (universal-java, universal-java-21)
  • Java-specific gem versions avoid C extension compilation in Bazel sandbox

Example Configuration

Gemfile:

gem "rubocop", "~> 1.50"

.ruby-version:

jruby-10.0.2.0

MODULE.bazel:

ruby = use_extension("@rules_ruby//ruby:extensions.bzl", "ruby")
ruby.toolchain(
    name = "ruby",
    version_file = "//:.ruby-version",
)
ruby.bundle_fetch(
    name = "bundle",
    gem_checksums = {
        # ... gem checksums including Java-specific versions
        "json-2.15.1-java": "a6185eebe724a6937f60729e4998276d6b3de3ecc35be34f8e47c1eb40903ecf",
        "racc-1.8.1-java": "54f2e6d1e1b91c154013277d986f52a90e5ececbe91465d29172e49342732b98",
    },
    gemfile = "//:Gemfile",
    gemfile_lock = "//:Gemfile.lock",
)

tools/lint/linters.bzl:

load("@aspect_rules_lint//lint:rubocop.bzl", "lint_rubocop_aspect")

rubocop = lint_rubocop_aspect(
    binary = "//tools/lint:rubocop",
    configs = ["//:rubocop.yml"],
)

Testing

  • Added example Ruby file with intentional violations (example/src/hello.rb)
  • Integration test in example/test:rubocop (marked manual, expected to fail)
  • Updated example/test/lint_test.bats to verify RuboCop output
  • Verified linter detects 12 offenses including:
    • Missing frozen string literal comment
    • Unused variable assignments
    • String literal style violations
    • Line length violations
    • Indentation issues

🤖 Generated with Claude Code

@aspect-workflows
Copy link

aspect-workflows bot commented Oct 14, 2025

Test

All tests were cache hits

5 tests (100.0%) were fully cached saving 2s.


Test

example

8 test targets passed

Targets
//tools/format:format_test_HTML_Jinja_with_djlint [k8-fastbuild]3s
//tools/format:format_test_JavaScript_with_prettier [k8-fastbuild]17s
//tools/format:format_test_Markdown_with_prettier [k8-fastbuild]3s
//tools/format:format_test_Protocol_Buffer_with_buf [k8-fastbuild]814ms
//tools/format:format_test_Python_with_ruff [k8-fastbuild]447ms
//tools/format:format_test_SQL_with_prettier [k8-fastbuild]2s
//tools/format:format_test_Scala_with_scalafmt [k8-fastbuild]5s
//tools/format:format_test_Starlark_with_buildifier [k8-fastbuild]416ms

Total test execution time was 32s. 33 tests (80.5%) were fully cached saving 14s.


Test (WORKSPACE) (Test)

example

8 test targets passed

Targets
//tools/format:format_test_HTML_Jinja_with_djlint [k8-fastbuild]3s
//tools/format:format_test_JavaScript_with_prettier [k8-fastbuild]8s
//tools/format:format_test_Markdown_with_prettier [k8-fastbuild]1s
//tools/format:format_test_Protocol_Buffer_with_buf [k8-fastbuild]656ms
//tools/format:format_test_Python_with_ruff [k8-fastbuild]240ms
//tools/format:format_test_SQL_with_prettier [k8-fastbuild]976ms
//tools/format:format_test_Scala_with_scalafmt [k8-fastbuild]4s
//tools/format:format_test_Starlark_with_buildifier [k8-fastbuild]238ms

Total test execution time was 19s. 13 tests (61.9%) were fully cached saving 8s.


Lint [.]      Lint [example]

cgrindel and others added 5 commits October 14, 2025 12:06
Implements RuboCop linter integration for rules_lint, enabling Ruby code
linting with support for rb_library, rb_binary, and rb_test targets from
rules_ruby.

Changes:
- Add lint/rubocop.bzl with full RuboCop aspect implementation
  - rubocop_action() for running RuboCop as a Bazel action
  - rubocop_fix() for autocorrect support with patch generation
  - lint_rubocop_aspect() factory function targeting Ruby rule kinds
  - Support for .rubocop.yml configuration files
  - SARIF output for machine-readable reports
  - Color output support via RUBOCOP_FORCE_COLOR

- Add rules_ruby integration to example workspace
  - Add [email protected] dependency to example/MODULE.bazel
  - Configure Ruby 3.3.0 toolchain
  - Create rb_library target for example Ruby code

- Add example files demonstrating RuboCop
  - example/src/hello.rb with intentional style violations
  - example/.rubocop.yml with common RuboCop configuration
  - example/tools/lint/rubocop_wrapper.sh for RuboCop execution

- Add integration tests
  - rubocop_test in example/test/BUILD.bazel
  - Test configuration in example/tools/lint/linters.bzl

- Update documentation
  - Add Ruby/RuboCop to main README.md supported tools table
  - Add bzl_library entry in lint/BUILD.bazel
  - Comprehensive docstrings with usage examples

The implementation follows the same patterns as existing linters (ruff,
flake8, eslint) and properly generates both human-readable and
machine-readable outputs with exit codes.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
Replace the simple wrapper script approach with Bundler-based gem
management using rules_ruby, following best practices similar to how
other linters (flake8, eslint) handle language-specific package managers.

Changes:
- Add Gemfile and Gemfile.lock with rubocop ~> 1.50 dependency
- Configure rules_ruby bundle_fetch in MODULE.bazel to manage gems
- Register Ruby toolchains for proper Bazel integration
- Replace sh_binary wrapper with alias to @bundle//bin:rubocop
- Remove rubocop_wrapper.sh script (no longer needed)
- Update rubocop.bzl documentation with Bundler setup instructions
- Add --server false flag to disable RuboCop server mode
- Add XDG_CACHE_HOME workaround for sandbox cache directory
- Wrap all lines to 80 characters for consistency
- Redirect stderr to stdout (2>&1) for better error visibility

Benefits:
- Hermetic builds with pinned gem versions (RuboCop 1.81.1)
- Consistent RuboCop versions across all developers
- Full integration with Bazel's dependency management
- No external Ruby installation required

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
Copy link
Member

@alexeagle alexeagle left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice, could you update lint-test.bats also, similar to https://github.com/aspect-build/rules_lint/pull/641/files ?

cgrindel and others added 9 commits October 14, 2025 14:37
- Add .ruby-version file specifying jruby-10.0.2.0
- Create java21.bazelrc and update .bazelrc to use Java 21
- Regenerate Gemfile.lock with Java platform support (universal-java)
- Add checksums for Java-specific gems (json-2.15.1-java, racc-1.8.1-java)
- Update MODULE.bazel and WORKSPACE.bazel to reference .ruby-version
- Remove --server false flag from RuboCop invocations (it's the default)

JRuby 10 requires Java 21+ and provides Ruby 3.4 compatibility.
The Java platform gems avoid C extension compilation issues in Bazel sandbox.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
With the upgrade to Java 21 for JRuby 10 support, java17.bazelrc is no
longer referenced and can be removed.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
… var

RuboCop does not support a RUBOCOP_FORCE_COLOR environment variable.
Instead, color output is controlled via the --color command-line flag.

Changes:
- Add color parameter to rubocop_action() and rubocop_fix() functions
- Pass --color flag when color=True
- Remove incorrect RUBOCOP_FORCE_COLOR environment variable usage
- Pass color option from LintOptionsInfo to action functions

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
RuboCop's internal caching provides no benefit in Bazel since:
- Bazel caches actions at a higher level based on input hashes
- Each action runs in an isolated sandbox with ephemeral directories
- The .rubocop_cache directory is discarded after each action

Changes:
- Add --cache false flag to all RuboCop invocations
- Remove cache directory creation (mkdir -p .rubocop_cache)
- Remove XDG_CACHE_HOME environment variable management
- Simplify shell commands in rubocop_action(), rubocop_fix(), and JSON report generation

This makes the code simpler, more efficient, and consistent.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
Consolidate duplicated RuboCop command construction logic into a single
_build_rubocop_command() helper function.

Changes:
- Add _build_rubocop_command() to build shell commands with consistent patterns
- Use helper in both rubocop_action() and JSON report generation
- Fix inconsistency: remove "|| true" pattern, use consistent error handling
- Remove unnecessary "touch" command (shell redirect creates the file)
- Simplify callers by eliminating manual .format() calls

Benefits:
- Eliminates code duplication
- Ensures consistent command patterns across all RuboCop invocations
- Makes future command changes easier to maintain

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
Add missing progress_message parameter to the machine-readable (JSON)
output action for consistency with the human-readable action.

This provides better visibility during builds by showing:
"Generating machine-readable report for <target> with RuboCop"

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
Remove the unused env parameter from rubocop_action() and rubocop_fix()
since we don't need custom environment variables for RuboCop.

Changes:
- Remove env parameter from rubocop_action() signature and usage
- Remove env parameter from rubocop_fix() signature
- Simplify env dict in rubocop_fix() (no longer merging with empty dict)
- Remove env argument from all function calls

The patcher in rubocop_fix() still gets the JS_BINARY_* environment
variables it needs, but we no longer pass around an unnecessary empty dict.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
Enhance documentation clarity by:
- Making parameter types explicit (File objects, lists, booleans)
- Adding examples for Label parameters in lint_rubocop_aspect()
- Improving consistency between function docstrings
- Adding explanatory comments to JSON args section

Changes:
- rubocop_action(): Clarify that parameters are File objects and lists
- rubocop_fix(): Improve parameter type documentation
- lint_rubocop_aspect(): Add concrete examples for binary and configs
- JSON args: Add comments explaining why each flag is used

This makes the API more understandable and consistent with other linters.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
@cgrindel cgrindel marked this pull request as ready for review October 14, 2025 22:56
@cgrindel cgrindel requested a review from alexeagle October 14, 2025 22:56
@cgrindel
Copy link
Contributor Author

cgrindel commented Oct 19, 2025

@alexeagle I think this PR is ready for review. I noticed the CI / aspect timed out. Is there something that I need to address there?

@alexeagle alexeagle merged commit b9e403c into aspect-build:main Nov 5, 2025
6 of 7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants