Module: Kettle::Dev::PrismUtils
- Defined in:
- lib/kettle/dev/prism_utils.rb
Overview
Shared utilities for working with Prism AST nodes.
Provides parsing, node inspection, and source generation helpers
used by both PrismMerger and AppraisalsAstMerger.
Uses Prism’s native methods for source generation (via .slice) to preserve
original formatting and comments. For normalized output (e.g., adding parentheses),
use normalize_call_node instead.
Class Method Summary collapse
-
.block_call_to?(node, method_name) ⇒ Boolean
Check if a node is a block call to a specific method.
-
.call_to?(node, method_name) ⇒ Boolean
Check if a node is a specific method call.
-
.extract_const_name(node) ⇒ String?
Extract qualified constant name from a constant node.
-
.extract_literal_value(node) ⇒ String, ...
Extract literal value from string or symbol nodes.
-
.extract_statements(body_node) ⇒ Array<Prism::Node>
Extract statements from a Prism body node.
-
.find_leading_comments(parse_result, current_stmt, prev_stmt, body_node) ⇒ Array<Prism::Comment>
Find leading comments for a statement node Leading comments are those that appear after the previous statement and before the current statement.
-
.inline_comments_for_node(parse_result, stmt) ⇒ Array<Prism::Comment>
Find inline comments for a statement node Inline comments are those that appear on the same line as the statement’s end.
-
.node_to_source(node) ⇒ String
Convert a Prism AST node to Ruby source code Uses Prism’s native slice method which preserves the original source exactly.
-
.normalize_argument(arg) ⇒ String
Normalize an argument node to canonical format.
-
.normalize_call_node(node) ⇒ String
Normalize a call node to use parentheses format Converts
gem "foo"togem("foo")style. -
.parse_with_comments(source) ⇒ Prism::ParseResult
Parse Ruby source code and return Prism parse result with comments.
-
.statement_key(node, tracked_methods: %i[gem source eval_gemfile git_source])) ⇒ Array?
Generate a unique key for a statement node to identify equivalent statements Used for merge/append operations to detect duplicates.
Class Method Details
.block_call_to?(node, method_name) ⇒ Boolean
Check if a node is a block call to a specific method
196 197 198 |
# File 'lib/kettle/dev/prism_utils.rb', line 196 def block_call_to?(node, method_name) node.is_a?(Prism::CallNode) && node.name == method_name && !node.block.nil? end |
.call_to?(node, method_name) ⇒ Boolean
Check if a node is a specific method call
188 189 190 |
# File 'lib/kettle/dev/prism_utils.rb', line 188 def call_to?(node, method_name) node.is_a?(Prism::CallNode) && node.name == method_name end |
.extract_const_name(node) ⇒ String?
Extract qualified constant name from a constant node
78 79 80 81 82 83 84 85 86 87 |
# File 'lib/kettle/dev/prism_utils.rb', line 78 def extract_const_name(node) case node when Prism::ConstantReadNode node.name.to_s when Prism::ConstantPathNode parent = extract_const_name(node.parent) child = node.name || node.child&.name (parent && child) ? "#{parent}::#{child}" : child.to_s end end |
.extract_literal_value(node) ⇒ String, ...
Extract literal value from string or symbol nodes
55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 |
# File 'lib/kettle/dev/prism_utils.rb', line 55 def extract_literal_value(node) return unless node case node when Prism::StringNode then node.unescaped when Prism::SymbolNode then node.unescaped else # Attempt to handle array literals if node.respond_to?(:elements) && node.elements arr = node.elements.map do |el| case el when Prism::StringNode then el.unescaped when Prism::SymbolNode then el.unescaped end end return arr if arr.all? end nil end end |
.extract_statements(body_node) ⇒ Array<Prism::Node>
Extract statements from a Prism body node
27 28 29 30 31 32 33 34 35 |
# File 'lib/kettle/dev/prism_utils.rb', line 27 def extract_statements(body_node) return [] unless body_node if body_node.is_a?(Prism::StatementsNode) body_node.body.compact else [body_node].compact end end |
.find_leading_comments(parse_result, current_stmt, prev_stmt, body_node) ⇒ Array<Prism::Comment>
Find leading comments for a statement node
Leading comments are those that appear after the previous statement
and before the current statement
97 98 99 100 101 102 103 104 105 |
# File 'lib/kettle/dev/prism_utils.rb', line 97 def find_leading_comments(parse_result, current_stmt, prev_stmt, body_node) start_line = prev_stmt ? prev_stmt.location.end_line : body_node.location.start_line end_line = current_stmt.location.start_line parse_result.comments.select do |comment| comment.location.start_line > start_line && comment.location.start_line < end_line end end |
.inline_comments_for_node(parse_result, stmt) ⇒ Array<Prism::Comment>
Find inline comments for a statement node
Inline comments are those that appear on the same line as the statement’s end
112 113 114 115 116 117 |
# File 'lib/kettle/dev/prism_utils.rb', line 112 def inline_comments_for_node(parse_result, stmt) parse_result.comments.select do |comment| comment.location.start_line == stmt.location.end_line && comment.location.start_offset > stmt.location.end_offset end end |
.node_to_source(node) ⇒ String
Convert a Prism AST node to Ruby source code
Uses Prism’s native slice method which preserves the original source exactly.
This is preferable to Unparser for Prism nodes as it maintains original formatting
and comments without requiring transformation.
125 126 127 128 129 |
# File 'lib/kettle/dev/prism_utils.rb', line 125 def node_to_source(node) return "" unless node # Prism nodes have a slice method that returns the original source node.slice end |
.normalize_argument(arg) ⇒ String
Normalize an argument node to canonical format
152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 |
# File 'lib/kettle/dev/prism_utils.rb', line 152 def normalize_argument(arg) case arg when Prism::StringNode "\"#{arg.unescaped}\"" when Prism::SymbolNode ":#{arg.unescaped}" when Prism::KeywordHashNode # Handle hash arguments like {key: value} pairs = arg.elements.map do |assoc| key = case assoc.key when Prism::SymbolNode then "#{assoc.key.unescaped}:" when Prism::StringNode then "\"#{assoc.key.unescaped}\" =>" else "#{assoc.key.slice} =>" end value = normalize_argument(assoc.value) "#{key} #{value}" end.join(", ") pairs when Prism::HashNode # Handle explicit hash syntax pairs = arg.elements.map do |assoc| key_part = normalize_argument(assoc.key) value_part = normalize_argument(assoc.value) "#{key_part} => #{value_part}" end.join(", ") "{#{pairs}}" else # For other types (numbers, arrays, etc.), use the original source arg.slice.strip end end |
.normalize_call_node(node) ⇒ String
Normalize a call node to use parentheses format
Converts gem "foo" to gem("foo") style
135 136 137 138 139 140 141 142 143 144 145 146 147 |
# File 'lib/kettle/dev/prism_utils.rb', line 135 def normalize_call_node(node) return node.slice.strip unless node.is_a?(Prism::CallNode) method_name = node.name args = node.arguments&.arguments || [] if args.empty? "#{method_name}()" else arg_strings = args.map { |arg| normalize_argument(arg) } "#{method_name}(#{arg_strings.join(", ")})" end end |
.parse_with_comments(source) ⇒ Prism::ParseResult
Parse Ruby source code and return Prism parse result with comments
20 21 22 |
# File 'lib/kettle/dev/prism_utils.rb', line 20 def parse_with_comments(source) Prism.parse(source) end |
.statement_key(node, tracked_methods: %i[gem source eval_gemfile git_source])) ⇒ Array?
Generate a unique key for a statement node to identify equivalent statements
Used for merge/append operations to detect duplicates
42 43 44 45 46 47 48 49 50 |
# File 'lib/kettle/dev/prism_utils.rb', line 42 def statement_key(node, tracked_methods: %i[gem source eval_gemfile git_source]) return unless node.is_a?(Prism::CallNode) return unless tracked_methods.include?(node.name) first_arg = node.arguments&.arguments&.first arg_value = extract_literal_value(first_arg) [node.name, arg_value] if arg_value end |