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

Separated parser-part from DocComment class.

parent 96478894
Loading
Loading
Loading
Loading
+2 −198
Original line number Diff line number Diff line
require 'strscan'
require 'pp'

module JsDuck

  class DocComment
    def initialize(input)
      @current_tag = {:doc => ""}
      @tags = {:default => @current_tag}
      parse(purify(input))
    def initialize(tags)
      @tags = tags
    end

    # Sets the name property of the default at-tag.
@@ -53,198 +49,6 @@ module JsDuck
    def [](tagname)
      @tags[tagname]
    end

    # Extracts content inside /** ... */
    def purify(input)
      result = []
      input.each_line do |line|
        line.chomp!
        if line =~ /\A\/\*\*/ || line =~ /\*\/\Z/ then
          # ignore first and last line
        elsif line =~ /\A\s*\*\s?(.*)\Z/ then
          result << $1
        else
          result << line
        end
      end
      return result.join("\n")
    end

    def parse(input)
      @input = StringScanner.new(input)
      while !@input.eos? do
        if look(/@class\b/) then
          at_class
        elsif look(/@extends\b/) then
          at_extends
        elsif look(/@event\b/) then
          at_event
        elsif look(/@function\b/) then
          at_function
        elsif look(/@constructor\b/) then
          at_constructor
        elsif look(/@param\b/) then
          at_param
        elsif look(/@return\b/) then
          at_return
        elsif look(/@cfg\b/) then
          at_cfg
        elsif look(/@/) then
          @current_tag[:doc] += @input.scan(/@/)
        elsif look(/[^@]/) then
          @current_tag[:doc] += @input.scan(/[^@]+/)
        end
      end
      trim_docs
    end

    # The parsing process can leave whitespace at the ends of
    # doc-strings, here we get rid of it.
    def trim_docs
      # 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/)
      @current_tag = @tags[:class] = {:doc => ""}
      skip_white
      if look(/\w/) then
        @current_tag[:name] = ident_chain
      end
      skip_white
    end

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

    # matches @event name ...
    def at_event
      match(/@event/)
      @current_tag = @tags[:event] = {:doc => ""}
      skip_white
      if look(/\w/) then
        @current_tag[:name] = ident
      end
      skip_white
    end

    # matches @function name ...
    def at_function
      match(/@function/)
      @current_tag = @tags[:function] = {:doc => ""}
      skip_white
      if look(/\w/) then
        @current_tag[:name] = ident
      end
      skip_white
    end

    # matches @constructor ...
    # Which is equivalent of: @function constructor ...
    def at_constructor
      match(/@constructor/)
      @current_tag = @tags[:function] = {: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
      skip_white
      if look(/\{/) then
        @current_tag[:type] = typedef
      end
      skip_white
      if look(/\w/) then
        @current_tag[:name] = ident
      end
      skip_white
    end

    # matches @return {type} ...
    def at_return
      match(/@return/)
      @current_tag = @tags[:return] = {:doc => ""}
      skip_white
      if look(/\{/) then
        @current_tag[:type] = typedef
      end
      skip_white
    end

    # matches @cfg {type} name ...
    def at_cfg
      match(/@cfg/)
      @current_tag = @tags[:cfg] = {:doc => ""}
      skip_white
      if look(/\{/) then
        @current_tag[:type] = typedef
      end
      skip_white
      if look(/\w/) then
        @current_tag[:name] = ident
      end
      skip_white
    end

    # matches {...} and returns text inside brackets
    def typedef
      match(/\{/)
      name = @input.scan(/[^}]+/)
      match(/\}/)
      return name
    end

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

    # matches identifier and returns its name
    def ident
      @input.scan(/\w+/)
    end

    def look(re)
      @input.check(re)
    end

    def match(re)
      @input.scan(re)
    end

    def skip_white
      @input.scan(/\s+/)
    end

    def print
      pp @tags
    end
  end

end

doc_comment_parser.rb

0 → 100644
+206 −0
Original line number Diff line number Diff line
require 'strscan'

module JsDuck

  class DocCommentParser
    def parse(input)
      @current_tag = {:doc => ""}
      @tags = {:default => @current_tag}
      @input = StringScanner.new(purify(input))
      parse_loop
      trim_docs
      @tags
    end

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

    # Extracts content inside /** ... */
    def purify(input)
      result = []
      input.each_line do |line|
        line.chomp!
        if line =~ /\A\/\*\*/ || line =~ /\*\/\Z/ then
          # ignore first and last line
        elsif line =~ /\A\s*\*\s?(.*)\Z/ then
          result << $1
        else
          result << line
        end
      end
      return result.join("\n")
    end

    def parse_loop
      while !@input.eos? do
        if look(/@class\b/) then
          at_class
        elsif look(/@extends\b/) then
          at_extends
        elsif look(/@event\b/) then
          at_event
        elsif look(/@function\b/) then
          at_function
        elsif look(/@constructor\b/) then
          at_constructor
        elsif look(/@param\b/) then
          at_param
        elsif look(/@return\b/) then
          at_return
        elsif look(/@cfg\b/) then
          at_cfg
        elsif look(/@/) then
          @current_tag[:doc] += @input.scan(/@/)
        elsif look(/[^@]/) then
          @current_tag[:doc] += @input.scan(/[^@]+/)
        end
      end
    end

    # The parsing process can leave whitespace at the ends of
    # doc-strings, here we get rid of it.
    def trim_docs
      # 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/)
      @current_tag = @tags[:class] = {:doc => ""}
      skip_white
      if look(/\w/) then
        @current_tag[:name] = ident_chain
      end
      skip_white
    end

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

    # matches @event name ...
    def at_event
      match(/@event/)
      @current_tag = @tags[:event] = {:doc => ""}
      skip_white
      if look(/\w/) then
        @current_tag[:name] = ident
      end
      skip_white
    end

    # matches @function name ...
    def at_function
      match(/@function/)
      @current_tag = @tags[:function] = {:doc => ""}
      skip_white
      if look(/\w/) then
        @current_tag[:name] = ident
      end
      skip_white
    end

    # matches @constructor ...
    # Which is equivalent of: @function constructor ...
    def at_constructor
      match(/@constructor/)
      @current_tag = @tags[:function] = {: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
      skip_white
      if look(/\{/) then
        @current_tag[:type] = typedef
      end
      skip_white
      if look(/\w/) then
        @current_tag[:name] = ident
      end
      skip_white
    end

    # matches @return {type} ...
    def at_return
      match(/@return/)
      @current_tag = @tags[:return] = {:doc => ""}
      skip_white
      if look(/\{/) then
        @current_tag[:type] = typedef
      end
      skip_white
    end

    # matches @cfg {type} name ...
    def at_cfg
      match(/@cfg/)
      @current_tag = @tags[:cfg] = {:doc => ""}
      skip_white
      if look(/\{/) then
        @current_tag[:type] = typedef
      end
      skip_white
      if look(/\w/) then
        @current_tag[:name] = ident
      end
      skip_white
    end

    # matches {...} and returns text inside brackets
    def typedef
      match(/\{/)
      name = @input.scan(/[^}]+/)
      match(/\}/)
      return name
    end

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

    # matches identifier and returns its name
    def ident
      @input.scan(/\w+/)
    end

    def look(re)
      @input.check(re)
    end

    def match(re)
      @input.scan(re)
    end

    def skip_white
      @input.scan(/\s+/)
    end
  end

end
+3 −1
Original line number Diff line number Diff line
require 'lexer'
require 'doc_comment_parser'
require 'doc_comment'

module JsDuck
@@ -6,13 +7,14 @@ module JsDuck
  class Parser
    def initialize(input)
      @lex = Lexer.new(input)
      @doc_parser = DocCommentParser.new
      @docs = []
    end

    def parse
      while !@lex.empty? do
        if look(:doc_comment) then
          doc = DocComment.new(match(:doc_comment))
          doc = DocComment.new(@doc_parser.parse(match(:doc_comment)))
          block = code_block
          if block[:type] == :function then
            doc.set_default_name(*block[:name]) if block[:name]
+13 −9
Original line number Diff line number Diff line
require "doc_comment"
require "doc_comment_parser"
require "test/unit"

class TestDocComment < Test::Unit::TestCase
class TestDocCommentParser < Test::Unit::TestCase

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

  def test_function
    doc = JsDuck::DocComment.new("/**
    doc = parse_single("/**
 * @function foo
 * Some docs.
 * @param {Number} x doc for x
@@ -29,7 +33,7 @@ class TestDocComment < Test::Unit::TestCase
  end

  def test_constructor
    doc = JsDuck::DocComment.new("/**
    doc = parse_single("/**
 * @constructor
 * Some docs.
 */")
@@ -38,7 +42,7 @@ class TestDocComment < Test::Unit::TestCase
  end

  def test_class
    doc = JsDuck::DocComment.new("/**
    doc = parse_single("/**
 * @class my.package.Foo
 * @extends my.Bar
 * Some docs.
@@ -49,7 +53,7 @@ class TestDocComment < Test::Unit::TestCase
  end

  def test_event
    doc = JsDuck::DocComment.new("/**
    doc = parse_single("/**
 * @event mousedown
 * Fires when the mouse button is depressed.
 */")
@@ -58,7 +62,7 @@ class TestDocComment < Test::Unit::TestCase
  end

  def test_cfg
    doc = JsDuck::DocComment.new("/**
    doc = parse_single("/**
 * @cfg {Boolean} enabled
 * True to enable this.
 */")
@@ -68,7 +72,7 @@ class TestDocComment < Test::Unit::TestCase
  end

  def test_long_docs
    doc = JsDuck::DocComment.new("/**
    doc = parse_single("/**
 * @function foo
 *
 * Some docs.
@@ -88,7 +92,7 @@ class TestDocComment < Test::Unit::TestCase
  end

  def test_typeless_docs
    doc = JsDuck::DocComment.new("/**
    doc = parse_single("/**
 * @param x doc1
 * @return doc2
 */")