lib/rbi/parser.rb in rbi-0.1.1 vs lib/rbi/parser.rb in rbi-0.1.2

- old
+ new

@@ -1,9 +1,9 @@ # typed: strict # frozen_string_literal: true -require "yarp" +require "prism" module RBI class ParseError < StandardError extend T::Sig @@ -91,39 +91,37 @@ private sig { params(source: String, file: String).returns(Tree) } def parse(source, file:) - result = YARP.parse(source) + result = Prism.parse(source) unless result.success? message = result.errors.map { |e| "#{e.message}." }.join(" ") error = result.errors.first location = Loc.new(file: file, begin_line: error.location.start_line, begin_column: error.location.start_column) raise ParseError.new(message, location) end visitor = TreeBuilder.new(source, comments: result.comments, file: file) visitor.visit(result.value) visitor.tree - rescue YARP::ParseError => e - raise ParseError.new(e.message, Loc.from_yarp(file, e.location)) rescue ParseError => e raise e rescue => e last_node = visitor&.last_node last_location = if last_node - Loc.from_yarp(file, last_node.location) + Loc.from_prism(file, last_node.location) else Loc.new(file: file) end exception = UnexpectedParserError.new(e, last_location) exception.print_debug raise exception end - class Visitor < YARP::Visitor + class Visitor < Prism::Visitor extend T::Sig sig { params(source: String, file: String).void } def initialize(source, file:) super() @@ -132,23 +130,23 @@ @file = file end private - sig { params(node: YARP::Node).returns(Loc) } + sig { params(node: Prism::Node).returns(Loc) } def node_loc(node) - Loc.from_yarp(@file, node.location) + Loc.from_prism(@file, node.location) end - sig { params(node: T.nilable(YARP::Node)).returns(T.nilable(String)) } + sig { params(node: T.nilable(Prism::Node)).returns(T.nilable(String)) } def node_string(node) return unless node node.slice end - sig { params(node: YARP::Node).returns(String) } + sig { params(node: Prism::Node).returns(String) } def node_string!(node) T.must(node_string(node)) end end @@ -156,36 +154,29 @@ extend T::Sig sig { returns(Tree) } attr_reader :tree - sig { returns(T.nilable(YARP::Node)) } + sig { returns(T.nilable(Prism::Node)) } attr_reader :last_node - sig { params(source: String, comments: T::Array[YARP::Comment], file: String).void } + sig { params(source: String, comments: T::Array[Prism::Comment], file: String).void } def initialize(source, comments:, file:) super(source, file: file) - @comments_by_line = T.let(comments.to_h { |c| [c.location.start_line, c] }, T::Hash[Integer, YARP::Comment]) + @comments_by_line = T.let(comments.to_h { |c| [c.location.start_line, c] }, T::Hash[Integer, Prism::Comment]) @tree = T.let(Tree.new, Tree) @scopes_stack = T.let([@tree], T::Array[Tree]) - @last_node = T.let(nil, T.nilable(YARP::Node)) + @last_node = T.let(nil, T.nilable(Prism::Node)) @last_sigs = T.let([], T::Array[RBI::Sig]) @last_sigs_comments = T.let([], T::Array[Comment]) end - sig { override.params(node: T.nilable(YARP::Node)).void } - def visit(node) - return unless node - - @last_node = node - super - end - - sig { override.params(node: YARP::ClassNode).void } + sig { override.params(node: Prism::ClassNode).void } def visit_class_node(node) + @last_node = node scope = Class.new( node_string!(node.constant_path), superclass_name: node_string(node.superclass), loc: node_loc(node), comments: node_comments(node), @@ -194,69 +185,77 @@ current_scope << scope @scopes_stack << scope visit(node.body) collect_dangling_comments(node) @scopes_stack.pop + @last_node = nil end - sig { override.params(node: YARP::ConstantWriteNode).void } + sig { override.params(node: Prism::ConstantWriteNode).void } def visit_constant_write_node(node) + @last_node = node visit_constant_assign(node) + @last_node = nil end - sig { override.params(node: YARP::ConstantPathWriteNode).void } + sig { override.params(node: Prism::ConstantPathWriteNode).void } def visit_constant_path_write_node(node) + @last_node = node visit_constant_assign(node) + @last_node = nil end - sig { params(node: T.any(YARP::ConstantWriteNode, YARP::ConstantPathWriteNode)).void } + sig { params(node: T.any(Prism::ConstantWriteNode, Prism::ConstantPathWriteNode)).void } def visit_constant_assign(node) struct = parse_struct(node) current_scope << if struct struct elsif type_variable_definition?(node.value) TypeMember.new( case node - when YARP::ConstantWriteNode + when Prism::ConstantWriteNode node.name.to_s - when YARP::ConstantPathWriteNode + when Prism::ConstantPathWriteNode node_string!(node.target) end, node_string!(node.value), loc: node_loc(node), comments: node_comments(node), ) else Const.new( case node - when YARP::ConstantWriteNode + when Prism::ConstantWriteNode node.name.to_s - when YARP::ConstantPathWriteNode + when Prism::ConstantPathWriteNode node_string!(node.target) end, node_string!(node.value), loc: node_loc(node), comments: node_comments(node), ) end end - sig { override.params(node: YARP::DefNode).void } + sig { override.params(node: Prism::DefNode).void } def visit_def_node(node) + @last_node = node current_scope << Method.new( node.name.to_s, params: parse_params(node.parameters), sigs: current_sigs, loc: node_loc(node), comments: current_sigs_comments + node_comments(node), is_singleton: !!node.receiver, ) + @last_node = nil end - sig { override.params(node: YARP::ModuleNode).void } + sig { override.params(node: Prism::ModuleNode).void } def visit_module_node(node) + @last_node = node scope = Module.new( node_string!(node.constant_path), loc: node_loc(node), comments: node_comments(node), ) @@ -264,140 +263,186 @@ current_scope << scope @scopes_stack << scope visit(node.body) collect_dangling_comments(node) @scopes_stack.pop + @last_node = nil end - sig { override.params(node: YARP::ProgramNode).void } + sig { override.params(node: Prism::ProgramNode).void } def visit_program_node(node) + @last_node = node super collect_orphan_comments separate_header_comments set_root_tree_loc + @last_node = nil end - sig { override.params(node: YARP::SingletonClassNode).void } + sig { override.params(node: Prism::SingletonClassNode).void } def visit_singleton_class_node(node) + @last_node = node scope = SingletonClass.new( loc: node_loc(node), comments: node_comments(node), ) current_scope << scope @scopes_stack << scope visit(node.body) collect_dangling_comments(node) @scopes_stack.pop + @last_node = nil end - sig { params(node: YARP::CallNode).void } + sig { params(node: Prism::CallNode).void } def visit_call_node(node) - message = node.name + @last_node = node + message = node.name.to_s case message when "abstract!", "sealed!", "interface!" current_scope << Helper.new( message.delete_suffix("!"), loc: node_loc(node), comments: node_comments(node), ) when "attr_reader" args = node.arguments - return unless args.is_a?(YARP::ArgumentsNode) && args.arguments.any? + unless args.is_a?(Prism::ArgumentsNode) && args.arguments.any? + @last_node = nil + return + end + current_scope << AttrReader.new( *T.unsafe(args.arguments.map { |arg| node_string!(arg).delete_prefix(":").to_sym }), sigs: current_sigs, loc: node_loc(node), comments: current_sigs_comments + node_comments(node), ) when "attr_writer" args = node.arguments - return unless args.is_a?(YARP::ArgumentsNode) && args.arguments.any? + unless args.is_a?(Prism::ArgumentsNode) && args.arguments.any? + @last_node = nil + return + end + current_scope << AttrWriter.new( *T.unsafe(args.arguments.map { |arg| node_string!(arg).delete_prefix(":").to_sym }), sigs: current_sigs, loc: node_loc(node), comments: current_sigs_comments + node_comments(node), ) when "attr_accessor" args = node.arguments - return unless args.is_a?(YARP::ArgumentsNode) && args.arguments.any? + unless args.is_a?(Prism::ArgumentsNode) && args.arguments.any? + @last_node = nil + return + end + current_scope << AttrAccessor.new( *T.unsafe(args.arguments.map { |arg| node_string!(arg).delete_prefix(":").to_sym }), sigs: current_sigs, loc: node_loc(node), comments: current_sigs_comments + node_comments(node), ) when "enums" block = node.block - return unless block.is_a?(YARP::BlockNode) + unless block.is_a?(Prism::BlockNode) + @last_node = nil + return + end + body = block.body - return unless body.is_a?(YARP::StatementsNode) + unless body.is_a?(Prism::StatementsNode) + @last_node = nil + return + end + current_scope << TEnumBlock.new( - body.body.map { |stmt| T.cast(stmt, YARP::ConstantWriteNode).name.to_s }, + body.body.map { |stmt| T.cast(stmt, Prism::ConstantWriteNode).name.to_s }, loc: node_loc(node), comments: node_comments(node), ) when "extend" args = node.arguments - return unless args.is_a?(YARP::ArgumentsNode) && args.arguments.any? + unless args.is_a?(Prism::ArgumentsNode) && args.arguments.any? + @last_node = nil + return + end + current_scope << Extend.new( *T.unsafe(args.arguments.map { |arg| node_string!(arg) }), loc: node_loc(node), comments: node_comments(node), ) when "include" args = node.arguments - return unless args.is_a?(YARP::ArgumentsNode) && args.arguments.any? + unless args.is_a?(Prism::ArgumentsNode) && args.arguments.any? + @last_node = nil + return + end + current_scope << Include.new( *T.unsafe(args.arguments.map { |arg| node_string!(arg) }), loc: node_loc(node), comments: node_comments(node), ) when "mixes_in_class_methods" args = node.arguments - return unless args.is_a?(YARP::ArgumentsNode) && args.arguments.any? + unless args.is_a?(Prism::ArgumentsNode) && args.arguments.any? + @last_node = nil + return + end + current_scope << MixesInClassMethods.new( *T.unsafe(args.arguments.map { |arg| node_string!(arg) }), loc: node_loc(node), comments: node_comments(node), ) when "private", "protected", "public" args = node.arguments - if args.is_a?(YARP::ArgumentsNode) && args.arguments.any? + if args.is_a?(Prism::ArgumentsNode) && args.arguments.any? visit(node.arguments) last_node = @scopes_stack.last&.nodes&.last case last_node when Method, Attr - last_node.visibility = parse_visibility(node.name, node) + last_node.visibility = parse_visibility(node.name.to_s, node) else raise ParseError.new( "Unexpected token `#{node.message}` before `#{last_node&.string&.strip}`", node_loc(node), ) end else - current_scope << parse_visibility(node.name, node) + current_scope << parse_visibility(node.name.to_s, node) end when "prop", "const" parse_tstruct_field(node) when "requires_ancestor" block = node.block - return unless block.is_a?(YARP::BlockNode) + unless block.is_a?(Prism::BlockNode) + @last_node = nil + return + end + body = block.body - return unless body.is_a?(YARP::StatementsNode) + unless body.is_a?(Prism::StatementsNode) + @last_node = nil + return + end + current_scope << RequiresAncestor.new( node_string!(body), loc: node_loc(node), comments: node_comments(node), ) @@ -409,16 +454,18 @@ parse_send_args(node.arguments), loc: node_loc(node), comments: node_comments(node), ) end + + @last_node = nil end private # Collect all the remaining comments within a node - sig { params(node: YARP::Node).void } + sig { params(node: Prism::Node).void } def collect_dangling_comments(node) first_line = node.location.start_line last_line = node.location.end_line last_node_last_line = node.child_nodes.last&.location&.end_line @@ -447,11 +494,11 @@ @tree.comments end # Preserve blank lines in comments if last_line && line > last_line + 1 - recv << BlankLine.new(loc: Loc.from_yarp(@file, comment.location)) + recv << BlankLine.new(loc: Loc.from_prism(@file, comment.location)) end recv << parse_comment(comment) last_line = line end @@ -474,11 +521,11 @@ comments = @last_sigs_comments.dup @last_sigs_comments.clear comments end - sig { params(node: YARP::Node).returns(T::Array[Comment]) } + sig { params(node: Prism::Node).returns(T::Array[Comment]) } def node_comments(node) comments = [] start_line = node.location.start_line start_line -= 1 unless @comments_by_line.key?(start_line) @@ -492,25 +539,25 @@ end comments end - sig { params(node: YARP::Comment).returns(Comment) } + sig { params(node: Prism::Comment).returns(Comment) } def parse_comment(node) - Comment.new(node.location.slice.gsub(/^# ?/, "").rstrip, loc: Loc.from_yarp(@file, node.location)) + Comment.new(node.location.slice.gsub(/^# ?/, "").rstrip, loc: Loc.from_prism(@file, node.location)) end - sig { params(node: T.nilable(YARP::Node)).returns(T::Array[Arg]) } + sig { params(node: T.nilable(Prism::Node)).returns(T::Array[Arg]) } def parse_send_args(node) args = T.let([], T::Array[Arg]) - return args unless node.is_a?(YARP::ArgumentsNode) + return args unless node.is_a?(Prism::ArgumentsNode) node.arguments.each do |arg| case arg - when YARP::KeywordHashNode + when Prism::KeywordHashNode arg.elements.each do |assoc| - next unless assoc.is_a?(YARP::AssocNode) + next unless assoc.is_a?(Prism::AssocNode) args << KwArg.new( node_string!(assoc.key).delete_suffix(":"), T.must(node_string(assoc.value)), ) @@ -521,47 +568,47 @@ end args end - sig { params(node: T.nilable(YARP::Node)).returns(T::Array[Param]) } + sig { params(node: T.nilable(Prism::Node)).returns(T::Array[Param]) } def parse_params(node) params = [] - return params unless node.is_a?(YARP::ParametersNode) + return params unless node.is_a?(Prism::ParametersNode) node.requireds.each do |param| - next unless param.is_a?(YARP::RequiredParameterNode) + next unless param.is_a?(Prism::RequiredParameterNode) params << ReqParam.new( param.name.to_s, loc: node_loc(param), comments: node_comments(param), ) end node.optionals.each do |param| - next unless param.is_a?(YARP::OptionalParameterNode) + next unless param.is_a?(Prism::OptionalParameterNode) params << OptParam.new( param.name.to_s, node_string!(param.value), loc: node_loc(param), comments: node_comments(param), ) end rest = node.rest - if rest.is_a?(YARP::RestParameterNode) + if rest.is_a?(Prism::RestParameterNode) params << RestParam.new( rest.name&.to_s || "*args", loc: node_loc(rest), comments: node_comments(rest), ) end node.keywords.each do |param| - next unless param.is_a?(YARP::KeywordParameterNode) + next unless param.is_a?(Prism::KeywordParameterNode) value = param.value params << if value KwOptParam.new( param.name.to_s.delete_suffix(":"), @@ -577,62 +624,62 @@ ) end end rest_kw = node.keyword_rest - if rest_kw.is_a?(YARP::KeywordRestParameterNode) + if rest_kw.is_a?(Prism::KeywordRestParameterNode) params << KwRestParam.new( rest_kw.name&.to_s || "**kwargs", loc: node_loc(rest_kw), comments: node_comments(rest_kw), ) end block = node.block - if block.is_a?(YARP::BlockParameterNode) + if block.is_a?(Prism::BlockParameterNode) params << BlockParam.new( block.name&.to_s || "&block", loc: node_loc(block), comments: node_comments(block), ) end params end - sig { params(node: YARP::CallNode).returns(Sig) } + sig { params(node: Prism::CallNode).returns(Sig) } def parse_sig(node) @last_sigs_comments = node_comments(node) builder = SigBuilder.new(@source, file: @file) builder.current.loc = node_loc(node) builder.visit_call_node(node) builder.current end - sig { params(node: T.any(YARP::ConstantWriteNode, YARP::ConstantPathWriteNode)).returns(T.nilable(Struct)) } + sig { params(node: T.any(Prism::ConstantWriteNode, Prism::ConstantPathWriteNode)).returns(T.nilable(Struct)) } def parse_struct(node) send = node.value - return unless send.is_a?(YARP::CallNode) + return unless send.is_a?(Prism::CallNode) return unless send.message == "new" recv = send.receiver return unless recv return unless node_string(recv) =~ /(::)?Struct/ members = [] keyword_init = T.let(false, T::Boolean) args = send.arguments - if args.is_a?(YARP::ArgumentsNode) + if args.is_a?(Prism::ArgumentsNode) args.arguments.each do |arg| case arg - when YARP::SymbolNode + when Prism::SymbolNode members << arg.value - when YARP::KeywordHashNode + when Prism::KeywordHashNode arg.elements.each do |assoc| - next unless assoc.is_a?(YARP::AssocNode) + next unless assoc.is_a?(Prism::AssocNode) key = node_string!(assoc.key) val = node_string(assoc.value) keyword_init = val == "true" if key == "keyword_init:" @@ -642,13 +689,13 @@ end end end name = case node - when YARP::ConstantWriteNode + when Prism::ConstantWriteNode node.name.to_s - when YARP::ConstantPathWriteNode + when Prism::ConstantPathWriteNode node_string!(node.target) end loc = node_loc(node) comments = node_comments(node) @@ -657,14 +704,14 @@ visit(send.block) @scopes_stack.pop struct end - sig { params(send: YARP::CallNode).void } + sig { params(send: Prism::CallNode).void } def parse_tstruct_field(send) args = send.arguments - return unless args.is_a?(YARP::ArgumentsNode) + return unless args.is_a?(Prism::ArgumentsNode) name_arg, type_arg, *rest = args.arguments return unless name_arg return unless type_arg @@ -673,14 +720,14 @@ loc = node_loc(send) comments = node_comments(send) default_value = T.let(nil, T.nilable(String)) rest&.each do |arg| - next unless arg.is_a?(YARP::KeywordHashNode) + next unless arg.is_a?(Prism::KeywordHashNode) arg.elements.each do |assoc| - next unless assoc.is_a?(YARP::AssocNode) + next unless assoc.is_a?(Prism::AssocNode) if node_string(assoc.key) == "default:" default_value = node_string(assoc.value) end end @@ -694,11 +741,11 @@ else raise ParseError.new("Unexpected message `#{send.message}`", loc) end end - sig { params(name: String, node: YARP::Node).returns(Visibility) } + sig { params(name: String, node: Prism::Node).returns(Visibility) } def parse_visibility(name, node) case name when "public" Public.new(loc: node_loc(node)) when "protected" @@ -732,13 +779,13 @@ end_line: last_loc&.end_line || 0, end_column: last_loc&.end_column || 0, ) end - sig { params(node: T.nilable(YARP::Node)).returns(T::Boolean) } + sig { params(node: T.nilable(Prism::Node)).returns(T::Boolean) } def type_variable_definition?(node) - return false unless node.is_a?(YARP::CallNode) + return false unless node.is_a?(Prism::CallNode) return false unless node.block node.message == "type_member" || node.message == "type_template" end end @@ -754,25 +801,25 @@ super @current = T.let(Sig.new, Sig) end - sig { override.params(node: YARP::CallNode).void } + sig { override.params(node: Prism::CallNode).void } def visit_call_node(node) case node.message when "sig" args = node.arguments - if args.is_a?(YARP::ArgumentsNode) + if args.is_a?(Prism::ArgumentsNode) args.arguments.each do |arg| @current.is_final = node_string(arg) == ":final" end end when "abstract" @current.is_abstract = true when "checked" args = node.arguments - if args.is_a?(YARP::ArgumentsNode) + if args.is_a?(Prism::ArgumentsNode) arg = node_string(args.arguments.first) @current.checked = arg&.delete_prefix(":")&.to_sym end when "override" @current.is_override = true @@ -780,17 +827,17 @@ @current.is_overridable = true when "params" visit(node.arguments) when "returns" args = node.arguments - if args.is_a?(YARP::ArgumentsNode) + if args.is_a?(Prism::ArgumentsNode) first = args.arguments.first @current.return_type = node_string!(first) if first end when "type_parameters" args = node.arguments - if args.is_a?(YARP::ArgumentsNode) + if args.is_a?(Prism::ArgumentsNode) args.arguments.each do |arg| @current.type_params << node_string!(arg).delete_prefix(":") end end when "void" @@ -799,10 +846,10 @@ visit(node.receiver) visit(node.block) end - sig { override.params(node: YARP::AssocNode).void } + sig { override.params(node: Prism::AssocNode).void } def visit_assoc_node(node) @current.params << SigParam.new( node_string!(node.key).delete_suffix(":"), node_string!(T.must(node.value)), )