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

Support for mixins.

The most basic support for mixins.  Detecting them when they are
listed in "mixins:" section at the beginning of Ext.define()
configuration object - some things can be between them, but it
will still break fairly easily.

- Listing mixed in classes in generated docs.
- Listing the mixed in members in methods, events, etc tables
  the same way as inherited members.
parent 59ad951d
Loading
Loading
Loading
Loading
+18 −6
Original line number Diff line number Diff line
@@ -18,6 +18,12 @@ module JsDuck
      @doc[:extends] ? @classes[@doc[:extends]] : nil
    end

    # Returns array of mixin class instances.
    # Returns empty array if no mixins
    def mixins
      @doc[:mixins] ? @doc[:mixins].collect {|classname| @classes[classname] } : []
    end

    # Returns true when this class inherits from the specified class.
    # Also returns true when the class itself is the one we are asking about.
    def inherits_from?(class_name)
@@ -51,9 +57,9 @@ module JsDuck
      ms
    end

    # Returns hash of public members of class (and parent classes).
    # Members are methods, properties, cfgs, events (member type
    # is speified through 'type' parameter).
    # Returns hash of public members of class (and of parent classes
    # and mixin classes).  Members are methods, properties, cfgs,
    # events (member type is specified through 'type' parameter).
    #
    # When parent and child have members with same name,
    # member from child overrides tha parent member.
@@ -61,11 +67,17 @@ module JsDuck
    # We also set :member property to each member to the full class
    # name where it belongs, so one can tell them apart afterwards.
    def members_hash(type)
      parent_members = parent ? parent.members_hash(type) : {}
      all_members = parent ? parent.members_hash(type) : {}

      mixins.each do |mix|
        all_members.merge!(mix.members_hash(type))
      end

      @doc[type].each do |m|
        parent_members[m[:name]] = m if !m[:private]
        all_members[m[:name]] = m if !m[:private]
      end
      parent_members

      all_members
    end

    # A way to access full class name with similar syntax to
+9 −0
Original line number Diff line number Diff line
@@ -109,6 +109,7 @@ module JsDuck
        :name => detect_name(:class, doc_map, code, :full_name),
        :doc => detect_doc(docs),
        :extends => detect_extends(doc_map, code),
        :mixins => detect_mixins(doc_map, code),
        :xtype => detect_xtype(doc_map),
        :singleton => !!doc_map[:singleton],
        :private => !!doc_map[:private],
@@ -222,6 +223,14 @@ module JsDuck
      end
    end

    def detect_mixins(doc_map, code)
      if code[:type] == :ext_define && code[:mixins]
        code[:mixins]
      else
        []
      end
    end

    def detect_xtype(doc_map)
      doc_map[:xtype] ? doc_map[:xtype].first[:name] : nil
    end
