Module: RuboCop::Cop::Asjer::RailsClassOrderCorrector

Included in:
RailsClassOrder
Defined in:
lib/rubocop/cop/asjer/rails_class_order.rb

Overview

Autocorrect helpers for RailsClassOrder cop

Instance Method Summary collapse

Instance Method Details

#autocorrect(corrector, body, original, sorted) ⇒ Object



95
96
97
98
99
100
101
102
103
104
105
# File 'lib/rubocop/cop/asjer/rails_class_order.rb', line 95

def autocorrect(corrector, body, original, sorted)
  ordered_targets = ordered_target_nodes(original)
  corrector.replace(
    correction_range_for(ordered_targets),
    rebuilt_source(
      sorted,
      ordered_targets,
      trailing_sibling: trailing_sibling?(body, ordered_targets.last)
    )
  )
end

#build_sorted_source(sorted, reference_targets) ⇒ Object



160
161
162
163
164
# File 'lib/rubocop/cop/asjer/rails_class_order.rb', line 160

def build_sorted_source(sorted, reference_targets)
  indent = ' ' * reference_targets.first.loc.column
  grouped = sorted.group_by { |m| method_type(m) }
  format_grouped_source(grouped, indent)
end

#collect_adjacent_comments(node_pos) ⇒ Object



128
129
130
131
132
133
134
# File 'lib/rubocop/cop/asjer/rails_class_order.rb', line 128

def collect_adjacent_comments(node_pos)
  expected_line = node_pos.first_line - 1
  comments_before_node(node_pos).take_while do |comment|
    pos = comment.loc.expression
    (pos.last_line == expected_line).tap { expected_line = pos.first_line - 1 }
  end.reverse
end

#comments_before_node(node_pos) ⇒ Object



136
137
138
# File 'lib/rubocop/cop/asjer/rails_class_order.rb', line 136

def comments_before_node(node_pos)
  processed_source.comments.select { |c| c.loc.expression.end_pos < node_pos.begin_pos }.reverse
end

#correction_range_for(ordered_targets) ⇒ Object



109
110
111
112
113
114
115
116
# File 'lib/rubocop/cop/asjer/rails_class_order.rb', line 109

def correction_range_for(ordered_targets)
  # Stop at the last expression boundary so any newline/blank-line
  # suffix after the final target stays in the surrounding source.
  range_between(
    range_with_comments(ordered_targets.first).begin_pos,
    range_with_comments(ordered_targets.last).end_pos
  )
end

#format_grouped_source(grouped, indent) ⇒ Object



213
214
215
216
217
218
219
# File 'lib/rubocop/cop/asjer/rails_class_order.rb', line 213

def format_grouped_source(grouped, indent)
  self.class::TYPE_ORDER.keys.filter_map do |type|
    next unless grouped[type]&.any?

    grouped[type].map { |m| source_with_comments(m) }.join("\n#{indent}")
  end.join("\n\n#{indent}")
end

#full_method_range(node) ⇒ Object



140
141
142
143
144
145
146
147
# File 'lib/rubocop/cop/asjer/rails_class_order.rb', line 140

def full_method_range(node)
  range = range_with_comments(node)
  source = processed_source.buffer.source
  line_start = source.rindex("\n", range.begin_pos - 1)&.+(1) || 0
  end_pos = source[range.end_pos] == "\n" ? range.end_pos + 1 : range.end_pos

  range_between(line_start, skip_trailing_blank_lines(source, end_pos))
end

#normalize_interstitial_chunk(chunk) ⇒ Object



205
206
207
208
209
210
211
# File 'lib/rubocop/cop/asjer/rails_class_order.rb', line 205

def normalize_interstitial_chunk(chunk)
  return if chunk.nil? || chunk.strip.empty?

  # Strip leading blank lines only; trailing ones stay attached so later
  # preserved chunks keep their separator spacing.
  chunk.sub(/\A(?:[ \t]*\n)+/, '')
