Commit 81ebeea8 authored by Rene Saarsoo's avatar Rene Saarsoo
Browse files

Take the new JsDuck::Class#find_members into use.

Discard JsDuck::Class methods:

- members
- constructor_first
- members_hash
- local_members_hash
- get_members
- all_members
- reset_members_lookup!

The new #find_members replaces most of them.

Moved sorting and ordering of constructor into FullExporter,
so the #constructor_first method still exists, but is elsewhere.
Also added tests for exporter, but the export format still
remains the same.

The code for warning about statics in singleton moved to Lint.

Related to this there's one change in behavior - static members
in singleton are no more cursed into instance members.  That's
because a singleton actually can contain static members, though
it's silly and JSDuck warns about that, but you can even access
those static members in a clumsy way:

    MySingleton.statics().staticMethod();

This is where statics: {} end up when you define them in Ext 4 way
inside a singleton class.  And I've seen some classes in Ext/Touch
codebase even doing so... it's nevertheless silly.

As for the bogus @static tag use in singleton classes for members
that aren't really static - these just need to be fixed.
parent f737e618
Loading
Loading
Loading
Loading
+4 −4
Original line number Diff line number Diff line
@@ -28,17 +28,17 @@ module JsDuck
    def export(cls)
      {
        :name => cls[:name],
        :members => export_members(cls, :members),
        :statics => export_members(cls, :statics),
        :members => export_members(cls, false),
        :statics => export_members(cls, true),
      }
    end

    private

    def export_members(cls, context)
    def export_members(cls, static)
      h = {}
      Class.default_members_hash.each_key do |type|
        h[type] = cls.members(type, context).map {|m| m[:name] }
        h[type] = cls.find_members(:tagname => type, :static => static).map {|m| m[:name] }
      end
      h
    end
+19 −127
Original line number Diff line number Diff line
@@ -114,73 +114,6 @@ module JsDuck
      return full_name == class_name || (parent ? parent.inherits_from?(class_name) : false)
    end

    # Returns array of all public members of particular type in a class,
    # sorted by name.
    #
    # For methods the the constructor is listed first.
    #
    # See members_hash for details.
    def members(type, context=:members)
      ms = members_hash(type, context).values
      ms.sort! {|a,b| a[:name] <=> b[:name] }
      type == :method ? constructor_first(ms) : ms
    end

    # If methods list contains constructor, rename it with class name
    # and move into beginning of methods list.
    def constructor_first(ms)
      constr = ms.find {|m| m[:name] == "constructor" }
      if constr
        ms.delete(constr)
        ms.unshift(constr)
      end
      ms
    end

    # Returns hash of all members in 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.
    def members_hash(type, context=:members)
      # Singletons have no static members
      if @doc[:singleton] && context == :statics
        # Warn if singleton has static members
        @doc[:members].each do |m|
          if m[:meta] && m[:meta][:static]
            ctx = m[:files][0]
            msg = "Singleton class #{@doc[:name]} can't have static members, remove the @static tag."
            Logger.instance.warn(:sing_static, msg, ctx[:filename], ctx[:linenr])
          end
        end
        return {}
      end

      ms = parent ? parent.members_hash(type, context) : {}

      mixins.each do |mix|
        merge!(ms, mix.members_hash(type, context))
      end

      # For static members, exclude everything not explicitly marked as inheritable
      if context == :statics
        ms.delete_if {|key, member| !member[:inheritable] }
      end

      merge!(ms, local_members_hash(type, context))

      # If singleton has static members, include them as if they were
      # instance members.  Otherwise they will be completely excluded
      # from the docs, as the static members block is not created for
      # singletons.
      if @doc[:singleton]
        merge!(ms, local_members_hash(type, :statics))
      end

      ms
    end

    # merges second members hash into first one
    def merge!(hash1, hash2, skip_overrides=false)
      hash2.each_pair do |name, m|
@@ -230,53 +163,6 @@ module JsDuck
      end
    end

    # Helper method to get the direct members of this class
    def local_members_hash(type, context)
      is_static = (context == :statics)
      local_members = {}

      @doc[:members].find_all do |m|
        static = (m[:meta] || {})[:static] || false
        m[:tagname] == type && static == is_static
      end.each do |m|
        local_members[m[:name]] = m
      end

      local_members
    end

    # Returns members by name. An array of one or more members, or
    # empty array when nothing matches.
    #
    # Optionally one can also specify type name to differenciate
    # between different types of members.
    #
    # Finally static flag can be specified. True to look only at
    # static members, false to look at instance members, and nil to
    # look at both.
    def get_members(name, type_name=nil, static=nil)
      # build hash of all members
      unless @members_map
        @members_map = {}
        all_members.each do |m|
          @members_map[m[:name]] = (@members_map[m[:name]] || []) + [m]
        end
      end

      ms = @members_map[name] || []
      ms = ms.find_all {|m| m[:tagname] == type_name } if type_name
      # static = true | false | nil
      ms = ms.find_all {|m| m[:meta][:static] } if static == true
      ms = ms.find_all {|m| !m[:meta][:static] } if static == false
      return ms
    end

    # Call this when renaming or moving members inside class.
    def reset_members_lookup!
      @members_map = nil
    end


    # Generates local members hash by ID
    def new_local_members_hash
      unless @map_by_id
