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

Big rewrite.

All the complex comment and code analyzing is now done by Merger class,
the doc_comment_parser now just parses input into a linear array of at-tags.
The Merger completely replaces DocComment class and most of the complex
work in DocCommentParser.

Most of the tests are now also at top level tc_jsduck - so that we
mainly test the whole jsduck module, allowing the internal classes to be
more easily reorganized.

@ignore and @hide are now simply aliases of @private.
parent 92912147
Loading
Loading
Loading
Loading
+4 −26
Original line number Diff line number Diff line
$:.unshift File.dirname(__FILE__) # For running the actual JsDuck app

require 'jsduck/lexer'
require 'jsduck/doc_comment'
require 'jsduck/doc_comment_parser'
require 'jsduck/parser'
require 'jsduck/doc_comment_parser'
require 'jsduck/merger'

require 'pp'

module JsDuck
  def JsDuck.parse(input)
    doc_parser = DocCommentParser.new
    merger = Merger.new
    documentation = []

    Parser.new(input).parse.each do |docset|
      # Parsing of doc-block may result in several doc-comment
      # objects. Only the first one of these gets augmented with
      # information inferred from the code that follows doc-block.
      comments = doc_parser.parse(docset[:comment]).map { |d| DocComment.new(d) }
      comments.each { |c| documentation << c }
      doc = comments[0]
      code = docset[:code]

      if code[:type] == :function then
        doc.set_default_name(*code[:name]) if code[:name]
        doc.set_default_params(code[:params])
      elsif code[:type] == :assignment then
        doc.set_default_name(*code[:left])
        if code[:right] then
          right = code[:right]
          if right[:type] == :function then
            doc.set_default_params(right[:params])
          elsif right[:type] == :ext_extend then
            doc.set_default_extends(right[:extend])
          elsif right[:type] == :literal then
            doc.set_default_type(right[:class])
          end
        end
      end
      documentation << merger.merge(doc_parser.parse(docset[:comment]), docset[:code])
    end

    documentation

lib/jsduck/doc_comment.rb

deleted100644 → 0
+0 −69
Original line number Diff line number Diff line

module JsDuck

  class DocComment
    def initialize(tags)
      @tags = tags

      [:class, :method, :event, :cfg, :property].each do |name|
        if @tags[name] then
          @root_tag = @tags[name]
        end
      end
    end

    # Sets the name property of the default at-tag.
    #
    # When name begins with uppercase it's considered to be class
    # name, otherwise a method name.
    #
    # When the name consists of several parts like foo.bar.baz, then
    # the parts should be passed as multiple arguments.
    def set_default_name(*name_chain)
      name = name_chain.last
      tagname = (name[0,1] == name[0,1].upcase) ? :class : :method

      if !@root_tag then
        @root_tag = {:name => (tagname == :method) ? name : name_chain.join(".")}
        @root_tag[:doc] = @tags[:default][:doc]
        @tags[tagname] = @root_tag
        @tags.delete(:default)
      elsif !@root_tag[:name] then
        @root_tag[:name] = name
      end
    end

    # Sets default name for superclass
    def set_default_extends(*name_chain)
      @tags[:class] = {:doc => ""} unless @tags[:class]
      @tags[:class][:extends] = name_chain.join(".") unless @tags[:class][:extends]
    end

    # sets default names and possibly other properties of params
    def set_default_params(params)
      if @tags[:param] then
        0.upto(params.length-1) do |i|
          if @tags[:param][i] then
            params[i].each do |key, val|
              @tags[:param][i][key] = val unless @tags[:param][i][key]
            end
          else
            @tags[:param] << params[i]
          end
        end
      else
        @tags[:param] = params
      end
    end

    # Sets default type of @cfg or @property
    def set_default_type(type)
      @root_tag[:type] = type if @tags[:cfg] || @tags[:property]
    end

    def [](tagname)
      @tags[tagname]
    end
  end

end
+43 −104
Original line number Diff line number Diff line
@@ -4,31 +4,20 @@ module JsDuck

  class DocCommentParser
    def parse(input)
      @root_tags = []
      add_root_tag(:default, {:doc => ""})
      @tags = []
      @input = StringScanner.new(purify(input))
      parse_loop
      @root_tags.each {|tagset| trim_docs(tagset)}
      @root_tags
      # The parsing process can leave whitespace at the ends of
      # doc-strings, here we get rid of it.  Additionally null all empty docs
      @tags.each do |tag|
        tag[:doc].strip!
        tag[:doc] = nil if tag[:doc] == ""
      end

    def add_root_tag(tagname, definition)
      # When previous tagset was an empty :default, then delete it
      if @root_tags.length == 1 &&
          @root_tags[0].keys.length == 1 &&
          @root_tags[0][:default] &&
          @root_tags[0][:default][:doc] == "" then
        @root_tags = []
      end
      @current_tag = definition
      @tags = {tagname => @current_tag}
      @root_tags << @tags
      # Get rid of empty default tag
      if @tags.first && @tags.first[:tagname] == :default && !@tags.first[:doc]
        @tags.shift
      end

    # curses the current :default tag into tagname.
    def set_root_tag(tagname)
      @current_tag = @tags[tagname] = @tags[:default]
      @tags.delete(:default)
      @tags
    end

    # Extracts content inside /** ... */
@@ -47,20 +36,25 @@ module JsDuck
      return result.join("\n")
    end

    def add_tag(tag)
      @tags << @current_tag = {:tagname => tag, :doc => ""}
    end

    def parse_loop
      add_tag(:default)
      while !@input.eos? do
        if look(/@class\b/) then
          at_class
        elsif look(/@extends\b/) then
          at_extends
        elsif look(/@singleton\b/) then
          at_singleton
          boolean_at_tag(/@singleton/, :singleton)
        elsif look(/@event\b/) then
          at_event
        elsif look(/@method\b/) then
          at_method
        elsif look(/@constructor\b/) then
          at_constructor
          boolean_at_tag(/@constructor/, :constructor)
        elsif look(/@param\b/) then
          at_param
        elsif look(/@return\b/) then
@@ -72,11 +66,11 @@ module JsDuck
        elsif look(/@type\b/) then
          at_type
        elsif look(/@private\b/) then
          match_boolean_at_tag(/@private/, :private)
          boolean_at_tag(/@private/, :private)
        elsif look(/@ignore\b/) then
          match_boolean_at_tag(/@ignore/, :ignore)
          boolean_at_tag(/@ignore/, :private)
        elsif look(/@hide\b/) then
          match_boolean_at_tag(/@hide/, :hide)
          boolean_at_tag(/@hide/, :private)
        elsif look(/@/) then
          @current_tag[:doc] += @input.scan(/@/)
        elsif look(/[^@]/) then
@@ -85,70 +79,26 @@ module JsDuck
      end
    end

    # The parsing process can leave whitespace at the ends of
    # doc-strings, here we get rid of it.
    def trim_docs(tags)
      # trim the :doc property of each at-tag
      tags.each_value do |tag|
        if tag.instance_of?(Hash) && tag[:doc]
          tag[:doc].strip!
        end
      end
      # trim :doc properties of parameters
      tags[:param] && tags[:param].each {|p| p[:doc].strip! }
    end

    # matches @class name ...
    def at_class
      match(/@class/)
      add_root_tag(:class, {:doc => ""})
      skip_horiz_white
      if look(/\w/) then
        @current_tag[:name] = ident_chain
      end
      add_tag(:class)
      maybe_ident_chain(:name)
      skip_white
    end

    # matches @extends name ...
    def at_extends
      match(/@extends/)
      unless @tags[:class]
        @tags[:class] = @tags[:default]
      end
      @current_tag = @tags[:class]
      skip_horiz_white
      if look(/\w/) then
        @current_tag[:extends] = ident_chain
      end
      skip_white
    end

    # matches @singleton
    def at_singleton
      match(/@singleton/)
      unless @tags[:class]
        @tags[:class] = @tags[:default]
      end
      @current_tag = @tags[:class]
      @current_tag[:singleton] = true
      skip_white
    end

    # matches @ignore
    # sets :ignore property to true on the root tag
    def at_ignore
      match(/@ignore/)
      tagname = [:class, :method, :event, :property, :default].find {|name| @tags[name]}
      if tagname
        @tags[tagname][:ignore] = true
      end
      add_tag(:extends)
      maybe_ident_chain(:extends)
      skip_white
    end

    # matches @event name ...
    def at_event
      match(/@event/)
      add_root_tag(:event, {:doc => ""})
      add_tag(:event)
      maybe_name
      skip_white
    end
@@ -156,28 +106,15 @@ module JsDuck
    # matches @method name ...
    def at_method
      match(/@method/)
      set_root_tag(:method)
      add_tag(:method)
      maybe_name
      skip_white
    end

    # matches @constructor ...
    # Which is equivalent of: @method constructor ...
    def at_constructor
      match(/@constructor/)
      add_root_tag(:method, {:doc => "", :name => "constructor"})
      skip_white
    end

    # matches @param {type} variable ...
    def at_param
      match(/@param/)
      @current_tag = {:doc => ""}
      if @tags[:param] then
        @tags[:param] << @current_tag
      else
        @tags[:param] = [@current_tag]
      end
      add_tag(:param)
      maybe_type
      maybe_name
      skip_white
@@ -186,7 +123,7 @@ module JsDuck
    # matches @return {type} ...
    def at_return
      match(/@return/)
      @current_tag = @tags[:return] = {:doc => ""}
      add_tag(:return)
      maybe_type
      skip_white
    end
@@ -194,7 +131,7 @@ module JsDuck
    # matches @cfg {type} name ...
    def at_cfg
      match(/@cfg/)
      add_root_tag(:cfg, {:doc => ""})
      add_tag(:cfg)
      maybe_type
      maybe_name
      skip_white
@@ -208,7 +145,7 @@ module JsDuck
    # so do we.
    def at_property
      match(/@property/)
      set_root_tag(:property)
      add_tag(:property)
      maybe_type
      maybe_name
      skip_white
