Commit c6a285d2 authored by Rene Saarsoo's avatar Rene Saarsoo
Browse files

Extract ScopedTraverser class.

Move common functionality from Fires and MethodCalls to
ScopedTraverser, which provides the ability to loop recursively
through AST tree while keeping track of the variables bound
to `this`.
parent 85cc9795
Loading
Loading
Loading
Loading
+8 −24
Original line number Diff line number Diff line
require "jsduck/util/singleton"
require "jsduck/js/scoped_traverser"

module JsDuck
  module Js
@@ -10,49 +11,32 @@ module JsDuck

      # Returns array of event names fired by the given function.
      # When no events fired, empty array is returned.
      def detect(node)
        @this_map = {
          "this" => true
        }
      def detect(function_node)
        @traverser = Js::ScopedTraverser.new

        detect_body(node["body"]["body"]).sort.uniq
      end

      private

      def detect_body(body_nodes)
        events = []

        body_nodes.each do |node|
        @traverser.traverse(function_node["body"]) do |node|
          if fire_event?(node)
            events << node["arguments"][0].to_value
          elsif this_var?(node)
            var_name = node["id"].to_s
            @this_map[var_name] = true
          else
            events.concat(detect_body(node.body))
          end
        end

        events
        events.sort.uniq
      end

      private

      # True when node is this.fireEvent("name") call.
      # Also true for me.fireEvent() when me == this.
      def fire_event?(node)
        node.call_expression? &&
          node["callee"].member_expression? &&
          @this_map[node["callee"]["object"].to_s] &&
          @traverser.this?(node["callee"]["object"].to_s) &&
          node["callee"]["property"].to_s == "fireEvent" &&
          node["arguments"].length > 0 &&
          node["arguments"][0].value_type == "String"
      end

      # True when initialization of variable with `this`
      def this_var?(node)
        node.type == "VariableDeclarator" && node["init"].type == "ThisExpression"
      end

    end
  end
end
+8 −26
Original line number Diff line number Diff line
require "jsduck/util/singleton"
require "jsduck/js/scoped_traverser"

module JsDuck
  module Js
@@ -10,34 +11,20 @@ module JsDuck

      # Returns array of method names called by the given function.
      # When no methods called, empty array is returned.
      def detect(node)
        @this_map = {
          "this" => true
        }
      def detect(function_node)
        @traverser = Js::ScopedTraverser.new

        detect_body(node["body"]["body"]).sort.uniq
      end

      private

      def detect_body(body_nodes)
        methods = []

        body_nodes.each do |node|
        @traverser.traverse(function_node["body"]) do |node|
          if method_call?(node)
            methods << node["callee"]["property"].to_s
          end

          if this_var?(node)
            var_name = node["id"].to_s
            @this_map[var_name] = true
        end

          methods.concat(detect_body(node.body))
        methods.sort.uniq
      end

        methods
      end
      private

      # True when node is this.someMethod() call.
      # Also true for me.someMethod() when me == this.
@@ -45,12 +32,7 @@ module JsDuck
        node.call_expression? &&
          node["callee"].member_expression? &&
          node["callee"].raw["computed"] == false &&
          @this_map[node["callee"]["object"].to_s]
      end

      # True when initialization of variable with `this`
      def this_var?(node)
        node.type == "VariableDeclarator" && node["init"].type == "ThisExpression"
          @traverser.this?(node["callee"]["object"].to_s)
      end

    end
+42 −0
Original line number Diff line number Diff line
module JsDuck
  module Js

    # Traverses syntax tree while keeping track of variables that are
    # bound to `this`.
    class ScopedTraverser
      def initialize
        @this_map = {
          "this" => true
        }
      end

      # Loops recursively over all the sub-nodes of the given node,
      # calling the provided block for each sub-node.
      def traverse(node, &block)
        node.body.each do |child|
          yield child

          if this_var?(child)
            var_name = child["id"].to_s
            @this_map[var_name] = true
          end

          traverse(child, &block)
        end
      end

      # True when variable with given name is bound to `this`.
      def this?(var_name)
        @this_map[var_name]
      end

      private

      # True when initialization of variable with `this`
      def this_var?(node)
        node.type == "VariableDeclarator" && node["init"].type == "ThisExpression"
      end

    end
  end
end