@@ -293,7 +179,9 @@ module JsDuck
    # Generates global members hash by ID
    def new_global_members_hash
      unless @global_map_by_id
        @global_map_by_id = parent ? parent.new_global_members_hash : {}
        # Make copy of parent class members.
        # Otherwise we'll be merging directly into parent class.
        @global_map_by_id = parent ? parent.new_global_members_hash.clone : {}

        mixins.each do |mix|
          merge!(@global_map_by_id, mix.new_global_members_hash)
@@ -346,8 +234,16 @@ module JsDuck
      ms
    end

    # This must be called whenever member hashes are changed.
    # It updates the :id fields of members and clears the caches.
    def update_members!(members)
      members.each do |m|
        m[:id] = Class.member_id(m)
      end
      invalidate_search_cache!
    end

    # Clears the search cache.
    # This is used by InheritDoc after transforming configs into properties.
    # This is also REALLY BAD - try to get rid of it.
    def invalidate_search_cache!
      @map_by_id = nil
@@ -359,17 +255,6 @@ module JsDuck
      mixins.each {|mix| mix.invalidate_search_cache! }
    end


    # Returns all members of class, including the inherited and mixed in ones
    def all_members
      all = []
      [:cfg, :property, :method, :event, :css_mixin, :css_var].each do |type|
        all += members(type, :members)
        all += members(type, :statics)
      end
      all
    end

    # Returns all local members of class
    def all_local_members
      @doc[:members]
