Commit 8b52608c authored by Rene Saarsoo's avatar Rene Saarsoo
Browse files

Expand @fires with events fired by called methods.

A new processor to recursively populate the @fires list with events
fired by methods called from a particular method,
and by methods called from there,
etc.
parent b3740a5b
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -12,6 +12,7 @@ require 'jsduck/process/overrides'
require 'jsduck/process/inherit_doc'
require 'jsduck/process/versions'
require 'jsduck/process/return_values'
require 'jsduck/process/fires'
require 'jsduck/process/lint'
require 'jsduck/process/circular_deps'

@@ -68,6 +69,7 @@ module JsDuck
      Process::InheritDoc.new(relations).process_all!
      Process::Versions.new(relations, opts).process_all!
      Process::ReturnValues.new(relations).process_all!
      Process::Fires.new(relations).process_all!
      Process::Lint.new(relations).process_all!
      relations
    end
+1 −1
Original line number Diff line number Diff line
@@ -345,7 +345,7 @@ module JsDuck

      def detect_fires(ast)
        if ast.function? && !ast.ext_empty_fn?
          Js::Fires.detect(ast).map {|e| {:name => e} }
          Js::Fires.detect(ast)
        else
          []
        end
+71 −0
Original line number Diff line number Diff line
require 'jsduck/logger'

module JsDuck
  module Process

    # Expands lists of fired events to take into account events fired
    # by all the methods that get called by a method.
    class Fires
      def initialize(relations)
        @relations = relations
        # Map of methods for which all @fires tags have been detected.
        # So we don't repeat the resolving.
        @detected = {}
        # Map of methods that we're currently resolving.
        # So we don't recurse into infinity.
        @call_chain = {}
      end

      # Populates @fires tags with additional events.
      def process_all!
        @relations.each do |cls|
          cls.find_members(:tagname => :method, :static => false).each do |m|
            @call_chain = {m[:name] => true}
            detect_fires(cls, m)
          end
        end
      end

      private

      def detect_fires(cls, m)
        if m[:autodetected][:fires] && m[:method_calls] && !detected?(m)
          m[:fires] = events_from_methods(cls, m[:method_calls]).concat(m[:fires] || []).sort.uniq
          mark_detected(m)
        end

        m[:fires]
      end

      def mark_detected(m)
        @detected[m[:owner]] = {} unless @detected[m[:owner]]
        @detected[m[:owner]][m[:name]] = true
      end

      def detected?(m)
        cls = @detected[m[:owner]]
        cls && cls[m[:name]]
      end

      def events_from_methods(cls, methods)
        events = []

        methods.each do |name|
          if !@call_chain[name]
            @call_chain[name] = true
            m = cls.find_members(:tagname => :method, :name => name, :static => false)[0]
            if m
              fires = detect_fires(cls, m)
              events.concat(fires) if fires
            end
            @call_chain[name] = false
          end
        end

        events.sort.uniq
      end

    end

  end
end
+7 −7
Original line number Diff line number Diff line
@@ -25,18 +25,18 @@ module JsDuck::Tag
    end

    def process_doc(h, tags, pos)
      h[:fires] = tags.map {|t| t[:events] }.flatten.map {|name| {:name => name} }
      h[:fires] = tags.map {|t| t[:events] }.flatten
    end

    def format(m, formatter)
      cls = formatter.relations[m[:owner]]

      m[:fires].each do |e|
        if cls.find_members({:tagname => :event, :name => e[:name]}).length > 0
          e[:link] = formatter.link(m[:owner], e[:name], e[:name], :event)
      m[:fires] = m[:fires].map do |name|
        if cls.find_members({:tagname => :event, :name => name}).length > 0
          formatter.link(m[:owner], name, name, :event)
        else
          JsDuck::Logger.warn(:fires, "@fires references unknown event: #{e[:name]}", m[:files][0])
          e[:link] = e[:name]
          JsDuck::Logger.warn(:fires, "@fires references unknown event: #{name}", m[:files][0])
          name
        end
      end
    end
@@ -45,7 +45,7 @@ module JsDuck::Tag
      return [
        "<h3 class='pa'>Fires</h3>",
        "<ul>",
          m[:fires].map {|e| "<li>#{e[:link]}</li>" },
          m[:fires].map {|e| "<li>#{e}</li>" },
        "</ul>",
      ]
    end
+96 −3
Original line number Diff line number Diff line
@@ -2,11 +2,11 @@ require "mini_parser"

describe JsDuck::Aggregator do
  def parse(string)
    Helper::MiniParser.parse(string)
    Helper::MiniParser.parse(string, :fires => true)
  end

  def parse_fires(string)
    parse(string)["global"][:members][0][:fires].map {|e| e[:name] }
  def parse_fires(string, cls_name = "global")
    parse(string)[cls_name][:members][0][:fires]
  end

  describe "@fires with single event" do
@@ -85,4 +85,97 @@ describe JsDuck::Aggregator do
    end
  end

  describe "method calling another method which fires an event" do
    let(:fires) do
      parse_fires(<<-EOS, "Foo")
        /** @class Foo */

        /** */
        function foo() {
            this.bar();
            this.fireEvent("click")
        }

        /** */
        function bar() {
            this.fireEvent("dblclick");
        }
      EOS
    end

    it "lists events fired by both methods" do
      fires.should == ["click", "dblclick"]
    end
  end

  describe "method calling itself" do
    let(:fires) do
      parse_fires(<<-EOS, "Foo")
        /** @class Foo */

        /** */
        function foo() {
            this.foo();
            this.fireEvent("click")
        }
      EOS
    end

    it "lists just the event fired by himself" do
      fires.should == ["click"]
    end
  end

  describe "method calling another method that calls yet another method" do
    let(:fires) do
      parse_fires(<<-EOS, "Foo")
        /** @class Foo */

        /** */
        function foo() {
            this.bar();
            this.fireEvent("click")
        }

        /** */
        function bar() {
            this.baz();
            this.fireEvent("dblclick");
        }

        /** */
        function baz() {
            this.fireEvent("exit");
        }
      EOS
    end

    it "lists events fired by all the methods" do
      fires.should == ["click", "dblclick", "exit"]
    end
  end

  describe "method with explicit @fires" do
    let(:fires) do
      parse_fires(<<-EOS, "Foo")
        /** @class Foo */

        /** @fires huh */
        function foo() {
            this.bar();
            this.fireEvent("click")
        }

        /** */
        function bar() {
            this.fireEvent("dblclick");
        }
      EOS
    end

    it "blocks lookup of events fired by called methods" do
      fires.should == ["huh"]
    end
  end

end
Loading