Skip to content

Add RuboCop linting and newline enforcement#193

Open
justin808 wants to merge 11 commits into
masterfrom
justin808/add-rubocop-linting
Open

Add RuboCop linting and newline enforcement#193
justin808 wants to merge 11 commits into
masterfrom
justin808/add-rubocop-linting

Conversation

@justin808
Copy link
Copy Markdown
Member

@justin808 justin808 commented Oct 2, 2025

Summary

Adds comprehensive linting and code quality enforcement using RuboCop.

Changes

1. RuboCop Configuration

  • Added RuboCop with extensions (rubocop-rake, rubocop-rspec)
  • Created .rubocop.yml with sensible defaults
  • Enforces frozen string literals, trailing newlines, consistent style

2. Rake Tasks (rakelib/lint.rake)

  • rake lint - Run all linters
  • rake lint:fix - Auto-fix issues
  • rake check_newlines - Verify files end with newlines
  • rake fix_newlines - Fix missing newlines
  • rake ci - Run all checks (specs, lint, newlines)

3. Pre-commit Hook (bin/install-hooks)

  • Installable script for git pre-commit hook
  • Checks newlines on all files
  • Runs RuboCop on staged Ruby files
  • Prevents commits with issues

4. GitHub Actions (.github/workflows/lint.yml)

  • Runs on all PRs and pushes to master
  • Enforces linting in CI
  • Checks for missing newlines

Benefits

  • ✅ Consistent code style across the project
  • ✅ All files guaranteed to end with newlines
  • ✅ Automated enforcement both locally and in CI
  • ✅ Easy to fix issues with rake lint:fix and rake fix_newlines

Installation

To install pre-commit hook locally:

./bin/install-hooks

This is a separate PR from the release script changes for independent review.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Installer for a Git pre-commit hook that runs lint and trailing-newline checks before commits.
    • Local "ci" task to run specs, linting, and newline validation in one command.
    • Tasks to check and auto-fix missing final newlines; lint auto-correct task added.
  • Chores

    • CI lint workflow added to run on pushes and pull requests.
    • RuboCop configs added/updated to enforce style and suppress existing offenses.
    • Development tooling updated and minimum Ruby requirement raised to >= 3.0; one CI job's Ruby version bumped.

Note

Medium Risk
Medium risk because it introduces a breaking minimum Ruby version bump and new CI/pre-commit gates that can block development/release workflows if misconfigured.

Overview
Adds a dedicated GitHub Actions Lint workflow and updates the existing ruby.yml workflow to run on main and test Rails 6.1 with Ruby 3.0.6.

Introduces RuboCop configuration (.rubocop.yml + .rubocop_todo.yml), new rake tasks in rakelib/lint.rake for lint, lint:fix, and repository-wide check_newlines/fix_newlines, and wires them into a new rake ci task.

Updates the gemspec to add RuboCop dev dependencies, require Ruby >= 3.0.0, and exclude bin/install-hooks from gem executables; adds bin/install-hooks to install a pre-commit hook that runs staged-file newline checks and RuboCop, and annotates existing eval usage with rubocop:disable Security/Eval plus rationale comments.

Reviewed by Cursor Bugbot for commit f6b7c6d. Bugbot is set up for automated code reviews on this repo. Configure here.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Oct 2, 2025

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Adds linting infrastructure: CI lint workflow, RuboCop configs and todo, Rake lint/newline tasks, a pre-commit hook installer, RuboCop dev dependencies, and updates Ruby to 3.0.6 for the rails_6_1 CI job.

Changes

Cohort / File(s) Summary
CI Lint Workflow
.github/workflows/lint.yml
New GitHub Actions workflow triggering on push/PR to main/master; checks out repo, sets up Ruby 3.0.6 with Bundler cache, runs bundle exec rubocop and bundle exec rake check_newlines.
CI Job Update
.github/workflows/ruby.yml
Updated rails_6_1 job Ruby version: 2.7.63.0.6.
RuboCop Configs
.rubocop.yml, .rubocop_todo.yml
Adds project RuboCop config (inherits todo), enables rubocop-rake and rubocop-rspec, sets TargetRubyVersion 3.0 and many per-cop settings/excludes recorded in generated todo file.
Rake and Lint Tasks
Rakefile, rakelib/lint.rake
Adds ci aggregate task and lint tasks: rubocop, rubocop:autocorrect, lint, lint:fix, check_newlines, fix_newlines; introduces binary_file? helper and MAX_FILE_SIZE.
Git Hook Installer
bin/install-hooks
Adds installer script to write a Bash pre-commit hook that runs rake check_newlines and RuboCop on staged Ruby files; creates hooks dir, writes hook, sets executable bit, and provides error messages and usage guidance.
Gemspec / Ruby Requirement
cypress-on-rails.gemspec
Adds development dependencies: rubocop (~> 1.81), rubocop-rake (~> 0.7), rubocop-rspec (~> 3.7) and sets s.required_ruby_version = '>= 3.0.0'.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor Dev as Developer
  participant GH as GitHub
  participant GA as Actions: Lint Job
  participant Runner as ubuntu-latest
  participant Bundler as Bundler/Ruby
  participant Rake as Rake

  Dev->>GH: Push / Open PR to main/master
  GH-->>GA: Trigger lint workflow
  GA->>Runner: checkout repo
  GA->>Bundler: setup Ruby 3.0.6 + bundler cache
  GA->>Bundler: bundle install
  GA->>Bundler: bundle exec rubocop
  GA->>Rake: bundle exec rake check_newlines
  GA-->>GH: Report status (pass/fail)
Loading
sequenceDiagram
  autonumber
  actor Dev as Developer
  participant Git as Git client
  participant Hook as pre-commit hook
  participant Rake as Rake
  participant RuboCop as RuboCop

  Dev->>Git: git commit
  Git->>Hook: run pre-commit
  Hook->>Rake: bundle exec rake check_newlines
  alt Missing final newline
    Rake-->>Hook: exit non-zero
    Hook-->>Dev: abort commit, print fix instructions
  else All files OK
    Hook->>Hook: detect staged *.rb / *.rake
    alt Ruby files staged
      Hook->>RuboCop: bundle exec rubocop on staged files
      alt Lint failures
        RuboCop-->>Hook: exit non-zero
        Hook-->>Dev: abort commit, print rubocop instructions
      else Clean
        RuboCop-->>Git: zero exit
        Hook-->>Git: allow commit
      end
    else No Ruby files
      Hook-->>Git: allow commit
    end
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • Pay attention to: rakelib/lint.rake newline detection/fixing and binary-file heuristic; pre-commit installer error handling; CI workflow Ruby version consistency and Bundler/cache steps.

Suggested reviewers

  • grantspeelman

Poem

I hop where tidy rubies gleam,
Newlines hum and linters dream.
Hooks stand guard on every commit,
Rake and RuboCop keep code fit.
🥕🐇

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The pull request title 'Add RuboCop linting and newline enforcement' accurately and concisely summarizes the main changes: introducing RuboCop configuration, linting tasks, newline enforcement, and related tooling.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Merge Conflict Detection ✅ Passed ✅ No merge conflicts detected when merging into master
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch justin808/add-rubocop-linting

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@claude
Copy link
Copy Markdown

claude Bot commented Oct 2, 2025

Code Review - PR #193: Add RuboCop linting and newline enforcement

Summary

This PR adds comprehensive linting infrastructure with RuboCop, automated newline enforcement, and CI/CD integration. Overall, this is a well-structured and valuable addition to the project that will improve code quality and consistency.

✅ Strengths

1. Comprehensive Linting Setup

  • Well-configured .rubocop.yml with sensible defaults for a gem project
  • Appropriate exclusions (vendor, tmp, pkg, node_modules, specs_e2e)
  • Target Ruby version 2.6 aligns with gem compatibility needs
  • Good balance between strictness and pragmatism

2. Developer Experience

  • Clear rake tasks with intuitive naming (lint, lint:fix, check_newlines, fix_newlines)
  • Excellent pre-commit hook with helpful error messages and auto-fix suggestions
  • Optional hook installation via ./bin/install-hooks - non-invasive approach
  • CI task consolidation with rake ci

3. CI/CD Integration

  • Separate workflow file keeps concerns separated
  • Uses modern GitHub Actions (checkout@v4, ruby/setup-ruby@v1)
  • Bundler caching for faster CI runs

⚠️ Issues & Suggestions

High Priority

1. Ruby Version Mismatch in CI (.github/workflows/lint.yml:19)
The lint workflow uses Ruby 3.2, but .rubocop.yml targets Ruby 2.6, and the main test workflow tests against 2.7.6, 3.1.3, and 3.2.2. Recommend using Ruby 2.7.6 (minimum supported) to ensure RuboCop rules work across all versions.

2. Missing File Encoding Check in Newline Tasks (rakelib/lint.rake:28, 50)
File.read without explicit encoding may fail on files with non-UTF-8 encoding or binary files. Add encoding handling and rescue for binary files.

3. Inefficient File Globbing (rakelib/lint.rake:23, 45)
Reads all files then filters, including large directories like vendor/ and node_modules/. Exclude directories in glob pattern or use regex-based rejection.

Medium Priority

4. Pre-commit Hook Runs Against All Files (bin/install-hooks:16)
Checks ALL files on every commit, even if only one file changed. This can be slow in large repos. Only check staged files to improve performance.

5. Shell Script Quoting Issue (bin/install-hooks:24, 26)
If filenames contain spaces, the RuboCop command will break. Use xargs for proper handling of filenames with spaces.

