Commit 564f7871 authored by Rene Saarsoo's avatar Rene Saarsoo
Browse files

Auto-detection of @chainable tag.

When doc-comment contains @return {OwnerClass} this,
automatically add @chainable tag too.
parent d0366e31
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -12,6 +12,7 @@ require 'jsduck/assets'
require 'jsduck/json_duck'
require 'jsduck/io'
require 'jsduck/importer'
require 'jsduck/chainable'
require 'jsduck/lint'
require 'jsduck/template_dir'
require 'jsduck/class_writer'
@@ -48,6 +49,7 @@ module JsDuck
      @relations = filter_classes(result)
      InheritDoc.new(@relations).resolve_all
      Importer.import(@opts.imports, @relations, @opts.new_since)
      Chainable.auto_detect(@relations)
      Lint.new(@relations).run

      # Initialize guides, videos, examples, ...
+57 −0
Original line number Diff line number Diff line
module JsDuck

  # Auto-detector of @chainable tags.
  #
  # Adds @chainable tag when doc-comment contains @return {OwnerClass}
  # this.  Also the other way around: when @chainable found, adds
  # appropriate @return.
  class Chainable
    # Only this static method of this class should be called.
    def self.auto_detect(relations)
      Chainable.new(relations).process_all!
    end

    def initialize(relations)
      @relations = relations
      @cls = nil
    end

    def process_all!
      @relations.each do |cls|
        @cls = cls
        cls.find_members(:tagname => :method, :local => true, :static => false).each do |m|
          process(m)
        end
      end
    end

    private

    def process(m)
      if chainable?(m)
        add_return_this(m)
      elsif returns_this?(m)
        add_chainable(m)
      end
    end

    def chainable?(m)
      m[:meta][:chainable]
    end

    def returns_this?(m)
      m[:return] && m[:return][:type] == @cls[:name] && m[:return][:doc] =~ /\Athis\b/
    end

    def add_chainable(m)
      m[:meta][:chainable] = true
    end

    def add_return_this(m)
      if m[:return][:type] == "undefined" && m[:return][:doc] == ""
        m[:return] = {:type => @cls[:name], :doc => "this"}
      end
    end
  end

end
+162 −0
Original line number Diff line number Diff line
require "jsduck/aggregator"
require "jsduck/source_file"
require "jsduck/class"
require "jsduck/relations"
require "jsduck/chainable"

describe JsDuck::Aggregator do
  def parse(string)
    agr = JsDuck::Aggregator.new
    agr.aggregate(JsDuck::SourceFile.new(string))
    relations = JsDuck::Relations.new(agr.result.map {|doc| JsDuck::Class.new(doc) })
    JsDuck::Chainable.auto_detect(relations)
    relations
  end

  describe "both @return this and @chainable in method doc" do
    let(:cls) do
      parse(<<-EOS)["MyClass"]
        /** */
        Ext.define("MyClass", {
            /**
             * @return {MyClass} this The instance itself.
             * @chainable
             */
            bar: function() {}
        });
      EOS
    end

    it "detects method as chainable" do
      cls[:members][0][:meta][:chainable].should == true
    end

    it "keeps the original @return docs" do
      cls[:members][0][:return][:doc].should == "this The instance itself."
    end
  end

  describe "simple @chainable in method doc" do
    let(:cls) do
      parse(<<-EOS)["MyClass"]
        /** */
        Ext.define("MyClass", {
            /**
             * @chainable
             */
            bar: function() {}
        });
      EOS
    end

    it "detects method as chainable" do
      cls[:members][0][:meta][:chainable].should == true
    end

    it "adds @return {MyClass} this" do
      cls[:members][0][:return][:type].should == "MyClass"
      cls[:members][0][:return][:doc].should == "this"
    end
  end

  describe "an @return {MyClass} this in method doc" do
    let(:cls) do
      parse(<<-EOS)["MyClass"]
        /** */
        Ext.define("MyClass", {
            /**
             * @return {MyClass} this
             */
            bar: function() {}
        });
      EOS
    end

    it "detects @return {MyClass} this" do
      cls[:members][0][:return][:type].should == "MyClass"
      cls[:members][0][:return][:doc].should == "this"
    end

    it "adds @chainable tag" do
      cls[:members][0][:meta][:chainable].should == true
    end
  end

  describe "an @return {MyClass} this and other docs in method doc" do
    let(:cls) do
      parse(<<-EOS)["MyClass"]
        /** */
        Ext.define("MyClass", {
            /**
             * @return {MyClass} this and some more...
             */
            bar: function() {}
        });
      EOS
    end

    it "detects @return {MyClass} this" do
      cls[:members][0][:return][:type].should == "MyClass"
      cls[:members][0][:return][:doc].should == "this and some more..."
    end

    it "adds @chainable tag" do
      cls[:members][0][:meta][:chainable].should == true
    end
  end

  describe "an @return {MyClass} thisBlah in method doc" do
    let(:cls) do
      parse(<<-EOS)["MyClass"]
        /** */
        Ext.define("MyClass", {
            /**
             * @return {MyClass} thisBlah
             */
            bar: function() {}
        });
      EOS
    end

    it "doesn't add @chainable tag" do
      cls[:members][0][:meta][:chainable].should_not == true
    end
  end

  describe "an @return {OtherClass} this in method doc" do
    let(:cls) do
      parse(<<-EOS)["MyClass"]
        /** */
        Ext.define("MyClass", {
            /**
             * @return {OtherClass} this
             */
            bar: function() {}
        });
      EOS
    end

    it "doesn't add @chainable tag" do
      cls[:members][0][:meta][:chainable].should_not == true
    end
  end

  describe "an @return {MyClass} no-this in method doc" do
    let(:cls) do
      parse(<<-EOS)["MyClass"]
        /** */
        Ext.define("MyClass", {
            /**
             * @return {MyClass}
             */
            bar: function() {}
        });
      EOS
    end

    it "doesn't add @chainable tag" do
      cls[:members][0][:meta][:chainable].should_not == true
    end
  end

end