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

Better display of mixins, requires, uses info.

Mixins are now separated to two groups: Direct mixins from current
class, Inherited mixins from parent classes.

Uses and Requires now also print warnings when unknown classes
referenced.

When any of these classes is missing, the class name will still be rendered
in overview page, but it won't be a link.

Additionally allow wildcard '*' in @requires/uses - don't throw warning
on it, but simply leave as is (not expanding the wildcard either).
parent 3ed3ed03
Loading
Loading
Loading
Loading
+52 −9
Original line number Diff line number Diff line
@@ -8,8 +8,16 @@ module JsDuck
  class Class
    attr_accessor :relations

    def initialize(doc)
    # Creates JSDuck class.
    #
    # Pass true as second parameter to create a placeholder class.
    def initialize(doc, class_exists=true)
      @doc = doc

      # Wrap classname into custom string class that allows
      # differenciating between existing and missing classes.
      @doc[:name] = ClassNameString.new(@doc[:name], class_exists)

      @doc[:members] = Class.default_members_hash if !@doc[:members]
      @doc[:statics] = Class.default_members_hash if !@doc[:statics]
      @relations = nil
@@ -44,15 +52,25 @@ module JsDuck
      p ? p.superclasses + [p]  : []
    end

    # Returns array of mixin class instances.
    # Returns empty array if no mixins
    # Returns all direct mixins of this class. Same as #deps(:mixins).
    def mixins
      @doc[:mixins] ? @doc[:mixins].collect {|classname| lookup(classname) }.compact : []
      deps(:mixins)
    end

    # Returns an array of class instances this class directly depends on.
    # Possible types are:
    #
    # - :mixins
    # - :requires
    # - :uses
    #
    def deps(type)
      @doc[type] ? @doc[type].collect {|classname| lookup(classname) } : []
    end

    # Returns all mixins this class and its parent classes
    def all_mixins
      mixins + (parent ? parent.all_mixins : [])
    # Same ase #deps, but pulls out the dependencies from all parent classes.
    def parent_deps(type)
      parent ? parent.deps(type) + parent.parent_deps(type) : []
    end

    # Looks up class object by name
@@ -60,10 +78,18 @@ module JsDuck
    def lookup(classname)
      if @relations[classname]
        @relations[classname]
      elsif !@relations.ignore?(classname)
      elsif @relations.ignore?(classname) || classname =~ /\*/
        # Ignore explicitly ignored classes and classnames with
        # wildcards in them.  We could expand the wildcard, but that
        # can result in a very long list of classes, like when
        # somebody requires 'Ext.form.*', so for now we do the
        # simplest thing and ignore it.
        Class.new({:name => classname}, false)
      else
        context = @doc[:files][0]
        Logger.instance.warn(:extend, "Class #{classname} not found", context[:filename], context[:linenr])
        nil
        # Create placeholder class
        Class.new({:name => classname}, false)
      end
    end

@@ -276,4 +302,21 @@ module JsDuck
    end
  end

  # String class for classnames that has extra method #exists? which
  # returns false when class with such name doesn't exist.
  #
  # This ability is used by JsDuck::Renderer, which only receives
  # names of various classes but needs to only render existing classes
  # as links.
  class ClassNameString < String
    def initialize(str, exists=true)
      super(str)
      @exists = exists
    end

    def exists?
      @exists
    end
  end

end
+6 −1
Original line number Diff line number Diff line
@@ -20,7 +20,12 @@ module JsDuck
      h[:superclasses] = cls.superclasses.collect {|c| c.full_name }
      h[:subclasses] = @relations.subclasses(cls).collect {|c| c.full_name }
      h[:mixedInto] = @relations.mixed_into(cls).collect {|c| c.full_name }
      h[:allMixins] = cls.all_mixins.collect {|c| c.full_name }

      h[:mixins] = cls.deps(:mixins).collect {|c| c.full_name }
      h[:parentMixins] = cls.parent_deps(:mixins).collect {|c| c.full_name }
      h[:requires] = cls.deps(:requires).collect {|c| c.full_name }
      h[:uses] = cls.deps(:uses).collect {|c| c.full_name }

      h
    end

+1 −1
Original line number Diff line number Diff line
@@ -15,7 +15,7 @@ module JsDuck
      @warning_docs = [
        [:global, "Member doesn't belong to any class"],
        [:inheritdoc, "@inheritdoc referring to unknown class or member"],
        [:extend, "@extend or @mixin referring to unknown class"],
        [:extend, "@extend/mixin/requires/uses referring to unknown class"],
        [:link, "{@link} to unknown class or member"],
        [:link_private, "{@link} to private member"],
        [:link_ambiguous, "{@link} is ambiguous"],
+13 −14
Original line number Diff line number Diff line
@@ -43,7 +43,8 @@ module JsDuck
      items = [
        render_alternate_class_names,
        render_tree,
        render_dependencies(:allMixins, "Mixins"),
        render_dependencies(:mixins, "Mixins"),
        render_dependencies(:parentMixins, "Inherited mixins"),
        render_dependencies(:requires, "Requires"),
        render_dependencies(:uses, "Uses"),
        render_files,
@@ -67,7 +68,7 @@ module JsDuck
      return if !@cls[type] || @cls[type].length == 0
      return [
        "<h4>#{title}</h4>",
        @cls[type].map {|name| "<div class='dependency'>#{render_link(name)}</div>" },
        @cls[type].map {|name| "<div class='dependency'>#{name.exists? ? render_link(name) : name}</div>" },
      ]
    end

@@ -88,23 +89,21 @@ module JsDuck
    # We still create the tree, but without links in it.
    def render_tree
      return if !@cls[:extends] || @cls[:extends] == "Object"
      tree = ["<h4>Hierarchy</h4>"]

      if @cls[:superclasses].length > 0
        tree + render_class_tree(@cls[:superclasses].concat([@cls[:name]]), {:first => true, :links => true})
      else
        tree + render_class_tree([@cls[:extends], @cls[:name]], {:first => true})
      end
      return [
        "<h4>Hierarchy</h4>",
        render_class_tree(@cls[:superclasses] + [@cls[:name]])
      ]
    end

    def render_class_tree(superclasses, o)
      return "" if superclasses.length == 0
    def render_class_tree(classes, i=0)
      return "" if classes.length <= i

      name = superclasses[0]
      name = classes[i]
      return [
        "<div class='subclass #{o[:first] ? 'first-child' : ''}'>",
          superclasses.length > 1 ? (o[:links] ? render_link(name) : name) : "<strong>#{name}</strong>",
          render_class_tree(superclasses.slice(1, superclasses.length-1), {:links => o[:links]}),
        "<div class='subclass #{i == 0 ? 'first-child' : ''}'>",
          classes.length-1 == i ? "<strong>#{name}</strong>" : (name.exists? ? render_link(name) : name),
          render_class_tree(classes, i+1),
        "</div>",
      ]
    end