end

#ordered_target_nodes(original) ⇒ Object



107
# File 'lib/rubocop/cop/asjer/rails_class_order.rb', line 107

def ordered_target_nodes(original) = original.sort_by { |method| full_method_range(method).begin_pos }

#preceding_comments(node) ⇒ Object



126
# File 'lib/rubocop/cop/asjer/rails_class_order.rb', line 126

def preceding_comments(node) = collect_adjacent_comments(node.loc.expression)

#preserved_interstitial_source(ordered_targets) ⇒ Object



193
194
195
196
197
198
199
200
201
202
203
# File 'lib/rubocop/cop/asjer/rails_class_order.rb', line 193

def preserved_interstitial_source(ordered_targets)
  source = processed_source.buffer.source

  ordered_targets
    .map { |method| full_method_range(method) }
    .each_cons(2)
    .filter_map do |left, right|
      normalize_interstitial_chunk(source[left.end_pos...right.begin_pos])
    end
    .join
end

#range_with_comments(node) ⇒ Object



120
121
122
123
124
# File 'lib/rubocop/cop/asjer/rails_class_order.rb', line 120

def range_with_comments(node)
  comments = preceding_comments(node)
  start_pos = comments.empty? ? node.loc.expression.begin_pos : comments.first.loc.expression.begin_pos
  range_between(start_pos, node.loc.expression.end_pos)
end

#rebuilt_source(sorted, ordered_targets, trailing_sibling:) ⇒ Object



166
167
168
169
170
171
172
173
174
175
176
177
178
179
# File 'lib/rubocop/cop/asjer/rails_class_order.rb', line 166

def rebuilt_source(sorted, ordered_targets, trailing_sibling:)
  preserved = preserved_interstitial_source(ordered_targets)
  rebuilt = [
    build_sorted_source(sorted, ordered_targets).rstrip,
    preserved
  ].reject(&:empty?).join("\n\n")
  trimmed = trim_trailing_newlines(rebuilt)

  # Keep end-of-class output flush unless the final preserved chunk
  # already carried a trailing blank line before it was moved.
  return trimmed if trailing_sibling || !trailing_blank_line?(preserved)

  "#{trimmed}\n"
end

#skip_trailing_blank_lines(source, pos) ⇒ Object



149
150
151
152
153
154
155
156
157
158
# File 'lib/rubocop/cop/asjer/rails_class_order.rb', line 149

def skip_trailing_blank_lines(source, pos)
  while pos < source.length
    line_end = source.index("\n", pos)
    break unless line_end
    break unless source[pos...line_end].strip.empty?

    pos = line_end + 1
  end
  pos
end

#source_with_comments(node) ⇒ Object



221
222
223
224
# File 'lib/rubocop/cop/asjer/rails_class_order.rb', line 221

def source_with_comments(node)
  range = range_with_comments(node)
  processed_source.buffer.source[range.begin_pos...range.end_pos].lstrip
end

#trailing_blank_line?(source) ⇒ Boolean

Returns:

  • (Boolean)


187
188
189
190
191
# File 'lib/rubocop/cop/asjer/rails_class_order.rb', line 187

def trailing_blank_line?(source)
  return false unless source&.end_with?("\n")

  source[(source.rindex("\n", source.length - 2)&.+(1) || 0)...-1].strip.empty?
end

#trailing_sibling?(body, last_target) ⇒ Boolean

Returns:

  • (Boolean)


118
# File 'lib/rubocop/cop/asjer/rails_class_order.rb', line 118

def trailing_sibling?(body, last_target) = body.children.index(last_target) < body.children.size - 1

#trim_trailing_newlines(source) ⇒ Object



181
182
183
184
185
# File 'lib/rubocop/cop/asjer/rails_class_order.rb', line 181

def trim_trailing_newlines(source)
  trimmed = source.dup
  trimmed.chop! while trimmed.end_with?("\n")
  trimmed
end