@@ -430,6 +315,13 @@ module JsDuck
      short
    end

    # Generates member :id from member hash
    def self.member_id(m)
      # Sanitize $ in member names with something safer
      name = m[:name].gsub(/\$/, 'S-')
      "#{m[:meta][:static] ? 'static-' : ''}#{m[:tagname]}-#{name}"
    end

    # Returns default hash that has empty array for each member type
    def self.default_members_hash
      return {
+10 −10
Original line number Diff line number Diff line
@@ -158,7 +158,7 @@ module JsDuck
          Logger.instance.warn(:link, "#{input} links to non-existing class", file, line)
          return text
        elsif member
          ms = get_members(cls, member, type, static)
          ms = find_members(cls, {:name => member, :tagname => type, :static => static})
          if ms.length == 0
            Logger.instance.warn(:link, "#{input} links to non-existing member", file, line)
            return text
@@ -171,7 +171,7 @@ module JsDuck
            # report ambiguity.
            instance_ms = ms.find_all {|m| !m[:meta][:static] }
            if instance_ms.length > 1
              alternatives = instance_ms.map {|m| m[:tagname].to_s }.join(", ")
              alternatives = instance_ms.map {|m| "#{m[:tagname]} in #{m[:owner]}" }.join(", ")
              Logger.instance.warn(:link_ambiguous, "#{input} is ambiguous: "+alternatives, file, line)
            elsif instance_ms.length == 0
              static_ms = ms.find_all {|m| m[:meta][:static] }
@@ -215,7 +215,7 @@ module JsDuck

    def replace_magic_link(cls, member)
      if cls && member
        if @relations[cls] && get_matching_member(cls, member)
        if @relations[cls] && get_matching_member(cls, {:name => member})
          return link(cls, member, cls+"."+member)
        else
          warn_magic_link("#{cls}##{member} links to non-existing " + (@relations[cls] ? "member" : "class"))
@@ -225,7 +225,7 @@ module JsDuck
          return link(cls, nil, cls)
        else
          cls2, member2 = split_to_cls_and_member(cls)
          if @relations[cls2] && get_matching_member(cls2, member2)
          if @relations[cls2] && get_matching_member(cls2, {:name => member2})
            return link(cls2, member2, cls2+"."+member2)
          elsif cls =~ /\.(js|css|html|php)\Z/
            # Ignore common filenames
@@ -234,7 +234,7 @@ module JsDuck
          end
        end
      elsif !cls && member
        if get_matching_member(@class_context, member)
        if get_matching_member(@class_context, {:name => member})
          return link(@class_context, member, member)
        elsif member =~ /\A([A-F0-9]{3}|[A-F0-9]{6})\Z/i || member =~ /\A[0-9]/
          # Ignore HEX color codes and
@@ -261,7 +261,7 @@ module JsDuck
      # Use the canonical class name for link (not some alternateClassName)
      cls = @relations[cls].full_name
      # prepend type name to member name
      member = member && get_matching_member(cls, member, type, static)
      member = member && get_matching_member(cls, {:name => member, :tagname => type, :static => static})

      @link_tpl.gsub(/(%[\w#-])/) do
        case $1
@@ -281,8 +281,8 @@ module JsDuck
      end
    end

    def get_matching_member(cls, member, type=nil, static=nil)
      ms = get_members(cls, member, type, static).find_all {|m| !m[:private] }
    def get_matching_member(cls, query)
      ms = find_members(cls, query).find_all {|m| !m[:private] }
      if ms.length > 1
        instance_ms = ms.find_all {|m| !m[:meta][:static] }
        instance_ms.length > 0 ? instance_ms[0] : ms.find_all {|m| m[:meta][:static] }[0]
@@ -291,8 +291,8 @@ module JsDuck
      end
    end

    def get_members(cls, member, type=nil, static=nil)
      @relations[cls] ? @relations[cls].get_members(member, type, static) : []
    def find_members(cls, query)
      @relations[cls] ? @relations[cls].find_members(query) : []
    end

    # Formats doc-comment for placement into HTML.
+23 −4
Original line number Diff line number Diff line
@@ -4,7 +4,7 @@ module JsDuck

  # Exporter for all the class docs.
  class FullExporter
    def initialize(relations, opts)
    def initialize(relations, opts={})
      @relations = relations
      # opts parameter is here just for compatibility with other exporters
    end
@@ -14,9 +14,9 @@ module JsDuck
      h = cls.to_hash
      h[:members] = {}
      h[:statics] = {}
      Class.default_members_hash.each_key do |key|
        h[:members][key] = cls.members(key)
        h[:statics][key] = cls.members(key, :statics)
      Class.default_members_hash.each_key do |tagname|
        h[:members][tagname] = export_members(cls, {:tagname => tagname, :static => false})
        h[:statics][tagname] = export_members(cls, {:tagname => tagname, :static => true})
      end
      h[:component] = cls.inherits_from?("Ext.Component")
      h[:superclasses] = cls.superclasses.collect {|c| c.full_name }
@@ -31,6 +31,25 @@ module JsDuck
      h
    end

    private

    # Looks up members, and sorts them so that constructor method is first
    def export_members(cls, cfg)
      ms = cls.find_members(cfg)
      ms.sort! {|a,b| a[:name] <=> b[:name] }
      cfg[:tagname] == :method ? constructor_first(ms) : ms
    end

    # If methods list contains constructor, move it into the beginning.
    def constructor_first(ms)
      constr = ms.find {|m| m[:name] == "constructor" }
      if constr
        ms.delete(constr)
        ms.unshift(constr)
      end
      ms
    end

  end

end
+7 −8
Original line number Diff line number Diff line
@@ -53,9 +53,8 @@ module JsDuck
      members.each do |m|
        m[:tagname] = :cfg
      end
      # The members lookup table inside class is no more valid, so
      # reset it.
      cls.reset_members_lookup!
      # Ask class to update its internal caches for these members
      cls.update_members!(members)
    end

    # For auto-detected members/classes (which have @private == :inherit)
@@ -73,6 +72,7 @@ module JsDuck
    #
    # If the parent also has @inheritdoc, continues recursively.
    def find_parent(m)

      inherit = m[:inheritdoc] || {}
      if inherit[:cls]
        # @inheritdoc MyClass#member
@@ -118,7 +118,6 @@ module JsDuck
        end
      end

      #pp parent[:doc]
      return parent[:inheritdoc] ? find_parent(parent) : parent
    end

@@ -132,8 +131,8 @@ module JsDuck
        # Auto-detected properties can override either a property or a
        # config. So look for both types.
        if tagname == :property
          cfg = cls.get_members(name, :cfg, static || false)[0]
          prop = cls.get_members(name, :property, static || false)[0]
          cfg = cls.find_members(:name => name, :tagname => :cfg, :static => static || false)[0]
          prop = cls.find_members(:name => name, :tagname => :property, :static => static || false)[0]

          if cfg && prop
            prop
@@ -148,10 +147,10 @@ module JsDuck
        else
          # Unless the auto-detected member is detected as static,
          # look only at instance members.
          cls.get_members(name, tagname, static || false)[0]
          cls.find_members(:name => name, :tagname => tagname, :static => static || false)[0]
        end
      else
        cls.get_members(name, tagname, static)[0]
        cls.find_members(:name => name, :tagname => tagname, :static => static)[0]
      end
    end

Loading