Commit 45eee369 authored by Rene Saarsoo's avatar Rene Saarsoo
Browse files

New Log::WarningsParser class for parsing --warnings.

parent 28924047
Loading
Loading
Loading
Loading
+166 −0
Original line number Diff line number Diff line
require 'strscan'

module JsDuck
  module Log

    # Parses the warnings passed in from command line
    #
    # Grammar:
    #
    # <warnings>     := <warning> [ "," <warning> ]*
    #
    # <warning>      := ["+" | "-"] <type> [<params-block>] [<path-block>]
    #
    # <type>         := \w+
    #
    # <params-block> := "(" [<params>] ")"
    #
    # <params>       := <param> [ "," <param> ]*
    #
    # <param>        := \w+ | ""
    #
    # <path-block>   := ":" <path>
    #
    # <path>         := .*
    #
    class WarningsParser
      def initialize(string)
        @scanner = StringScanner.new(string)
      end

      # Parses the warnings string.
      #
      # For example the following string:
      #
      #     +tag,-nodoc(class,private):/some/path
      #
      # is parsed into the following structure:
      #
      #     [
      #       {
      #         :type => :tag,
      #         :enabled => true,
      #         :params => [],
      #         :path => nil,
      #       },
      #       {
      #         :type => :nodoc,
      #         :enabled => false,
      #         :params => [:class, :private],
      #         :path => "/some/path",
      #       },
      #     ]
      #
      # When scanning fails, raises an exception with a descriptive
      # message.
      def parse
        results = []

        while !eos?
          results << warning
          match(/,/)
        end

        results
      end

      private

      def warning
        return {
          :enabled => enabled,
          :type => type,
          :params => params,
          :path => path,
        }
      end

      def enabled
        if match(/\+/)
          true
        elsif match(/-/)
          false
        else
          true
        end
      end

      def type
        require(/\w+/).to_sym
      end

      def params
        if match(/\(/)
          ps = []

          while !look(/\)/)
            ps << param
            break unless match(/,/)
          end

          require(/\)/)

          ps
        else
          []
        end
      end

      def param
        if p = match(/\w+/)
          p.to_sym
        elsif look(/,/)
          nil
        else
          unexpected_char
        end
      end

      def path
        if match(/:/)
          match(/[^,]*/).strip
        else
          nil
        end
      end

      # scans a pattern, throws error on failure
      def require(re)
        if m = match(re)
          m
        else
          unexpected_char
        end
      end

      # Reports unexpected character
      def unexpected_char
        # do successful empty scan, so we can use #pre_match and #post_match
        @scanner.scan(//)
        raise "Unexpected '#{@scanner.peek(1)}' at --warnings='#{@scanner.pre_match}<HERE>#{@scanner.post_match}'"
      end

      # scans a pattern, ignoring the optional whitespace before it
      def match(re)
        skip_ws
        @scanner.scan(re)
      end

      def look(re)
        skip_ws
        @scanner.check(re)
      end

      def eos?
        skip_ws
        @scanner.eos?
      end

      def skip_ws
        @scanner.scan(/\s*/)
      end

    end

  end
end
+7 −9
Original line number Diff line number Diff line
@@ -6,6 +6,7 @@ require 'jsduck/util/io'
require 'jsduck/util/parallel'
require 'jsduck/tag_registry'
require 'jsduck/js/ext_patterns'
require 'jsduck/log/warnings_parser'

module JsDuck

@@ -672,7 +673,7 @@ module JsDuck
          Logger.verbose = true
        end

        opts.on('--warnings=+A,-B,+C', Array,
        opts.on('--warnings=+A,-B,+C',
          "Turns warnings selectively on/off.",
          "",
          " +all - to turn on all warnings.",
@@ -696,15 +697,12 @@ module JsDuck
          "(Those with '+' in front of them default to on)",
          "",
          *Logger.doc_warnings) do |warnings|
          warnings.each do |op|
            # XXX: Can't rely any more on the Array type of OptionParser
            if op =~ /^([-+]?)(\w+)(?:\(([^)]*)\))?(?::(.*))?$/
              enable = !($1 == "-")
              name = $2.to_sym
              params = ($3 || "").split(/,/).map {|p| p.strip }.map {|p| (p == "") ? nil : p.to_sym }
              path = $4
              Logger.set_warning(name, enable, path, params)
          begin
            Log::WarningsParser.new(warnings).parse.each do |w|
              Logger.set_warning(w[:type], w[:enabled], w[:path], w[:params])
            end
          rescue Exception => e
            Logger.warn(nil, e.message)
          end
        end

+130 −0
Original line number Diff line number Diff line
require "jsduck/log/warnings_parser"

describe JsDuck::Log::WarningsParser do
  def parse(s)
    JsDuck::Log::WarningsParser.new(s).parse
  end

  describe "parsing empty string" do
    it "results in empty array" do
      parse("").should == []
    end
  end

  describe "parsing +foo,bar_bar,-baz" do
    let(:warnings) { parse("+foo, bar_bar, -baz") }

    it "results in 3 warning defs" do
      warnings.length.should == 3
    end

    describe "first" do
      let(:w) { warnings[0] }

      it "is of type :foo" do
        w[:type].should == :foo
      end

      it "is enabled" do
        w[:enabled].should == true
      end
    end

    describe "second" do
      let(:w) { warnings[1] }

      it "is of type :bar_bar" do
        w[:type].should == :bar_bar
      end

      it "is enabled" do
        w[:enabled].should == true
      end
    end

    describe "third" do
      let(:w) { warnings[2] }

      it "is of type :baz" do
        w[:type].should == :baz
      end

      it "is disabled" do
        w[:enabled].should == false
      end
    end
  end

  describe "parsing foo:/some/path" do
    let(:w) { parse("foo:/some/path ")[0] }

    it "detects path" do
      w[:path].should == "/some/path"
    end
  end

  describe "parsing two warnings with path" do
    let(:warnings) { parse("foo:/some/path,bar:/other/path") }

    it "detects two warnings" do
      warnings.length.should == 2
    end
  end

  describe "parsing nodoc(class,public)" do
    let(:w) { parse("nodoc(class,public)")[0] }

    it "detects params" do
      w[:params].should == [:class, :public]
    end
  end

  describe "parsing nodoc(,private)" do
    let(:w) { parse("nodoc(,private)")[0] }

    it "detects also empty params" do
      w[:params].should == [nil, :private]
    end
  end

  describe "parsing invalid warning type" do
    it "raises an exception" do
      begin
        parse("?123")
      rescue Exception => e
        e.message.should == "Unexpected '?' at --warnings='<HERE>?123'"
      end
    end
  end

  describe "parsing invalid stuff after warning type" do
    it "raises an exception" do
      begin
        parse("tag?123")
      rescue Exception => e
        e.message.should == "Unexpected '?' at --warnings='tag<HERE>?123'"
      end
    end
  end

  describe "parsing invalid warning param" do
    it "raises an exception" do
      begin
        parse("nodoc(?)")
      rescue Exception => e
        e.message.should == "Unexpected '?' at --warnings='nodoc(<HERE>?)'"
      end
    end
  end

  describe "parsing invalid stuff after warning param" do
    it "raises an exception" do
      begin
        parse("nodoc(foo?)")
      rescue Exception => e
        e.message.should == "Unexpected '?' at --warnings='nodoc(foo<HERE>?)'"
      end
    end
  end

end