6. RuboCop Configuration: Frozen String Literals (.rubocop.yml:54-58)
Enabling this will require adding frozen_string_literal comments to ALL Ruby files in the project. This wasn't mentioned in the PR description. Either run rake lint:fix before merging to add all frozen string literals across the codebase, or start with Enabled: false and enable in a follow-up PR.

7. Missing Version Constraints (cypress-on-rails.gemspec:25-27)
No version constraints could lead to breaking changes when RuboCop releases major versions. Add version constraints like '~> 1.50' for rubocop.

Low Priority

8. Task Description Clarity (rakelib/lint.rake:8-9)
The -A flag does auto-correct all (unsafe corrections too). Consider documenting this in the task description.

9. CI Task Doesn't Match Default (Rakefile:9, 11)
default doesn't include linting, but ci does. This inconsistency means local rake runs won't catch lint issues. Consider whether default should also run lint.

10. Missing CHANGELOG Entry
No CHANGELOG.md entry for these significant changes. Add entry documenting new rake tasks, RuboCop addition, and pre-commit hook.

🔒 Security Considerations

✅ No security concerns identified:

  • Pre-commit hook is opt-in (requires manual installation)
  • No credentials or secrets handling
  • RuboCop extensions are from trusted sources

🧪 Test Coverage

⚠️ Missing Tests:

  • No tests for the new rake tasks (check_newlines, fix_newlines)
  • bin/install-hooks script is untested

Consider adding basic specs to verify the rake tasks work correctly.

📊 Performance Considerations

🎯 Recommendations Summary

