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,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ast
4
+ module Merge
5
+ ##
6
+ # Detects TOML frontmatter at the beginning of a document.
7
+ #
8
+ # TOML frontmatter is delimited by `+++` at the start and end,
9
+ # and must begin on the first line of the document (optionally
10
+ # preceded by a UTF-8 BOM). This format is commonly used by
11
+ # Hugo and other static site generators.
12
+ #
13
+ # @example TOML frontmatter
14
+ # +++
15
+ # title = "My Document"
16
+ # author = "Jane Doe"
17
+ # +++
18
+ #
19
+ # @example Usage
20
+ # detector = TomlFrontmatterDetector.new
21
+ # regions = detector.detect_all(markdown_source)
22
+ # # => [#<Region type=:toml_frontmatter content="title = \"My Document\"\n...">]
23
+ #
24
+ class TomlFrontmatterDetector < RegionDetectorBase
25
+ ##
26
+ # Pattern for detecting TOML frontmatter.
27
+ # - Must start at beginning of document (or after BOM)
28
+ # - Opening delimiter is `+++` followed by optional whitespace and newline
29
+ # - Content is captured (non-greedy)
30
+ # - Closing delimiter is `+++` at start of line, followed by optional whitespace and newline/EOF
31
+ #
32
+ FRONTMATTER_PATTERN = /\A(?:\xEF\xBB\xBF)?(\+\+\+[ \t]*\r?\n)(.*?)(^\+\+\+[ \t]*(?:\r?\n|\z))/m
33
+
34
+ ##
35
+ # @return [Symbol] the type identifier for TOML frontmatter regions
36
+ #
37
+ def region_type
38
+ :toml_frontmatter
39
+ end
40
+
41
+ ##
42
+ # Detects TOML frontmatter at the beginning of the document.
43
+ #
44
+ # @param source [String] the source document to scan
45
+ # @return [Array<Region>] array containing at most one Region for frontmatter
46
+ #
47
+ def detect_all(source)
48
+ return [] if source.nil? || source.empty?
49
+
50
+ match = source.match(FRONTMATTER_PATTERN)
51
+ return [] unless match
52
+
53
+ opening_delimiter = match[1]
54
+ content = match[2]
55
+ closing_delimiter = match[3]
56
+
57
+ # Calculate line numbers
58
+ start_line = 1
59
+
60
+ # Count total newlines in the full match to determine end line
61
+ full_match = match[0]
62
+ total_newlines = full_match.count("\n")
63
+ end_line = total_newlines + (full_match.end_with?("\n") ? 0 : 1)
64
+
65
+ [
66
+ Region.new(
67
+ type: region_type,
68
+ content: content,
69
+ start_line: start_line,
70
+ end_line: end_line,
71
+ delimiters: [opening_delimiter.strip, closing_delimiter.strip],
72
+ metadata: {format: :toml},
73
+ ),
74
+ ]
75
+ end
76
+
77
+ private
78
+
79
+ ##
80
+ # @return [Array<Region>] array containing at most one Region
81
+ #
82
+ def build_regions(source, matches)
83
+ # Not used - detect_all is overridden directly
84
+ raise NotImplementedError, "TomlFrontmatterDetector overrides detect_all directly"
85
+ end
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ast
4
+ module Merge
5
+ # Version information for Ast::Merge
6
+ module Version
7
+ # Current version of the ast-merge gem
8
+ VERSION = "1.0.0"
9
+ end
10
+ VERSION = Version::VERSION # traditional location
11
+ end
12
+ end
@@ -0,0 +1,108 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ast
4
+ module Merge
5
+ ##
6
+ # Detects YAML frontmatter at the beginning of a document.
7
+ #
8
+ # YAML frontmatter is delimited by `---` at the start and end,
9
+ # and must begin on the first line of the document (optionally
10
+ # preceded by a UTF-8 BOM).
11
+ #
12
+ # @example YAML frontmatter
13
+ # ---
14
+ # title: My Document
15
+ # author: Jane Doe
16
+ # ---
17
+ #
18
+ # @example Usage
19
+ # detector = YamlFrontmatterDetector.new
20
+ # regions = detector.detect_all(markdown_source)
21
+ # # => [#<Region type=:yaml_frontmatter content="title: My Document\n...">]
22
+ #
23
+ class YamlFrontmatterDetector < RegionDetectorBase
24
+ ##
25
+ # Pattern for detecting YAML frontmatter.
26
+ # - Must start at beginning of document (or after BOM)
27
+ # - Opening delimiter is `---` followed by optional whitespace and newline
28
+ # - Content is captured (non-greedy)
29
+ # - Closing delimiter is `---` at start of line, followed by optional whitespace and newline/EOF
30
+ #
31
+ FRONTMATTER_PATTERN = /\A(?:\xEF\xBB\xBF)?(---[ \t]*\r?\n)(.*?)(^---[ \t]*(?:\r?\n|\z))/m
32
+
33
+ ##
34
+ # @return [Symbol] the type identifier for YAML frontmatter regions
35
+ #
36
+ def region_type
37
+ :yaml_frontmatter
38
+ end
39
+
40
+ ##
41
+ # Detects YAML frontmatter at the beginning of the document.
42
+ #
43
+ # @param source [String] the source document to scan
44
+ # @return [Array<Region>] array containing at most one Region for frontmatter
45
+ #
46
+ def detect_all(source)
47
+ return [] if source.nil? || source.empty?
48
+
49
+ match = source.match(FRONTMATTER_PATTERN)
50
+ return [] unless match
51
+
52
+ opening_delimiter = match[1]
53
+ content = match[2]
54
+ closing_delimiter = match[3]
55
+
56
+ # Calculate line numbers
57
+ # Frontmatter starts at line 1 (or after BOM)
58
+ start_line = 1
59
+ # Count newlines in content to determine end line
60
+ # Opening delimiter ends at line 1
61
+ # Content spans from line 2 to line 2 + content_lines - 1
62
+ # Closing delimiter is on the next line
63
+ content_newlines = content.count("\n")
64
+ # end_line is the line with the closing ---
65
+ end_line = start_line + 1 + content_newlines
66
+
67
+ # Adjust if content ends without newline
68
+ end_line - 1 if content.end_with?("\n") && content_newlines > 0
69
+
70
+ # Actually, let's calculate more carefully
71
+ # Line 1: ---
72
+ # Line 2 to N: content
73
+ # Line N+1: ---
74
+ if content.empty?
75
+ 0
76
+ else
77
+ content.count("\n") + (content.end_with?("\n") ? 0 : 1)
78
+ end
79
+
80
+ # Simplify: count total newlines in the full match to determine end line
81
+ full_match = match[0]
82
+ total_newlines = full_match.count("\n")
83
+ end_line = total_newlines + (full_match.end_with?("\n") ? 0 : 1)
84
+
85
+ [
86
+ Region.new(
87
+ type: region_type,
88
+ content: content,
89
+ start_line: start_line,
90
+ end_line: end_line,
91
+ delimiters: [opening_delimiter.strip, closing_delimiter.strip],
92
+ metadata: {format: :yaml},
93
+ ),
94
+ ]
95
+ end
96
+
97
+ private
98
+
99
+ ##
100
+ # @return [Array<Region>] array containing at most one Region
101
+ #
102
+ def build_regions(source, matches)
103
+ # Not used - detect_all is overridden directly
104
+ raise NotImplementedError, "YamlFrontmatterDetector overrides detect_all directly"
105
+ end
106
+ end
107
+ end
108
+ end
data/lib/ast/merge.rb ADDED
@@ -0,0 +1,165 @@
1
+ # frozen_string_literal: true
2
+
3
+ # External gems
4
+ require "version_gem"
5
+
6
+ # This gem - only version can be required (never autoloaded)
7
+ require_relative "merge/version"
8
+
9
+ module Ast
10
+ module Merge
11
+ # Base error class for all AST merge operations.
12
+ # All *-merge gems should have their Error class inherit from this.
13
+ # @api public
14
+ class Error < StandardError; end
15
+
16
+ # Base class for parse errors in merge operations.
17
+ #
18
+ # This class provides a flexible interface that can be extended by
19
+ # specific merge implementations. It supports:
20
+ # - An `errors` array for parser-specific error objects
21
+ # - An optional `content` attribute for the source that failed to parse
22
+ #
23
+ # Subclasses (TemplateParseError, DestinationParseError) identify whether
24
+ # the error occurred in the template or destination file.
25
+ #
26
+ # @example Basic usage with errors array
27
+ # raise ParseError.new(errors: [syntax_error])
28
+ #
29
+ # @example With content for debugging
30
+ # raise ParseError.new(errors: parse_result.errors, content: source_code)
31
+ #
32
+ # @example With custom message
33
+ # raise ParseError.new("Custom message", errors: [e])
34
+ #
35
+ # @api public
36
+ class ParseError < Error
37
+ # @return [Array] Parser-specific error objects (e.g., Prism::ParseError, RBS::BaseError)
38
+ attr_reader :errors
39
+
40
+ # @return [String, nil] The source content that failed to parse (optional)
41
+ attr_reader :content
42
+
43
+ # Initialize a new ParseError.
44
+ #
45
+ # @param message [String, nil] Custom error message (auto-generated if nil)
46
+ # @param errors [Array] Array of parser-specific error objects
47
+ # @param content [String, nil] The source content that failed to parse
48
+ def initialize(message = nil, errors: [], content: nil)
49
+ @errors = Array(errors)
50
+ @content = content
51
+ super(message || build_message)
52
+ end
53
+
54
+ private
55
+
56
+ # Build a default error message from the errors array.
57
+ # Override in subclasses for more specific messages.
58
+ #
59
+ # @return [String] Error message
60
+ def build_message
61
+ if @errors.empty?
62
+ "Unknown #{self.class.name.split("::").map(&:downcase).join(" ")}"
63
+ else
64
+ error_messages = @errors.map { |e| e.respond_to?(:message) ? e.message : e.to_s }
65
+ "#{self.class.name.split("::").map(&:downcase).join(" ")}: #{error_messages.join(", ")}"
66
+ end
67
+ end
68
+ end
69
+
70
+ # Raised when the template file has syntax errors.
71
+ #
72
+ # Template files are the "source of truth" that destination files
73
+ # are merged against. When a template cannot be parsed, the merge
74
+ # operation cannot proceed.
75
+ #
76
+ # @example Handling template parse errors
77
+ # begin
78
+ # merger = SmartMerger.new(template, destination)
79
+ # rescue Ast::Merge::TemplateParseError => e
80
+ # puts "Template syntax error: #{e.message}"
81
+ # e.errors.each { |error| puts " #{error.message}" }
82
+ # end
83
+ #
84
+ # @api public
85
+ class TemplateParseError < ParseError; end
86
+
87
+ # Raised when the destination file has syntax errors.
88
+ #
89
+ # Destination files contain user customizations that should be preserved
90
+ # during merges. When a destination cannot be parsed, the merge operation
91
+ # cannot proceed.
92
+ #
93
+ # @example Handling destination parse errors
94
+ # begin
95
+ # merger = SmartMerger.new(template, destination)
96
+ # rescue Ast::Merge::DestinationParseError => e
97
+ # puts "Destination syntax error: #{e.message}"
98
+ # e.errors.each { |error| puts " #{error.message}" }
99
+ # end
100
+ #
101
+ # @api public
102
+ class DestinationParseError < ParseError; end
103
+
104
+ # Raised when the document contains text that matches the region placeholder.
105
+ #
106
+ # Region placeholders are used internally to mark positions in a document
107
+ # where nested regions will be substituted after merging. If the document
108
+ # already contains text that looks like a placeholder, the merge cannot
109
+ # proceed safely.
110
+ #
111
+ # @example Handling placeholder collision
112
+ # begin
113
+ # merger = SmartMerger.new(template, destination, regions: [...])
114
+ # rescue Ast::Merge::PlaceholderCollisionError => e
115
+ # # Use a custom placeholder to avoid the collision
116
+ # merger = SmartMerger.new(template, destination,
117
+ # regions: [...],
118
+ # region_placeholder: "###MY_CUSTOM_PLACEHOLDER_"
119
+ # )
120
+ # end
121
+ #
122
+ # @api public
123
+ class PlaceholderCollisionError < Error
124
+ # @return [String] The placeholder that caused the collision
125
+ attr_reader :placeholder
126
+
127
+ # Initialize a new PlaceholderCollisionError.
128
+ #
129
+ # @param placeholder [String] The placeholder string that was found in the document
130
+ def initialize(placeholder)
131
+ @placeholder = placeholder
132
+ super(
133
+ "Document contains placeholder text '#{placeholder}'. " \
134
+ "Use the :region_placeholder option to specify a custom placeholder."
135
+ )
136
+ end
137
+ end
138
+
139
+ autoload :AstNode, "ast/merge/ast_node"
140
+ autoload :Comment, "ast/merge/comment"
141
+ autoload :ConflictResolverBase, "ast/merge/conflict_resolver_base"
142
+ autoload :DebugLogger, "ast/merge/debug_logger"
143
+ autoload :FencedCodeBlockDetector, "ast/merge/fenced_code_block_detector"
144
+ autoload :FileAnalyzable, "ast/merge/file_analyzable"
145
+ autoload :Freezable, "ast/merge/freezable"
146
+ autoload :FreezeNodeBase, "ast/merge/freeze_node_base"
147
+ autoload :MatchRefinerBase, "ast/merge/match_refiner_base"
148
+ autoload :MatchScoreBase, "ast/merge/match_score_base"
149
+ autoload :MergeResultBase, "ast/merge/merge_result_base"
150
+ autoload :MergerConfig, "ast/merge/merger_config"
151
+ autoload :NodeTyping, "ast/merge/node_typing"
152
+ autoload :Region, "ast/merge/region"
153
+ autoload :RegionDetectorBase, "ast/merge/region_detector_base"
154
+ autoload :RegionMergeable, "ast/merge/region_mergeable"
155
+ autoload :SectionTyping, "ast/merge/section_typing"
156
+ autoload :SmartMergerBase, "ast/merge/smart_merger_base"
157
+ autoload :Text, "ast/merge/text"
158
+ autoload :TomlFrontmatterDetector, "ast/merge/toml_frontmatter_detector"
159
+ autoload :YamlFrontmatterDetector, "ast/merge/yaml_frontmatter_detector"
160
+ end
161
+ end
162
+
163
+ Ast::Merge::Version.class_eval do
164
+ extend VersionGem::Basic
165
+ end
data/lib/ast-merge.rb ADDED
@@ -0,0 +1,4 @@
1
+ # For technical reasons, if we move to Zeitwerk, this cannot be require_relative.
2
+ # See: https://github.com/fxn/zeitwerk#for_gem_extension
3
+ # Hook for other libraries to load this library (e.g. via bundler)
4
+ require "ast/merge"
data/sig/ast/merge.rbs ADDED
@@ -0,0 +1,195 @@
1
+ module Ast
2
+ module Merge
3
+ VERSION: String
4
+
5
+ # Base error class for all merge operations
6
+ class Error < StandardError
7
+ end
8
+
9
+ # Raised when parsing fails (template or destination)
10
+ class ParseError < Error
11
+ attr_reader errors: untyped
12
+ attr_reader content: String?
13
+
14
+ def initialize: (?String? message, ?errors: untyped, ?content: String?) -> void
15
+ end
16
+
17
+ # Raised when parsing the template (source) content fails
18
+ class TemplateParseError < ParseError
19
+ def initialize: (?String? message, ?errors: untyped, ?content: String?) -> void
20
+ end
21
+
22
+ # Raised when parsing the destination content fails
23
+ class DestinationParseError < ParseError
24
+ def initialize: (?String? message, ?errors: untyped, ?content: String?) -> void
25
+ end
26
+
27
+ # Base class for freeze block nodes in AST merge libraries.
28
+ class FreezeNode
29
+ # Pattern configuration for freeze block markers
30
+ MARKER_PATTERNS: Hash[Symbol, Hash[Symbol, Regexp]]
31
+
32
+ # Default pattern when none specified
33
+ DEFAULT_PATTERN: Symbol
34
+
35
+ # Error raised when a freeze block has invalid structure
36
+ class InvalidStructureError < StandardError
37
+ attr_reader start_line: Integer?
38
+ attr_reader end_line: Integer?
39
+ attr_reader unclosed_nodes: Array[untyped]
40
+
41
+ def initialize: (
42
+ String message,
43
+ ?start_line: Integer?,
44
+ ?end_line: Integer?,
45
+ ?unclosed_nodes: Array[untyped]
46
+ ) -> void
47
+ end
48
+
49
+ # Simple location struct for compatibility with AST nodes
50
+ class Location < Struct[Integer]
51
+ attr_accessor start_line: Integer
52
+ attr_accessor end_line: Integer
53
+
54
+ def cover?: (Integer line) -> bool
55
+ end
56
+
57
+ # Class methods
58
+ def self.register_pattern: (Symbol name, start: Regexp, end_pattern: Regexp) -> Hash[Symbol, Regexp]
59
+ def self.start_pattern: (?Symbol pattern_type) -> Regexp
60
+ def self.end_pattern: (?Symbol pattern_type) -> Regexp
61
+ def self.freeze_start?: (String? line, ?Symbol pattern_type) -> bool
62
+ def self.freeze_end?: (String? line, ?Symbol pattern_type) -> bool
63
+ def self.pattern_types: () -> Array[Symbol]
64
+
65
+ # Instance attributes
66
+ attr_reader start_line: Integer
67
+ attr_reader end_line: Integer
68
+ attr_reader content: String?
69
+ attr_reader start_marker: String?
70
+ attr_reader end_marker: String?
71
+ attr_reader pattern_type: Symbol
72
+
73
+ def initialize: (
74
+ start_line: Integer,
75
+ end_line: Integer,
76
+ ?start_marker: String?,
77
+ ?end_marker: String?,
78
+ ?pattern_type: Symbol
79
+ ) -> void
80
+
81
+ def location: () -> Location
82
+ def slice: () -> String?
83
+ def freeze_node?: () -> bool
84
+ def signature: () -> Array[untyped]
85
+ def inspect: () -> String
86
+ def to_s: () -> String
87
+
88
+ private
89
+
90
+ def validate_line_order!: () -> void
91
+ end
92
+
93
+ # Debug logging module
94
+ module DebugLogger
95
+ def self.env_var_name: () -> String
96
+ def self.env_var_name=: (String name) -> String
97
+ def self.log_prefix: () -> String
98
+ def self.log_prefix=: (String prefix) -> String
99
+ def self.enabled?: () -> bool
100
+ def self.debug: (*untyped args) -> void
101
+ def self.info: (*untyped args) -> void
102
+ def self.warning: (*untyped args) -> void
103
+ def self.time: (String label) { () -> untyped } -> untyped
104
+ def self.log_node: (untyped node, ?label: String) -> void
105
+ def self.extract_lines: (untyped node) -> String
106
+ def self.safe_type_name: (untyped node) -> String
107
+ end
108
+
109
+ # Base module for file analysis classes
110
+ module FileAnalysisBase
111
+ # Required instance attributes (must be defined by including class)
112
+ attr_reader statements: Array[untyped]
113
+ attr_reader lines: Array[String]
114
+ attr_reader signature_generator: (^(untyped) -> (Array[untyped] | untyped | nil))?
115
+
116
+ # Get all freeze blocks from statements
117
+ def freeze_blocks: () -> Array[FreezeNode]
118
+
119
+ # Check if a line is within a freeze block
120
+ def in_freeze_block?: (Integer line_num) -> bool
121
+
122
+ # Get the freeze block containing the given line
123
+ def freeze_block_at: (Integer line_num) -> FreezeNode?
124
+
125
+ # Get structural signature for a statement at given index
126
+ def signature_at: (Integer index) -> Array[untyped]?
127
+
128
+ # Get a specific line (1-indexed)
129
+ def line_at: (Integer line_num) -> String?
130
+
131
+ # Get a normalized line (whitespace-trimmed)
132
+ def normalized_line: (Integer line_num) -> String?
133
+
134
+ # Generate signature for a node
135
+ def generate_signature: (untyped node) -> Array[untyped]?
136
+
137
+ # Check if a value represents a fallthrough node
138
+ def fallthrough_node?: (untyped value) -> bool
139
+
140
+ # Compute default signature for a node (abstract - must be implemented)
141
+ def compute_node_signature: (untyped node) -> Array[untyped]?
142
+ end
143
+
144
+ # Base merge result tracking
145
+ class MergeResult
146
+ DECISION_KEPT_TEMPLATE: Symbol
147
+ DECISION_KEPT_DEST: Symbol
148
+ DECISION_MERGED: Symbol
149
+ DECISION_ADDED: Symbol
150
+ DECISION_FREEZE_BLOCK: Symbol
151
+ DECISION_REPLACED: Symbol
152
+ DECISION_APPENDED: Symbol
153
+
154
+ attr_reader decisions: Array[Hash[Symbol, untyped]]
155
+
156
+ def initialize: () -> void
157
+ def track_decision: (
158
+ untyped node,
159
+ Symbol decision,
160
+ ?reason: String?
161
+ ) -> void
162
+ end
163
+
164
+ # Configuration object for SmartMerger options
165
+ class MergerConfig
166
+ VALID_PREFERENCES: Array[Symbol]
167
+
168
+ attr_reader signature_match_preference: Symbol | Hash[Symbol, Symbol]
169
+ attr_reader node_splitter: Hash[Symbol, untyped]?
170
+ attr_reader add_template_only_nodes: bool
171
+ attr_reader freeze_token: String?
172
+ attr_reader signature_generator: (^(untyped) -> (Array[untyped] | untyped | nil))?
173
+
174
+ def initialize: (
175
+ ?signature_match_preference: (Symbol | Hash[Symbol, Symbol]),
176
+ ?add_template_only_nodes: bool,
177
+ ?freeze_token: String?,
178
+ ?signature_generator: (^(untyped) -> (Array[untyped] | untyped | nil))?,
179
+ ?node_splitter: Hash[Symbol, untyped]?
180
+ ) -> void
181
+
182
+ def prefer_destination?: () -> bool
183
+ def prefer_template?: () -> bool
184
+ def to_h: (?default_freeze_token: String?) -> Hash[Symbol, untyped]
185
+ def with: (**untyped options) -> MergerConfig
186
+
187
+ def self.destination_wins: (?freeze_token: String?, ?signature_generator: (^(untyped) -> (Array[untyped] | untyped | nil))?, ?node_splitter: Hash[Symbol, untyped]?) -> MergerConfig
188
+ def self.template_wins: (?freeze_token: String?, ?signature_generator: (^(untyped) -> (Array[untyped] | untyped | nil))?, ?node_splitter: Hash[Symbol, untyped]?) -> MergerConfig
189
+
190
+ private
191
+
192
+ def validate_preference!: (Symbol preference) -> void
193
+ end
194
+ end
195
+ end
data.tar.gz.sig ADDED
Binary file