Commit 798a3164 authored by Rene Saarsoo's avatar Rene Saarsoo
Browse files

Merge branch 'properties'

parents 65fedc03 ab9edcb4
Loading
Loading
Loading
Loading
+17 −10
Original line number Diff line number Diff line
@@ -23,7 +23,8 @@ module JsDuck
  class DocParser
    # Pass in :css to be able to parse CSS doc-comments
    def initialize(mode = :js)
      @ident_pattern = (mode == :css) ? /\$[a-zA-Z0-9_-]*/ : /\w+/
      @ident_pattern = (mode == :css) ? /\$[\w-]*/ : /\w+/
      @ident_chain_pattern = (mode == :css) ? /\$[\w-]+(\.[\w-]+)*/ : /\w+(\.\w+)*/
    end

    def parse(input)
@@ -183,21 +184,27 @@ module JsDuck
      skip_white
    end

    # matches @param {type} variable ...
    # matches @param {type} name ...
    def at_param
      match(/@param/)
      add_tag(:param)
      maybe_type
      maybe_name
      maybe_ident_chain(:name)
      skip_white
    end

    # matches @return {type} ...
    # matches @return {type} [ return.name ] ...
    def at_return
      match(/@returns?/)
      add_tag(:return)
      maybe_type
      skip_white
      if look(/return\.\w/)
        @current_tag[:name] = ident_chain
      else
        @current_tag[:name] = "return"
      end
      skip_white
    end

    # matches @cfg {type} name ...
@@ -205,7 +212,7 @@ module JsDuck
      match(/@cfg/)
      add_tag(:cfg)
      maybe_type
      maybe_name
      maybe_ident_chain(:name)
      skip_white
    end

@@ -219,7 +226,7 @@ module JsDuck
      match(/@property/)
      add_tag(:property)
      maybe_type
      maybe_name
      maybe_ident_chain(:name)
      skip_white
    end

@@ -278,7 +285,7 @@ module JsDuck
      match(/@alias/)
      add_tag(:alias)
      skip_horiz_white
      if look(/\w/)
      if look(@ident_chain_pattern)
        @current_tag[:cls] = ident_chain
        if look(/#\w/)
          @input.scan(/#/)
@@ -347,7 +354,7 @@ module JsDuck
    # matches ident.chain if possible and sets it on @current_tag
    def maybe_ident_chain(propname)
      skip_horiz_white
      if look(/\w/)
      if look(@ident_chain_pattern)
        @current_tag[propname] = ident_chain
      end
    end
@@ -364,7 +371,7 @@ module JsDuck
    def class_list
      skip_horiz_white
      classes = []
      while look(/\w/)
      while look(@ident_chain_pattern)
        classes << ident_chain
        skip_horiz_white
      end
@@ -373,7 +380,7 @@ module JsDuck

    # matches chained.identifier.name and returns it
    def ident_chain
      @input.scan(/[\w.]+/)
      @input.scan(@ident_chain_pattern)
    end

    # matches identifier and returns its name
+12 −1
Original line number Diff line number Diff line
@@ -49,11 +49,12 @@ module JsDuck
      @formatter.doc_context = m
      m[:doc] = @formatter.format(m[:doc]) if m[:doc]
      m[:deprecated][:text] = @formatter.format(m[:deprecated][:text]) if m[:deprecated]
      if m[:params] || @formatter.too_long?(m[:doc])
      if m[:params] || (m[:properties] && m[:properties].length > 0) || @formatter.too_long?(m[:doc])
        m[:shortDoc] = @formatter.shorten(m[:doc])
      end
      m[:params] = format_params(m[:params]) if m[:params]
      m[:return] = format_return(m[:return]) if m[:return]
      m[:properties] = format_subproperties(m[:properties]) if m[:properties]
      m
    end

@@ -61,6 +62,7 @@ module JsDuck
      params.map do |p|
        p = p.clone
        p[:doc] = @formatter.format(p[:doc]) if p[:doc]
        p[:properties] = format_subproperties(p[:properties]) if p[:properties]
        p
      end
    end
@@ -71,6 +73,15 @@ module JsDuck
      r
    end

    def format_subproperties(items)
      items.map do |it|
        it = it.clone
        it[:doc] = @formatter.format(it[:doc]) if it[:doc]
        it[:properties] = format_subproperties(it[:properties]) if it[:properties]
        it
      end
    end

  end

end
+44 −3
Original line number Diff line number Diff line
@@ -164,6 +164,7 @@ module JsDuck
        :owner => detect_owner(doc_map) || owner,
        :type => detect_type(:cfg, doc_map, code),
        :doc => detect_doc(docs),
        :properties => detect_subproperties(docs, :cfg),
      }, doc_map)
    end

