Module: Docscribe::InlineRewriter::SourceHelpers

Defined in:
lib/docscribe/inline_rewriter/source_helpers.rb

Overview

Source-level helpers: ranges, insertion positions, indentation, and comment-block detection.

These helpers operate on raw source text and parser source locations rather than Ruby semantics.

Class Method Summary collapse

Class Method Details

.already_has_doc_immediately_above?(buffer, insert_pos) ⇒ Boolean

Note:

module_function: when included, also defines #already_has_doc_immediately_above? (instance visibility: private)

Whether any comment exists immediately above the insertion point.

This helper is retained for compatibility/legacy behavior checks.

Parameters:

  • buffer (Parser::Source::Buffer)
  • insert_pos (Integer)

Returns:

  • (Boolean)


246
247
248
249
250
251
252
253
254
255
# File 'lib/docscribe/inline_rewriter/source_helpers.rb', line 246

def already_has_doc_immediately_above?(buffer, insert_pos)
  src = buffer.source
  lines = src.lines
  current_line_index = (src[0...insert_pos] || '').count("\n")
  i = current_line_index - 1
  i -= 1 while i >= 0 && lines[i].strip.empty?
  return false if i.negative?

  !!(lines[i] =~ /^\s*#/)
end

.build_block_info(lines, start_idx, preserved_start_idx, end_idx) ⇒ Hash

Note:

module_function: when included, also defines #build_block_info (instance visibility: private)

Build block info hash from computed line ranges.

Parameters:

  • lines (Array<String>)
  • start_idx (Integer)
  • preserved_start_idx (Integer)
  • end_idx (Integer)

Returns:

  • (Hash)


145
146
147
148
149
150
151
152
153
# File 'lib/docscribe/inline_rewriter/source_helpers.rb', line 145

def build_block_info(lines, start_idx, preserved_start_idx, end_idx)
  positions = compute_positions(lines, start_idx, preserved_start_idx, end_idx)
  {
    lines: lines[start_idx..end_idx],
    preserved_lines: lines[start_idx...preserved_start_idx],
    doc_lines: lines[preserved_start_idx..end_idx],
    **positions
  }
end

.comment_block_removal_range(buffer, def_bol_pos) ⇒ Parser::Source::Range?

Note:

module_function: when included, also defines #comment_block_removal_range (instance visibility: private)

Compute the removable range for an existing doc-like block above a method.

Preserved directive lines (such as RuboCop directives or magic comments) are excluded from the returned range.

Parameters:

  • buffer (Parser::Source::Buffer)
  • def_bol_pos (Integer)

    beginning-of-line position of the target def

Returns:

  • (Parser::Source::Range, nil)


77
78
79
80
81
82
83
84
85
86
87
88
# File 'lib/docscribe/inline_rewriter/source_helpers.rb', line 77

def comment_block_removal_range(buffer, def_bol_pos)
  src = buffer.source
  lines = src.lines
  def_line_idx = (src[0...def_bol_pos] || '').count("\n")
  block_range = find_comment_block_range(lines, def_line_idx)
  return nil unless block_range

  preserved_start_idx = find_preserved_start_idx(lines, block_range[:start_idx], block_range[:end_idx])
  return nil unless doc_marker?(lines, preserved_start_idx..block_range[:end_idx])

  compute_removal_range(buffer, lines, preserved_start_idx, def_bol_pos)
end

.compute_positions(lines, start_idx, doc_start_idx, end_pos_idx) ⇒ Hash{start_pos: Integer, doc_start_pos: Integer, end_pos: Integer}

Note:

module_function: when included, also defines #compute_positions (instance visibility: private)

Compute source positions for a comment block.

Parameters:

  • lines (Array<String>)
  • start_idx (Integer)
  • doc_start_idx (Integer)
  • end_pos_idx (Integer)

Returns:

  • (Hash{start_pos: Integer, doc_start_pos: Integer, end_pos: Integer})


176
177
178
179
180
181
# File 'lib/docscribe/inline_rewriter/source_helpers.rb', line 176

def compute_positions(lines, start_idx, doc_start_idx, end_pos_idx)
  start_pos = start_idx.positive? ? (lines[0...start_idx] || []).join.length : 0
  doc_start_pos = doc_start_idx.positive? ? (lines[0...doc_start_idx] || []).join.length : 0
  end_pos = (lines[0..end_pos_idx] || []).join.length
  { start_pos: start_pos, doc_start_pos: doc_start_pos, end_pos: end_pos }
end

.compute_removal_range(buffer, lines, preserved_start_idx, def_bol_pos) ⇒ Parser::Source::Range

Note:

module_function: when included, also defines #compute_removal_range (instance visibility: private)

Compute the removal range for preserved start position.

Parameters:

  • buffer (Parser::Source::Buffer)
  • lines (Array<String>)
  • preserved_start_idx (Integer)
  • def_bol_pos (Integer)

Returns:

  • (Parser::Source::Range)


163
164
165
166
# File 'lib/docscribe/inline_rewriter/source_helpers.rb', line 163

def compute_removal_range(buffer, lines, preserved_start_idx, def_bol_pos)
  start_pos = preserved_start_idx.positive? ? (lines[0...preserved_start_idx] || []).join.length : 0
  Parser::Source::Range.new(buffer, start_pos, def_bol_pos)
end

.doc_comment_block_info(buffer, def_bol_pos) ⇒ Hash?

Note:

module_function: when included, also defines #doc_comment_block_info (instance visibility: private)

Return structured information about a contiguous doc-like comment block above a method.

Result includes:

  • all lines in the contiguous block

  • preserved directive prefix lines

  • editable doc lines

  • source positions for replacement

Returns nil if no doc-like block is present.

Parameters:

  • buffer (Parser::Source::Buffer)
  • def_bol_pos (Integer)

    beginning-of-line position of the target def

Returns:

  • (Hash, nil)


54
55
56
57
58
59
60
61
62
63
64
65
66
# File 'lib/docscribe/inline_rewriter/source_helpers.rb', line 54

def doc_comment_block_info(buffer, def_bol_pos)
  lines = buffer.source.lines
  def_line_idx = (buffer.source[0...def_bol_pos] || '').count("\n")
  block_range = find_comment_block_range(lines, def_line_idx)
  return nil unless block_range

  start_idx = block_range[:start_idx]
  end_idx = block_range[:end_idx]
  preserved_start_idx = find_preserved_start_idx(lines, start_idx, end_idx)
  return nil unless doc_marker?(lines, preserved_start_idx..end_idx)

  build_block_info(lines, start_idx, preserved_start_idx, end_idx)
end

.doc_marker?(lines, range) ⇒ Boolean

Note:

module_function: when included, also defines #doc_marker? (instance visibility: private)

Whether a comment block range contains documentation markers.

Parameters:

  • lines (Array<String>)
  • range (Range)

    line index range

Returns:

  • (Boolean)


133
134
135
# File 'lib/docscribe/inline_rewriter/source_helpers.rb', line 133

def doc_marker?(lines, range)
  (lines[range] || []).any? { |line| doc_marker_line?(line) }
end

.doc_marker_line?(line) ⇒ Boolean

Note:

module_function: when included, also defines #doc_marker_line? (instance visibility: private)

Whether a comment line looks like documentation content.

Recognized forms include:

  • Docscribe header lines

  • YARD tags/directives beginning with ‘@`

Parameters:

  • line (String)

Returns:

  • (Boolean)


219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
# File 'lib/docscribe/inline_rewriter/source_helpers.rb', line 219

def doc_marker_line?(line)
  # Docscribe header line:
  #   # +A#foo+ -> Integer
  return true if line =~ /^\s*#\s*\+\S.*\+\s*->\s*\S/

  # YARD tags and directives:
  #   # @param ...
  #   # @return ...
  #   # @raise ...
  #   # @private / @protected
  #   # @!attribute ...
  # also matches indented attribute tag lines like:
  #   #   @return [Type]
  return true if line =~ /^\s*#\s*@/

  false
end

.find_comment_block_range(lines, def_line_idx) ⇒ Hash{start_idx: Integer, end_idx: Integer}?

Note:

module_function: when included, also defines #find_comment_block_range (instance visibility: private)

Find the range of a contiguous comment block directly above a method definition.

Walks upward from def_line_idx, skipping blank lines, then includes all contiguous comment lines.

Parameters:

  • lines (Array<String>)
  • def_line_idx (Integer)

Returns:

  • (Hash{start_idx: Integer, end_idx: Integer}, nil)


99
100
101
102
103
104
105
106
107
108
109
110
# File 'lib/docscribe/inline_rewriter/source_helpers.rb', line 99

def find_comment_block_range(lines, def_line_idx)
  i = def_line_idx - 1

  i -= 1 while i >= 0 && lines[i].strip.empty?
  return nil unless i >= 0 && lines[i] =~ /^\s*#/

  start_idx = i
  start_idx -= 1 while start_idx >= 0 && lines[start_idx] =~ /^\s*#/
  start_idx += 1

  { start_idx: start_idx, end_idx: i }
end

.find_preserved_start_idx(lines, start_idx, end_idx) ⇒ Integer

Note:

module_function: when included, also defines #find_preserved_start_idx (instance visibility: private)

Find the first index in a comment block after preserved directive-style lines.

Preserved lines include RuboCop directives and Ruby magic comments.

Parameters:

  • lines (Array<String>)
  • start_idx (Integer)
  • end_idx (Integer)

Returns:

  • (Integer)


121
122
123
124
125
# File 'lib/docscribe/inline_rewriter/source_helpers.rb', line 121

def find_preserved_start_idx(lines, start_idx, end_idx)
  idx = start_idx
  idx += 1 while idx <= end_idx && preserved_comment_line?(lines[idx])
  idx
end

.line_indent(node) ⇒ String

Note:

module_function: when included, also defines #line_indent (instance visibility: private)

Return the indentation prefix of a node’s source line.

Tabs and spaces are preserved exactly.

Parameters:

  • node (Parser::AST::Node)

Returns:

  • (String)

Raises:

  • (StandardError)


265
266
267
268
269
270
271
272
273
# File 'lib/docscribe/inline_rewriter/source_helpers.rb', line 265

def line_indent(node)
  line = node.loc.expression.source_line
  return '' unless line

  # Preserve tabs/spaces exactly.
  line[/\A[ \t]*/] || ''
rescue StandardError
  ''
end

.line_start_range(buffer, node) ⇒ Parser::Source::Range

Note:

module_function: when included, also defines #line_start_range (instance visibility: private)

Return a zero-width range at the beginning of the line containing a node.

Used as the insertion point for generated documentation.

Parameters:

  • buffer (Parser::Source::Buffer)
  • node (Parser::AST::Node)

Returns:

  • (Parser::Source::Range)


33
34
35
36
37
38
# File 'lib/docscribe/inline_rewriter/source_helpers.rb', line 33

def line_start_range(buffer, node)
  start_pos = node.loc.expression.begin_pos
  src = buffer.source
  bol = start_pos <= 0 ? -1 : src.rindex("\n", start_pos - 1) || -1
  Parser::Source::Range.new(buffer, bol + 1, bol + 1)
end

.node_name(node) ⇒ Symbol?

Note:

module_function: when included, also defines #node_name (instance visibility: private)

Extract the method name from a ‘:def` or `:defs` node.

Parameters:

  • node (Parser::AST::Node)

Returns:

  • (Symbol, nil)


18
19
20
21
22
23
# File 'lib/docscribe/inline_rewriter/source_helpers.rb', line 18

def node_name(node)
  case node.type
  when :def then node.children[0]
  when :defs then node.children[1]
  end
end

.preserved_comment_line?(line) ⇒ Boolean

Note:

module_function: when included, also defines #preserved_comment_line? (instance visibility: private)

Whether a comment line should be preserved during aggressive replacement.

Preserved lines include:

  • RuboCop directives

  • Ruby magic comments

  • tool directives such as ‘:nocov:` / `:stopdoc:`

Parameters:

  • line (String)

Returns:

  • (Boolean)


193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
# File 'lib/docscribe/inline_rewriter/source_helpers.rb', line 193

def preserved_comment_line?(line)
  # RuboCop directives
  return true if line =~ /^\s*#\s*rubocop:(disable|enable|todo)\b/

  # Ruby magic comments
  return true if line =~ /^\s*#\s*(?:frozen_string_literal|warn_indent)\s*:\s*(?:true|false)\b/i
  return true if line =~ /^\s*#.*\b(?:encoding|coding)\s*:\s*[\w.-]+\b/i

  # Tool directives like:
  #   # :nocov:
  #   # :stopdoc:
  #   # :nodoc:
  return true if line =~ /^\s*#\s*:\s*[\w-]+\s*:(?=\s|\z)/i

  false
end