ast-merge 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. checksums.yaml +7 -0
  2. checksums.yaml.gz.sig +0 -0
  3. data/CHANGELOG.md +46 -0
  4. data/CITATION.cff +20 -0
  5. data/CODE_OF_CONDUCT.md +134 -0
  6. data/CONTRIBUTING.md +227 -0
  7. data/FUNDING.md +74 -0
  8. data/LICENSE.txt +21 -0
  9. data/README.md +852 -0
  10. data/REEK +0 -0
  11. data/RUBOCOP.md +71 -0
  12. data/SECURITY.md +21 -0
  13. data/lib/ast/merge/ast_node.rb +87 -0
  14. data/lib/ast/merge/comment/block.rb +195 -0
  15. data/lib/ast/merge/comment/empty.rb +78 -0
  16. data/lib/ast/merge/comment/line.rb +138 -0
  17. data/lib/ast/merge/comment/parser.rb +278 -0
  18. data/lib/ast/merge/comment/style.rb +282 -0
  19. data/lib/ast/merge/comment.rb +36 -0
  20. data/lib/ast/merge/conflict_resolver_base.rb +399 -0
  21. data/lib/ast/merge/debug_logger.rb +271 -0
  22. data/lib/ast/merge/fenced_code_block_detector.rb +211 -0
  23. data/lib/ast/merge/file_analyzable.rb +307 -0
  24. data/lib/ast/merge/freezable.rb +82 -0
  25. data/lib/ast/merge/freeze_node_base.rb +434 -0
  26. data/lib/ast/merge/match_refiner_base.rb +312 -0
  27. data/lib/ast/merge/match_score_base.rb +135 -0
  28. data/lib/ast/merge/merge_result_base.rb +169 -0
  29. data/lib/ast/merge/merger_config.rb +258 -0
  30. data/lib/ast/merge/node_typing.rb +373 -0
  31. data/lib/ast/merge/region.rb +124 -0
  32. data/lib/ast/merge/region_detector_base.rb +114 -0
  33. data/lib/ast/merge/region_mergeable.rb +364 -0
  34. data/lib/ast/merge/rspec/shared_examples/conflict_resolver_base.rb +416 -0
  35. data/lib/ast/merge/rspec/shared_examples/debug_logger.rb +174 -0
  36. data/lib/ast/merge/rspec/shared_examples/file_analyzable.rb +193 -0
  37. data/lib/ast/merge/rspec/shared_examples/freeze_node_base.rb +219 -0
  38. data/lib/ast/merge/rspec/shared_examples/merge_result_base.rb +106 -0
  39. data/lib/ast/merge/rspec/shared_examples/merger_config.rb +202 -0
  40. data/lib/ast/merge/rspec/shared_examples/reproducible_merge.rb +115 -0
  41. data/lib/ast/merge/rspec/shared_examples.rb +26 -0
  42. data/lib/ast/merge/rspec.rb +4 -0
  43. data/lib/ast/merge/section_typing.rb +303 -0
  44. data/lib/ast/merge/smart_merger_base.rb +417 -0
  45. data/lib/ast/merge/text/conflict_resolver.rb +161 -0
  46. data/lib/ast/merge/text/file_analysis.rb +168 -0
  47. data/lib/ast/merge/text/line_node.rb +142 -0
  48. data/lib/ast/merge/text/merge_result.rb +42 -0
  49. data/lib/ast/merge/text/section.rb +93 -0
  50. data/lib/ast/merge/text/section_splitter.rb +397 -0
  51. data/lib/ast/merge/text/smart_merger.rb +141 -0
  52. data/lib/ast/merge/text/word_node.rb +86 -0
  53. data/lib/ast/merge/text.rb +35 -0
  54. data/lib/ast/merge/toml_frontmatter_detector.rb +88 -0
  55. data/lib/ast/merge/version.rb +12 -0
  56. data/lib/ast/merge/yaml_frontmatter_detector.rb +108 -0
  57. data/lib/ast/merge.rb +165 -0
  58. data/lib/ast-merge.rb +4 -0
  59. data/sig/ast/merge.rbs +195 -0
  60. data.tar.gz.sig +0 -0
  61. metadata +326 -0
  62. metadata.gz.sig +0 -0
