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

Check for circular dependencies.

Instead of crashing mysteriously, the circular @mixins and @extends
now result in fatal error message which clearly shows the
dependency chain that's causing the problem.
parent 6643e6d5
Loading
Loading
Loading
Loading
+39 −0
Original line number Diff line number Diff line
module JsDuck

  # Checks for circular dependencies
  class CircularDeps

    # Checks class for circular dependencies.
    #
    # When all OK, returns false.
    #
    # When circular dependencies found returns a string describing the
    # problematic dependency chain e.g. "Foo extends Bar mixins Foo".
    def check(cls, names = [])
      names += [cls[:name]]

      if cls.parent && chain = track_circular(" extends ", cls.parent, names)
        return chain
      end

      cls.mixins.each do |mixin|
        if chain = track_circular(" mixins ", mixin, names)
          return chain
        end
      end

      false
    end

    def track_circular(type, cls, names)
      names += [type]
      if names.include?(cls[:name])
        (names + [cls[:name]]).join("")
      else
        check(cls, names)
      end
    end

  end

end
+13 −0
Original line number Diff line number Diff line
require 'jsduck/logger'
require 'jsduck/class'
require 'jsduck/circular_deps'

module JsDuck

@@ -20,6 +21,7 @@ module JsDuck
      warn_duplicate_members
      warn_singleton_statics
      warn_empty_enums
      fail_on_circular_dependencies
    end

    # print warning for each member or parameter with no name
@@ -117,6 +119,17 @@ module JsDuck
      end
    end

    # Print fatal error message for circular dependency.
    def fail_on_circular_dependencies
      circular_deps = CircularDeps.new
      @relations.each do |cls|
        if chain = circular_deps.check(cls)
          Logger.fatal("Class #{cls[:name]} has a circular dependency: #{chain}")
          exit 1
        end
      end
    end

    # Loops through all members of all classes
    def each_member(&block)
      @relations.each {|cls| cls.all_local_members.each(&block) }