diff --git a/lib/spoom/model/builder.rb b/lib/spoom/model/builder.rb index e3d8a22d..489558b5 100644 --- a/lib/spoom/model/builder.rb +++ b/lib/spoom/model/builder.rb @@ -45,10 +45,28 @@ def visit_module(node) @namespace.pop end + sig { override.params(node: SyntaxTree::Assign).void } + def visit_assign(node) + parent = @scopes.last + return unless parent&.descendant_of?("T::Enum") + return if node.child_nodes.empty? + + assign_name = node_string(node.child_nodes.first) + return unless assign_name + + # Connect the enum value to the parent in the model scope + scope_key = "#{parent.full_name}::#{assign_name}" + @model.scopes[scope_key] ||= [] + @model.scopes[scope_key] << parent + + parent.enum_values << assign_name + end + sig { override.params(node: SyntaxTree::DefNode).void } def visit_def(node) loc = node_loc(node) current_scope.defs << Method.new(loc, node_string(node.name)) + node.child_nodes.each { |child| visit(child) } end sig { override.params(node: SyntaxTree::CallNode).void } @@ -93,6 +111,40 @@ def visit_vcall(node) visit_send(Send.new(node: node, name: node_string(node.value))) end + sig { override.params(node: SyntaxTree::Case).void } + def visit_case(node) + kase = Case.new(node_loc(node), current_scope) + current_scope.cases << kase + + curr_node = node.consequent + + while curr_node.respond_to?(:consequent) + unless curr_node.is_a?(SyntaxTree::When) + curr_node = curr_node.consequent + next + end + + kase.conditions << node_string(curr_node.arguments) + curr_node = curr_node.consequent + end + + return unless curr_node.is_a?(SyntaxTree::Else) + + statement = curr_node.statements.body.first + return if statement.nil? || !statement.respond_to?(:receiver) || !statement.respond_to?(:message) + + receiver = statement.receiver + return if !receiver.respond_to?(:value) || !receiver.value.respond_to?(:value) + + message = statement.message + return unless message.respond_to?(:value) + + receiver_val = statement.receiver.value.value + method_val = statement.message.value + + kase.absurd = true if receiver_val == "T" && method_val == "absurd" + end + private sig { params(send: Send).void } @@ -105,6 +157,8 @@ def visit_send(send) current_scope.attrs << Attr.new(loc, send.name, name) end when "const", "prop" + return unless send.args.size >= 1 + loc = node_loc(send.node) name = node_string(T.must(send.args[0])) type = node_string(T.must(send.args[1])) @@ -117,6 +171,24 @@ def visit_send(send) send.args.each do |arg| current_scope.includes << Ref.new(node_string(arg)) end + when "serialize" + recv = send.recv + return unless recv + + recv_name = node_string(recv) + send.recv_name = recv_name + send.location = node_loc(send.node) + + current_scope.calls_to_serialize << send + when "deserialize" + recv = send.recv + return unless recv + + recv_name = node_string(recv) + send.recv_name = recv_name + send.location = node_loc(send.node) + + current_scope.calls_to_deserialize << send end end @@ -170,6 +242,8 @@ class Send < T::Struct const :recv, T.nilable(SyntaxTree::Node), default: nil const :args, T::Array[SyntaxTree::Node], default: [] const :block, T.nilable(SyntaxTree::Node), default: nil + prop :location, Location, default: Location.none + prop :recv_name, T.nilable(String), default: nil end class << self diff --git a/lib/spoom/model/model.rb b/lib/spoom/model/model.rb index bd11a8c6..5b274e53 100644 --- a/lib/spoom/model/model.rb +++ b/lib/spoom/model/model.rb @@ -58,6 +58,18 @@ class Scope < Symbol sig { returns(T::Array[Prop]) } attr_reader :props + sig { returns T::Array[String] } + attr_reader :enum_values + + sig { returns T::Array[Case] } + attr_reader :cases + + sig { returns T::Array[Send] } + attr_reader :calls_to_serialize + + sig { returns T::Array[Send] } + attr_reader :calls_to_deserialize + sig { params(location: Location, full_name: String).void } def initialize(location, full_name) super(location) @@ -67,6 +79,10 @@ def initialize(location, full_name) @props = T.let([], T::Array[Prop]) @includes = T.let([], T::Array[T.any(Ref, Module)]) @attrs = T.let([], T::Array[Attr]) + @enum_values = T.let([], T::Array[String]) + @cases = T.let([], T::Array[Case]) + @calls_to_serialize = T.let([], T::Array[Send]) + @calls_to_deserialize = T.let([], T::Array[Send]) end sig { params(full_name: String).returns(T::Boolean) } @@ -190,6 +206,33 @@ def to_s end end + class Case < Symbol + extend T::Sig + + sig { returns(T::Array[String]) } + attr_reader :conditions + + sig { returns(T::Boolean) } + attr_accessor :absurd + + sig { returns(Scope) } + attr_accessor :scope + + sig { params(location: Location, scope: Scope).void } + def initialize(location, scope) + super(location) + + @conditions = T.let([], T::Array[String]) + @absurd = T.let(false, T::Boolean) + @scope = scope + end + + sig { returns(T::Boolean) } + def absurd? + absurd + end + end + class Prop < Symbol extend T::Sig @@ -350,6 +393,19 @@ def modules classes end + sig { returns(T::Array[Case]) } + def cases + cases = T.let([], T::Array[Case]) + + @scopes.each do |_full_name, scopes| + scopes.each do |scope| + cases.concat(scope.cases) + end + end + + cases + end + sig { params(class_name: String).returns(T::Array[Class]) } def subclasses_of(class_name) subclasses = T.let([], T::Array[Class]) diff --git a/lib/spoom/model/visitor.rb b/lib/spoom/model/visitor.rb index e706a13e..19c68bdd 100644 --- a/lib/spoom/model/visitor.rb +++ b/lib/spoom/model/visitor.rb @@ -58,6 +58,11 @@ def visit_method(symbol) def visit_prop(symbol) # no-op end + + sig { params(symbol: Case).void } + def visit_case(symbol) + # no-op + end end sig { params(visitor: Visitor).void } @@ -104,5 +109,12 @@ def accept(visitor) visitor.visit_prop(self) end end + + class Case + sig { override.params(visitor: Visitor).void } + def accept(visitor) + visitor.visit_case(self) + end + end end end