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

Split Importer class into Versions and Importer.

The main class is now called Versions and it handles the generation of
@since and @new tags.  The actual importing work gets done by Importer
class, which we now mock out when testing the Versions class logic -
no more testing of private methods.
parent a8b5388a
Loading
Loading
Loading
Loading
+2 −2
Original line number Diff line number Diff line
@@ -11,7 +11,7 @@ require 'jsduck/process/accessors'
require 'jsduck/process/ext4_events'
require 'jsduck/process/overrides'
require 'jsduck/process/inherit_doc'
require 'jsduck/process/importer'
require 'jsduck/process/versions'
require 'jsduck/process/return_values'
require 'jsduck/process/lint'
require 'jsduck/process/circular_deps'
@@ -102,7 +102,7 @@ module JsDuck
    def apply_extra_processing
      Process::CircularDeps.new(@relations).process_all!
      Process::InheritDoc.new(@relations).process_all!
      Process::Importer.new(@relations, @opts).process_all!
      Process::Versions.new(@relations, @opts).process_all!
      Process::ReturnValues.new(@relations).process_all!
      Process::Lint.new(@relations).process_all!
    end
+5 −74
Original line number Diff line number Diff line
require 'jsduck/util/json'
require 'jsduck/util/null_object'
require 'jsduck/logger'
require 'jsduck/util/parallel'
require 'jsduck/logger'