@@ -0,0 +1,278 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "style"
4
+ require_relative "line"
5
+ require_relative "empty"
6
+ require_relative "block"
7
+
8
+ module Ast
9
+ module Merge
10
+ module Comment
11
+ # Parser for building comment AST from source lines.
12
+ #
13
+ # This parser takes an array of source lines and produces an array of
14
+ # AstNode objects (Block, Line, Empty) that represent the structure
15
+ # of a comment-only file or section.
16
+ #
17
+ # The parser is style-aware and can handle:
18
+ # - Line comments (`#`, `//`, `--`, `;`)
19
+ # - HTML-style comments (`<!-- ... -->`)
20
+ # - C-style block comments (`/* ... */`)
21
+ #
22
+ # @example Parsing Ruby-style comments
23
+ # lines = ["# frozen_string_literal: true", "", "# A comment block"]
24
+ # parser = Parser.new(lines)
25
+ # nodes = parser.parse
26
+ # # => [Block(...), Empty(...), Block(...)]
27
+ #
28
+ # @example Parsing C-style block comments
29
+ # lines = ["/* Header comment", " * with multiple lines", " */"]
30
+ # parser = Parser.new(lines, style: :c_style_block)
31
+ # nodes = parser.parse
32
+ # # => [Block(raw_content: "/* Header comment\n * with multiple lines\n */")]
33
+ #
34
+ # @example Auto-detecting comment style
35
+ # lines = ["// JavaScript comment", "// continues here"]
36
+ # parser = Parser.new(lines, style: :auto)
37
+ # nodes = parser.parse
38
+ #
39
+ class Parser
40
+ # @return [Array<String>] The source lines
41
+ attr_reader :lines
42
+
43
+ # @return [Style] The comment style configuration
44
+ attr_reader :style
45
+
46
+ # Initialize a new Parser.
47
+ #
48
+ # @param lines [Array<String>] Source lines (without trailing newlines)
49
+ # @param style [Style, Symbol, nil] The comment style (:hash_comment, :c_style_line, etc.)
50
+ # Pass :auto to attempt auto-detection.
51
+ def initialize(lines, style: nil)
52
+ @lines = lines || []
53
+ @style = resolve_style(style)
54
+ end
55
+
56
+ # Parse the lines into an AST.
57
+ #
58
+ # Groups contiguous comment lines into Block nodes,
59
+ # and represents blank lines as Empty nodes.
60
+ #
61
+ # @return [Array<AstNode>] Array of parsed nodes
62
+ def parse
63
+ return [] if lines.empty?
64
+
65
+ if style.supports_block_comments?
66
+ parse_with_block_comments
67
+ else
68
+ parse_line_comments
69
+ end
70
+ end
71
+
72
+ # Class method for convenient one-shot parsing.
73
+ #
74
+ # @param lines [Array<String>] Source lines
75
+ # @param style [Style, Symbol, nil] Comment style
76
+ # @return [Array<AstNode>] Parsed nodes
77
+ def self.parse(lines, style: nil)
78
+ new(lines, style: style).parse
79
+ end
80
+
81
+ private
82
+
83
+ # Resolve the style parameter to a Style instance.
84
+ #
85
+ # @param style [Style, Symbol, nil] Style configuration
86
+ # @return [Style] Resolved style instance
87
+ def resolve_style(style)
88
+ case style
89
+ when Style
90
+ style
91
+ when :auto
92
+ auto_detect_style
93
+ when Symbol
94
+ Style.for(style)
95
+ when nil
96
+ Style.for(Style::DEFAULT_STYLE)
97
+ else
98
+ raise ArgumentError, "Invalid style: #{style.inspect}"
99
+ end
100
+ end
101
+
102
+ # Auto-detect the comment style from the source lines.
103
+ #
104
+ # Looks at the first non-empty line to determine the style.
105
+ #
106
+ # @return [Style] Detected style (defaults to hash_comment)
107
+ def auto_detect_style
108
+ first_content = lines.find { |l| !l.to_s.strip.empty? }
109
+ return Style.for(:hash_comment) unless first_content
110
+
111
+ stripped = first_content.to_s.strip
112
+
113
+ # Check each style's pattern
114
+ Style::STYLES.each do |name, config|
115
+ if config[:line_pattern]&.match?(stripped)
116
+ return Style.for(name)
117
+ end
118
+ if config[:block_start_pattern]&.match?(stripped)
119
+ return Style.for(name)
120
+ end
121
+ end
122
+
123
+ # Default to hash_comment
124
+ Style.for(:hash_comment)
125
+ end
126
+
127
+ # Parse lines using line-comment style (e.g., #, //, --, ;).
128
+ #
129
+ # Groups contiguous comment lines into Block nodes.
130
+ #
131
+ # @return [Array<AstNode>] Parsed nodes
132
+ def parse_line_comments
133
+ nodes = []
134
+ current_block = []
135
+
136
+ lines.each_with_index do |line, idx|
137
+ line_number = idx + 1
138
+ stripped = line.to_s.rstrip
139
+
140
+ if stripped.empty?
141
+ # Blank line - flush current block and add Empty
142
+ if current_block.any?
143
+ nodes << build_block(current_block)
144
+ current_block = []
145
+ end
146
+ nodes << Empty.new(line_number: line_number, text: line.to_s)
147
+ elsif style.match_line?(stripped)
148
+ # Comment line - add to current block
149
+ current_block << Line.new(
150
+ text: stripped,
151
+ line_number: line_number,
152
+ style: style,
153
+ )
154
+ else
155
+ # Non-comment, non-empty line
156
+ # Flush current block and treat this as content
157
+ if current_block.any?
158
+ nodes << build_block(current_block)
159
+ current_block = []
160
+ end
161
+ # Add as a single line (non-comment content in a comment-only context)
162
+ nodes << Line.new(
163
+ text: stripped,
164
+ line_number: line_number,
165
+ style: Style.for(:hash_comment), # Fallback style for non-comment lines
166
+ )
167
+ end
168
+ end
169
+
170
+ # Flush remaining block
171
+ if current_block.any?
172
+ nodes << build_block(current_block)
173
+ end
174
+
175
+ nodes
176
+ end
177
+
178
+ # Parse lines that may contain block comments (e.g., /* ... */, <!-- ... -->).
179
+ #
180
+ # Handles both single-line and multi-line block comments.
181
+ #
182
+ # @return [Array<AstNode>] Parsed nodes
183
+ def parse_with_block_comments
184
+ nodes = []
185
+ current_block_lines = []
186
+ in_block_comment = false
187
+
188
+ lines.each_with_index do |line, idx|
189
+ line_number = idx + 1
190
+ stripped = line.to_s.rstrip
191
+
192
+ if stripped.empty? && !in_block_comment
193
+ # Blank line outside block comment
194
+ if current_block_lines.any?
195
+ nodes << build_raw_block(current_block_lines)
196
+ current_block_lines = []
197
+ end
198
+ nodes << Empty.new(line_number: line_number, text: line.to_s)
199
+ elsif style.match_block_start?(stripped)
200
+ # Starting a block comment
201
+ # Flush any pending content first
202
+ if current_block_lines.any? && !in_block_comment
203
+ nodes << build_raw_block(current_block_lines)
204
+ current_block_lines = []
205
+ end
206
+
207
+ current_block_lines << {line: stripped, line_number: line_number}
208
+ in_block_comment = true
209
+
210
+ # Check if block ends on same line
211
+ if style.match_block_end?(stripped)
212
+ nodes << build_raw_block(current_block_lines)
213
+ current_block_lines = []
214
+ in_block_comment = false
215
+ end
216
+ elsif in_block_comment
217
+ # Inside a block comment
218
+ current_block_lines << {line: stripped.empty? ? line.to_s : stripped, line_number: line_number}
219
+
220
+ # Check if block ends
221
+ if style.match_block_end?(stripped)
222
+ nodes << build_raw_block(current_block_lines)
223
+ current_block_lines = []
224
+ in_block_comment = false
225
+ end
226
+ elsif style.supports_line_comments? && style.match_line?(stripped)
227
+ # Line comment (in a style that supports both line and block)
228
+ current_block_lines << {line: stripped, line_number: line_number}
229
+ else
230
+ # Other content - flush and add as-is
231
+ if current_block_lines.any?
232
+ nodes << build_raw_block(current_block_lines)
233
+ current_block_lines = []
234
+ end
235
+ nodes << Line.new(
236
+ text: stripped,
237
+ line_number: line_number,
238
+ style: style,
239
+ )
240
+ end
241
+ end
242
+
243
+ # Flush remaining block
244
+ if current_block_lines.any?
245
+ nodes << build_raw_block(current_block_lines)
246
+ end
247
+
248
+ nodes
249
+ end
250
+
251
+ # Build a Block from accumulated line comment nodes.
252
+ #
253
+ # @param comment_lines [Array<Line>] The comment lines
254
+ # @return [Block] Block containing the lines
255
+ def build_block(comment_lines)
256
+ Block.new(children: comment_lines, style: style)
257
+ end
258
+
259
+ # Build a Block from accumulated raw lines (for block-style comments).
260
+ #
261
+ # @param line_data [Array<Hash>] Array of { line:, line_number: } hashes
262
+ # @return [Block] Block with raw content
263
+ def build_raw_block(line_data)
264
+ raw_content = line_data.map { |d| d[:line] }.join("\n")
265
+ start_line = line_data.first[:line_number]
266
+ end_line = line_data.last[:line_number]
267
+
268
+ Block.new(
269
+ raw_content: raw_content,
270
+ start_line: start_line,
271
+ end_line: end_line,
272
+ style: style,
273
+ )
274
+ end
275
+ end
276
+ end
277
+ end
278
+ end
@@ -0,0 +1,282 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ast
4
+ module Merge
5
+ module Comment
6
+ # Configuration for different comment syntax styles.
7
+ #
8
+ # Supports multiple comment syntax patterns used across programming languages:
9
+ # - `:hash_comment` - Ruby/Python/YAML/Shell style (`# comment`)
10
+ # - `:html_comment` - HTML/XML/Markdown style (`<!-- comment -->`)
11
+ # - `:c_style_line` - C/JavaScript/Go line comments (`// comment`)
12
+ # - `:c_style_block` - C/JavaScript/CSS block comments (`/* comment */`)
13
+ # - `:semicolon_comment` - Lisp/Clojure/Assembly style (`; comment`)
14
+ # - `:double_dash_comment` - SQL/Haskell/Lua style (`-- comment`)
15
+ #
16
+ # @example Using a predefined style
17
+ # style = Style.for(:hash_comment)
18
+ # style.line_start #=> "#"
19
+ # style.match_line?("# hello") #=> true
20
+ #
21
+ # @example Registering a custom style
22
+ # Style.register(:percent_comment,
23
+ # line_start: "%",
24
+ # line_pattern: /^\s*%/
25
+ # )
26
+ #
27
+ class Style
28
+ # @return [Symbol] The style identifier
29
+ attr_reader :name
30
+
31
+ # @return [String, nil] Line comment start delimiter (e.g., "#", "//")
32
+ attr_reader :line_start
33
+
34
+ # @return [String, nil] Line comment end delimiter (for HTML-style: "-->")
35
+ attr_reader :line_end
36
+
37
+ # @return [String, nil] Block comment start delimiter (e.g., "/*")
38
+ attr_reader :block_start
39
+
40
+ # @return [String, nil] Block comment end delimiter (e.g., "*/")
41
+ attr_reader :block_end
42
+
43
+ # @return [Regexp] Pattern to match a single comment line
44
+ attr_reader :line_pattern
45
+
46
+ # @return [Regexp, nil] Pattern to match block comment start
47
+ attr_reader :block_start_pattern
48
+
49
+ # @return [Regexp, nil] Pattern to match block comment end
50
+ attr_reader :block_end_pattern
51
+
52
+ # Predefined comment styles.
53
+ # Mutable to allow runtime registration of custom styles.
54
+ # @return [Hash{Symbol => Hash}] Registered comment styles
55
+ STYLES = {
56
+ hash_comment: {
57
+ line_start: "#",
58
+ line_end: nil,
59
+ block_start: nil,
60
+ block_end: nil,
61
+ line_pattern: /^\s*#/,
62
+ block_start_pattern: nil,
63
+ block_end_pattern: nil,
64
+ },
65
+ html_comment: {
66
+ line_start: "<!--",
67
+ line_end: "-->",
68
+ block_start: "<!--",
69
+ block_end: "-->",
70
+ line_pattern: /^\s*<!--.*-->\s*$/,
71
+ block_start_pattern: /^\s*<!--/,
72
+ block_end_pattern: /-->\s*$/,
73
+ },
74
+ c_style_line: {
75
+ line_start: "//",
76
+ line_end: nil,
77
+ block_start: nil,
78
+ block_end: nil,
79
+ line_pattern: %r{^\s*//},
80
+ block_start_pattern: nil,
81
+ block_end_pattern: nil,
82
+ },
83
+ c_style_block: {
84
+ line_start: nil,
85
+ line_end: nil,
86
+ block_start: "/*",
87
+ block_end: "*/",
88
+ line_pattern: nil,
89
+ block_start_pattern: %r{^\s*/\*},
90
+ block_end_pattern: %r{\*/\s*$},
91
+ },
92
+ semicolon_comment: {
93
+ line_start: ";",
94
+ line_end: nil,
95
+ block_start: nil,
96
+ block_end: nil,
97
+ line_pattern: /^\s*;/,
98
+ block_start_pattern: nil,
99
+ block_end_pattern: nil,
100
+ },
101
+ double_dash_comment: {
102
+ line_start: "--",
103
+ line_end: nil,
104
+ block_start: nil,
105
+ block_end: nil,
106
+ line_pattern: /^\s*--/,
107
+ block_start_pattern: nil,
108
+ block_end_pattern: nil,
109
+ },
110
+ }.freeze
111
+
112
+ # Default style when none specified
113
+ # @return [Symbol]
114
+ DEFAULT_STYLE = :hash_comment
115
+
116
+ class << self
117
+ # Get a Style instance for a given style name.
118
+ #
119
+ # @param name [Symbol] Style name (e.g., :hash_comment, :c_style_line)
120
+ # @return [Style] The style configuration
121
+ # @raise [ArgumentError] if style name is not registered
122
+ def for(name)
123
+ name = name&.to_sym || DEFAULT_STYLE
124
+ config = STYLES[name]
125
+ raise ArgumentError, "Unknown comment style: #{name}" unless config
126
+
127
+ new(name, **config)
128
+ end
129
+
130
+ # Register a custom comment style.
131
+ #
132
+ # @param name [Symbol] Style identifier
133
+ # @param line_start [String, nil] Line comment start delimiter
134
+ # @param line_end [String, nil] Line comment end delimiter
135
+ # @param block_start [String, nil] Block comment start delimiter
136
+ # @param block_end [String, nil] Block comment end delimiter
137
+ # @param line_pattern [Regexp, nil] Pattern to match comment lines
138
+ # @param block_start_pattern [Regexp, nil] Pattern to match block start
139
+ # @param block_end_pattern [Regexp, nil] Pattern to match block end
140
+ # @return [Hash] The registered style configuration
141
+ # @raise [ArgumentError] if name already exists
142
+ def register(name, line_start: nil, line_end: nil, block_start: nil, block_end: nil,
143
+ line_pattern: nil, block_start_pattern: nil, block_end_pattern: nil)
144
+ name = name.to_sym
145
+ if STYLES.key?(name)
146
+ raise ArgumentError, "Style :#{name} already registered"
147
+ end
148
+
149
+ config = {
150
+ line_start: line_start,
151
+ line_end: line_end,
152
+ block_start: block_start,
153
+ block_end: block_end,
154
+ line_pattern: line_pattern,
155
+ block_start_pattern: block_start_pattern,
156
+ block_end_pattern: block_end_pattern,
157
+ }
158
+
159
+ # Modify STYLES (it's frozen, so we need to work around)
160
+ STYLES.dup.tap do |styles|
161
+ styles[name] = config
162
+ remove_const(:STYLES)
163
+ const_set(:STYLES, styles.freeze)
164
+ end
165
+
166
+ config
167
+ end
168
+
169
+ # List all registered style names.
170
+ #
171
+ # @return [Array<Symbol>] Available style names
172
+ def available_styles
173
+ STYLES.keys
174
+ end
175
+
176
+ # Check if a style supports line comments.
177
+ #
178
+ # @param name [Symbol] Style name
179
+ # @return [Boolean] true if style has line comment support
180
+ def supports_line_comments?(name)
181
+ config = STYLES[name.to_sym]
182
+ config && config[:line_pattern]
183
+ end
184
+
185
+ # Check if a style supports block comments.
186
+ #
187
+ # @param name [Symbol] Style name
188
+ # @return [Boolean] true if style has block comment support
189
+ def supports_block_comments?(name)
190
+ config = STYLES[name.to_sym]
191
+ config && config[:block_start_pattern]
192
+ end
193
+ end
194
+
195
+ # Initialize a new Style.
196
+ #
197
+ # @param name [Symbol] Style identifier
198
+ # @param line_start [String, nil] Line comment start delimiter
199
+ # @param line_end [String, nil] Line comment end delimiter
200
+ # @param block_start [String, nil] Block comment start delimiter
201
+ # @param block_end [String, nil] Block comment end delimiter
202
+ # @param line_pattern [Regexp, nil] Pattern to match comment lines
203
+ # @param block_start_pattern [Regexp, nil] Pattern to match block start
204
+ # @param block_end_pattern [Regexp, nil] Pattern to match block end
205
+ def initialize(name, line_start: nil, line_end: nil, block_start: nil, block_end: nil,
206
+ line_pattern: nil, block_start_pattern: nil, block_end_pattern: nil)
207
+ @name = name
208
+ @line_start = line_start
209
+ @line_end = line_end
210
+ @block_start = block_start
211
+ @block_end = block_end
212
+ @line_pattern = line_pattern
213
+ @block_start_pattern = block_start_pattern
214
+ @block_end_pattern = block_end_pattern
215
+ end
216
+
217
+ # Check if a line matches this style's line comment pattern.
218
+ #
219
+ # @param line [String] The line to check
220
+ # @return [Boolean] true if line is a comment in this style
221
+ def match_line?(line)
222
+ return false unless line_pattern
223
+
224
+ line_pattern.match?(line.to_s)
225
+ end
226
+
227
+ # Check if a line starts a block comment.
228
+ #
229
+ # @param line [String] The line to check
230
+ # @return [Boolean] true if line starts a block comment
231
+ def match_block_start?(line)
232
+ return false unless block_start_pattern
233
+
234
+ block_start_pattern.match?(line.to_s)
235
+ end
236
+
237
+ # Check if a line ends a block comment.
238
+ #
239
+ # @param line [String] The line to check
240
+ # @return [Boolean] true if line ends a block comment
241
+ def match_block_end?(line)
242
+ return false unless block_end_pattern
243
+
244
+ block_end_pattern.match?(line.to_s)
245
+ end
246
+
247
+ # Extract content from a line comment, removing the delimiter.
248
+ #
249
+ # @param line [String] The comment line
250
+ # @return [String] The comment content without delimiters
251
+ def extract_line_content(line)
252
+ return line.to_s unless line_start
253
+
254
+ content = line.to_s.sub(/^\s*#{Regexp.escape(line_start)}\s?/, "")
255
+ if line_end
256
+ content = content.sub(/\s*#{Regexp.escape(line_end)}\s*$/, "")
257
+ end
258
+ content.rstrip
259
+ end
260
+
261
+ # Check if this style supports line comments.
262
+ #
263
+ # @return [Boolean] true if line comments are supported
264
+ def supports_line_comments?
265
+ !line_pattern.nil?
266
+ end
267
+
268
+ # Check if this style supports block comments.
269
+ #
270
+ # @return [Boolean] true if block comments are supported
271
+ def supports_block_comments?
272
+ !block_start_pattern.nil?
273
+ end
274
+
275
+ # @return [String] Human-readable representation
276
+ def inspect
277
+ "#<Comment::Style:#{name}>"
278
+ end
279
+ end
280
+ end
281
+ end
282
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ast
4
+ module Merge
5
+ # Comment AST nodes for representing comment-only content.
6
+ #
7
+ # This module provides generic, language-agnostic comment representation
8
+ # that supports multiple comment syntax styles:
9
+ # - `:hash_comment` - Ruby/Python/YAML/Shell style (`# comment`)
10
+ # - `:html_comment` - HTML/XML/Markdown style (`<!-- comment -->`)
11
+ # - `:c_style_line` - C/JavaScript/Go line comments (`// comment`)
12
+ # - `:c_style_block` - C/JavaScript/CSS block comments (`/* comment */`)
13
+ # - `:semicolon_comment` - Lisp/Clojure/Assembly style (`; comment`)
14
+ # - `:double_dash_comment` - SQL/Haskell/Lua style (`-- comment`)
15
+ #
16
+ # @example Parsing Ruby-style comments
17
+ # lines = ["# frozen_string_literal: true", "", "# Main comment"]
18
+ # nodes = Comment::Parser.parse(lines, style: :hash_comment)
19
+ #
20
+ # @example Parsing JavaScript-style comments
21
+ # lines = ["// Header comment", "// continues here"]
22
+ # nodes = Comment::Parser.parse(lines, style: :c_style_line)
23
+ #
24
+ # @example Auto-detecting style
25
+ # lines = ["<!-- HTML comment -->"]
26
+ # nodes = Comment::Parser.parse(lines, style: :auto)
27
+ #
28
+ module Comment
29
+ autoload :Style, "ast/merge/comment/style"
30
+ autoload :Line, "ast/merge/comment/line"
31
+ autoload :Empty, "ast/merge/comment/empty"
32
+ autoload :Block, "ast/merge/comment/block"
33
+ autoload :Parser, "ast/merge/comment/parser"
34
+ end
35
+ end
36
+ end