Skip to content

Instantly share code, notes, and snippets.

@sambostock
Created October 31, 2023 00:39
Show Gist options
  • Save sambostock/f7a5cbba9b1b8d72905285d2fc04fb60 to your computer and use it in GitHub Desktop.
Save sambostock/f7a5cbba9b1b8d72905285d2fc04fb60 to your computer and use it in GitHub Desktop.
Heredoc indentation cop

Heredoc Indentation Cop

This is a scrappy implementation of a cop that checks the following for heredocs:

  • Contents may only be indented in increments of 2 spaces.
  • Contents may not be indented using spaces.
  • Lines may be at max indented 2 spaces more than the line before, ignoring blank lines.

Usage

  1. Copy heredoc_internal_indentation.rb to wherever you want in your project
  2. Add the following .rubocop.yml, with the correct require location
require: './heredoc_internal_indentation.rb'

Custom/HeredocInternalIndentation:
  Enabled: true
  1. Try running Rubocop normally, and either it works, or you find the bugs I missed.

Oversights

  • There are no tests. I just ran it against fixture.rb.
  • The offense it tied to the node that opens the heredoc instead of offending line.
  • This should probably use the indentation config from the IndentationWidth cop.
  • Perhaps the cop should only trigger on certain heredoc delimiters (e.g. SQL).
  • The code could probably use refactoring.
  • Probably others things. 🤷‍♂️
<<~ONE_SPACE
blah
one space
ONE_SPACE
<<~TWO_SPACES
blah
two spaces
TWO_SPACES
<<~THREE_SPACES
blah
three spaces
THREE_SPACES
<<~TAB
blah
tab
TAB
<<~OVER_INDENTED
blah
over indented
OVER_INDENTED
<<~SKIP_LINE_BUT_FINE
blah
fine
also fine
SKIP_LINE_BUT_FINE
<<~SKIP_LINE_AND_OVERINDENTED
blah
over indented
SKIP_LINE_AND_OVERINDENTED
# frozen_string_literal: true
module RuboCop
module Cop
module Custom
class HeredocInternalIndentation < Cop
include Heredoc
def on_heredoc(node)
indentation_and_offsets = node.loc.heredoc_body.source.each_line.with_index.map do |line, index|
next if line.strip.empty?
indentation = line[/^\s+/]
next if indentation.nil? || indentation.empty?
[indentation, index]
end.compact
indentation_and_offsets.each_with_index do |(indentation, line_offset), index|
add_offense(node, message: 'Heredoc must not be indented using tabs') if indentation.include?("\t")
add_offense(node, message: "Heredoc must be indented in increments of two spaces") if indentation.length.odd?
previous_indentation = indentation_and_offsets.fetch(index - 1, nil)&.first
if previous_indentation && indentation.length > previous_indentation.length + 2
add_offense(node, message: "Heredoc must be indented in increments of two spaces")
end
end
end
end
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment