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)


198
199
200
201
202
203
204
205
206
207
# File 'lib/docscribe/inline_rewriter/source_helpers.rb', line 198

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

.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)


104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
# File 'lib/docscribe/inline_rewriter/source_helpers.rb', line 104

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")
  i = def_line_idx - 1

  # Skip blank lines directly above def
  i -= 1 while i >= 0 && lines[i].strip.empty?

  # Nearest non-blank line must be a comment to remove anything
  return nil unless i >= 0 && lines[i] =~ /^\s*#/

  # Walk upward to include the entire contiguous comment block
  start_idx = i
  start_idx -= 1 while start_idx >= 0 && lines[start_idx] =~ /^\s*#/
  start_idx += 1

  # Preserve leading directive-style comments (currently: rubocop directives)
  removable_start_idx = start_idx
  removable_start_idx += 1 while removable_start_idx <= i && preserved_comment_line?(lines[removable_start_idx])

  # If the whole block is preserved directives, there is nothing to remove
  return nil if removable_start_idx > i

  # SAFETY: only remove if the remaining block looks like documentation
  remaining = lines[removable_start_idx..i]
  return nil unless remaining.any? { |line| doc_marker_line?(line) }

  start_pos = removable_start_idx.positive? ? lines[0...removable_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
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
# File 'lib/docscribe/inline_rewriter/source_helpers.rb', line 54

def doc_comment_block_info(buffer, def_bol_pos)
  src = buffer.source
  lines = src.lines
  def_line_idx = src[0...def_bol_pos].count("\n")
  i = def_line_idx - 1

  # Skip blank lines directly above def
  i -= 1 while i >= 0 && lines[i].strip.empty?

  # Nearest non-blank line must be a comment
  return nil unless i >= 0 && lines[i] =~ /^\s*#/

  # Walk upward to include the entire contiguous comment block
  end_idx = i
  start_idx = i
  start_idx -= 1 while start_idx >= 0 && lines[start_idx] =~ /^\s*#/
  start_idx += 1

  # Preserve leading directive-style comments
  removable_start_idx = start_idx
  removable_start_idx += 1 while removable_start_idx <= end_idx && preserved_comment_line?(lines[removable_start_idx])

  return nil if removable_start_idx > end_idx

  remaining = lines[removable_start_idx..end_idx]
  return nil unless remaining.any? { |line| doc_marker_line?(line) }

  start_pos = start_idx.positive? ? lines[0...start_idx].join.length : 0
  doc_start_pos = removable_start_idx.positive? ? lines[0...removable_start_idx].join.length : 0
  end_pos = lines[0..end_idx].join.length

  {
    lines: lines[start_idx..end_idx],
    preserved_lines: lines[start_idx...removable_start_idx],
    doc_lines: lines[removable_start_idx..end_idx],
    start_pos: start_pos,
    doc_start_pos: doc_start_pos,
    end_pos: end_pos
  }
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)


172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
# File 'lib/docscribe/inline_rewriter/source_helpers.rb', line 172

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

.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)


217
218
219
220
221
222
223
224
225
# File 'lib/docscribe/inline_rewriter/source_helpers.rb', line 217

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)


146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
# File 'lib/docscribe/inline_rewriter/source_helpers.rb', line 146

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