module JsDuck
  module Process

    # Reads in JSDuck exports of different versions of docs.
    class Importer
      def initialize(relations, opts={})
        @relations = relations
        @opts = opts
      end

      # Loads in exported docs and generates @since and @new tags based
      # on that data.
      def process_all!
        if @opts[:imports].length > 0
          generate_since_tags(read_all(@opts[:imports]), @relations, @opts[:new_since])
        end
      end

      # Reads in data for all versions, returning array of
      # version/class-data pairs.  We don't use a hash to preserve the
      # order of versions (from oldest to newest).
      def read_all(imports)
        imports.map do |ver|
      def import(versions)
        versions.map do |ver|
          {
            :version => ver[:version],
            :classes => ver[:path] ? read(ver) : current_version,
@@ -33,6 +20,8 @@ module JsDuck
        end
      end

      private

      def current_version
        Util::NullObject.new(:[] => Util::NullObject.new(:[] => true))
      end
@@ -63,64 +52,6 @@ module JsDuck
        index
      end

      # Using the imported versions data, adds @since tags to all
      # classes/members.
      def generate_since_tags(versions, relations, new_since=nil)
        new_versions = build_new_versions_map(versions, new_since)

        relations.each do |cls|
          v = cls[:since] || class_since(versions, cls)
          cls[:since] = v
          cls[:new] = true if new_versions[v]

          cls.all_local_members.each do |m|
            v = m[:since] || member_since(versions, cls, m)
            m[:since] = v
            m[:new] = true if new_versions[v]
          end
        end
      end

      # Generates a lookup table of versions that we are going to label
      # with @new tags.  By default we use the latest version, otherwise
      # use all versions since the latest.
      def build_new_versions_map(versions, new_since=nil)
        new_versions = {}

        if new_since
          versions.map {|v| v[:version] }.each do |v|
            if v == new_since || !new_versions.empty?
              new_versions[v] = true
            end
          end
        else
          new_versions[versions.last[:version]] = true
        end

        new_versions
      end

      def member_since(versions, cls, m)
        versions.each do |ver|
          c = ver[:classes][cls[:name]]
          return ver[:version] if c && c[m[:id]]
          cls[:alternateClassNames].each do |name|
            c = ver[:classes][name]
            return ver[:version] if c && c[m[:id]]
          end
        end
      end

      # Returns name of the version since which the class is available
      def class_since(versions, cls)
        versions.each do |ver|
          return ver[:version] if ver[:classes][cls[:name]]
          cls[:alternateClassNames].each do |name|
            return ver[:version] if ver[:classes][name]
          end
        end
      end

    end

  end
+87 −0
Original line number Diff line number Diff line
require 'jsduck/process/importer'

module JsDuck
  module Process

    # Generates @since and @new tags by importing JSDuck exports of
    # older versions of the same project and looking in which version
    # a class or method first appeared.
    class Versions
      def initialize(relations, opts={}, importer=nil)
        @relations = relations
        @opts = opts
        # Allow different importer to be injected for testing
        @importer = importer || Process::Importer.new
      end

      # Loads in exported docs and generates @since and @new tags.
      def process_all!
        if @opts[:imports].length > 0
          generate_since_tags(@importer.import(@opts[:imports]))
        end
      end

      private

      # Using the imported versions data, adds @since tags to all
      # classes/members.
      def generate_since_tags(versions)
        new_versions = build_new_versions_map(versions)

        @relations.each do |cls|
          v = cls[:since] || class_since(versions, cls)
          cls[:since] = v
          cls[:new] = true if new_versions[v]

          cls.all_local_members.each do |m|
            v = m[:since] || member_since(versions, cls, m)
            m[:since] = v
            m[:new] = true if new_versions[v]
          end
        end
      end

      # Generates a lookup table of versions that we are going to label
      # with @new tags.  By default we use the latest version, otherwise
      # use all versions since the latest.
      def build_new_versions_map(versions)
        new_versions = {}

        if @opts[:new_since]
          versions.map {|v| v[:version] }.each do |v|
            if v == @opts[:new_since] || !new_versions.empty?
              new_versions[v] = true
            end
          end
        else
          new_versions[versions.last[:version]] = true
        end

        new_versions
      end

      def member_since(versions, cls, m)
        versions.each do |ver|
          c = ver[:classes][cls[:name]]
          return ver[:version] if c && c[m[:id]]
          cls[:alternateClassNames].each do |name|
            c = ver[:classes][name]
            return ver[:version] if c && c[m[:id]]
          end
        end
      end

      # Returns name of the version since which the class is available
      def class_since(versions, cls)
        versions.each do |ver|
          return ver[:version] if ver[:classes][cls[:name]]
          cls[:alternateClassNames].each do |name|
            return ver[:version] if ver[:classes][name]
          end
        end
      end

    end

  end
end

spec/importer_spec.rb

deleted100644 → 0
+0 −187
Original line number Diff line number Diff line
require "jsduck/process/importer"
require "jsduck/class"

describe "JsDuck::Process::Importer#generate_since_tags" do

  before do
    importer = JsDuck::Process::Importer.new([])

    @versions = [
      {
        :version => "1.0", :classes => {
          "VeryOldClass" => {"cfg-foo" => true},
          "ExplicitNewClass" => {},
        },
      },
      {
        :version => "2.0", :classes => {
          "VeryOldClass" => {"cfg-foo" => true, "cfg-bar" => true},
          "OldClass" => {},
          "ClassWithOldName" => {},
        },
      },
      {
        :version => "3.0", :classes => importer.current_version
      }
    ]

    @relations = [
      {:name => "VeryOldClass", :alternateClassNames => [], :members => [
          {:tagname => :cfg, :id => "cfg-foo"},
          {:tagname => :cfg, :id => "cfg-bar"},
          {:tagname => :cfg, :id => "cfg-baz"},
          {:tagname => :cfg, :id => "cfg-zap", :since => "1.0"},
          {:tagname => :cfg, :id => "cfg-new", :new => true},
        ]},
      {:name => "OldClass", :alternateClassNames => []},
      {:name => "NewClass", :alternateClassNames => []},
      {:name => "ClassWithNewName", :alternateClassNames => ["ClassWithOldName"]},
      {:name => "ExplicitSinceClass", :since => "1.0", :alternateClassNames => []},
      {:name => "ExplicitNewClass", :new => true, :alternateClassNames => []},
    ].map {|cfg| JsDuck::Class.new(cfg) }

    importer.generate_since_tags(@versions, @relations)

    # build className/member index for easy lookup in specs
    @stuff = {}
    @relations.each do |cls|
      @stuff[cls[:name]] = cls
      cls[:members].each do |cfg|
        @stuff[cls[:name]+"#"+cfg[:id]] = cfg
      end
    end
  end

  # @since

  it "adds @since 1.0 to VeryOldClass" do
    @stuff["VeryOldClass"][:since].should == "1.0"
  end

  it "adds @since 2.0 to OldClass" do
    @stuff["OldClass"][:since].should == "2.0"
  end

  it "adds @since 3.0 to NewClass" do
    @stuff["NewClass"][:since].should == "3.0"
  end

  it "adds @since 2.0 to ClassWithNewName" do
    @stuff["ClassWithNewName"][:since].should == "2.0"
  end

  it "doesn't override explicit @since 1.0 in ExplicitSinceClass" do
    @stuff["ExplicitSinceClass"][:since].should == "1.0"
  end

  it "adds @since 1.0 to #foo" do
    @stuff["VeryOldClass#cfg-foo"][:since].should == "1.0"
  end

  it "adds @since 2.0 to #bar" do
    @stuff["VeryOldClass#cfg-bar"][:since].should == "2.0"
  end

  it "adds @since 3.0 to #baz" do
    @stuff["VeryOldClass#cfg-baz"][:since].should == "3.0"
  end

  it "doesn't override explicit @since 1.0 in #zap" do
    @stuff["VeryOldClass#cfg-zap"][:since].should == "1.0"
  end

  # @new

  it "doesn't add @new to VeryOldClass" do
    @stuff["VeryOldClass"][:new].should_not == true
  end

  it "doesn't add @new to OldClass" do
    @stuff["OldClass"][:new].should_not == true
  end

  it "adds @new to NewClass" do
    @stuff["NewClass"][:new].should == true
  end

  it "doesn't add @new to ClassWithNewName" do
    @stuff["ClassWithNewName"][:new].should_not == true
  end

  it "doesn't add @new to ExplicitSinceClass" do
    @stuff["ExplicitSinceClass"][:new].should_not == true
  end

  it "keeps explicit @new on ExplicitNewClass" do
    # Though it seems like a weird case, there could be a situation
    # where 1.0 had class Foo, which was removed in 2.0, but in 3.0 a
    # completely unrelated Foo class was introduced.
    @stuff["ExplicitNewClass"][:new].should == true
  end

  it "doesn't add @new to #foo" do
    @stuff["VeryOldClass#cfg-foo"][:new].should_not == true
  end

  it "doesn't add @new to #bar" do
    @stuff["VeryOldClass#cfg-bar"][:new].should_not == true
  end

  it "adds @new to #baz" do
    @stuff["VeryOldClass#cfg-baz"][:new].should == true
  end

  it "doesn't add @new to #zap" do
    @stuff["VeryOldClass#cfg-zap"][:new].should_not == true
  end

  it "keeps explicit @new in #new" do
    @stuff["VeryOldClass#cfg-new"][:new].should == true
  end

end

describe "JsDuck::Process::Importer#generate_since_tags with explicit new_since" do

  before do
    importer = JsDuck::Process::Importer.new([])

    @versions = [
      {
        :version => "1.0", :classes => {
          "VeryOldClass" => {},
        },
      },
      {
        :version => "2.0", :classes => {
          "OldClass" => {},
        },
      },
      {
        :version => "3.0", :classes => importer.current_version
      }
    ]

    @relations = [
      {:name => "VeryOldClass", :alternateClassNames => []},
      {:name => "OldClass", :alternateClassNames => []},
      {:name => "NewClass", :alternateClassNames => []},
    ].map {|cfg| JsDuck::Class.new(cfg) }

    importer.generate_since_tags(@versions, @relations, "2.0")
  end

  # @since

  it "gives no @new to VeryOldClass" do
    @relations[0][:new].should_not == true
  end

  it "gives @new to OldClass" do
    @relations[1][:new].should == true
  end

  it "gives no @new to NewClass" do
    @relations[2][:new].should == true
  end
end

spec/versions_spec.rb

0 → 100644
+193 −0
Original line number Diff line number Diff line
require "jsduck/process/versions"
require "jsduck/util/null_object"
require "jsduck/class"

describe JsDuck::Process::Versions do

  def current_version
    JsDuck::Util::NullObject.new(:[] => JsDuck::Util::NullObject.new(:[] => true))
  end

  describe "without :new_since option" do
    before do
      @versions = [
        {
          :version => "1.0", :classes => {
            "VeryOldClass" => {"cfg-foo" => true},
            "ExplicitNewClass" => {},
          },
        },
        {
          :version => "2.0", :classes => {
            "VeryOldClass" => {"cfg-foo" => true, "cfg-bar" => true},
            "OldClass" => {},
            "ClassWithOldName" => {},
          },
        },
        {
          :version => "3.0", :classes => current_version
        }
      ]

      importer = JsDuck::Util::NullObject.new(:import => @versions)

      @relations = [
        {:name => "VeryOldClass", :alternateClassNames => [], :members => [
            {:tagname => :cfg, :id => "cfg-foo"},
            {:tagname => :cfg, :id => "cfg-bar"},
            {:tagname => :cfg, :id => "cfg-baz"},
            {:tagname => :cfg, :id => "cfg-zap", :since => "1.0"},
            {:tagname => :cfg, :id => "cfg-new", :new => true},
          ]},
        {:name => "OldClass", :alternateClassNames => []},
        {:name => "NewClass", :alternateClassNames => []},
        {:name => "ClassWithNewName", :alternateClassNames => ["ClassWithOldName"]},
        {:name => "ExplicitSinceClass", :since => "1.0", :alternateClassNames => []},
        {:name => "ExplicitNewClass", :new => true, :alternateClassNames => []},
      ].map {|cfg| JsDuck::Class.new(cfg) }

      JsDuck::Process::Versions.new(@relations, {:imports => [1,2,3]}, importer).process_all!

      # build className/member index for easy lookup in specs
      @stuff = {}
      @relations.each do |cls|
        @stuff[cls[:name]] = cls
        cls[:members].each do |cfg|
          @stuff[cls[:name]+"#"+cfg[:id]] = cfg
        end
      end
    end

    # @since

    it "adds @since 1.0 to VeryOldClass" do
      @stuff["VeryOldClass"][:since].should == "1.0"
    end

    it "adds @since 2.0 to OldClass" do
      @stuff["OldClass"][:since].should == "2.0"
    end

    it "adds @since 3.0 to NewClass" do
      @stuff["NewClass"][:since].should == "3.0"
    end

    it "adds @since 2.0 to ClassWithNewName" do
      @stuff["ClassWithNewName"][:since].should == "2.0"
    end

    it "doesn't override explicit @since 1.0 in ExplicitSinceClass" do
      @stuff["ExplicitSinceClass"][:since].should == "1.0"
    end

    it "adds @since 1.0 to #foo" do
      @stuff["VeryOldClass#cfg-foo"][:since].should == "1.0"
    end

    it "adds @since 2.0 to #bar" do
      @stuff["VeryOldClass#cfg-bar"][:since].should == "2.0"
    end

    it "adds @since 3.0 to #baz" do
      @stuff["VeryOldClass#cfg-baz"][:since].should == "3.0"
    end

    it "doesn't override explicit @since 1.0 in #zap" do
      @stuff["VeryOldClass#cfg-zap"][:since].should == "1.0"
    end

    # @new

    it "doesn't add @new to VeryOldClass" do
      @stuff["VeryOldClass"][:new].should_not == true
    end

    it "doesn't add @new to OldClass" do
      @stuff["OldClass"][:new].should_not == true
    end

    it "adds @new to NewClass" do
      @stuff["NewClass"][:new].should == true
    end

    it "doesn't add @new to ClassWithNewName" do
      @stuff["ClassWithNewName"][:new].should_not == true
    end

    it "doesn't add @new to ExplicitSinceClass" do
      @stuff["ExplicitSinceClass"][:new].should_not == true
    end

    it "keeps explicit @new on ExplicitNewClass" do
      # Though it seems like a weird case, there could be a situation
      # where 1.0 had class Foo, which was removed in 2.0, but in 3.0 a
      # completely unrelated Foo class was introduced.
      @stuff["ExplicitNewClass"][:new].should == true
    end

    it "doesn't add @new to #foo" do
      @stuff["VeryOldClass#cfg-foo"][:new].should_not == true
    end

    it "doesn't add @new to #bar" do
      @stuff["VeryOldClass#cfg-bar"][:new].should_not == true
    end

    it "adds @new to #baz" do
      @stuff["VeryOldClass#cfg-baz"][:new].should == true
    end

    it "doesn't add @new to #zap" do
      @stuff["VeryOldClass#cfg-zap"][:new].should_not == true
    end

    it "keeps explicit @new in #new" do
      @stuff["VeryOldClass#cfg-new"][:new].should == true
    end

  end

  describe "with explicit :new_since option" do
    before do
      @versions = [
        {
          :version => "1.0", :classes => {
            "VeryOldClass" => {},
          },
        },
        {
          :version => "2.0", :classes => {
            "OldClass" => {},
          },
        },
        {
          :version => "3.0", :classes => current_version
        }
      ]
      importer = JsDuck::Util::NullObject.new(:import => @versions)

      @relations = [
        {:name => "VeryOldClass", :alternateClassNames => []},
        {:name => "OldClass", :alternateClassNames => []},
        {:name => "NewClass", :alternateClassNames => []},
      ].map {|cfg| JsDuck::Class.new(cfg) }

      JsDuck::Process::Versions.new(@relations, {:imports => [1,2,3], :new_since => "2.0"}, importer).process_all!
    end

    # @since

    it "gives no @new to VeryOldClass" do
      @relations[0][:new].should_not == true
    end

    it "gives @new to OldClass" do
      @relations[1][:new].should == true
    end

    it "gives no @new to NewClass" do
      @relations[2][:new].should == true
    end
  end

end