Before Merging:

  1. ✋ Fix Ruby version mismatch in lint.yml (Usage instructions #1)
  2. ✋ Add encoding handling to newline tasks (Seems to generate a cypress directory in the main folder #2)
  3. ✋ Either disable frozen string literals or run autofixes on entire codebase (Fixes for first run #6)
  4. 👍 Add version constraints to RuboCop gems (Making cypress on rails more generic #7)
  5. 👍 Add CHANGELOG entry (Add deprecation warnings #10)

Follow-up PRs:

Overall Assessment

LGTM with changes requested 🟡

This is high-quality work that will significantly improve the project's code quality and maintainability. The configuration is sensible, the developer experience is excellent, and the CI integration is clean. The issues identified are mostly minor optimizations, except for items #1, #2, and #6 which should be addressed before merging.

Great job on this PR! 🎉

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Nitpick comments (3)
cypress-on-rails.gemspec (1)

25-27: Pin RuboCop versions to prevent breaking changes.

Development dependencies without version constraints can introduce breaking changes or security vulnerabilities when newer versions are released. Consider pinning to major versions to ensure consistent behavior across environments.

Apply this diff to add version constraints:

-  s.add_development_dependency 'rubocop'
-  s.add_development_dependency 'rubocop-rake'
-  s.add_development_dependency 'rubocop-rspec'
+  s.add_development_dependency 'rubocop', '~> 1.0'
+  s.add_development_dependency 'rubocop-rake', '~> 0.6'
+  s.add_development_dependency 'rubocop-rspec', '~> 3.0'
rakelib/lint.rake (2)

23-29: Consider memory efficiency for large files.

Reading entire files into memory (line 28) could cause issues with large files (e.g., lockfiles, generated assets). Consider checking file size before reading or using a buffered approach that only reads the last bytes.

Apply this diff to add a size check:

     next unless File.file?(file)
 
+    # Skip files larger than 10MB to avoid memory issues
+    next if File.size(file) > 10 * 1024 * 1024
+    
     content = File.read(file)
     files_without_newline << file unless content.empty? || content.end_with?("\n")

Alternatively, for a more robust solution that handles large files:

# Check only the last byte(s) for newline without loading entire file
File.open(file, 'rb') do |f|
  f.seek(-1, IO::SEEK_END) rescue next
  files_without_newline << file unless f.read(1) == "\n"
end

23-25: Extract duplicate file filtering logic.

The file exclusion logic is duplicated between check_newlines and fix_newlines. Extract this into a shared helper method to follow DRY principles and ease maintenance.

Add this helper at the top of the file (after line 1):

# frozen_string_literal: true

def files_to_check
  Dir.glob('**/*.{rb,rake,yml,yaml,md,gemspec,ru,erb,js,json}').each do |file|
    next if file.include?('vendor/') || file.include?('node_modules/') || file.include?('.git/')
    next if file.include?('pkg/') || file.include?('tmp/') || file.include?('coverage/')
    next unless File.file?(file)
    yield file
  end
end

Then update both tasks:

 task :check_newlines do
   files_without_newline = []
 
-  Dir.glob('**/*.{rb,rake,yml,yaml,md,gemspec,ru,erb,js,json}').each do |file|
-    next if file.include?('vendor/') || file.include?('node_modules/') || file.include?('.git/')
-    next if file.include?('pkg/') || file.include?('tmp/') || file.include?('coverage/')
-    next unless File.file?(file)
-
+  files_to_check do |file|
     content = File.read(file)
     files_without_newline << file unless content.empty? || content.end_with?("\n")
   end

Also applies to: 45-47

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8615fc7 and 5c95af2.

📒 Files selected for processing (6)
  • .github/workflows/lint.yml (1 hunks)
  • .rubocop.yml (1 hunks)
  • Rakefile (1 hunks)
  • bin/install-hooks (1 hunks)
  • cypress-on-rails.gemspec (1 hunks)
  • rakelib/lint.rake (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: claude-review
  • GitHub Check: rails_6_1
  • GitHub Check: rails_7_2
  • GitHub Check: rails_8
🔇 Additional comments (3)
rakelib/lint.rake (1)

3-11: LGTM!

The RuboCop task definitions are correct and use safe, hardcoded commands.

Rakefile (1)

8-9: LGTM!

The CI task provides a convenient entry point for running all checks. The task dependencies are correctly defined and the tasks exist in the appropriate files.

.rubocop.yml (1)

5-7: Verify Ruby 2.6 support or update to a supported version
Ruby 2.6 reached EOL in March 2022 and no longer receives security updates. Confirm whether the project must still support 2.6; if not, bump TargetRubyVersion in .rubocop.yml to 2.7 or 3.x.

Comment thread .github/workflows/lint.yml
Comment thread bin/install-hooks Outdated
Comment thread bin/install-hooks Outdated
Comment thread rakelib/lint.rake Outdated
@MUTOgen
Copy link
Copy Markdown
Contributor

MUTOgen commented Oct 3, 2025

LGTM
Let's try this out on CI

@justin808 justin808 force-pushed the justin808/add-rubocop-linting branch from c354937 to 2b30230 Compare October 23, 2025 20:55
@claude
Copy link
Copy Markdown

claude Bot commented Oct 23, 2025

PR Review: Add RuboCop linting and newline enforcement

Summary

This PR adds comprehensive linting infrastructure with RuboCop, custom rake tasks, pre-commit hooks, and CI integration. Overall, this is a well-structured and valuable addition to the project that will improve code quality and consistency.

Strengths

1. Comprehensive Implementation

  • Well-organized rake tasks for linting and newline enforcement
  • Both local pre-commit hooks and CI integration
  • Auto-fix capabilities (rake lint:fix, rake fix_newlines)
  • Clear documentation in the PR description

2. Good RuboCop Configuration

  • Reasonable line length limit (120 chars)
  • Appropriate exclusions for specs, rake tasks, and generated code
  • Plugin support for RSpec and Rake
  • Sensible defaults that aren't overly strict

3. Developer Experience

  • Clear installation script (bin/install-hooks)
  • Helpful error messages with guidance
  • Optional hook bypass with --no-verify
  • Unified CI task for all checks

Issues & Concerns

Critical: Ruby Version Inconsistency

Issue: The PR enforces Ruby 3.0+ but the existing CI workflow still tests against Ruby 2.7.6 for Rails 6.1:

  • .rubocop.yml:6 sets TargetRubyVersion: 3.0
  • cypress-on-rails.gemspec:29 requires >= 3.0.0
  • .github/workflows/lint.yml:19 uses Ruby 3.0
  • BUT .github/workflows/ruby.yml:18 uses Ruby 2.7.6 for the rails_6_1 job

Impact:

  • The rails_6_1 CI job will fail with the new gemspec requirement
  • Potential breaking change for users on Ruby 2.7

Recommendation: Either:

  1. Keep Ruby 2.7 support and update all configs to 2.7, OR
  2. Update .github/workflows/ruby.yml to use Ruby 3.0+ for all jobs (may require dropping Rails 6.1 on Ruby 2.7 support)

Medium: Missing Lint CI Integration

Issue: The new lint workflow (.github/workflows/lint.yml) is not integrated with the main CI workflow.

Current State:

  • Linting runs as a separate workflow
  • Main test workflow doesn't include lint checks
  • Developers might push code that passes tests but fails linting

Recommendation: Add lint checks to the existing .github/workflows/ruby.yml or ensure both workflows are required for PRs.

Low: Pre-commit Hook Shell Compatibility

File: bin/install-hooks:50

Issue: The pre-commit hook uses #!/bin/sh but includes Bash-specific syntax patterns (particularly the grep with complex regex).

Code:

files=$(git diff --cached --name-only --diff-filter=ACM | grep -E '\\.(rb|rake)$')

Recommendation: Change shebang to #!/usr/bin/env bash for better compatibility, or ensure the script is POSIX-compliant.

Low: File Globbing Patterns Could Be More Efficient

File: rakelib/lint.rake:23,45

Issue: The glob pattern includes many file types but manually checks directories:

Dir.glob('**/*.{rb,rake,yml,yaml,md,gemspec,ru,erb,js,json}').each do |file|
  next if file.include?('vendor/') || file.include?('node_modules/')
  # ...

Recommendation: Use glob exclusions instead:

Dir.glob('**/*.{rb,rake,yml,yaml,md,gemspec,ru,erb,js,json}', File::FNM_EXTGLOB)
  .reject { |f| f.start_with?('vendor/', 'node_modules/', '.git/', 'pkg/', 'tmp/', 'coverage/') }

This is more efficient and cleaner.

Low: Hardcoded File Extensions

Files: rakelib/lint.rake:23,45 and bin/install-hooks:24

Issue: File extensions are hardcoded in multiple places. If you need to add/remove extensions, you'd have to update multiple locations.

Recommendation: Consider defining file patterns in a shared location (e.g., in the Rakefile or a shared module).

Performance Considerations

Pre-commit Hook Performance

The pre-commit hook runs RuboCop on all staged Ruby files, which is good. However, for large commits, this could be slow. Consider:

  • Adding a timeout or file count warning
  • Documenting the expected performance impact

Newline Checks

The check_newlines task reads entire file contents into memory for each file. For large projects, this could be memory-intensive. Consider using File.foreach with a single-line read or checking just the last character.

Security Concerns

No Major Security Issues

The changes don't introduce security vulnerabilities. The pre-commit hook is local-only and doesn't expose sensitive data.

Minor: Shell Injection in Pre-commit Hook

File: bin/install-hooks:26

While not currently exploitable (since $files comes from git diff), it's good practice to quote variables:

bundle exec rubocop "$files"

However, this might break if there are multiple files. A more robust approach:

if [ -n "$files" ]; then
  echo "$files" | xargs bundle exec rubocop
fi

Test Coverage

Missing Tests for New Rake Tasks

The PR doesn't include tests for the new rake tasks in rakelib/lint.rake. Consider adding specs for:

  • check_newlines behavior
  • fix_newlines behavior
  • File exclusion logic

Missing Tests for Pre-commit Hook Installer

No tests for bin/install-hooks. Consider adding integration tests or at least manual testing documentation.

Code Quality & Best Practices

Excellent Use of Frozen String Literals

Good adherence to Ruby best practices with frozen string literal comments.

Clear Task Descriptions

All rake tasks have clear descriptions, which is excellent for discoverability.

Good Error Handling

The tasks provide helpful output and exit codes, making debugging easier.

Recommendations Summary

Must Fix (Before Merge)

  1. Resolve Ruby version inconsistency - Critical compatibility issue

Should Fix (High Priority)

  1. Integrate lint checks into main CI workflow
  2. Add tests for new rake tasks

Nice to Have (Low Priority)

  1. Fix shell compatibility in pre-commit hook
  2. Optimize file globbing patterns
  3. Quote variables in shell script
  4. Document performance characteristics

Conclusion

This is a high-quality PR that adds important infrastructure for code quality. The implementation is thoughtful and well-documented. The main blocker is the Ruby version inconsistency that needs to be resolved before merging.

Recommendation: Request changes to fix the Ruby version issue, then approve with suggestions for follow-up improvements.


Great work on this PR! The linting infrastructure will definitely help maintain code quality going forward. 🎉

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (1)
rakelib/lint.rake (1)

41-63: Issues with memory, atomicity, and error handling are covered in prior review.

The prior review comment on lines 45-55 thoroughly addresses the concerns with this task including memory efficiency, TOCTOU risks, lack of error handling, and non-atomic writes. Please refer to that comment for detailed recommendations.

🧹 Nitpick comments (1)
rakelib/lint.rake (1)

19-39: Consider optimizing memory usage for large files.

The task reads entire file contents into memory to check the final character. For large files, this could be inefficient. Consider checking only the last byte using File.size and File.read(file, 1, File.size(file) - 1) or similar approach.

Example optimization:

-    content = File.read(file)
-    files_without_newline << file unless content.empty? || content.end_with?("\n")
+    if File.size(file) > 0
+      File.open(file, 'rb') do |f|
+        f.seek(-1, IO::SEEK_END)
+        files_without_newline << file unless f.read(1) == "\n"
+      end
+    end
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5c95af2 and 2b30230.

📒 Files selected for processing (6)
  • .github/workflows/lint.yml (1 hunks)
  • .rubocop.yml (1 hunks)
  • Rakefile (1 hunks)
  • bin/install-hooks (1 hunks)
  • cypress-on-rails.gemspec (1 hunks)
  • rakelib/lint.rake (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • bin/install-hooks
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: claude-review
  • GitHub Check: rails_8
  • GitHub Check: rails_7_2
🔇 Additional comments (6)
.github/workflows/lint.yml (2)

1-11: LGTM: Workflow triggers and job setup are appropriate.

The workflow correctly triggers on pushes and PRs to the main branches, ensuring linting runs on all relevant changes.


16-26: Ruby version consistency achieved.

The workflow uses Ruby 3.0, which correctly aligns with .rubocop.yml (TargetRubyVersion: 3.0) and cypress-on-rails.gemspec (required_ruby_version >= 3.0.0). The linting steps are properly configured.

Rakefile (1)

6-9: LGTM: CI task composition is clean and comprehensive.

The new ci task effectively orchestrates specs, linting, and newline checks. Preserving the existing default task maintains backward compatibility for developers who run rake without arguments.

cypress-on-rails.gemspec (1)

25-29: LGTM: Development dependencies and Ruby requirement are properly configured.

The RuboCop dependencies use appropriate version constraints, and the Ruby version requirement (>= 3.0.0) aligns with the workflow and RuboCop configuration. All dependencies are correctly scoped as development-only.

.rubocop.yml (1)

1-76: LGTM: RuboCop configuration is well-balanced and properly aligned.

The configuration appropriately:

  • Targets Ruby 3.0 (matching workflow and gemspec)
  • Enables NewCops for ongoing maintenance
  • Excludes common vendor/generated directories
  • Relaxes metrics for specs and rake files where verbosity is acceptable
  • Enforces trailing newlines and frozen string literals (best practices)
  • Sets reasonable RSpec limits
rakelib/lint.rake (1)

1-17: LGTM: Lint task wrappers are clean and properly structured.

The RuboCop wrapper tasks and orchestration tasks (lint/lint:fix) are well-organized. The use of sh will automatically fail the rake task if RuboCop exits with a non-zero status, providing appropriate error propagation.

@claude
Copy link
Copy Markdown

claude Bot commented Oct 23, 2025

Pull Request Review: Add RuboCop linting and newline enforcement

Summary

This PR introduces comprehensive linting infrastructure with RuboCop and newline enforcement, which is a valuable addition to the project's code quality tooling. The implementation is generally well-structured, but there are several areas that need attention.


🔴 Critical Issues

1. Pre-commit Hook Security & Compatibility Issues (bin/install-hooks:24-26)

files=$(git diff --cached --name-only --diff-filter=ACM | grep -E '\.(rb|rake)$')
if [ -n "$files" ]; then
  bundle exec rubocop $files

Problems:

  • Shell injection vulnerability: $files is unquoted, which can break or cause security issues with filenames containing spaces or special characters
  • Word splitting: Unquoted variables will undergo word splitting, potentially breaking the command

Recommendation:

files=$(git diff --cached --name-only --diff-filter=ACM | grep -E '\.(rb|rake)$')
if [ -n "$files" ]; then
  echo "$files" | xargs -r bundle exec rubocop
fi

Or use an array-based approach for better safety.

2. Potential Performance Issue in Rake Tasks (rakelib/lint.rake:27-32, 52-60)

The newline checking tasks read entire file contents into memory for every file, which could be problematic for:

  • Large files (especially in specs_e2e/ which is excluded by RuboCop but not by newline checks)
  • Binary files that slip through the glob pattern

Recommendations:

  1. Add explicit size limit or streaming approach for large files
  2. Add binary file detection to skip non-text files
  3. Consider excluding the same directories as RuboCop (specs_e2e, e2e, vendor, etc.)

⚠️ Moderate Issues

3. Inconsistent Exclusion Patterns

The RuboCop config excludes specs_e2e/**/* and e2e/**/*, but the newline rake tasks don't. This creates an inconsistency where:

  • RuboCop won't lint these files
  • But newline checks will still run on them

Recommendation: Align the excluded directories between RuboCop and newline tasks.

4. GitHub Actions: Redundant Lint Execution (.github/workflows/ruby.yml:23-24)

The main workflow now runs bundle exec rake lint in each job, but there's also a separate lint.yml workflow. This means:

  • Lint checks run twice on every PR (once in lint.yml, once in each ruby.yml job)
  • This increases CI time and resource usage unnecessarily

My recommendation: Remove the lint steps from ruby.yml since you have a dedicated lint workflow.

5. Ruby Version Pinning in CI (.github/workflows/lint.yml:19)

This uses Ruby 3.0 (latest patch), but ruby.yml uses 3.0.6 for Rails 6.1. For consistency and reproducibility, consider pinning to the same version across all workflows.


📝 Code Quality & Best Practices

6. Missing Error Handling

bin/install-hooks doesn't handle edge cases:

  • What if .git/hooks creation fails (permissions)?
  • What if the hook file write fails?
  • No validation that we're in a git repository

Recommendation: Add basic error handling

7. Code Style: Prefer Consistent Naming

In rakelib/lint.rake:9-10, the naming auto_correct doesn't match RuboCop's actual flag -A. Consider renaming to rubocop:autocorrect or rubocop:fix.


🔒 Security Considerations

8. No Major Security Issues

Beyond the shell injection issue in #1, the code is secure. Consider adding the rubocop-security gem for additional security-focused linting rules.


✅ What's Good

  1. Excellent documentation in the PR description
  2. Frozen string literals enforced - good for performance
  3. Sensible RuboCop defaults that aren't overly strict
  4. Pre-commit hook is a great developer experience improvement
  5. Rake task organization is clean and follows conventions
  6. CI integration ensures linting is enforced
  7. Auto-fix capabilities (rake lint:fix, rake fix_newlines) are very helpful

🧪 Test Coverage

Issue: No tests were added for the new functionality.

Missing test coverage:

  • Tests for check_newlines and fix_newlines rake tasks
  • Tests for the pre-commit hook installer behavior
  • Edge cases: empty files, files without extensions, binary files, etc.

🎯 Action Items

Must Fix (Before Merge)

  1. Fix shell injection vulnerability in bin/install-hooks (quoting issue)
  2. Decide on lint workflow duplication strategy (remove from one workflow)

Should Fix

  1. Add excluded directories to newline tasks to match RuboCop exclusions
  2. Add binary file detection to prevent reading non-text files
  3. Add error handling to bin/install-hooks
  4. Pin Ruby version consistently across all workflows

Nice to Have

  1. Add test coverage for new rake tasks
  2. Consider adding more RuboCop plugins (security, performance)
  3. Add file size limits for newline checking

📊 Overall Assessment

Code Quality: 7/10 - Good structure, but has some bugs and missing error handling
Security: 8/10 - One shell injection issue, otherwise safe
Performance: 7/10 - Potential issues with large files
Test Coverage: 3/10 - No tests for new functionality
Best Practices: 8/10 - Follows Ruby conventions well

Recommendation: Request changes to fix the critical issues, then approve after those are addressed.


Great work on improving code quality infrastructure! Once the critical issues are addressed, this will be a solid addition to the project. 🚀

@claude
Copy link
Copy Markdown

claude Bot commented Oct 24, 2025

Pull Request Review: Add RuboCop Linting and Newline Enforcement

Overall Assessment

This is a well-structured PR that adds comprehensive linting infrastructure to the project. The implementation is clean, thoughtful, and follows best practices. The changes are isolated and won't impact existing functionality while providing valuable code quality improvements.

✅ Strengths

1. Excellent RuboCop Configuration

  • Sensible defaults with appropriate exclusions for generated and external code
  • Good balance between strictness and practicality (e.g., 120 char line limit)
  • Proper use of RuboCop plugins (rubocop-rake, rubocop-rspec)
  • Thoughtful exemptions for specs and generated files

2. Robust Newline Checking Implementation

The rakelib/lint.rake file shows strong engineering:

  • Binary file detection with proper error handling
  • File size limits (10MB) to prevent performance issues
  • Efficient file reading (only checks last 2 bytes for newlines)
  • Clear, actionable error messages
  • Both check and auto-fix capabilities

3. Developer Experience

  • Clear, user-friendly error messages with emojis
  • Pre-commit hook installer with comprehensive error handling
  • Multiple ways to run lints (manual, rake tasks, CI)
  • Hook can be bypassed with --no-verify when needed

4. CI/CD Integration

  • Clean separation of lint workflow from test workflow
  • Runs on both PRs and pushes to master/main
  • Uses modern GitHub Actions (v4 for checkout, v1 for ruby/setup-ruby)

🔍 Issues & Recommendations

Critical Issues

None found. This PR is production-ready.

Minor Suggestions

1. Pre-commit Hook: Check for Bundle Install

File: bin/install-hooks:22,33

The pre-commit hook assumes bundle exec will work, but doesn't verify. Consider adding a check:

if ! command -v bundle &> /dev/null; then
  echo "⚠️  Warning: bundler not found. Install with: gem install bundler"
  exit 1
fi

2. Rake Task: Potential Edge Case

File: rakelib/lint.rake:54-57

The newline check seeks to [f.size - 2, 0].max but only checks if it ends with \n. On Windows, files might use \r\n (CRLF). While this project likely targets Unix-like systems, consider documenting this behavior or handling CRLF.

3. Binary File Detection: Non-printable Character Range

File: rakelib/lint.rake:30

The regex "\x01-\x08\x0B\x0C\x0E-\x1F\x7F-\xFF" is correct but could benefit from a comment explaining why certain bytes (like \x09 tab, \x0A newline, \x0D carriage return) are excluded from the range.

4. Gemspec: Potential Compatibility Issue

File: cypress-on-rails.gemspec:29

The new required_ruby_version = '>= 3.0.0' is a breaking change for users on Ruby 2.7 (which the previous CI used). Consider:

  • Documenting this in the CHANGELOG/release notes
  • Bumping the major/minor version when this is released
  • Alternatively, keeping Ruby 2.7 support if there's no strong need for 3.0+ features in this gem

The PR description doesn't mention this breaking change, so it might catch users off guard.

5. GitHub Actions: Ruby Version Consistency

File: .github/workflows/lint.yml:19

The lint workflow uses Ruby 3.0.6, but the main ruby.yml workflow now also uses 3.0.6 (changed from 2.7.6). This is good consistency, but the higher Rails versions use 3.1.3 and 3.2.2. Consider:

  • Using the same Ruby version as the minimum supported (3.0.6) across all linting
  • Or using the latest stable Ruby for linting to catch any future compatibility issues early

6. Documentation: Installation Instructions

The PR body mentions ./bin/install-hooks but doesn't document that users need to run this after pulling the PR. Consider adding to:

  • Project README
  • CONTRIBUTING.md (if one exists)
  • A notice in the PR description for existing contributors

🎯 Code Quality Assessment

Category Rating Notes
Code Quality ⭐⭐⭐⭐⭐ Clean, idiomatic Ruby with proper error handling
Best Practices ⭐⭐⭐⭐⭐ Follows Ruby and RuboCop community standards
Security ⭐⭐⭐⭐⭐ No security concerns; safe file operations
Performance ⭐⭐⭐⭐⭐ Efficient implementations with size limits
Maintainability ⭐⭐⭐⭐⭐ Well-organized, easy to extend
Documentation ⭐⭐⭐⭐ Good inline comments; could use more user docs
Testing ⭐⭐⭐ No tests for new rake tasks (see below)

🧪 Test Coverage Concerns

The new rake tasks in rakelib/lint.rake don't have accompanying tests. While these are relatively simple scripts, consider adding specs for:

  • Binary file detection logic
  • Newline checking edge cases (empty files, very small files, CRLF files)
  • File exclusion patterns

Example test structure:

# spec/rake/lint_rake_spec.rb
RSpec.describe 'lint rake tasks' do
  describe 'binary_file?' do
    it 'detects files with null bytes as binary'
    it 'detects files with high non-printable ratio as binary'
    it 'identifies text files correctly'
  end
end

🔒 Security Review

No security concerns identified

  • File operations use safe Ruby methods
  • No shell injection risks (proper use of arrays with sh and xargs)
  • Binary file detection prevents reading potentially dangerous files
  • File size limits prevent DoS through large file reads
  • Git hook doesn't introduce security vulnerabilities

📊 Performance Considerations

Performance is well-considered

  • 10MB file size limit prevents memory issues
  • Efficient seeking to file end rather than reading entire file
  • Excluded directories reduce unnecessary file scanning
  • .reject and .select chains could be combined for minor performance gain, but current implementation is more readable

🎬 Next Steps / Recommendations

  1. Merge this PR - It's ready and adds significant value
  2. Document the Ruby version requirement change in release notes
  3. Consider adding a CONTRIBUTING.md with setup instructions including hook installation
  4. Add tests for the new rake tasks (can be a follow-up PR)
  5. Consider adding the ci rake task to your main CI workflow

📝 Nitpicks (Optional)

  1. bin/install-hooks:16 - The heredoc for bash could use proper quoting for the variable references (though they're fine in this context)
  2. The ci task in Rakefile could have a desc that mentions it's useful locally to simulate CI

Recommendation: ✅ APPROVE with minor suggestions

This is excellent work! The linting infrastructure is well-implemented and will significantly improve code quality going forward. The minor suggestions above are optional improvements that could be addressed in follow-up PRs.

Great job, @justin808! 🎉

🤖 Review generated with Claude Code

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (2)
bin/install-hooks (1)

20-26: Inconsistency: Newline check runs on all files, not just staged files.

The check_newlines task checks the entire repository, while the RuboCop check (lines 28-38) only lints staged files. This inconsistency means developers could be blocked from committing due to pre-existing issues in unstaged files, making incremental fixes difficult.

Consider one of these approaches:

  1. Document this as intentional behavior if the goal is to enforce repository-wide newline compliance
  2. Make check_newlines accept a file list parameter and pass only staged files

For approach 2, you could modify the Rake task to accept files as arguments and update the hook:

  # Check for files missing newlines
  echo "Checking for files missing newlines..."
- bundle exec rake check_newlines
+ staged_files=$(git diff --cached --name-only --diff-filter=ACM)
+ if [ -n "$staged_files" ]; then
+   echo "$staged_files" | xargs bundle exec ruby -e '
+     files = ARGV
+     files_without_newline = files.select do |f|
+       File.file?(f) && File.size(f) > 0 && File.size(f) < 10485760 &&
+       File.open(f, "rb") { |file| file.seek([file.size - 2, 0].max); tail = file.read; !tail.end_with?("\n") }
+     end
+     if files_without_newline.any?
+       puts "Files missing final newline:"
+       files_without_newline.each { |f| puts "  #{f}" }
+       exit 1
+     end
+   '
+ fi
  if [ $? -ne 0 ]; then
    echo "❌ Some files are missing final newlines. Run 'bundle exec rake fix_newlines' to fix."
    exit 1
  fi
rakelib/lint.rake (1)

82-87: Add error handling and atomic writes to prevent file corruption.

The current implementation has risks flagged in previous reviews that remain unaddressed:

  1. Memory inefficiency: Line 83 reads the entire file into memory, problematic for files approaching the 10MB limit
  2. No atomic writes: Writing directly (line 85) without a temp file risks corruption if the write fails partway
  3. No error handling: File.write failures are not caught

Apply this diff to add error handling and atomic writes:

    # Read file to check if it needs a newline
-   content = File.read(file)
-   unless content.empty? || content.end_with?("\n")
-     File.write(file, content + "\n")
-     fixed_files << file
+   # Check if newline is needed by reading only the end
+   needs_newline = File.open(file, 'rb') do |f|
+     return false if f.size == 0
+     f.seek([f.size - 2, 0].max)
+     tail = f.read
+     !tail.end_with?("\n")
+   end
+   
+   if needs_newline
+     begin
+       # Atomic write: write to temp file, then rename
+       temp_file = "#{file}.tmp.#{Process.pid}"
+       content = File.read(file)
+       File.write(temp_file, content + "\n")
+       File.rename(temp_file, file)
+       fixed_files << file
+     rescue StandardError => e
+       warn "Failed to fix #{file}: #{e.message}"
+       File.delete(temp_file) if File.exist?(temp_file)
+     end
    end

Alternatively, for a simpler approach that just appends:

-   content = File.read(file)
-   unless content.empty? || content.end_with?("\n")
-     File.write(file, content + "\n")
-     fixed_files << file
+   # Check if newline is needed
+   needs_newline = File.open(file, 'rb') do |f|
+     next false if f.size == 0
+     f.seek([f.size - 2, 0].max)
+     tail = f.read
+     !tail.end_with?("\n")
+   end
+   
+   if needs_newline
+     begin
+       File.open(file, 'a') { |f| f.write("\n") }
+       fixed_files << file
+     rescue StandardError => e
+       warn "Failed to fix #{file}: #{e.message}"
+     end
    end
🧹 Nitpick comments (1)
rakelib/lint.rake (1)

41-68: LGTM: Memory-efficient newline checking.

The implementation efficiently reads only the last 2 bytes (line 55) rather than loading entire files into memory. The file filtering chain (size, type, binary check) is well-designed.

Minor: Line 57 could be simplified slightly:

-      files_without_newline << file unless tail.nil? || tail.empty? || tail.end_with?("\n")
+      files_without_newline << file if tail && !tail.empty? && !tail.end_with?("\n")
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between df7bcfd and ec4e82a.

📒 Files selected for processing (4)
  • .github/workflows/lint.yml (1 hunks)
  • .github/workflows/ruby.yml (1 hunks)
  • bin/install-hooks (1 hunks)
  • rakelib/lint.rake (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • .github/workflows/ruby.yml
  • .github/workflows/lint.yml
🔇 Additional comments (6)
bin/install-hooks (4)

1-10: LGTM: Git repository validation.

The repository check and error handling are appropriate. The frozen string literal pragma and clean script structure follow Ruby best practices.


28-38: Good: Safe handling of filenames with special characters.

The use of printf with xargs correctly handles filenames containing spaces and special characters, addressing the command injection vulnerability from previous reviews. The || true on line 30 prevents grep from failing when no Ruby files are staged.


43-65: LGTM: Comprehensive error handling for file operations.

Each file operation (directory creation, file write, chmod) is properly wrapped with error handling that provides clear error messages and exits with a non-zero status.


67-72: LGTM: Clear user guidance.

The success message clearly explains what the hook does and provides the --no-verify option for emergency bypasses.

rakelib/lint.rake (2)

3-17: LGTM: Clean task definitions and dependency structure.

The RuboCop tasks and lint aliases are well-structured using standard Rake patterns. The use of task dependencies (:rubocop, 'rubocop:autocorrect') is idiomatic.


19-36: LGTM: Robust binary file detection.

The binary_file? helper appropriately checks for null bytes and non-printable character ratios. The error handling (lines 33-35) that assumes unreadable files are binary is a safe default for this use case.

@claude
Copy link
Copy Markdown

claude Bot commented Oct 27, 2025

Pull Request Review: Add RuboCop Linting and Newline Enforcement

Summary

This PR adds comprehensive linting infrastructure using RuboCop with automated enforcement via pre-commit hooks and CI. The implementation is well-structured and addresses code quality systematically.

✅ Strengths

1. Excellent Progressive Enhancement Strategy

  • Using .rubocop_todo.yml to document existing violations without blocking progress is the right approach
  • Allows CI to pass while tracking technical debt for future cleanup
  • The inherit_from pattern is clean and maintainable

2. Comprehensive Security Fixes (commit ec4e82a)

  • Fixed shell injection vulnerability in pre-commit hook using printf and xargs
  • Proper handling of filenames with spaces/special characters
  • Good security awareness shown in the evolution of the code

3. Performance Optimizations

  • Binary file detection prevents unnecessary processing
  • 10MB file size limit prevents memory issues
  • Efficient file filtering using reject/select chains
  • Reading only last bytes for newline detection instead of entire file (rakelib/lint.rake:54-57)

4. Good CI/CD Integration

  • Dedicated lint workflow (.github/workflows/lint.yml)
  • Consistent Ruby version (3.0.6) across workflows
  • Proper separation of concerns (dedicated lint.yml vs main ruby.yml)

5. Developer Experience

  • Clear error messages with actionable remediation steps
  • Pre-commit hook is installable via simple script
  • Ability to skip hooks with --no-verify for emergencies
  • Well-documented rake tasks

🔍 Issues and Recommendations

Critical Issues

None found - The security and performance issues identified during development were properly addressed.

Test Coverage - Main Gap

The PR adds significant new functionality but doesn't include tests for:

  • check_newlines task behavior
  • fix_newlines task behavior
  • binary_file? helper method
  • Edge cases: empty files, files without extensions, symlinks

Code Quality Improvements

1. Hardcoded Excluded Directories

The excluded directories are duplicated between check_newlines and fix_newlines. Extract to a constant at the top of rakelib/lint.rake

2. Magic Numbers (rakelib/lint.rake:30-31)

Document the binary detection thresholds as constants

3. Pre-commit Hook Performance

The hook checks ALL files for newlines on every commit. For large repos, consider only checking staged files

📋 Checklist for Merge

  • ✅ Code follows Ruby best practices
  • ✅ Security vulnerabilities addressed
  • ✅ Performance optimizations implemented
  • ✅ CI/CD integration complete
  • ⚠️ Test coverage for new rake tasks - Consider adding
  • ✅ Documentation complete (PR description is excellent)
  • ✅ Ruby version consistency enforced

Overall Assessment

This is a high-quality PR that adds valuable tooling to the project. The security fixes, performance optimizations, and progressive enhancement strategy (using .rubocop_todo.yml) demonstrate excellent engineering judgment.

The main gap is test coverage for the new rake tasks, but this doesn't block the PR given the quality of the implementation and the comprehensive manual testing evident from the commit history.

Recommendation: APPROVE with suggestion to add tests in follow-up PR

Great work on this contribution! The attention to security, performance, and developer experience is commendable.


Review generated by Claude Code

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
.rubocop_todo.yml (1)

1-582: Consider documenting the plan to gradually resolve todo items.

The todo file effectively captures all current RuboCop violations, which is valuable for an incremental linting rollout. However, the codebase would benefit from a brief note (e.g., in a DEVELOPMENT.md or similar) documenting the plan to progressively shrink this file over time—for example, by targeting specific files or cops in future PRs. This helps set expectations and keeps the team focused on making progress.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ec4e82a and 90366b0.

📒 Files selected for processing (4)
  • .rubocop.yml (1 hunks)
  • .rubocop_todo.yml (1 hunks)
  • cypress-on-rails.gemspec (1 hunks)
  • rakelib/lint.rake (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • cypress-on-rails.gemspec
  • rakelib/lint.rake
  • .rubocop.yml
🔇 Additional comments (3)
.rubocop_todo.yml (3)

1-7: Verify inheritance relationship with .rubocop.yml.

This auto-generated todo file is a standard way to suppress existing RuboCop violations in an existing codebase. Per the PR description, .rubocop.yml should inherit from this file. Confirm that the inheritance is correctly configured in .rubocop.yml (typically via inherit_from: .rubocop_todo.yml).


428-429: Clarify rationale for disabling Style/FrozenStringLiteralComment and Style/StringLiterals globally.

Unlike other cops which exclude specific files, these two style rules are disabled entirely (Enabled: false). This is an unusual pattern for a todo file—most violations are typically excluded per-file. Confirm whether this is intentional, and consider documenting the reasoning (e.g., performance, scope reduction, or planned future enforcement).

Also applies to: 556-557


212-237: Metric thresholds are reasonable for an existing codebase.

The configured maximums (e.g., AbcSize: 51, MethodLength: 28, ClassLength: 200) are appropriate for gradually introducing linting. These allow the team to address high-priority violations first while maintaining a feasible baseline.

@claude
Copy link
Copy Markdown

claude Bot commented Nov 2, 2025

Code Review - PR #193: Add RuboCop linting and newline enforcement

Thank you for this comprehensive linting setup! This is a well-structured PR that will significantly improve code quality. Here's my detailed review:


Strengths

1. Excellent Security Fixes (Commit ec4e82a)

  • Fixed shell injection vulnerability in the pre-commit hook by using printf and xargs instead of direct shell expansion
  • Proper handling of filenames with spaces and special characters
  • Good security awareness shown in the evolution of the code

2. Performance Optimizations

  • Smart binary file detection (binary_file? in rakelib/lint.rake:20-36)
  • File size limit (10MB) prevents memory issues
  • Efficient file reading for newline checks (reads only last 2 bytes instead of entire file at rakelib/lint.rake:54-57)
  • Proper directory exclusion patterns matching RuboCop config

3. Git Worktree Support

  • Latest commit adds proper support for git worktrees (Conductor environments)
  • Good edge case handling in bin/install-hooks:6-27

4. Progressive Adoption Strategy

  • Using .rubocop_todo.yml to track existing violations allows CI to pass while documenting technical debt
  • Enables adoption without blocking development
  • Clear path for gradual improvement

5. Comprehensive CI Integration

  • Dedicated lint workflow (.github/workflows/lint.yml)
  • Proper Ruby version consistency (3.0.6)
  • Good separation of concerns

🔍 Issues & Concerns

Critical: Potential Race Condition in Pre-commit Hook

Location: bin/install-hooks:52-55

files=$(git diff --cached --name-only --diff-filter=ACM | grep -E '\.(rb|rake)$' || true)
if [ -n "$files" ]; then
  printf '%s\n' "$files" | xargs bundle exec rubocop

Problem: The hook runs check_newlines on ALL files in the repo, then runs RuboCop only on staged files. This creates an inconsistency:

  1. A developer might stage only Ruby files
  2. check_newlines checks the entire repo and fails on an unstaged YAML file
  3. Developer cannot commit their Ruby changes even though their staged files are fine

Recommendation:

# Check newlines only on staged files
staged_files=$(git diff --cached --name-only --diff-filter=ACM | grep -E '\.(rb|rake|yml|yaml|md|gemspec|ru|erb|js|json)$' || true)
if [ -n "$staged_files" ]; then
  echo "Checking staged files for missing newlines..."
  printf '%s\n' "$staged_files" | while read -r file; do
    if [ -f "$file" ]; then
      if \! tail -c1 "$file" | read -r _; then
        echo "❌ Missing newline: $file"
        exit 1
      fi
    fi
  done
fi

High: HEREDOC Security in Bash Script

Location: bin/install-hooks:38

The HEREDOC content contains bash code that will be written to a file. While this is intentional, there's a subtle issue:

hook_content = <<~HOOK
  #\!/usr/bin/env bash
  # ...
HOOK

Issue: If any Ruby variables were to be interpolated here, they could introduce injection risks. Currently safe since there's no interpolation, but consider using a non-interpolating HEREDOC for clarity:

hook_content = <<~'HOOK'  # Note the single quotes
  #\!/usr/bin/env bash
  # ...
HOOK

This makes it explicit that no Ruby interpolation should occur.

Medium: Empty Chunk Handling in Binary Detection

Location: rakelib/lint.rake:25

chunk = file.read(8192) || ''

Issue: File#read returns an empty string for empty files, not nil. The || '' is defensive but won't actually catch the case you're protecting against. More importantly:

non_printable.to_f / chunk.size > 0.3

If chunk.size == 0, this will be 0.0 / 0 = NaN, which is not > 0.3, so it works correctly but could be clearer:

chunk = file.read(8192)
return false if chunk.nil? || chunk.empty?
# ... rest of logic

Medium: Error Handling Inconsistency

Location: rakelib/lint.rake:33-35

rescue StandardError
  # If we can't read the file, assume it's not something we should check
  true

Issue: This silently treats unreadable files as binary, which could hide permission issues or other problems. Consider logging or at least being more specific about exceptions:

rescue Errno::EACCES, Errno::EISDIR
  # Permission denied or is a directory
  true
rescue StandardError => e
  warn "Warning: Could not check if binary: #{filepath}: #{e.message}"
  true

Low: Task Naming Inconsistency

Location: rakelib/lint.rake:9

task 'rubocop:autocorrect' do

vs. the standard RuboCop naming:

The PR description mentions renaming from auto_correct to autocorrect to match RuboCop conventions, which is good. However, RuboCop actually uses both:

  • rubocop -a (safe auto-correct)
  • rubocop -A (all auto-corrections)

Consider providing both tasks:

task 'rubocop:autocorrect' do
  sh 'bundle exec rubocop -a'
end

task 'rubocop:autocorrect_all' do
  sh 'bundle exec rubocop -A'
end

Currently using -A which is the more aggressive option - this might not be what users expect from the default autocorrect.


📊 Test Coverage

Concern: There are no automated tests for the new functionality:

  • No tests for rakelib/lint.rake tasks
  • No tests for the bin/install-hooks script
  • No integration tests for the pre-commit hook behavior

Recommendation:
Add RSpec tests for the rake tasks:

# spec/rakelib/lint_rake_spec.rb
require 'spec_helper'
require 'rake'

RSpec.describe 'lint.rake' do
  before(:all) do
    load File.expand_path('../../../rakelib/lint.rake', __FILE__)
    Rake::Task.define_task(:environment)
  end

  describe '#binary_file?' do
    it 'detects binary files' do
      # Test with fixture files
    end

    it 'handles empty files' do
      # Test edge case
    end
  end

  describe 'check_newlines' do
    it 'fails when files are missing newlines' do
      # Create temp file without newline
    end
  end
end

🎯 Best Practices Observations

Good:

  1. ✅ Frozen string literal comments added
  2. ✅ Proper error messages with emojis for UX
  3. ✅ Good separation of concerns (separate workflow file)
  4. ✅ Version pinning for RuboCop gems
  5. ✅ Comprehensive .rubocop_todo.yml documenting existing issues

Could Improve:

  1. ⚠️ Consider adding a --fast flag option for check_newlines to skip binary detection for faster CI
  2. ⚠️ The binary_file? method could be memoized since it's called for both check and fix
  3. ⚠️ Consider making the file patterns configurable via ENV or config file

🔐 Security Assessment

Overall: Good

The security fixes in commit ec4e82a addressed the main concerns. Current security posture:

Good:

  • Proper filename handling with printf and xargs
  • No direct shell expansion of user-controlled input
  • Proper error handling for file operations

⚠️ Watch:

  • The pre-commit hook runs bundle exec commands which trust the local Gemfile - ensure developers understand the risk of running hooks in untrusted repos

🚀 Performance Considerations

Overall: Excellent

The performance optimizations are well thought out:

  • Binary detection prevents unnecessary text processing
  • 10MB file size limit
  • Reading only last 2 bytes for newline check
  • Efficient glob patterns and directory exclusions

Potential Improvement:
For large repos, consider parallelizing the newline checks:

require 'parallel'

Parallel.each(files, in_threads: 4) do |file|
  # check newline
end

📝 Minor Suggestions

  1. Documentation: Consider adding a docs/LINTING.md explaining the linting setup and how to use it
  2. Install Script Feedback: The bin/install-hooks script could check if a hook already exists and ask before overwriting
  3. Version Compatibility: Document why Ruby 3.0+ is required (breaking change for users)
  4. Gemfile Update: Ensure the main Gemfile or Gemfile.lock is updated to reflect the new Ruby version requirement

🎯 Summary & Recommendation

Overall Assessment:Approve with Minor Changes

This is a solid, well-thought-out PR that demonstrates good software engineering practices. The progressive adoption strategy with .rubocop_todo.yml is excellent, and the security fixes show good awareness.

Must Fix Before Merge:

  1. ✋ Fix the race condition in pre-commit hook (check only staged files for newlines)
  2. ✋ Add non-interpolating HEREDOC for security clarity

Should Fix:

  1. Add basic test coverage for rake tasks
  2. Improve error handling in binary detection
  3. Document Ruby version requirement change

Nice to Have:

  1. Add documentation
  2. Make autocorrect less aggressive by default
  3. Consider performance optimizations for large repos

Estimated Effort: ~2-3 hours to address the must-fix items.


Great work on this PR! The linting infrastructure will be a valuable addition to the project. 🎉

@justin808 justin808 force-pushed the justin808/add-rubocop-linting branch from 4984c55 to ca72bba Compare November 5, 2025 07:43
@justin808 justin808 force-pushed the justin808/add-rubocop-linting branch from ca72bba to f691c3a Compare February 15, 2026 23:55
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
.rubocop_todo.yml (2)

9-170: Consider auto-fixing safe-autocorrect cops now instead of excluding them.

A large portion of the excluded cops (e.g., Layout/SpaceAfterComma, Layout/TrailingWhitespace, Layout/EmptyLineAfterMagicComment, Layout/ElseAlignment, etc.) support safe autocorrection. Running rubocop -a on these files would shrink the todo significantly with no risk of behavior change, reducing the debt carried forward.


211-237: Generous metric thresholds worth tracking.

AbcSize: 51, CyclomaticComplexity: 13, PerceivedComplexity: 15, and MethodLength: 28 are quite high and indicate complex methods in the codebase. These are fine for initial adoption, but consider creating a tracking issue to progressively lower them as code is refactored.

justin808 and others added 7 commits March 3, 2026 22:32
Set up automated linting and code quality tools:

1. Added RuboCop with extensions:
   - rubocop-rake for rake task linting
   - rubocop-rspec for RSpec linting
   - Created .rubocop.yml with sensible defaults

2. Created rake tasks for linting (rakelib/lint.rake):
   - rake lint - Run all linters
   - rake lint:fix - Auto-fix issues
   - rake check_newlines - Verify files end with newlines
   - rake fix_newlines - Fix missing newlines

3. Added pre-commit hook installer (bin/install-hooks):
   - Checks for missing newlines
   - Runs RuboCop on staged Ruby files
   - Prevents commits with linting issues

4. Added GitHub Actions workflow (.github/workflows/lint.yml):
   - Runs on all PRs and pushes to master
   - Enforces linting in CI
   - Checks for missing newlines

5. Updated CI workflow:
   - Added :ci task to run specs, lint, and newline checks
   - Ensures all checks pass before merge

To install pre-commit hook: ./bin/install-hooks

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

Co-Authored-By: Claude <noreply@anthropic.com>
- Set minimum Ruby version to 3.0 across all configurations
- Updated .rubocop.yml TargetRubyVersion from 2.6 to 3.0
- Updated GitHub Actions workflow to use Ruby 3.0
- Added required_ruby_version >= 3.0.0 to gemspec
- Pinned RuboCop versions for consistency:
  - rubocop ~> 1.81
  - rubocop-rake ~> 0.7
  - rubocop-rspec ~> 3.7
- Updated to use new plugin syntax instead of require

This ensures consistency between CI, local development, and RuboCop
configuration. Ruby 3.0 is a reasonable minimum for modern projects.

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

Co-Authored-By: Claude <noreply@anthropic.com>
…zations

- Update Rails 6.1 CI job to use Ruby 3.0.6 (consistent with gemspec requirement)
- Add lint checks to all CI jobs in main workflow
- Fix pre-commit hook to use bash instead of sh for better compatibility
- Optimize file globbing in rake tasks for better performance
- Use more efficient reject/select pattern instead of multiple next statements
SECURITY FIXES:
- Fix shell injection vulnerability in pre-commit hook by using printf and xargs
- Add proper quoting and error handling for filenames with spaces/special chars

PERFORMANCE OPTIMIZATIONS:
- Add binary file detection to skip non-text files in newline checks
- Limit file size checks to 10MB to prevent memory issues
- Read only last bytes for newline detection instead of entire file
- Align excluded directories with RuboCop config (add specs_e2e, e2e, spec/fixtures)

CI IMPROVEMENTS:
- Remove redundant lint execution from main workflow (keep dedicated lint.yml)
- Align Ruby versions across workflows (use 3.0.6 consistently)

CODE QUALITY:
- Rename rubocop:auto_correct to rubocop:autocorrect (match RuboCop conventions)
- Add comprehensive error handling to install-hooks script
- Check for git repository before installing hooks
- Handle permission errors gracefully
- Generate .rubocop_todo.yml to suppress existing violations
- This allows CI to pass while tracking technical debt
- Fix string interpolation style in lint.rake
- Fix hash indentation in gemspec
- Remove conflicting cops from main config (delegated to TODO)
- All existing violations are now documented for future cleanup
- Add support for git worktrees (Conductor environments)
- Properly detect git directory location
- Handle both regular repos and worktrees
- Install pre-commit hooks successfully
Refactor git directory detection loop to use early next/break
instead of deeply nested conditionals, reducing nesting from
4 levels to 3.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@justin808 justin808 force-pushed the justin808/add-rubocop-linting branch from 9d2a842 to 353ed05 Compare March 4, 2026 08:33
Comment thread bin/install-hooks
Comment thread bin/install-hooks
Comment thread rakelib/lint.rake
@claude
Copy link
Copy Markdown

claude Bot commented Mar 4, 2026

PR Review: Add RuboCop linting and newline enforcement

Good initiative adding linting infrastructure. Here are the key concerns to address before merging:

Breaking Change: Minimum Ruby Version Bump

Adding s.required_ruby_version = '>= 3.0.0' in the gemspec is a breaking change for any gem users still on Ruby 2.7. This needs to be documented in the CHANGELOG and likely warrants a major or minor version bump. Also worth noting that Ruby 3.0 itself reached end-of-life in March 2024 — consider setting the minimum to >= 3.1 instead, which still has security support.

Security/Eval Being Silenced

The generated .rubocop_todo.yml excludes Security/Eval for lib/cypress_on_rails/command_executor.rb and a template file. Using eval to execute arbitrary Ruby code is the core of this gem's functionality — that's understood — but silencing the cop with no comment explaining the intentional use creates a bad precedent. Consider adding a # rubocop:disable Security/Eval inline comment with a brief explanation of why eval is safe/required here, rather than suppressing it globally in the todo file.

Duplicate File Discovery Logic

The check_newlines and fix_newlines tasks in rakelib/lint.rake share ~10 identical lines of file discovery logic (glob pattern, excluded_dirs, size/binary filters). Extract this into a shared helper so there is one place to update.

NewCops: enable is Aggressive

Setting NewCops: enable in .rubocop.yml means every RuboCop upgrade will automatically apply new cops as errors, potentially breaking CI without any code changes. A safer approach is NewCops: warn so new cops surface as warnings and can be deliberately promoted.

Pre-commit Hook Checks All Files, Not Just Staged Ones

The generated pre-commit hook runs bundle exec rake check_newlines, which scans every file in the repo. For large projects this will be noticeably slow. Consider limiting the newline check to staged files in the pre-commit hook (similar to how RuboCop is already scoped to staged files).

bin/install-hooks Silently Overwrites Existing Hooks

If a developer already has a custom pre-commit hook, this script silently replaces it. At a minimum, the script should detect an existing hook and ask before overwriting (or back it up first).

Comment thread rakelib/lint.rake Outdated
Comment thread rakelib/lint.rake
Comment thread bin/install-hooks
justin808 and others added 2 commits March 6, 2026 18:10
- Extract NewlineChecker module to deduplicate file-filtering logic
  and avoid polluting global namespace with MAX_FILE_SIZE constant
- Fix fix_newlines to use File.open append mode for safer writes
  with error handling
- Fix pre-commit hook to check only staged files for missing newlines
  instead of all files in the repository
- Back up existing pre-commit hook before overwriting
- Exclude install-hooks from gem executables (dev-only script)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Rake files are already excluded from Metrics/BlockLength in
.rubocop.yml, making this inline disable unnecessary.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Comment thread rakelib/lint.rake Outdated
Comment thread rakelib/lint.rake
Comment thread .rubocop_todo.yml
Comment thread bin/install-hooks Outdated

- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pinning to 3.0.6 (an older, EOL Ruby version) in a new workflow is a missed opportunity.

Ruby 3.0 reached end-of-life in March 2024. Lint failures on modern Ruby (3.3/3.4) may silently go undetected. Consider using the same version strategy as your test matrix, or at least a supported Ruby like 3.3 or 3.4:

Suggested change
with:
ruby-version: '3.4'

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

The lint workflow uses 3.0.6 to match the gem's minimum supported version (required_ruby_version >= 3.0.0). Upgrading the minimum Ruby version and testing across a matrix would be a separate discussion/PR. This PR focuses on adding linting infrastructure.

Comment thread cypress-on-rails.gemspec
@claude
Copy link
Copy Markdown

claude Bot commented Mar 7, 2026

Review summary posted as inline comments. Key issues: (1) fix_newlines bug - next inside File.open block does not skip outer each, all files get newlines appended #193 (comment) (2) Division by zero in binary_file? for empty files #193 (comment) (3) Security/Eval globally suppressed in rubocop_todo #193 (comment) (4) xargs without null delimiters in pre-commit hook #193 (comment) (5) Breaking change required_ruby_version >= 3.0.0 needs CHANGELOG entry and CI should use a non-EOL Ruby #193 (comment)

@claude
Copy link
Copy Markdown

claude Bot commented Mar 7, 2026

PR Review: Add RuboCop linting and newline enforcement

Overall this is a solid addition. There are a few issues worth addressing before merging.

Bug: fix_newlines task will corrupt all files

The critical issue is in rakelib/lint.rake lines 73-86. The next inside the nested File.open block does NOT skip the outer each loop. In Ruby, next in a nested block exits only the innermost block. Every file will have a newline appended unconditionally, regardless of whether it already ends with one.

Fix: capture the return value of the File.open block and use next unless needs_newline in the outer loop.

Other issues (see inline comments)

  • exit 1 in Rake tasks should be abort (Rake convention)
  • Security/Eval excluded globally in .rubocop_todo.yml - prefer inline rubocop:disable with explanation at the call site
  • Ruby 3.0 reached EOL in March 2024; consider 3.2+ in CI
  • The $? idiom in the bash hook is fragile; prefer if ! bundle exec rubocop ...; then

Comment thread rakelib/lint.rake
Comment thread rakelib/lint.rake Outdated
Comment thread .rubocop_todo.yml Outdated
- name: Run RuboCop
run: bundle exec rubocop

- name: Check for files missing newlines
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Ruby 3.0 reached end-of-life in March 2024 and no longer receives security fixes. Consider using a supported version here (and in ruby.yml which was just bumped to the same EOL version):

Suggested change
- name: Check for files missing newlines
ruby-version: '3.3'

Using a minor version without a patch (e.g. '3.3') lets ruby/setup-ruby pick the latest patch release automatically, reducing maintenance overhead.

Comment thread bin/install-hooks Outdated
- Fix fix_newlines: `next` inside File.open block doesn't skip
  outer `each` iteration - use return value pattern instead
- Fix binary_file?: guard against division by zero on empty files
- Fix pre-commit hook: use null-delimited xargs (-0) for safe
  handling of filenames with spaces

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@claude
Copy link
Copy Markdown

claude Bot commented Mar 7, 2026

Review: Add RuboCop linting and newline enforcement

Good overall approach. The NewlineChecker module extraction and binary-file detection are well thought out. Issues found below, roughly by severity.


BREAKING CHANGE: required_ruby_version >= 3.0.0

The gemspec now drops Ruby 2.x support. This should be documented in CHANGELOG.md as a breaking change and reflected in the next version bump. Users on Ruby 2.7 will get a gem install failure with no guidance to upgrade.


BUG in pre-commit hook: NUL bytes lost in command substitution (affects multiple staged files)

See bin/install-hooks lines 60-62. The ruby_files variable is set via dollar(git diff ... -z | grep -z ...) which uses NUL-delimited output, but bash command substitution strips all NUL bytes. With a single staged file this is invisible. With two or more staged Ruby files the filenames are concatenated without any separator (e.g. foo.rbbar.rb), and since xargs -0 expects NUL delimiters it sees the whole string as one argument, causing RuboCop to error with No such file.

Fix: avoid the variable and pipe directly so NUL delimiters are preserved end-to-end.


.rubocop_todo.yml: 582 suppressed violations

Pragmatic to let CI pass immediately, but worth opening a tracking issue so the debt gets addressed incrementally rather than persisting forever.


Branch trigger inconsistency

lint.yml triggers on both master and main; ruby.yml triggers only on master. If the default branch ever moves to main, tests would stop running on push. Both workflows should use the same branch list.


Minor notes

  • lint.yml pins ruby-version to 3.0.6. Consider using 3.0 (latest patch) to avoid manual version bumps as patches are released.
  • NewlineChecker FILE_EXTENSIONS glob does not match Rakefile (no extension), so that file is never checked for a trailing newline.
  • f.seek([f.size - 2, 0].max) reads only 2 bytes, which works for LF but would miss CRLF line endings. Unlikely to matter here.

Comment thread bin/install-hooks Outdated
Comment thread cypress-on-rails.gemspec
Comment thread .github/workflows/lint.yml Outdated
Comment thread .github/workflows/lint.yml
- bin/install-hooks: fix NUL byte stripping that broke multi-file
  staging. Pipe `git diff -z | grep -z` directly through `xargs -0`
  instead of capturing into a bash variable (which strips NULs and
  concatenates filenames into one bogus path).
- CHANGELOG.md: document breaking change raising required_ruby_version
  to >= 3.0.0.
- rakelib/lint.rake: use `abort` instead of `exit 1` in check_newlines
  for cleaner Rake failure semantics.
- .github/workflows/lint.yml: switch ruby-version to '3.0' so
  setup-ruby auto-resolves the latest 3.0.x patch.
- .github/workflows/ruby.yml: trigger on both master and main to align
  with lint.yml.
- command_executor.rb / eval.rb template: move the Security/Eval
  suppression inline at the call sites with a rationale comment, and
  drop the file-level exclude from .rubocop_todo.yml so the intent is
  visible at the eval rather than hidden in the todo file.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@claude
Copy link
Copy Markdown

claude Bot commented May 12, 2026

Code Review: Add RuboCop linting and newline enforcement

Good foundational PR — adding RuboCop and newline enforcement is the right call. A few issues worth addressing before merging:


Bugs / Functional Gaps

check_newlines has two coverage blind spots:

  1. Extension-less files are skipped. FILE_EXTENSIONS = '**/*.{rb,rake,yml,...}' only matches files with a listed extension. Gemfile, Rakefile, Gemfile.lock, etc. are silently excluded. Notably, .rubocop_todo.yml lists Gemfile as having a missing trailing newline — so RuboCop would flag it, but rake check_newlines would not. The two enforcement mechanisms are inconsistent with each other.

  2. Dotfiles are skipped. Ruby's Dir.glob does not match dotfiles by default. .rubocop.yml, .rubocop_todo.yml, .github/**/*.yml, etc. will never be checked. You can fix both gaps by adding File::FNM_DOTMATCH and extending the glob (or using Dir['**/{*,.*}'] filtered by extension).


Breaking Change Risk

Ruby >= 3.0.0 is a significant breaking change. Any downstream project on Ruby 2.x that has cypress-on-rails in its Gemfile will get a resolution failure after upgrading to this version. The CHANGELOG entry is correct, but consider whether this warrants a major version bump or at least a more prominent deprecation window. Also, CI now only tests Ruby 3.0.6 — if the intent is to support all of 3.0.x, the matrix should include 3.0.x through the current latest 3.x to give a real coverage window.


bin/install-hooks — Backup Overwrite

If a developer runs ./bin/install-hooks twice, the second run copies the already-installed hook to .bak, silently losing their original custom hook. Consider adding a timestamp/UUID suffix, or skipping the backup entirely if the existing hook was written by this installer (e.g., check for a known sentinel comment).


.rubocop_todo.yml — 577 Lines of Exclusions

This is a valid bootstrap strategy, but as-is, RuboCop provides essentially no enforcement on existing code. The todo file disables or excludes virtually every cop that would catch real issues. It would be worth adding a comment at the top with a plan (e.g., link to a milestone issue) and targeting a few high-value, auto-correctable cops (Layout/TrailingWhitespace, Layout/TrailingEmptyLines) for removal from the todo list in a follow-up. Right now Style/FrozenStringLiteralComment is globally disabled rather than in the todo, which is a stronger signal that it's intentionally skipped — that's fine, but worth documenting why.


Minor

  • NewlineChecker is defined inside a .rake file, making it untestable. If the logic grows, consider extracting it to lib/tasks/support/newline_checker.rb.
  • NewCops: enable in .rubocop.yml means every RuboCop patch upgrade can introduce new failing cops in CI. This is somewhat at odds with the todo-list approach. Consider NewCops: pending (the default) to opt-in intentionally.
  • The lint CI workflow calls bundle exec rubocop directly rather than bundle exec rake lint — fine for now, but inconsistent with the rake task abstraction the PR introduces.

Comment thread rakelib/lint.rake
MAX_FILE_SIZE = 10 * 1024 * 1024
EXCLUDED_DIRS = %w[vendor/ node_modules/ .git/ pkg/ tmp/ coverage/ specs_e2e/ e2e/ spec/fixtures/].freeze
FILE_EXTENSIONS = '**/*.{rb,rake,yml,yaml,md,gemspec,ru,erb,js,json}'

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This glob only matches files that have one of the listed extensions. Extension-less files (Gemfile, Rakefile, Gemfile.lock, Dockerfile, etc.) are silently excluded from both check_newlines and fix_newlines. Meanwhile, RuboCop's Layout/TrailingEmptyLines does check Gemfile (it's in .rubocop_todo.yml), so the two enforcement mechanisms diverge.

One fix is to add a separate glob for common extension-less files:

Suggested change
FILE_EXTENSIONS = '**/*.{rb,rake,yml,yaml,md,gemspec,ru,erb,js,json}'
EXTENSIONLESS_FILES = %w[Gemfile Rakefile Gemfile.lock].freeze

and include them in text_files.

Comment thread rakelib/lint.rake
File.open(file, 'rb') do |f|
f.seek([f.size - 2, 0].max)
tail = f.read
files_without_newline << file unless tail.nil? || tail.empty? || tail.end_with?("\n")
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Ruby's Dir.glob skips dotfiles by default — .rubocop.yml, .rubocop_todo.yml, and any files in .github/ won't be included in the check. Pass File::FNM_DOTMATCH to cover them:

Suggested change
files_without_newline << file unless tail.nil? || tail.empty? || tail.end_with?("\n")
Dir.glob(FILE_EXTENSIONS, File::FNM_DOTMATCH)

Comment thread bin/install-hooks
backup = "#{pre_commit_hook}.bak"
FileUtils.cp(pre_commit_hook, backup)
puts "Backed up existing hook to #{backup}"
end
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

If the developer runs ./bin/install-hooks a second time, this copies the already-installed hook to .bak, silently discarding their original custom hook. Consider skipping the backup (or using a timestamped name) when the existing hook was written by this installer — for example, check for a sentinel comment:

existing_content = File.read(pre_commit_hook)
if existing_content.include?('# Pre-commit hook to run linters and check for newlines')
  puts "Replacing previously installed hook (no backup needed)"
else
  backup = "#{pre_commit_hook}.bak"
  FileUtils.cp(pre_commit_hook, backup)
  puts "Backed up existing hook to #{backup}"
end

Comment thread .rubocop.yml
- rubocop-rspec

AllCops:
TargetRubyVersion: 3.0
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

NewCops: enable means every RuboCop patch version upgrade can introduce new failing cops that block CI without any opt-in decision. This is somewhat at odds with the todo-list approach in this PR — the todo list was generated to silence existing violations, but new cops added in future RuboCop releases will fire immediately on new code.

Consider NewCops: pending (the RuboCop default), which prints a warning about new cops without failing the run, letting you consciously opt each one in:

Suggested change
TargetRubyVersion: 3.0
NewCops: pending

Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

Bugbot Autofix is ON, but it could not run because on-demand usage is turned off. To enable Bugbot Autofix, turn on on-demand usage and set a spend limit in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit f6b7c6d. Configure here.

Comment thread bin/install-hooks
if [ "$missing_newline" -ne 0 ]; then
echo "Some files are missing final newlines. Run 'bundle exec rake fix_newlines' to fix."
exit 1
fi
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pre-commit hook newline check too broad for fix command

Medium Severity

The pre-commit hook's newline check iterates over ALL staged files (including binary files like images, fonts, compiled assets) via unfiltered git diff --cached --name-only, while the suggested fix command fix_newlines only operates on specific text extensions defined in NewlineChecker::FILE_EXTENSIONS and explicitly skips binary files. Staging a binary file that doesn't end with \n blocks the commit with a message to run fix_newlines, but that command won't fix it. Note the RuboCop section of the same hook properly filters to *.rb and *.rake files.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit f6b7c6d. Configure here.

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