module Prism
module Merge
VERSION: String
module Version
VERSION: String
end
class Error < StandardError
end
class ParseError < Error
attr_reader content: String
attr_reader parse_result: untyped
def initialize: (String message, content: String, parse_result: untyped) -> void
end
class TemplateParseError < ParseError
end
class DestinationParseError < ParseError
end
# Wrapper to represent freeze blocks as first-class nodes.
# Freeze blocks can be placed inside:
# - ClassNode, ModuleNode, SingletonClassNode (class/module definitions)
# - DefNode (method definitions)
# - LambdaNode (lambda/proc definitions)
# - CallNode with blocks (e.g., RSpec describe/context blocks)
class FreezeNode
class InvalidStructureError < StandardError
attr_reader start_line: Integer?
attr_reader end_line: Integer?
attr_reader unclosed_nodes: Array[untyped]
def initialize: (String message, ?start_line: Integer?, ?end_line: Integer?, ?unclosed_nodes: Array[untyped]) -> void
end
class Location
attr_reader start_line: Integer
attr_reader end_line: Integer
def initialize: (Integer start_line, Integer end_line) -> void
end
attr_reader start_line: Integer
attr_reader end_line: Integer
attr_reader content: String
attr_reader nodes: Array[untyped]
attr_reader start_marker: String?
attr_reader end_marker: String?
def initialize: (
start_line: Integer,
end_line: Integer,
analysis: FileAnalysis,
?nodes: Array[untyped],
?overlapping_nodes: Array[untyped]?,
?start_marker: String?,
?end_marker: String?
) -> void
def location: () -> Location
def signature: () -> Array[Symbol | Integer]
def line_range: () -> Range[Integer]
def contains_line?: (Integer line_num) -> bool
def overlaps?: (untyped other) -> bool
def to_s: () -> String
def inspect: () -> String
private
def validate_structure!: () -> void
end
class FileAnalysis
FREEZE_START: Regexp
FREEZE_END: Regexp
FREEZE_BLOCK: Regexp
type freeze_block = {
range: Range[Integer],
line_range: Range[Integer],
text: String,
start_marker: String?
}
type node_info = {
node: untyped,
index: Integer,
leading_comments: Array[untyped],
inline_comments: Array[untyped],
signature: Array[untyped]?,
line_range: Range[Integer]
}
attr_reader content: String
attr_reader parse_result: untyped
attr_reader lines: Array[String]
attr_reader statements: Array[untyped]
attr_reader freeze_blocks: Array[freeze_block]
# Custom signature generator return types:
# - Array[untyped]: Used as the node's signature for matching
# - nil: Node gets no signature (won't be matched by signature)
# - Prism::Node or FreezeNode: Falls through to default signature computation
def initialize: (String content, ?signature_generator: (^(untyped) -> (Array[untyped] | untyped | nil))?, ?freeze_token: String?) -> void
def valid?: () -> bool
def extract_statements: () -> Array[untyped]
def extract_freeze_blocks: () -> Array[freeze_block]
def line_to_node_map: () -> Hash[Integer, Array[untyped]]
def node_to_line_map: () -> Hash[untyped, Range[Integer]]
def nodes_with_comments: () -> Array[node_info]
def comment_map: () -> Hash[Integer, Array[untyped]]
def signature_at: (Integer index) -> Array[untyped]?
def generate_signature: (untyped node) -> Array[untyped]?
def in_freeze_block?: (Integer line_num) -> bool
def freeze_block_at: (Integer line_num) -> freeze_block?
def normalized_line: (Integer line_num) -> String?
def line_at: (Integer line_num) -> String?
private
def build_line_to_node_map: () -> Hash[Integer, Array[untyped]]
def build_node_to_line_map: () -> Hash[untyped, Range[Integer]]
def extract_nodes_with_comments: () -> Array[node_info]
def find_leading_comments: (untyped current_stmt, untyped? prev_stmt, untyped body_node) -> Array[untyped]
def inline_comments_for_node: (untyped stmt) -> Array[untyped]
def build_comment_map: () -> Hash[Integer, Array[untyped]]
def default_signature: (untyped node) -> Array[untyped]
# Compute structural signature for node matching
# Returns signature arrays like:
# [:def, Symbol, Array[Symbol]] - method definitions
# [:class, String] - class definitions
# [:module, String] - module definitions
# [:singleton_class, String] - singleton class definitions
# [:const, Symbol | String] - constant assignments
# [:local_var, Symbol] - local variable assignments
# [:ivar, Symbol] - instance variable assignments
# [:cvar, Symbol] - class variable assignments
# [:gvar, Symbol] - global variable assignments
# [:multi_write, Array[Symbol | String]] - multiple assignment
# [:if, String] | [:unless, String] - conditionals
# [:case, String] | [:case_match, String] - case statements
# [:while, String] | [:until, String] - while/until loops
# [:for, String, String] - for loops
# [:begin, String] - begin blocks
# [:call, Symbol, String?] - method calls
# [:call_with_block, Symbol, String?] - method calls with blocks
# [:super, Symbol] - super calls
# [:forwarding_super, Symbol] - forwarding super calls
# [:lambda, String] - lambda expressions
# [:pre_execution, Integer] - BEGIN blocks
# [:post_execution, Integer] - END blocks
# [:parens, String] - parenthesized expressions
# [:embedded, String] - embedded statements
# [:other, String, Integer] - fallback for unknown nodes
def compute_node_signature: (untyped node) -> Array[untyped]
# Extract first argument value from a CallNode
def extract_first_argument_value: (untyped node) -> (String | Symbol | nil)
end
class FileAligner
class Anchor < Struct[untyped]
attr_accessor template_start: Integer
attr_accessor template_end: Integer
attr_accessor dest_start: Integer
attr_accessor dest_end: Integer
attr_accessor match_type: Symbol
attr_accessor score: Integer
def template_range: () -> Range[Integer]
def dest_range: () -> Range[Integer]
def length: () -> Integer
end
class Boundary < Struct[untyped]
attr_accessor template_range: Range[Integer]?
attr_accessor dest_range: Range[Integer]?
attr_accessor prev_anchor: Anchor?
attr_accessor next_anchor: Anchor?
def template_lines: () -> Array[Integer]
def dest_lines: () -> Array[Integer]
end
attr_reader template_analysis: FileAnalysis
attr_reader dest_analysis: FileAnalysis
attr_reader anchors: Array[Anchor]
attr_reader boundaries: Array[Boundary]
def initialize: (FileAnalysis template_analysis, FileAnalysis dest_analysis) -> void
def align: () -> Array[Boundary]
private
def find_anchors: () -> void
def build_line_map: (FileAnalysis analysis) -> Hash[Integer, String]
def find_exact_line_matches: (Hash[Integer, String] template_map, Hash[Integer, String] dest_map) -> Array[Hash[Symbol, untyped]]
def merge_consecutive_matches: (Array[Hash[Symbol, untyped]] matches) -> Array[Anchor]
def add_node_signature_anchors: () -> void
def add_freeze_block_anchors: () -> void
def compute_boundaries: () -> void
def ranges_overlap?: (Range[Integer]? range1, Range[Integer]? range2) -> bool
end
class MergeResult
DECISION_KEPT_TEMPLATE: Symbol
DECISION_KEPT_DEST: Symbol
DECISION_APPENDED: Symbol
DECISION_REPLACED: Symbol
DECISION_FREEZE_BLOCK: Symbol
type line_metadata = {
decision: Symbol,
template_line: Integer?,
dest_line: Integer?,
comment: String?,
result_line: Integer
}
attr_reader lines: Array[String]
attr_reader line_metadata: Array[line_metadata]
def initialize: () -> void
def add_line: (String content, decision: Symbol, ?template_line: Integer?, ?dest_line: Integer?, ?comment: String?) -> void
def add_lines_from: (Array[String] source_lines, decision: Symbol, source: Symbol, start_line: Integer, ?comment: String?) -> void
def add_node: (FileAnalysis::node_info node_info, decision: Symbol, source: Symbol, ?source_analysis: FileAnalysis?) -> void
def to_s: () -> String
def statistics: () -> Hash[Symbol, Integer]
def lines_by_decision: (Symbol decision) -> Array[line_metadata]
def debug_output: () -> String
end
class ConflictResolver
type boundary_content = {
lines: Array[String],
nodes: Array[FileAnalysis::node_info],
has_freeze_block: bool,
line_range: Range[Integer]?
}
attr_reader template_analysis: FileAnalysis
attr_reader dest_analysis: FileAnalysis
attr_reader add_template_only_nodes: bool
def initialize: (FileAnalysis template_analysis, FileAnalysis dest_analysis, ?preference: (Symbol | Hash[Symbol, Symbol]), ?add_template_only_nodes: bool) -> void
def resolve: (FileAligner::Boundary boundary, MergeResult result) -> void
private
def extract_boundary_content: (FileAnalysis analysis, Range[Integer]? line_range) -> boundary_content
def ranges_overlap?: (Range[Integer] range1, Range[Integer] range2) -> bool
def add_content_to_result: (boundary_content content, MergeResult result, Symbol source, Symbol decision) -> void
def merge_boundary_content: (boundary_content template_content, boundary_content dest_content, FileAligner::Boundary boundary, MergeResult result) -> void
def build_signature_map: (Array[FileAnalysis::node_info] nodes) -> Hash[Array[untyped], Array[FileAnalysis::node_info]]
def add_line_safe: (MergeResult result, String content, **untyped kwargs) -> void
def handle_orphan_lines: (boundary_content template_content, boundary_content dest_content, MergeResult result) -> void
def find_orphan_lines: (FileAnalysis analysis, Range[Integer]? line_range, Array[FileAnalysis::node_info] nodes) -> Array[Integer]
end
class SmartMerger
attr_reader template_analysis: FileAnalysis
attr_reader dest_analysis: FileAnalysis
attr_reader aligner: FileAligner
attr_reader resolver: ConflictResolver
attr_reader result: MergeResult
type merge_debug_result = {
content: String,
debug: String,
statistics: Hash[Symbol, Integer]
}
# Custom signature generator return types:
# - Array[untyped]: Used as the node's signature for matching
# - nil: Node gets no signature (won't be matched by signature)
# - Prism::Node or FreezeNode: Falls through to default signature computation
def initialize: (
String template_content,
String dest_content,
?signature_generator: (^(untyped) -> (Array[untyped] | untyped | nil))?,
?preference: (Symbol | Hash[Symbol, Symbol]),
?add_template_only_nodes: bool,
?freeze_token: String?,
?max_recursion_depth: (Integer | Float),
?current_depth: Integer,
?node_typing: Hash[Symbol, untyped]?
) -> void
def merge: () -> String
def merge_with_debug: () -> merge_debug_result
private
def process_merge: (Array[FileAligner::Boundary] boundaries) -> void
def build_timeline: (Array[FileAligner::Boundary] boundaries) -> Array[Hash[Symbol, untyped]]
def process_anchor: (FileAligner::Anchor anchor) -> void
def add_freeze_block_from_dest: (FileAligner::Anchor anchor) -> void
def add_signature_match_from_dest: (FileAligner::Anchor anchor) -> void
def add_exact_match_from_template: (FileAligner::Anchor anchor) -> void
def process_boundary: (FileAligner::Boundary boundary) -> void
# Find a node that overlaps with a line range
def find_node_in_range: (FileAnalysis analysis, Integer start_line, Integer end_line) -> untyped?
# Find a node at a specific line (deprecated)
def find_node_at_line: (FileAnalysis analysis, Integer line_num) -> untyped?
# Determine if two matching nodes should be recursively merged
# Returns true for ClassNode, ModuleNode, SingletonClassNode, and CallNode/BeginNode
# with blocks that contain mergeable statements.
# Returns false if max_recursion_depth has been reached (safety valve).
def should_merge_recursively?: (untyped? template_node, untyped? dest_node) -> bool
# Check if a body (StatementsNode) contains statements that could be merged
# Returns true if body contains CallNode, DefNode, ClassNode, assignments, etc.
def body_has_mergeable_statements?: (untyped? body) -> bool
# Check if a statement is mergeable (can generate a signature)
# Returns true for CallNode, DefNode, ClassNode, ModuleNode, assignments, conditionals, etc.
def mergeable_statement?: (untyped node) -> bool
# Check if a node's body contains freeze block markers
# @param node The node to check for freeze markers
# @param analysis The FileAnalysis for the file containing the node
def node_contains_freeze_blocks?: (untyped node, FileAnalysis analysis) -> bool
# Recursively merge the body of matching class, module, or call-with-block nodes
def merge_node_body_recursively: (untyped template_node, untyped dest_node, FileAligner::Anchor anchor) -> void
# Extract the body content of a node (without declaration and closing 'end')
def extract_node_body: (untyped node, FileAnalysis analysis) -> String
end end end