@@ -175,6 +176,7 @@ module JsDuck
        :owner => detect_owner(doc_map),
        :type => detect_type(:property, doc_map, code),
        :doc => detect_doc(docs),
        :properties => detect_subproperties(docs, :property),
      }, doc_map)
    end

@@ -318,6 +320,7 @@ module JsDuck
          :doc => doc,
          # convert to boolean for JavaScript export, otherwise it's 0 or nil
          :optional => !!(doc =~ /\(optional\)/i),
          :properties => ex[:properties] || [],
        }
      end
      params
@@ -334,25 +337,63 @@ module JsDuck
    end

    def detect_explicit_params(docs)
      docs.find_all {|tag| tag[:tagname] == :param}
      combine_properties(docs.find_all {|tag| tag[:tagname] == :param})
    end

    def detect_subproperties(docs, tagname)
      prop_docs = docs.find_all {|tag| tag[:tagname] == tagname}
      prop_docs.length > 0 ? combine_properties(prop_docs)[0][:properties] : []
    end

    def combine_properties(raw_items)
      # First item can't be namespaced, if it is ignore the rest.
      if raw_items[0] && raw_items[0][:name] =~ /\./
        return [raw_items[0]]
      end

      # build name-index of all items
      index = {}
      raw_items.each {|it| index[it[:name]] = it }

      # If item name has no dots, add it directly to items array.
      # Otherwise look up the parent of item and add it as the
      # property of that parent.
      items = []
      raw_items.each do |it|
        if it[:name] =~ /^(.+)\.([^.]+)$/
          it[:name] = $2
          parent = index[$1]
          parent[:properties] = [] unless parent[:properties]
          parent[:properties] << it
        else
          items << it
        end
      end
      items
    end

    def detect_return(doc_map, default_type="undefined")
      ret = doc_map[:return] ? doc_map[:return].first : {}
      return {
        :type => ret[:type] || default_type,
        :name => ret[:name] || "return",
        :doc => ret[:doc] || "",
        :properties => doc_map[:return] ? detect_subproperties(doc_map[:return], :return) : []
      }
    end

    # Combines :doc-s of most tags
    # Ignores tags that have doc comment themselves
    # Ignores tags that have doc comment themselves and subproperty tags
    def detect_doc(docs)
      ignore_tags = [:param, :return]
      doc_tags = docs.find_all { |tag| !ignore_tags.include?(tag[:tagname]) }
      doc_tags = docs.find_all { |tag| !ignore_tags.include?(tag[:tagname]) && !subproperty?(tag) }
      doc_tags.map { |tag| tag[:doc] }.compact.join(" ")
    end

    def subproperty?(tag)
      (tag[:tagname] == :cfg || tag[:tagname] == :property) && tag[:name] =~ /\./
    end

    # Build map of at-tags for quick lookup
    def build_doc_map(docs)
      map = {}
+284 −0
Original line number Diff line number Diff line
require "jsduck/aggregator"
require "jsduck/source_file"

