def analyze_code(code, binding = Object::TOPLEVEL_BINDING)
ast = Prism.parse(code, scopes: [binding.local_variables]).value
*parents, target_node = find_target ast, code.bytesize
return unless target_node
calculate_scope = -> { TypeAnalyzer.calculate_target_type_scope(binding, parents, target_node).last }
calculate_type_scope = ->(node) { TypeAnalyzer.calculate_target_type_scope binding, [*parents, target_node], node }
case target_node
when Prism::StringNode
return unless target_node.closing&.empty?
call_node, args_node = parents.last(2)
return unless call_node.is_a?(Prism::CallNode) && call_node.receiver.nil?
return unless args_node.is_a?(Prism::ArgumentsNode) && args_node.arguments.size == 1
if call_node.name == :require || call_node.name == :require_relative
[call_node.name, target_node.content]
end
when Prism::SymbolNode
return unless !target_node.closing || target_node.closing.empty?
name = target_node.value.to_s
if parents.last.is_a? Prism::BlockArgumentNode
receiver_type, _scope = calculate_type_scope.call target_node
[:call, name, receiver_type, false]
else
[:symbol, name] unless name.empty?
end
when Prism::CallNode, Prism::CallTargetNode
return if target_node.is_a?(Prism::CallNode) && target_node.opening
name = target_node.message.to_s
return [:lvar_or_method, name, calculate_scope.call] if target_node.receiver.nil?
self_call = target_node.receiver.is_a? Prism::SelfNode
op = target_node.call_operator
receiver_type, _scope = calculate_type_scope.call target_node.receiver
receiver_type = receiver_type.nonnillable if op == '&.'
[op == '::' ? :call_or_const : :call, name, receiver_type, self_call]
when Prism::LocalVariableReadNode, Prism::LocalVariableTargetNode
[:lvar_or_method, target_node.name.to_s, calculate_scope.call]
when Prism::ItLocalVariableReadNode
[:lvar_or_method, 'it', calculate_scope.call]
when Prism::ConstantPathNode, Prism::ConstantPathTargetNode
name = target_node.name.to_s
if target_node.parent
receiver, scope = calculate_type_scope.call(target_node.parent)
[:const, name, receiver, scope]
else
scope = calculate_scope.call
[:const, name, Types::SingletonType.new(Object), scope]
end
when Prism::ConstantReadNode, Prism::ConstantTargetNode
[:const, target_node.name.to_s, nil, calculate_scope.call]
when Prism::GlobalVariableReadNode, Prism::GlobalVariableTargetNode
[:gvar, target_node.name.to_s, calculate_scope.call]
when Prism::InstanceVariableReadNode, Prism::InstanceVariableTargetNode
[:ivar, target_node.name.to_s, calculate_scope.call]
when Prism::ClassVariableReadNode, Prism::ClassVariableTargetNode
[:cvar, target_node.name.to_s, calculate_scope.call]
end
end