-
-
Notifications
You must be signed in to change notification settings - Fork 62
Add RuboCop linting and newline enforcement #193
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
name: Lint | ||
|
||
on: | ||
push: | ||
branches: [master, main] | ||
pull_request: | ||
branches: [master, main] | ||
|
||
jobs: | ||
lint: | ||
runs-on: ubuntu-latest | ||
|
||
steps: | ||
- uses: actions/checkout@v4 | ||
|
||
- name: Set up Ruby | ||
uses: ruby/setup-ruby@v1 | ||
with: | ||
ruby-version: '3.0' | ||
bundler-cache: true | ||
|
||
- name: Run RuboCop | ||
run: bundle exec rubocop | ||
|
||
- name: Check for files missing newlines | ||
run: bundle exec rake check_newlines |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
plugins: | ||
- rubocop-rake | ||
- rubocop-rspec | ||
|
||
AllCops: | ||
TargetRubyVersion: 3.0 | ||
NewCops: enable | ||
Exclude: | ||
- 'vendor/**/*' | ||
- 'spec/fixtures/**/*' | ||
- 'tmp/**/*' | ||
- 'pkg/**/*' | ||
- 'node_modules/**/*' | ||
- 'specs_e2e/**/*' | ||
- 'e2e/**/*' | ||
|
||
# Ensure all files end with a newline | ||
Layout/TrailingEmptyLines: | ||
Enabled: true | ||
EnforcedStyle: final_newline | ||
|
||
# Ensure no trailing whitespace | ||
Layout/TrailingWhitespace: | ||
Enabled: true | ||
|
||
# Line length - be reasonable but not too strict | ||
Layout/LineLength: | ||
Max: 120 | ||
Exclude: | ||
- 'spec/**/*' | ||
- 'lib/generators/**/*' | ||
|
||
# Allow longer blocks in specs and rake tasks | ||
Metrics/BlockLength: | ||
Exclude: | ||
- 'spec/**/*' | ||
- '**/*.rake' | ||
- 'Rakefile' | ||
- '*.gemspec' | ||
|
||
# Allow longer methods in rake tasks | ||
Metrics/MethodLength: | ||
Exclude: | ||
- '**/*.rake' | ||
- 'Rakefile' | ||
|
||
# String literals | ||
Style/StringLiterals: | ||
Enabled: true | ||
EnforcedStyle: single_quotes | ||
ConsistentQuotesInMultiline: true | ||
|
||
# Frozen string literal pragma | ||
Style/FrozenStringLiteralComment: | ||
Enabled: true | ||
EnforcedStyle: always | ||
Exclude: | ||
- 'spec/**/*' | ||
|
||
# Documentation | ||
Style/Documentation: | ||
Enabled: false | ||
|
||
# Allow compact module/class definitions | ||
Style/ClassAndModuleChildren: | ||
Enabled: false | ||
|
||
# RSpec specific | ||
RSpec/ExampleLength: | ||
Max: 20 | ||
|
||
RSpec/MultipleExpectations: | ||
Max: 5 | ||
|
||
RSpec/NestedGroups: | ||
Max: 4 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
#!/usr/bin/env ruby | ||
# frozen_string_literal: true | ||
|
||
require 'fileutils' | ||
|
||
hooks_dir = File.expand_path('../.git/hooks', __dir__) | ||
pre_commit_hook = File.join(hooks_dir, 'pre-commit') | ||
|
||
# Create pre-commit hook content | ||
hook_content = <<~HOOK | ||
#!/bin/sh | ||
# Pre-commit hook to run linters and check for newlines | ||
|
||
# Check for files missing newlines | ||
echo "Checking for files missing newlines..." | ||
bundle exec rake check_newlines | ||
if [ $? -ne 0 ]; then | ||
echo "❌ Some files are missing final newlines. Run 'bundle exec rake fix_newlines' to fix." | ||
exit 1 | ||
fi | ||
|
||
# Run RuboCop on staged Ruby files | ||
echo "Running RuboCop on staged files..." | ||
files=$(git diff --cached --name-only --diff-filter=ACM | grep -E '\\.(rb|rake)$') | ||
if [ -n "$files" ]; then | ||
bundle exec rubocop $files | ||
if [ $? -ne 0 ]; then | ||
echo "❌ RuboCop failed. Fix issues or run 'bundle exec rake lint:fix'" | ||
exit 1 | ||
fi | ||
Comment on lines
+24
to
+30
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Critical: Fix command injection vulnerability in git hook. Line 26 has an unquoted variable Apply this diff to properly quote the variable: # Run RuboCop on staged Ruby files
echo "Running RuboCop on staged files..."
files=$(git diff --cached --name-only --diff-filter=ACM | grep -E '\\.(rb|rake)$')
if [ -n "$files" ]; then
- bundle exec rubocop $files
+ bundle exec rubocop $files
if [ $? -ne 0 ]; then
echo "❌ RuboCop failed. Fix issues or run 'bundle exec rake lint:fix'"
exit 1 Wait, I need to check the actual quoting. Let me re-examine line 26 in the provided code. Actually, looking at line 26: - bundle exec rubocop $files
+ bundle exec rubocop "$files" However, this still has an issue - if there are multiple files, they'll be treated as a single argument. The proper fix is: files=$(git diff --cached --name-only --diff-filter=ACM | grep -E '\\.(rb|rake)$')
if [ -n "$files" ]; then
- bundle exec rubocop $files
+ echo "$files" | xargs bundle exec rubocop
if [ $? -ne 0 ]; then Or use a while loop for better handling: - files=$(git diff --cached --name-only --diff-filter=ACM | grep -E '\\.(rb|rake)$')
- if [ -n "$files" ]; then
- bundle exec rubocop $files
- if [ $? -ne 0 ]; then
- echo "❌ RuboCop failed. Fix issues or run 'bundle exec rake lint:fix'"
- exit 1
- fi
- fi
+ git diff --cached --name-only --diff-filter=ACM | grep -E '\\.(rb|rake)$' | \
+ while IFS= read -r file; do
+ bundle exec rubocop "$file"
+ if [ $? -ne 0 ]; then
+ echo "❌ RuboCop failed. Fix issues or run 'bundle exec rake lint:fix'"
+ exit 1
+ fi
+ done
🤖 Prompt for AI Agents
|
||
fi | ||
|
||
echo "✅ All checks passed!" | ||
HOOK | ||
|
||
# Create hooks directory if it doesn't exist | ||
FileUtils.mkdir_p(hooks_dir) | ||
|
||
# Write the pre-commit hook | ||
File.write(pre_commit_hook, hook_content) | ||
|
||
# Make it executable | ||
File.chmod(0o755, pre_commit_hook) | ||
|
||
puts '✅ Pre-commit hook installed successfully!' | ||
puts 'The hook will:' | ||
puts ' - Check that all files end with a newline' | ||
puts ' - Run RuboCop on staged Ruby files' | ||
puts '' | ||
puts 'To skip the hook temporarily, use: git commit --no-verify' |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
# frozen_string_literal: true | ||
|
||
desc 'Run RuboCop' | ||
task :rubocop do | ||
sh 'bundle exec rubocop' | ||
end | ||
|
||
desc 'Run RuboCop with auto-correct' | ||
task 'rubocop:auto_correct' do | ||
sh 'bundle exec rubocop -A' | ||
end | ||
|
||
desc 'Run all linters' | ||
task lint: :rubocop | ||
|
||
desc 'Auto-fix all linting issues' | ||
task 'lint:fix' => 'rubocop:auto_correct' | ||
|
||
desc 'Ensure all files end with newline' | ||
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) | ||
|
||
content = File.read(file) | ||
files_without_newline << file unless content.empty? || content.end_with?("\n") | ||
end | ||
|
||
if files_without_newline.any? | ||
puts 'Files missing final newline:' | ||
files_without_newline.each { |f| puts " #{f}" } | ||
exit 1 | ||
else | ||
puts '✓ All files end with newline' | ||
end | ||
end | ||
|
||
desc 'Fix files missing final newline' | ||
task :fix_newlines do | ||
fixed_files = [] | ||
|
||
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) | ||
|
||
content = File.read(file) | ||
unless content.empty? || content.end_with?("\n") | ||
File.write(file, content + "\n") | ||
fixed_files << file | ||
end | ||
end | ||
Comment on lines
+45
to
+55
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add error handling and consider TOCTOU risks. The current implementation has potential issues:
Apply this diff to improve safety: content = File.read(file)
unless content.empty? || content.end_with?("\n")
- File.write(file, content + "\n")
- fixed_files << file
+ begin
+ # Write to temp file first for atomicity
+ temp_file = "#{file}.tmp"
+ File.write(temp_file, content + "\n")
+ File.rename(temp_file, file)
+ fixed_files << file
+ rescue => e
+ warn "Failed to fix #{file}: #{e.message}"
+ File.delete(temp_file) if File.exist?(temp_file)
+ end
end Or consider using unless content.empty? || content.end_with?("\n")
begin
File.open(file, 'a') { |f| f.write("\n") }
fixed_files << file
rescue => e
warn "Failed to fix #{file}: #{e.message}"
end
end 🤖 Prompt for AI Agents
|
||
|
||
if fixed_files.any? | ||
puts "Fixed #{fixed_files.length} files:" | ||
fixed_files.each { |f| puts " #{f}" } | ||
else | ||
puts '✓ All files already end with newline' | ||
end | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Newline check runs on all files, not just staged.
The
check_newlines
task (line 16) checks all files in the repository, not just staged files. This is inconsistent with the RuboCop check which only lints staged files. This could:Consider checking only staged files or documenting this intentional behavior.
If you want to check only staged files, apply this diff: