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

Use StandardTagParser for @return, @throws, @type.

The parsing now takes a config parameter to turn on/off parsing of Type
and Name sections.

This cleans up the DocScanner class considerably as all those complex
methods are now in StandardTagParser, leaving the DocScanner with
basics such as #look and #match, plus the big #standard_tag.
parent f09ba43f
Loading
Loading
Loading
Loading
+17 −119
Original line number Diff line number Diff line
@@ -18,9 +18,6 @@ module JsDuck
      @input = nil # set to StringScanner in subclass
    end

    # Provides access to the tag that's currently being parsed
    attr_reader :current_tag

    # Provides access to StringScanner
    attr_reader :input

@@ -35,119 +32,15 @@ module JsDuck
      @current_tag[:doc] = "" unless @current_tag.has_key?(:doc)
    end

    # Parses standard pattern governing several builtin tags.
    # Parses standard pattern common in several builtin tags, which
    # goes like this:
    #
    #     @tag {Type} [some.name=default]
    #
    def standard_tag(tagdef)
      StandardTagParser.new(self).parse(tagdef)
    end

    # matches {type} if possible and sets it on @current_tag
    # Also checks for {optionality=} in type definition.
    def maybe_type
      skip_horiz_white
      if look(/\{/)
        tdf = typedef
        @current_tag[:type] = tdf[:type]
        @current_tag[:optional] = true if tdf[:optional]
      end
    end

    # matches: <ident-chain> | "[" <ident-chain> [ "=" <default-value> ] "]"
    def maybe_name_with_default
      skip_horiz_white
      if look(/\[/)
        match(/\[/)
        maybe_ident_chain(:name)
        skip_horiz_white
        if look(/=/)
          match(/=/)
          skip_horiz_white
          @current_tag[:default] = default_value
        end
        skip_horiz_white
        match(/\]/)
        @current_tag[:optional] = true
      else
        maybe_ident_chain(:name)
      end
    end

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

    # Attempts to allow balanced braces in default value.
    # When the nested parsing doesn't finish at closing "]",
    # roll back to beginning and simply grab anything up to closing "]".
    def default_value
      start_pos = @input.pos
      value = parse_balanced(/\[/, /\]/, /[^\[\]'"]*/)
      if look(/\]/)
        value
      else
        @input.pos = start_pos
        match(/[^\]]*/)
      end
    end

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

      name = parse_balanced(/\{/, /\}/, /[^{}'"]*/)

      if name =~ /=$/
        name = name.chop
        optional = true
      else
        optional = nil
      end

      match(/\}/)

      return {:type => name, :optional => optional}
    end

    # Helper method to parse a string up to a closing brace,
    # balancing opening-closing braces in between.
    # See StandardTagParser#parse for details.
    #
    # @param re_open  The beginning brace regex
    # @param re_close The closing brace regex
    # @param re_rest  Regex to match text without any braces and strings
    def parse_balanced(re_open, re_close, re_rest)
      result = parse_with_strings(re_rest)
      while look(re_open)
        result += match(re_open)
        result += parse_balanced(re_open, re_close, re_rest)
        result += match(re_close)
        result += parse_with_strings(re_rest)
      end
      result
    end

    # Helper for parse_balanced to parse rest of the text between
    # braces, taking account the strings which might occur there.
    def parse_with_strings(re_rest)
      result = match(re_rest)
      while look(/['"]/)
        result += parse_string('"') if look(/"/)
        result += parse_string("'") if look(/'/)
        result += match(re_rest)
      end
      result
    end

    # Parses "..." or '...' including the escape sequence \' or '\"
    def parse_string(quote)
      re_quote = Regexp.new(quote)
      re_rest = Regexp.new("(?:[^"+quote+"\\\\]|\\\\.)*")
      match(re_quote) + match(re_rest) + (match(re_quote) || "")
    def standard_tag(cfg)
      StandardTagParser.new(self).parse(cfg)
    end

    # matches chained.identifier.name and returns it
@@ -160,25 +53,30 @@ module JsDuck
      @input.scan(@ident_pattern)
    end

    # Looks for the existance of pattern.  Returns true on success.
    # Doesn't advance the scan pointer.
    def look(re)
      @input.check(re)
    end

    # Matches the given pattern and advances the scan pointer
    # returning the string that matched.  When the pattern doesn't
    # match, nil is returned.
    def match(re)
      @input.scan(re)
    end

    # Skips all whitespace.  Moves scan pointer to next non-whitespace
    # character.
    def skip_white
      @input.scan(/\s+/)
    end

    # skips horizontal whitespace (tabs and spaces)
    def skip_horiz_white
      @input.scan(/[ \t]+/)
    end
    # Shorthand alias
    # Skips horizontal whitespace (tabs and spaces).  Moves scan
    # pointer to next non-whitespace character or to the end of line.
    # Returns self to allow chaining.
    def hw
      skip_horiz_white
      @input.scan(/[ \t]+/)
      self
    end

+22 −6
Original line number Diff line number Diff line
@@ -11,15 +11,31 @@ module JsDuck
      @ds = doc_scanner
    end

    # Parses our tag.
    def parse(tagdef)
      tag = tagdef
      add_type(tag)
      add_name_with_default(tag)
    # Parses the standard tag pattern.
    #
    # Takes as parameter a configuration hash which can contain the
    # following keys:
    #
    # - :tagname => The :tagname of the hash to return.
    #
    # - :type => True to parse {Type} section.
    #            Produces :type and :optional keys.
    #
    # - :name => Trye to parse [some.name=default] section.
    #            Produces :name, :default and :optional keys.
    #
    # Returns tag definition hash containing the given :tagname and a
    # set of other fields depending on whether :type and :name configs
    # were specified and how their matching succeeded.
    #
    def parse(cfg)
      tag = {:tagname => cfg[:tagname]}
      add_type(tag) if cfg[:type]
      add_name_with_default(tag) if cfg[:name]
      tag
    end

    # matches {type} if possible and sets it on @current_tag
    # matches {type} if possible and sets it on given tag hash.
    # Also checks for {optionality=} in type definition.
    def add_type(tag)
      if hw.look(/\{/)
+1 −1
Original line number Diff line number Diff line
@@ -9,7 +9,7 @@ module JsDuck::Tag

    # @cfg {Type} [name=default] (required) ...
    def parse(p)
      tag = p.standard_tag({:tagname => :cfg})
      tag = p.standard_tag({:tagname => :cfg, :type => true, :name => true})
      tag[:optional] = false if parse_required(p)
      tag
    end
+1 −1
Original line number Diff line number Diff line
@@ -9,7 +9,7 @@ module JsDuck::Tag

    # @var {Type} [name=default] ...
    def parse(p)
      p.standard_tag({:tagname => :css_var})
      p.standard_tag({:tagname => :css_var, :type => true, :name => true})
    end
  end
end
+1 −1
Original line number Diff line number Diff line
@@ -9,7 +9,7 @@ module JsDuck::Tag
    # @enum {Type} [name=default] ...
    def parse(p)
      # @enum is a special case of class
      tag = p.standard_tag({:tagname => :class})
      tag = p.standard_tag({:tagname => :class, :type => true, :name => true})
      tag[:enum] = true
      tag
    end
Loading