+6 −0
Original line number Diff line number Diff line
@@ -51,6 +51,7 @@ module JsDuck
      [
       "<table cellspacing='0'>",
        abstract_row("Extends:", @cls.parent ? class_link(@cls.parent.full_name) : "Object"),
        @cls.mixins.length > 0 ? abstract_row("Mixins:", mixins) : "",
        abstract_row("Defind In:", file_link),
        @subclasses[@cls] ? abstract_row("Subclasses:", subclasses) : "",
        @cls[:xtype] ? abstract_row("xtype:", @cls[:xtype]) : "",
@@ -72,6 +73,11 @@ module JsDuck
      subs.collect {|cls| class_link(cls.full_name, cls.short_name) }.join(", ")
    end

    def mixins
      mixs = @cls.mixins.sort {|a, b| a.full_name <=> b.full_name }
      mixs.collect {|cls| class_link(cls.full_name, cls.short_name) }.join(", ")
    end

    def abstract_row(label, info)
      "<tr><td class='label'>#{label}</td><td class='hd-info'>#{info}</td></tr>"
    end
+65 −10
Original line number Diff line number Diff line
@@ -181,29 +181,84 @@ module JsDuck
    # <ext-define> := "Ext" "." "define" "(" <string> "," <ext-define-cfg>
    def ext_define
      name = match("Ext", ".", "define", "(", :string)
      cfg = {}

      if look(",")
      if look(",", "{")
        match(",")
        cfg = ext_define_cfg
      else
        cfg = {}
      end

      return {
        :type => :ext_define,
        :name => name,
        :extend => cfg[:extend],
      }
      cfg[:type] = :ext_define
      cfg[:name] = name

      cfg
    end

    # <ext-define-cfg> := "{" "extend" ":" <string> "," ...
    # <ext-define-cfg> := "{" ( <extend> | <mixins> | <?> )*
    def ext_define_cfg
      match("{")
      cfg = {}
      if look("{", "extend", ":", :string)
        cfg[:extend] = match("{", "extend", ":", :string)
      found = true
      while found
        found = false
        if look("extend", ":", :string)
          cfg[:extend] = ext_define_extend
          found = true
        elsif look("mixins", ":", "{")
          cfg[:mixins] = ext_define_mixins
          found = true
        elsif look(:ident, ":")
          match(:ident, ":")
          if look(:string) || look(:number) || look(:regex) ||
              look("true") || look("false") ||
              look("null") || look("undefined")
            # Some key with literal value -- ignore
            @lex.next
            found = true
          elsif look("[")
            # Some key with array of strings -- ignore
            found = array_of_strings
          end
        end
        match(",") if look(",")
      end
      cfg
    end

    # <ext-define-extend> := "extend" ":" <string>
    def ext_define_extend
      match("extend", ":", :string)
    end

    # <ext-define-mixins> := "mixins" ":" "{" [ <ident> ":" <string> ","? ]* "}"
    def ext_define_mixins
      match("mixins", ":", "{")
      mixins = []
      while look(:ident, ":", :string)
        mixins << match(:ident, ":", :string)
        match(",") if look(",")
      end
      match("}") if look("}")
      mixins
    end

    # <array-of-strings> := "[" [ <string> ","? ]* "]"
    def array_of_strings
      match("[")
      while look(:string)
        match(:string)
        match(",") if look(",")
      end

      if look("]")
        match("]")
        true
      else
        false
      end
    end

    # <property-literal> := ( <ident> | <string> ) ":" <expression>
    def property_literal
      left = look(:ident) ? match(:ident) : match(:string)
+39 −4
Original line number Diff line number Diff line
@@ -97,14 +97,49 @@ describe JsDuck::Aggregator do
    end
  end

  describe "Ext.define() in code" do
    before do
      @doc = parse("/** */ Ext.define('MyClass', { extend: 'Your.Class' });")[0]
    end
  shared_examples_for "Ext.define" do
    it_should_behave_like "class"
    it "detects implied extends" do
      @doc[:extends].should == "Your.Class"
    end
    it "detects implied mixins" do
      @doc[:mixins].should == ["Ext.util.Observable", "Foo.Bar"]
    end
  end

  describe "basic Ext.define() in code" do
    before do
      @doc = parse(<<-EOS)[0]
        /** */
        Ext.define('MyClass', {
          extend: 'Your.Class',
          mixins: {
            obs: 'Ext.util.Observable',
            bar: 'Foo.Bar'
          }
        });
      EOS
    end
    it_should_behave_like "Ext.define"
  end

  describe "complex Ext.define() in code" do
    before do
      @doc = parse(<<-EOS)[0]
        /** */
        Ext.define('MyClass', {
          singleton: true,
          extend: 'Your.Class',
          alias: 'somealias',
          requires: ['Hohooo', 'hahaa'],
          mixins: {
            obs: 'Ext.util.Observable',
            bar: 'Foo.Bar'
          }
        });
      EOS
    end
    it_should_behave_like "Ext.define"
  end

  describe "class with cfgs" do