Module: Solargraph::Parser::ParserGem::NodeMethods
- Included in:
- FlowSensitiveTyping, NodeChainer, Solargraph::Parser::ParserGem::NodeProcessors::AndNode, Solargraph::Parser::ParserGem::NodeProcessors::BlockNode, Solargraph::Parser::ParserGem::NodeProcessors::CasgnNode, Solargraph::Parser::ParserGem::NodeProcessors::DefsNode, Solargraph::Parser::ParserGem::NodeProcessors::IfNode, Solargraph::Parser::ParserGem::NodeProcessors::IvasgnNode, Solargraph::Parser::ParserGem::NodeProcessors::LvasgnNode, Solargraph::Parser::ParserGem::NodeProcessors::MasgnNode, Solargraph::Parser::ParserGem::NodeProcessors::NamespaceNode, Solargraph::Parser::ParserGem::NodeProcessors::OrNode, Solargraph::Parser::ParserGem::NodeProcessors::ResbodyNode, Solargraph::Parser::ParserGem::NodeProcessors::SendNode, Solargraph::Parser::ParserGem::NodeProcessors::UntilNode, Solargraph::Parser::ParserGem::NodeProcessors::WhenNode, Solargraph::Parser::ParserGem::NodeProcessors::WhileNode
- Defined in:
- lib/solargraph/parser/parser_gem/node_methods.rb
Defined Under Namespace
Modules: DeepInference
Constant Summary collapse
- NIL_NODE =
::Parser::AST::Node.new(:nil)
Class Method Summary collapse
- .any_splatted_call?(nodes) ⇒ Boolean
- .call_nodes_from(node) ⇒ Array<Parser::AST::Node>
- .const_nodes_from(node) ⇒ Array<Parser::AST::Node>
- .convert_hash(node) ⇒ Hash{Symbol => Chain}
- .drill_signature(node, signature) ⇒ String
- .find_recipient_node(cursor) ⇒ Parser::AST::Node?
-
.find_recipient_node_by_text(source, offset) ⇒ Parser::AST::Node?
Text-based fallback for finding a method call recipient when the AST is unavailable (e.g., unparseable source with syntax errors).
- .get_node_end_position(node) ⇒ Position
- .get_node_start_position(node) ⇒ Position
- .infer_literal_node_type(node) ⇒ String?
- .pack_name(node) ⇒ Array<String>
- .repaired_find_recipient_node(cursor) ⇒ Parser::AST::Node?
-
.returns_from_method_body(node) ⇒ Array<Parser::AST::Node>
Find all the nodes within the provided node that potentially return a value.
-
.simple_convert(node) ⇒ String, ...
Convert a DSL method call argument with directly inferrable simple params.
- .simple_convert_array(node) ⇒ Array
- .simple_convert_hash(node) ⇒ Hash
- .splatted_call?(node) ⇒ Boolean
- .splatted_hash?(node) ⇒ Boolean
- .unpack_name(node) ⇒ String
-
.value_position_nodes_only(node) ⇒ Array<AST::Node>
Low-level value nodes in value position.
Class Method Details
.any_splatted_call?(nodes) ⇒ Boolean
196 197 198 |
# File 'lib/solargraph/parser/parser_gem/node_methods.rb', line 196 def any_splatted_call? nodes nodes.any? { |n| splatted_call?(n) } end |
.call_nodes_from(node) ⇒ Array<Parser::AST::Node>
Temporarily here for testing. Move to Solargraph::Parser.
203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 |
# File 'lib/solargraph/parser/parser_gem/node_methods.rb', line 203 def call_nodes_from node return [] unless node.is_a?(::Parser::AST::Node) result = [] if node.type == :block result.push node if Parser.is_ast_node?(node.children[0]) && node.children[0].children.length > 2 # @sg-ignore Need to add nil check here node.children[0].children[2..].each { |child| result.concat call_nodes_from(child) } end # @sg-ignore Need to add nil check here node.children[1..].each { |child| result.concat call_nodes_from(child) } elsif node.type == :send result.push node result.concat call_nodes_from(node.children.first) # @sg-ignore Need to add nil check here node.children[2..].each { |child| result.concat call_nodes_from(child) } elsif %i[super zsuper].include?(node.type) result.push node node.children.each { |child| result.concat call_nodes_from(child) } else node.children.each { |child| result.concat call_nodes_from(child) } end result end |
.const_nodes_from(node) ⇒ Array<Parser::AST::Node>
173 174 175 176 177 178 179 180 181 182 |
# File 'lib/solargraph/parser/parser_gem/node_methods.rb', line 173 def const_nodes_from node return [] unless Parser.is_ast_node?(node) result = [] if node.type == :const result.push node else node.children.each { |child| result.concat const_nodes_from(child) } end result end |
.convert_hash(node) ⇒ Hash{Symbol => Chain}
149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 |
# File 'lib/solargraph/parser/parser_gem/node_methods.rb', line 149 def convert_hash node return {} unless Parser.is_ast_node?(node) # @sg-ignore Translate to something flow sensitive typing understands return convert_hash(node.children[0]) if node.type == :kwsplat # @sg-ignore Translate to something flow sensitive typing understands if Parser.is_ast_node?(node.children[0]) && node.children[0].type == :kwsplat # @sg-ignore Translate to something flow sensitive typing understands return convert_hash(node.children[0]) end # @sg-ignore Translate to something flow sensitive typing understands return {} unless node.type == :hash result = {} # @sg-ignore Translate to something flow sensitive typing understands node.children.each do |pair| result[pair.children[0].children[0]] = Solargraph::Parser.chain(pair.children[1]) end result end |
.drill_signature(node, signature) ⇒ String
88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 |
# File 'lib/solargraph/parser/parser_gem/node_methods.rb', line 88 def drill_signature node, signature return signature unless node.is_a?(AST::Node) if %i[const cbase].include?(node.type) signature += drill_signature(node.children[0], signature) unless node.children[0].nil? signature += '::' unless signature.empty? signature += node.children[1].to_s elsif %i[lvar ivar cvar].include?(node.type) signature += '.' unless signature.empty? signature += node.children[0].to_s elsif node.type == :send signature += drill_signature(node.children[0], signature) unless node.children[0].nil? signature += '.' unless signature.empty? signature += node.children[1].to_s end signature end |
.find_recipient_node(cursor) ⇒ Parser::AST::Node?
256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 |
# File 'lib/solargraph/parser/parser_gem/node_methods.rb', line 256 def find_recipient_node cursor if cursor.source.repaired? && cursor.source.code[cursor.offset - 1] == '(' return repaired_find_recipient_node(cursor) end source = cursor.source position = cursor.position offset = cursor.offset tree = if source.synchronized? # @sg-ignore Need to add nil check here match = source.code[0..(offset - 1)].match(/,\s*\z/) if match # @sg-ignore Need to add nil check here source.tree_at(position.line, position.column - match[0].length) else source.tree_at(position.line, position.column) end else source.tree_at(position.line, position.column - 1) end # @type [AST::Node, nil] prev = nil tree.each do |node| if node.type == :send args = node.children[2..] # @sg-ignore Need to add nil check here if !args.empty? # @sg-ignore Need to add nil check here return node if prev && args.include?(prev) elsif source.synchronized? return node if source.code[0..(offset - 1)] =~ /\(\s*\z/ && source.code[offset..] =~ /^\s*\)/ elsif source.code[0..(offset - 1)] =~ /\([^(]*\z/ return node end end prev = node end find_recipient_node_by_text(source, offset) end |
.find_recipient_node_by_text(source, offset) ⇒ Parser::AST::Node?
Text-based fallback for finding a method call recipient when the AST is unavailable (e.g., unparseable source with syntax errors).
Scans backward from cursor offset to find ‘(’ and the method name before it, then creates a minimal :send node.
304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 |
# File 'lib/solargraph/parser/parser_gem/node_methods.rb', line 304 def find_recipient_node_by_text source, offset code = source.code return nil if offset.nil? || offset <= 0 || offset > code.length # The '(' could be at offset-1 (cursor after '(') or at offset (cursor on '(') start_pos = offset - 1 if start_pos.positive? && code[start_pos] != '(' && code[offset] == '(' start_pos = offset end # Scan backward to find the matching '(' (handle nested parens) depth = 0 paren_pos = nil pos = start_pos while pos >= 0 case code[pos] when ')' depth += 1 when '(' if depth.zero? paren_pos = pos break end depth -= 1 end pos -= 1 end return nil if paren_pos.nil? # Skip whitespace before '(' to find method name idx = paren_pos - 1 idx -= 1 while idx >= 0 && code[idx] =~ /\s/ return nil if idx.negative? # Read method name (including ? and !) name_end = idx + 1 idx -= 1 while idx >= 0 && code[idx] =~ /[a-zA-Z0-9_?!]/ name_start = idx + 1 return nil if name_start >= name_end method_name = code[name_start...name_end] return nil if method_name.empty? # Check for receiver pattern: receiver.method( or receiver::method( idx = name_start - 1 idx -= 1 while idx >= 0 && code[idx] =~ /\s/ if idx >= 0 && code[idx] == '.' idx -= 1 idx -= 1 while idx >= 0 && code[idx] =~ /\s/ recv_end = idx + 1 idx -= 1 while idx >= 0 && code[idx] =~ /[a-zA-Z0-9_@$]/ recv_start = idx + 1 if recv_start < recv_end recv_name = code[recv_start...recv_end] unless recv_name.empty? receiver_node = ::Parser::AST::Node.new(:send, [nil, recv_name.to_sym]) return ::Parser::AST::Node.new(:send, [receiver_node, method_name.to_sym]) end end elsif idx >= 0 && idx.positive? && code[idx - 1] == ':' && code[idx] == ':' const_end = idx - 1 const_start = const_end const_start -= 1 while const_start.positive? && code[const_start - 1] =~ /[a-zA-Z0-9_]/ const_name = code[const_start...const_end] unless const_name.empty? || method_name.empty? const_node = ::Parser::AST::Node.new(:const, [nil, const_name.to_sym]) return ::Parser::AST::Node.new(:send, [const_node, method_name.to_sym]) end end # Simple method call without receiver ::Parser::AST::Node.new(:send, [nil, method_name.to_sym]) end |
.get_node_end_position(node) ⇒ Position
80 81 82 |
# File 'lib/solargraph/parser/parser_gem/node_methods.rb', line 80 def get_node_end_position node Position.new(node.loc.last_line, node.loc.last_column) end |
.get_node_start_position(node) ⇒ Position
74 75 76 |
# File 'lib/solargraph/parser/parser_gem/node_methods.rb', line 74 def get_node_start_position node Position.new(node.loc.line, node.loc.column) end |
.infer_literal_node_type(node) ⇒ String?
43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 |
# File 'lib/solargraph/parser/parser_gem/node_methods.rb', line 43 def infer_literal_node_type node return nil unless node.is_a?(AST::Node) if %i[str dstr].include?(node.type) return '::String' elsif node.type == :array return '::Array' elsif node.type == :nil return '::NilClass' elsif node.type == :hash return '::Hash' elsif node.type == :int return '::Integer' elsif node.type == :float return '::Float' elsif %i[sym dsym].include?(node.type) return '::Symbol' elsif node.type == :regexp return '::Regexp' elsif node.type == :irange return '::Range' elsif %i[true false].include?(node.type) return '::Boolean' # @todo Support `nil` keyword in types # elsif node.type == :nil # return 'NilClass' end nil end |
.pack_name(node) ⇒ Array<String>
22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
# File 'lib/solargraph/parser/parser_gem/node_methods.rb', line 22 def pack_name node # @type [Array<String>] parts = [] if node.is_a?(AST::Node) node.children.each do |n| if n.is_a?(AST::Node) if n.type == :cbase parts = [''] + pack_name(n) elsif n.type == :const parts += pack_name(n) end else parts.push n unless n.nil? end end end parts end |
.repaired_find_recipient_node(cursor) ⇒ Parser::AST::Node?
379 380 381 382 383 384 385 386 |
# File 'lib/solargraph/parser/parser_gem/node_methods.rb', line 379 def repaired_find_recipient_node cursor c = cursor.source.cursor_at([cursor.position.line, cursor.position.column - 1]) tree = c.source.tree_at(c.position.line, c.position.column) tree.each do |node| return node if node.type == :send end find_recipient_node_by_text(cursor.source, cursor.offset) end |
.returns_from_method_body(node) ⇒ Array<Parser::AST::Node>
Find all the nodes within the provided node that potentially return a value.
The node parameter typically represents a method’s logic, e.g., the second child (after the :args node) of a :def node. A simple one-line method would typically return itself, while a node with conditions would return the resulting node from each conditional branch. Nodes that follow a :return node are assumed to be unreachable. Nil values are converted to nil node types.
240 241 242 243 244 |
# File 'lib/solargraph/parser/parser_gem/node_methods.rb', line 240 def returns_from_method_body node # @todo is the || NIL_NODE necessary? # STDERR.puts("Evaluating expression: #{node.inspect}") DeepInference.from_method_body(node).map { |n| n || NIL_NODE } end |
.simple_convert(node) ⇒ String, ...
Convert a DSL method call argument with directly inferrable simple params. @sg-ignore “does not match inferred type ::String, ::Parser::AST::Node” - this probably comes from the ‘.children` call, which is not recognized as returning a literal value.
110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 |
# File 'lib/solargraph/parser/parser_gem/node_methods.rb', line 110 def simple_convert node return nil unless Parser.is_ast_node?(node) case node.type when :const unpack_name(node) when :str, :dstr, :int, :float, :sym, true, false node.children[0] when :array simple_convert_array(node) when :hash simple_convert_hash(node) else Solargraph::Parser.chain(node) end end |
.simple_convert_array(node) ⇒ Array
129 130 131 132 |
# File 'lib/solargraph/parser/parser_gem/node_methods.rb', line 129 def simple_convert_array node return [] unless Parser.is_ast_node?(node) && node.type == :array node.children.compact.map { |c| simple_convert(c) } end |
.simple_convert_hash(node) ⇒ Hash
136 137 138 139 140 141 142 143 144 145 |
# File 'lib/solargraph/parser/parser_gem/node_methods.rb', line 136 def simple_convert_hash node return {} unless Parser.is_ast_node?(node) && node.type == :hash result = {} # @param pair [Parser::AST::Node] node.children.each do |pair| next unless Parser.is_ast_node?(pair) && pair.children[0] result[pair.children[0].children[0]] = simple_convert(pair.children[1]) end result end |
.splatted_call?(node) ⇒ Boolean
190 191 192 193 |
# File 'lib/solargraph/parser/parser_gem/node_methods.rb', line 190 def splatted_call? node return false unless Parser.is_ast_node?(node) Parser.is_ast_node?(node.children[0]) && node.children[0].type == :kwsplat && node.children[0].children[0].type != :hash end |
.splatted_hash?(node) ⇒ Boolean
185 186 187 |
# File 'lib/solargraph/parser/parser_gem/node_methods.rb', line 185 def splatted_hash? node Parser.is_ast_node?(node.children[0]) && node.children[0].type == :kwsplat end |
.unpack_name(node) ⇒ String
16 17 18 |
# File 'lib/solargraph/parser/parser_gem/node_methods.rb', line 16 def unpack_name node pack_name(node).join('::') end |
.value_position_nodes_only(node) ⇒ Array<AST::Node>
Returns low-level value nodes in value position. Does not include explicit return statements.
250 251 252 |
# File 'lib/solargraph/parser/parser_gem/node_methods.rb', line 250 def value_position_nodes_only node DeepInference.value_position_nodes_only(node).map { |n| n || NIL_NODE } end |