@@ -221,9 +158,7 @@ module JsDuck
    # without them at all.
    def at_type
      match(/@type/)
      unless @tags[:property] then
        set_root_tag(:property)
      end
      add_tag(:type)
      skip_horiz_white
      if look(/\{/) then
        @current_tag[:type] = typedef
@@ -233,14 +168,10 @@ module JsDuck
      skip_white
    end

    # Used to match @private, @ignore, @hide
    # and set the corresponding property true on root tag
    def match_boolean_at_tag(regex, propname)
    # Used to match @private, @ignore, @hide, ...
    def boolean_at_tag(regex, propname)
      match(regex)
      tagname = [:class, :method, :event, :property, :default].find {|name| @tags[name]}
      if tagname
        @tags[tagname][propname] = true
      end
      add_tag(propname)
      skip_white
    end

@@ -260,6 +191,14 @@ module JsDuck
      end
    end

    # matches ident.chain if possible and sets it on @current_tag
    def maybe_ident_chain(propname)
      skip_horiz_white
      if look(/\w/) then
        @current_tag[propname] = ident_chain
      end
    end

    # matches {...} and returns text inside brackets
    def typedef
      match(/\{/)

lib/jsduck/merger.rb

0 → 100644
+239 −0
Original line number Diff line number Diff line

module JsDuck

  class Merger
    def merge(docs, code)
      case detect_doc_type(docs, code)
      when :class
        create_class(docs, code)
      when :event
        create_event(docs, code)
      when :method
        create_method(docs, code)
      when :cfg
        create_cfg(docs, code)
      when :property
        create_property(docs, code)
      end
    end

    # Detects whether the doc-comment is for class, cfg, event, method or property.
    def detect_doc_type(docs, code)
      doc_map = build_doc_map(docs)

      if doc_map[:class]
        :class
      elsif doc_map[:event]
        :event
      elsif doc_map[:method]
        :method
      elsif doc_map[:property]
        :property
      elsif code[:type] == :ext_extend
        :class
      elsif code[:type] == :assignment && class_name?(*code[:left])
        :class
      elsif code[:type] == :function && class_name?(code[:name])
        :class
      elsif doc_map[:cfg]
        :cfg
      elsif code[:type] == :function
        :method
      elsif code[:type] == :assignment && code[:right][:type] == :function
        :method
      else
        :property
      end
    end

    # Class name begins with upcase char
    def class_name?(*name_chain)
      name = name_chain.last
      return name[0,1] == name[0,1].upcase
    end

    def create_class(docs, code)
      groups = group_class_docs(docs)
      result = create_bare_class(groups[:class], code)
      result[:cfgs] = groups[:cfg].map { |tags| create_cfg(tags, {}) }
      result[:constructor] = create_method(groups[:constructor], {}) if groups[:constructor].length
      result
    end

    # Gathers all tags until first @cfg or @constructor into the first
    # bare :class group.
    #
    # Then gathers each @cfg and tags following it into :cfg group, so
    # that it becomes array of arrays of tags.  This is to allow some
    # configs to be marked with @private or whatever else.
    #
    # Finally gathers tags after @constructor into its group.
    def group_class_docs(docs)
      groups = {:class => [], :cfg => [], :constructor => []}
      group_name = :class
      docs.each do |tag|
        if tag[:tagname] == :cfg || tag[:tagname] == :constructor
          group_name = tag[:tagname]
          if tag[:tagname] == :cfg
            groups[:cfg] << []
          end
        end

        if group_name == :cfg
          groups[:cfg].last << tag
        else
          groups[group_name] << tag
        end
      end
      groups
    end

    def create_bare_class(docs, code)
      doc_map = build_doc_map(docs)
      return {
        :tagname => :class,
        :name => detect_name(:class, doc_map, code, :full_name),
        :doc => detect_doc(docs),
        :extends => detect_extends(doc_map, code),
        :singleton => !!doc_map[:singleton],
        :private => !!doc_map[:private],
      }
    end

    def create_method(docs, code)
      doc_map = build_doc_map(docs)
      return {
        :tagname => :method,
        :name => detect_name(:method, doc_map, code),
        :doc => detect_doc(docs),
        :params => detect_params(docs, code),
        :return => doc_map[:return] ? doc_map[:return].first : nil,
        :private => !!doc_map[:private],
      }
    end

    def create_event(docs, code)
      doc_map = build_doc_map(docs)
      return {
        :tagname => :event,
        :name => detect_name(:event, doc_map, code),
        :doc => detect_doc(docs),
        :params => detect_params(docs, code),
        :private => !!doc_map[:private],
      }
    end

    def create_cfg(docs, code)
      doc_map = build_doc_map(docs)
      return {
        :tagname => :cfg,
        :name => detect_name(:cfg, doc_map, code),
        :type => detect_type(:cfg, doc_map, code),
        :doc => detect_doc(docs),
        :private => !!doc_map[:private],
      }
    end

    def create_property(docs, code)
      doc_map = build_doc_map(docs)
      return {
        :tagname => :property,
        :name => detect_name(:prop, doc_map, code),
        :type => detect_type(:prop, doc_map, code),
        :doc => detect_doc(docs),
        :private => !!doc_map[:private],
      }
    end

    def detect_name(tagname, doc_map, code, name_type = :last_name)
      main_tag = doc_map[tagname] ? doc_map[tagname].first : {}
      if main_tag[:name]
        main_tag[:name]
      elsif doc_map[:constructor]
        "constructor"
      elsif code[:type] == :function
        code[:name]
      elsif code[:type] == :assignment
        name_type == :full_name ? code[:left].join(".") : code[:left].last
      end
    end

    def detect_type(tagname, doc_map, code)
      main_tag = doc_map[tagname] ? doc_map[tagname].first : {}
      if main_tag[:type]
        main_tag[:type]
      elsif doc_map[:type]
        doc_map[:type].first[:type]
      elsif code[:type] == :function
        :function
      elsif code[:type] == :assignment
        if code[:right][:type] == :function
          :function
        elsif code[:right][:type] == :literal
          code[:right][:class]
        end
      end
    end

    def detect_extends(doc_map, code)
      if doc_map[:extends]
        doc_map[:extends].first[:extends]
      elsif code[:type] == :assignment && code[:right][:type] == :ext_extend
        code[:right][:extend].join(".")
      end
    end

    def detect_params(docs, code)
      implicit = detect_implicit_params(code)
      explicit = detect_explicit_params(docs)
      # Override implicit parameters with explicit ones
      params = []
      [implicit.length, explicit.length].max.times do |i|
        im = implicit[i] || {}
        ex = explicit[i] || {}
        params << {
          :type => ex[:type] || im[:type],
          :name => ex[:name] || im[:name],
          :doc => ex[:doc] || im[:doc],
        }
      end
      params
    end

    def detect_implicit_params(code)
      if code[:type] == :function
        code[:params]
      elsif code[:type] == :assignment && code[:right] && code[:right][:type] == :function
        code[:right][:params]
      else
        []
      end
    end

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

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

    # Build map of at-tags for quick lookup
    def build_doc_map(docs)
      map = {}
      docs.each do |tag|
        if map[tag[:tagname]]
          map[tag[:tagname]] << tag
        else
          map[tag[:tagname]] = [tag]
        end
      end
      map
    end
  end

end
+13 −269
Original line number Diff line number Diff line
@@ -4,7 +4,7 @@ require "test/unit"
class TestDocCommentParser < Test::Unit::TestCase

  def parse_single(doc)
    return JsDuck::DocCommentParser.new.parse(doc)[0]
    return JsDuck::DocCommentParser.new.parse(doc)
  end

  def test_method
@@ -12,284 +12,28 @@ class TestDocCommentParser < Test::Unit::TestCase
 * @method foo
 * Some docs.
 * @param {Number} x doc for x
 * @param {Integer} y doc for y
 * @return {String} resulting value
 */")
    assert_equal("foo", doc[:method][:name])
    assert_equal("Some docs.", doc[:method][:doc])
    assert_equal(:method, doc[0][:tagname])
    assert_equal("foo", doc[0][:name])
    assert_equal("Some docs.", doc[0][:doc])

    assert_equal(2, doc[:param].length)
    assert_equal(:param, doc[1][:tagname])
    assert_equal("x", doc[1][:name])
    assert_equal("Number", doc[1][:type])
    assert_equal("doc for x", doc[1][:doc])

    assert_equal("x", doc[:param][0][:name])
    assert_equal("Number", doc[:param][0][:type])
    assert_equal("doc for x", doc[:param][0][:doc])

    assert_equal("y", doc[:param][1][:name])
    assert_equal("Integer", doc[:param][1][:type])
    assert_equal("doc for y", doc[:param][1][:doc])

    assert_equal("String", doc[:return][:type])
    assert_equal("resulting value", doc[:return][:doc])
  end

  def test_description_can_precede_method_tag
    doc = parse_single("/**
 * Method description
 * @param foo
 * @method blah
 * @return {String}
 */")
    assert_equal("blah", doc[:method][:name])
    assert_equal("Method description", doc[:method][:doc])
    assert_equal("foo", doc[:param][0][:name])
    assert_equal("String", doc[:return][:type])
  end

  def test_constructor
    doc = parse_single("/**
 * @constructor
 * Some docs.
 */")
    assert_equal("constructor", doc[:method][:name])
    assert_equal("Some docs.", doc[:method][:doc])
  end

  def test_class
    doc = parse_single("/**
 * @class my.package.Foo
 * @extends my.Bar
 * Some docs.
 */")
    assert_equal("my.package.Foo", doc[:class][:name])
    assert_equal("my.Bar", doc[:class][:extends])
    assert_equal("Some docs.", doc[:class][:doc])
  end

  def test_extends_implies_class
    doc = parse_single("/**
 * Class description
 * @extends my.Bar
 */")
    assert_equal("my.Bar", doc[:class][:extends])
    assert_equal("Class description", doc[:class][:doc])
  end

  def test_singleton_implies_class
    doc = parse_single("/**
 * Class description
 * @singleton
 */")
    assert_equal(true, doc[:class][:singleton])
    assert_equal("Class description", doc[:class][:doc])
  end

  def test_event
    doc = parse_single("/**
 * @event mousedown
 * Fires when the mouse button is depressed.
 */")
    assert_equal("mousedown", doc[:event][:name])
    assert_equal("Fires when the mouse button is depressed.", doc[:event][:doc])
  end

  def test_cfg
    doc = parse_single("/**
 * @cfg {Boolean} enabled
 * True to enable this.
 */")
    assert_equal("enabled", doc[:cfg][:name])
    assert_equal("Boolean", doc[:cfg][:type])
    assert_equal("True to enable this.", doc[:cfg][:doc])
  end

  def test_property
    doc = parse_single("/**
 * @property {Boolean} enabled
 * True when enabled.
 */")
    assert_equal("enabled", doc[:property][:name])
    assert_equal("Boolean", doc[:property][:type])
    assert_equal("True when enabled.", doc[:property][:doc])
  end

  def test_description_can_precede_property_tag
    doc = parse_single("/**
 * Property description
 * @property {Number} foo
 */")
    assert_equal("Property description", doc[:property][:doc])
    assert_equal("foo", doc[:property][:name])
    assert_equal("Number", doc[:property][:type])
  end

  def test_type
    doc = parse_single("/**
 * @property foo
 * @type {Boolean}
 * This is property
 */")
    assert_equal("foo", doc[:property][:name])
    assert_equal("Boolean", doc[:property][:type])
    assert_equal("This is property", doc[:property][:doc])
    assert_equal(:return, doc[2][:tagname])
    assert_equal("String", doc[2][:type])
    assert_equal("resulting value", doc[2][:doc])
  end

  def test_type_without_curlies
    doc = parse_single("/**
 * @property
 * @type Boolean|String
 */")
    assert_equal("Boolean|String", doc[:property][:type])
  end

  def test_type_implies_property
    doc = parse_single("/**
 * This is property
 * @type {Boolean}
 */")
    assert_equal("Boolean", doc[:property][:type])
    assert_equal("This is property", doc[:property][:doc])
  end

  def test_long_docs
    doc = parse_single("/**
 * @method foo
 *
 * Some docs.
 *
 * Nice docs.
 *
 * @param {Number} x some
 * long
 * docs.
 * @return {String} more
 * long
 * docs.
 */")
    assert_equal("Some docs.\n\nNice docs.", doc[:method][:doc])
    assert_equal("some\nlong\ndocs.", doc[:param][0][:doc])
    assert_equal("more\nlong\ndocs.", doc[:return][:doc])
  end

  def test_typeless_docs
    doc = parse_single("/**
 * @param x doc1
 * @return doc2
 */")
    assert_equal("x", doc[:param][0][:name])
    assert_equal("doc1", doc[:param][0][:doc])
    assert_equal("doc2", doc[:return][:doc])

    assert_equal(nil, doc[:param][0][:type])
    assert_equal(nil, doc[:return][:type])
  end

  def test_nameless_method
    doc = parse_single("/**
 * @method
 * Comment for this func.
 */")
    assert_equal(nil, doc[:method][:name])
    assert_equal("Comment for this func.", doc[:method][:doc])
  end

  def test_nameless_class
    doc = parse_single("/**
 * @class
 * Comment for this class.
 */")
    assert_equal(nil, doc[:class][:name])
    assert_equal("Comment for this class.", doc[:class][:doc])
  end

  def test_nameless_event
    doc = parse_single("/**
 * @event
 * Comment for event.
 */")
    assert_equal(nil, doc[:event][:name])
    assert_equal("Comment for event.", doc[:event][:doc])
  end

  def test_nameless_cfg
    doc = parse_single("/**
 * @cfg
 * Config comment.
 */")
    assert_equal(nil, doc[:cfg][:name])
    assert_equal("Config comment.", doc[:cfg][:doc])
  end

  def test_nameless_param
    doc = parse_single("/**
 * @param
 * My parameter.
 */")
    assert_equal(nil, doc[:param][0][:name])
    assert_equal("My parameter.", doc[:param][0][:doc])
  end

  def test_class_modifiers
    doc = parse_single("/**
 * @class Foo
 * Blah blah
 * @singleton
 * @private
 * @ignore
 * @hide
 */")
    assert(doc[:class][:singleton])
    assert_modifiers(doc[:class])
  end

  def test_method_modifiers
    doc = parse_single("/**
 * @method foo
 * Some method
 * @param {type} x
 * @private
 * @ignore
 * @hide
 */")
    assert_modifiers(doc[:method])
  end

  def test_event_modifiers
    doc = parse_single("/**
 * @event foo
 * Some prop
 * @param {type} x
 * @private
 * @ignore
 * @hide
 */")
    assert_modifiers(doc[:event])
  end

  def test_property_modifiers
    doc = parse_single("/**
 * @property foo
 * Some prop
 * @private
 * @ignore
 * @hide
 */")
    assert_modifiers(doc[:property])
  end

  def test_default_modifiers
    doc = parse_single("/**
 * Some docs
 * @private
 * @ignore
 * @hide
 */")
    assert_modifiers(doc[:default])
  end

  def assert_modifiers(tag)
    assert(tag[:private])
    assert(tag[:ignore])
    assert(tag[:hide])
    assert_equal(:type, doc[0][:tagname])
    assert_equal("Boolean|String", doc[0][:type])
  end

end
Loading