Class: Ast::Merge::Comment::HashTrackerBase
- Inherits:
-
Object
- Object
- Ast::Merge::Comment::HashTrackerBase
- Defined in:
- lib/ast/merge/comment/hash_tracker_base.rb
Overview
Shared base class for hash-comment (#) trackers across the merge family.
This base provides the common lookup, query, region-building, and
attachment API that every #-syntax format shares. Format-specific
subclasses override:
- #extract_comments — scanning/parsing logic for the format
- #owner_line_num — how to resolve a structural owner to a line number
The tracked-comment hash shape is the same as TrackedHashAdapter expects:
{ line: Integer, # 1-based line number
indent: Integer, # leading whitespace width
text: String, # comment text without prefix
full_line: Boolean, # true if the comment is the entire line
raw: String } # original source line
Defined Under Namespace
Classes: MissingTrackedLineError
Constant Summary collapse
- FULL_LINE_COMMENT_REGEX =
Matches a full-line hash-style comment.
/\A(?<indent>\s*)#\s?(?<text>.*)\z/
Instance Attribute Summary collapse
-
#comments ⇒ Array<Hash>
readonly
All extracted comments with metadata.
-
#lines ⇒ Array<String>
readonly
Source lines (chomped).
Instance Method Summary collapse
-
#augment(owners: [], **options) ⇒ Ast::Merge::Comment::Augmenter
Build a passive shared comment augmenter for this source.
-
#blank_line?(line_num) ⇒ Boolean
Check if a line is blank.
-
#comment_at(line_num) ⇒ Hash?
Get comment hash at a specific line.
-
#comment_attachment_for(owner, line_num: nil, leading_comments: nil, inline_comment: nil, trailing_comments: nil, **metadata) ⇒ Ast::Merge::Comment::Attachment
Build a passive shared comment attachment for an owner.
-
#comment_node_at(line_num) ⇒ Ast::Merge::Comment::Line?
Get a shared comment node at a specific line.
-
#comment_nodes ⇒ Array<Ast::Merge::Comment::Line>
Get all comments converted to shared comment nodes.
-
#comment_region_for_range(range, kind:, full_line_only: false) ⇒ Ast::Merge::Comment::Region
Get comments in a line range converted to a shared comment region.
-
#comments_in_range(range) ⇒ Array<Hash>
Get all comments in a line range.
-
#full_line_comment?(line_num) ⇒ Boolean
Check if a line is a full-line comment.
-
#initialize(lines, tree_haver_comments: nil) ⇒ HashTrackerBase
constructor
Initialize the tracker.
-
#inline_comment_at(line_num) ⇒ Hash?
Get trailing comment on the same line (inline comment).
-
#inline_comment_region_at(line_num, comment: nil) ⇒ Ast::Merge::Comment::Region?
Get a shared inline comment region at a line.
-
#leading_comment_region_before(line_num, comments: nil) ⇒ Ast::Merge::Comment::Region?
Get a shared leading comment region before a line.
-
#leading_comments_before(line_num) ⇒ Array<Hash>
Get leading full-line comments before a line, walking backward and skipping blank lines between consecutive comment blocks.
-
#line_at(line_num) ⇒ String?
Get raw line content.
- #owner_end_line(owner) ⇒ Object
-
#strip_preamble(comments, node_line) ⇒ Array<Hash>
Strip file-preamble comments from a leading-comment collection.
- #trailing_comment_owned_by?(comment, owner) ⇒ Boolean
-
#trailing_comment_region_after(line_num, upper_bound: nil, comments: nil, owner: nil) ⇒ Ast::Merge::Comment::Region?
Get a shared trailing comment region after a line/span.
-
#trailing_comments_after(line_num, upper_bound: nil, owner: nil) ⇒ Array<Hash>
Get adjacent full-line trailing comments after an owner line/span.
Constructor Details
#initialize(lines, tree_haver_comments: nil) ⇒ HashTrackerBase
Initialize the tracker. Subclasses may accept additional arguments
but should call super or reproduce the setup here.
When tree_haver_comments is provided the tracker skips its own
text-scanning path (+extract_comments+) and converts the tree_haver
comment objects into the internal hash format instead. This is the
preferred input when the calling backend has already obtained a flat,
deduplicated list from TreeHaver::Base::Parser#comments.
60 61 62 63 64 65 66 67 68 |
# File 'lib/ast/merge/comment/hash_tracker_base.rb', line 60 def initialize(lines, tree_haver_comments: nil) @lines = Array(lines) @comments = if tree_haver_comments comments_from_tree_haver(tree_haver_comments) else extract_comments end @comments_by_line = @comments.group_by { |c| c[:line] } end |
Instance Attribute Details
#comments ⇒ Array<Hash> (readonly)
Returns All extracted comments with metadata.
41 42 43 |
# File 'lib/ast/merge/comment/hash_tracker_base.rb', line 41 def comments @comments end |
#lines ⇒ Array<String> (readonly)
Returns Source lines (chomped).
44 45 46 |
# File 'lib/ast/merge/comment/hash_tracker_base.rb', line 44 def lines @lines end |
Instance Method Details
#augment(owners: [], **options) ⇒ Ast::Merge::Comment::Augmenter
Build a passive shared comment augmenter for this source.
389 390 391 392 393 394 395 396 397 |
# File 'lib/ast/merge/comment/hash_tracker_base.rb', line 389 def augment(owners: [], **) Augmenter.new( lines: @lines, comments: @comments, owners: owners, style: comment_style, ** ) end |
#blank_line?(line_num) ⇒ Boolean
Check if a line is blank.
321 322 323 324 325 |
# File 'lib/ast/merge/comment/hash_tracker_base.rb', line 321 def blank_line?(line_num) return false if line_num < 1 || line_num > @lines.length @lines[line_num - 1].to_s.strip.empty? end |
#comment_at(line_num) ⇒ Hash?
Get comment hash at a specific line.
78 79 80 |
# File 'lib/ast/merge/comment/hash_tracker_base.rb', line 78 def comment_at(line_num) @comments_by_line[line_num]&.first end |
#comment_attachment_for(owner, line_num: nil, leading_comments: nil, inline_comment: nil, trailing_comments: nil, **metadata) ⇒ Ast::Merge::Comment::Attachment
Build a passive shared comment attachment for an owner.
278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 |
# File 'lib/ast/merge/comment/hash_tracker_base.rb', line 278 def (owner, line_num: nil, leading_comments: nil, inline_comment: nil, trailing_comments: nil, **) resolved_line_num = line_num || owner_line_num(owner) resolved_end_line = owner_end_line(owner) || resolved_line_num leading_region = if resolved_line_num leading_comment_region_before(resolved_line_num, comments: leading_comments) end inline_region = (inline_comment_region_at(resolved_line_num, comment: inline_comment) if resolved_line_num) trailing_region = if resolved_end_line trailing_comment_region_after(resolved_end_line, comments: trailing_comments, owner: owner) end Attachment.new( owner: owner, leading_region: leading_region, inline_region: inline_region, trailing_region: trailing_region, metadata: .merge( line_num: resolved_line_num, end_line: resolved_end_line, source: :comment_tracker ) ) end |
#comment_node_at(line_num) ⇒ Ast::Merge::Comment::Line?
Get a shared comment node at a specific line.
93 94 95 96 97 98 |
# File 'lib/ast/merge/comment/hash_tracker_base.rb', line 93 def comment_node_at(line_num) comment = comment_at(line_num) return unless comment build_comment_node(comment) end |
#comment_nodes ⇒ Array<Ast::Merge::Comment::Line>
Get all comments converted to shared comment nodes.
85 86 87 |
# File 'lib/ast/merge/comment/hash_tracker_base.rb', line 85 def comment_nodes @comment_nodes ||= @comments.map { |c| build_comment_node(c) } end |
#comment_region_for_range(range, kind:, full_line_only: false) ⇒ Ast::Merge::Comment::Region
Get comments in a line range converted to a shared comment region.
118 119 120 121 122 123 124 125 126 127 128 129 130 131 |
# File 'lib/ast/merge/comment/hash_tracker_base.rb', line 118 def comment_region_for_range(range, kind:, full_line_only: false) selected = comments_in_range(range) selected = selected.select { |c| c[:full_line] } if full_line_only build_region( kind: kind, comments: selected, metadata: { range: range, full_line_only: full_line_only, source: :comment_tracker } ) end |
#comments_in_range(range) ⇒ Array<Hash>
Get all comments in a line range.
108 109 110 |
# File 'lib/ast/merge/comment/hash_tracker_base.rb', line 108 def comments_in_range(range) @comments.select { |c| range.cover?(c[:line]) } end |
#full_line_comment?(line_num) ⇒ Boolean
Check if a line is a full-line comment.
312 313 314 315 |
# File 'lib/ast/merge/comment/hash_tracker_base.rb', line 312 def full_line_comment?(line_num) comment = comment_at(line_num) comment&.dig(:full_line) || false end |
#inline_comment_at(line_num) ⇒ Hash?
Get trailing comment on the same line (inline comment).
191 192 193 194 |
# File 'lib/ast/merge/comment/hash_tracker_base.rb', line 191 def inline_comment_at(line_num) comment = comment_at(line_num) comment if comment && !comment[:full_line] end |
#inline_comment_region_at(line_num, comment: nil) ⇒ Ast::Merge::Comment::Region?
Get a shared inline comment region at a line.
201 202 203 204 205 206 207 208 209 210 211 212 213 |
# File 'lib/ast/merge/comment/hash_tracker_base.rb', line 201 def inline_comment_region_at(line_num, comment: nil) selected = [comment || inline_comment_at(line_num)].compact return if selected.empty? build_region( kind: :inline, comments: selected, metadata: { line_num: line_num, source: :comment_tracker } ) end |
#leading_comment_region_before(line_num, comments: nil) ⇒ Ast::Merge::Comment::Region?
Get a shared leading comment region before a line.
172 173 174 175 176 177 178 179 180 181 182 183 184 185 |
# File 'lib/ast/merge/comment/hash_tracker_base.rb', line 172 def leading_comment_region_before(line_num, comments: nil) selected = comments || leading_comments_before(line_num) selected = selected.select { |c| c[:full_line] } return if selected.empty? build_region( kind: :leading, comments: selected, metadata: { line_num: line_num, source: :comment_tracker } ) end |
#leading_comments_before(line_num) ⇒ Array<Hash>
Get leading full-line comments before a line, walking backward and skipping blank lines between consecutive comment blocks.
142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 |
# File 'lib/ast/merge/comment/hash_tracker_base.rb', line 142 def leading_comments_before(line_num) leading = [] current = line_num - 1 # Skip blank lines between the node and its leading comments current -= 1 while current >= 1 && blank_line?(current) while current >= 1 comment = comment_at(current) break unless comment && comment[:full_line] leading.unshift(comment) current -= 1 # Skip blank lines between consecutive comments current -= 1 while current >= 1 && blank_line?(current) end # If the collected comments extend all the way to the file's first # line, they are a preamble/header comment — not semantically owned # by this particular node. Strip preamble lines that are separated # from the node-specific comment block by a blank line. strip_preamble(leading, line_num) end |
#line_at(line_num) ⇒ String?
Get raw line content.
361 362 363 364 365 |
# File 'lib/ast/merge/comment/hash_tracker_base.rb', line 361 def line_at(line_num) return if line_num < 1 || line_num > @lines.length @lines[line_num - 1] end |
#owner_end_line(owner) ⇒ Object
367 368 369 370 371 |
# File 'lib/ast/merge/comment/hash_tracker_base.rb', line 367 def owner_end_line(owner) return owner.end_line if owner.respond_to?(:end_line) && owner.end_line owner_line_num(owner) end |
#strip_preamble(comments, node_line) ⇒ Array<Hash>
Strip file-preamble comments from a leading-comment collection.
Any comment block that starts at line 1 and is followed by a blank-line gap is a file header/preamble — it belongs to the file, not to any particular key. The gap is the definitive signal: it separates the preamble from node-specific comments (if any).
Unclaimed preamble comments are later picked up by the
Augmenter as a preamble_region and emitted once at the
top of the merged output.
341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 |
# File 'lib/ast/merge/comment/hash_tracker_base.rb', line 341 def strip_preamble(comments, node_line) return comments if comments.empty? return comments unless comments.first[:line] == 1 # Find blank-line gaps in the range from the first comment to the node. gaps = [] ((comments.first[:line])..node_line).each do |ln| gaps << ln if blank_line?(ln) end return comments if gaps.empty? # Everything at or before the first gap is preamble. # Keep only comments that appear after the first gap. comments.select { |c| c[:line] > gaps.first } end |
#trailing_comment_owned_by?(comment, owner) ⇒ Boolean
373 374 375 376 377 378 |
# File 'lib/ast/merge/comment/hash_tracker_base.rb', line 373 def trailing_comment_owned_by?(comment, owner) return true unless owner.respond_to?(:indent) && !owner.indent.nil? return true unless comment.key?(:indent) comment[:indent].to_i == owner.indent.to_i end |
#trailing_comment_region_after(line_num, upper_bound: nil, comments: nil, owner: nil) ⇒ Ast::Merge::Comment::Region?
Get a shared trailing comment region after a line/span.
249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 |
# File 'lib/ast/merge/comment/hash_tracker_base.rb', line 249 def trailing_comment_region_after(line_num, upper_bound: nil, comments: nil, owner: nil) selected = comments || trailing_comments_after(line_num, upper_bound: upper_bound, owner: owner) selected = selected.select { |c| c[:full_line] } return if selected.empty? build_region( kind: :trailing, comments: selected, metadata: { line_num: line_num, upper_bound: upper_bound, source: :comment_tracker } ) end |
#trailing_comments_after(line_num, upper_bound: nil, owner: nil) ⇒ Array<Hash>
Get adjacent full-line trailing comments after an owner line/span.
Trailing ownership is stricter than leading ownership: the first trailing comment must begin immediately on the next line after the owner. Once attached, blank lines between later comment lines are preserved inside the trailing region.
225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 |
# File 'lib/ast/merge/comment/hash_tracker_base.rb', line 225 def trailing_comments_after(line_num, upper_bound: nil, owner: nil) trailing = [] current = line_num + 1 max_line = upper_bound ? upper_bound - 1 : @lines.length return trailing if current > max_line || blank_line?(current) while current <= max_line comment = comment_at(current) break unless comment && comment[:full_line] && trailing_comment_owned_by?(comment, owner) trailing << comment current += 1 current += 1 while current <= max_line && blank_line?(current) end trailing end |