describe JsDuck::Aggregator do

  def parse(string)
    agr = JsDuck::Aggregator.new
    agr.aggregate(JsDuck::SourceFile.new(string))
    agr.result
  end

  shared_examples_for "object with properties" do
    it "has name" do
      @obj[:name].should == @name
    end

    it "has type" do
      @obj[:type].should == "Object"
    end

    it "has doc" do
      @obj[:doc].should == "Geographical coordinates"
    end

    it "contains 2 properties" do
      @obj[:properties].length.should == 2
    end

    describe "first property" do
      before do
        @prop = @obj[:properties][0]
      end

      it "has name without namespace" do
        @prop[:name].should == "lat"
      end

      it "has type" do
        @prop[:type].should == "Object"
      end

      it "has doc" do
        @prop[:doc].should == "Latitude"
      end

      it "contains 2 subproperties" do
        @prop[:properties].length.should == 2
      end

      describe "first subproperty" do
        it "has name without namespace" do
          @prop[:properties][0][:name].should == "numerator"
        end
      end

      describe "second subproperty" do
        it "has name without namespace" do
          @prop[:properties][1][:name].should == "denominator"
        end
      end
    end

    describe "second property" do
      before do
        @prop = @obj[:properties][1]
      end

      it "has name without namespace" do
        @prop[:name].should == "lng"
      end

      it "has type" do
        @prop[:type].should == "Number"
      end

      it "has doc" do
        @prop[:doc].should == "Longitude"
      end
    end
  end

  describe "method parameter with properties" do
    before do
      @doc = parse(<<-EOS)[0]
        /**
         * Some function
         * @param {Object} coord Geographical coordinates
         * @param {Object} coord.lat Latitude
         * @param {Number} coord.lat.numerator Numerator part of a fraction
         * @param {Number} coord.lat.denominator Denominator part of a fraction
         * @param {Number} coord.lng Longitude
         */
        function foo(x, y) {}
      EOS
    end

    it "is interpreted as single parameter" do
      @doc[:params].length.should == 1
    end

    describe "single param" do
      before do
        @obj = @doc[:params][0]
        @name = "coord"
      end

      it_should_behave_like "object with properties"
    end
  end

  describe "event parameter with properties" do
    before do
      @doc = parse(<<-EOS)[0]
        /**
         * @event
         * Some event
         * @param {Object} coord Geographical coordinates
         * @param {Object} coord.lat Latitude
         * @param {Number} coord.lat.numerator Numerator part of a fraction
         * @param {Number} coord.lat.denominator Denominator part of a fraction
         * @param {Number} coord.lng Longitude
         */
        "foo"
      EOS
    end

    it "is interpreted as single parameter" do
      @doc[:params].length.should == 1
    end

    describe "single param" do
      before do
        @obj = @doc[:params][0]
        @name = "coord"
      end

      it_should_behave_like "object with properties"
    end
  end

  describe "cfg with properties" do
    before do
      @doc = parse(<<-EOS)
        /**
         * @cfg {Object} coord Geographical coordinates
         * @cfg {Object} coord.lat Latitude
         * @cfg {Number} coord.lat.numerator Numerator part of a fraction
         * @cfg {Number} coord.lat.denominator Denominator part of a fraction
         * @cfg {Number} coord.lng Longitude
         */
      EOS
    end

    it "is interpreted as single config" do
      @doc.length.should == 1
    end

    describe "the config" do
      before do
        @obj = @doc[0]
        @name = "coord"
      end

      it_should_behave_like "object with properties"
    end
  end

  describe "property with properties" do
    before do
      @doc = parse(<<-EOS)
        /**
         * @property {Object} coord Geographical coordinates
         * @property {Object} coord.lat Latitude
         * @property {Number} coord.lat.numerator Numerator part of a fraction
         * @property {Number} coord.lat.denominator Denominator part of a fraction
         * @property {Number} coord.lng Longitude
         */
      EOS
    end

    it "is interpreted as single property" do
      @doc.length.should == 1
    end

    describe "the property" do
      before do
        @obj = @doc[0]
        @name = "coord"
      end

      it_should_behave_like "object with properties"
    end
  end

  describe "method return value with properties" do
    before do
      @obj = parse(<<-EOS)[0][:return]
        /**
         * Some function
         * @return {Object} Geographical coordinates
         * @return {Object} return.lat Latitude
         * @return {Number} return.lat.numerator Numerator part of a fraction
         * @return {Number} return.lat.denominator Denominator part of a fraction
         * @return {Number} return.lng Longitude
         */
        function foo() {}
      EOS
      @name = "return"
    end

    it_should_behave_like "object with properties"
  end

  # Tests with buggy syntax

  describe "config option with properties in wrong order" do
    before do
      @obj = parse(<<-EOS)[0]
        /**
         * @cfg {Object} coord Geographical coordinates
         * @cfg {Number} coord.lat.numerator Numerator part of a fraction
         * @cfg {Number} coord.lat.denominator Denominator part of a fraction
         * @cfg {Object} coord.lat Latitude
         * @cfg {Number} coord.lng Longitude
         */
      EOS
      @name = "coord"
    end

    it_should_behave_like "object with properties"
  end

  describe "only namespaced config options" do
    before do
      @doc = parse(<<-EOS)[0]
        /**
         * @cfg {Number} coord.lat Latitude
         * @cfg {Number} coord.lng Latitude
         */
      EOS
    end

    it "interpreted as just one config" do
      @doc[:name].should == "coord.lat"
    end
  end

  describe "normal config option name with dot after it" do
    before do
      @doc = parse(<<-EOS)[0]
        /**
         * @cfg {Number} coord. Coordinate
         */
      EOS
    end

    it "has no dot in name" do
      @doc[:name].should == "coord"
    end

    it "has dot in doc" do
      @doc[:doc].should == ". Coordinate"
    end
  end

  describe "normal config option name with dot before it" do
    before do
      @doc = parse(<<-EOS)[0]
        /**
         * @cfg {Number} .coord Coordinate
         */
      EOS
    end

    it "has empty name" do
      @doc[:name].should == ""
    end

    it "has dot in doc" do
      @doc[:doc].should == ".coord Coordinate"
    end
  end

end
+337 −0

File added.

Preview size limit exceeded, changes collapsed.

Loading