Commit 0de62d51 authored by Rene Saarsoo's avatar Rene Saarsoo
Browse files

New SCSS parser using the official SASS parser.

parent ac86f13b
Loading
Loading
Loading
Loading
+79 −0
Original line number Diff line number Diff line
require 'sass'

module JsDuck
  module Css

    # Parses SCSS using the official SASS parser.
    class SassParser
      def initialize(input, options = {})
        @input = input
        @docs = []
      end

      # Returns an array of docsets like the Js::Parser does.
      def parse
        root = Sass::Engine.new(@input, :syntax => :scss).to_tree
        find_doc_comments(root.children)
        @docs
      end

      private

      def find_doc_comments(nodes)
        prev_comment = nil

        nodes.each do |node|
          if node.class == Sass::Tree::CommentNode
            if node.type == :normal && node.value[0] =~ /\A\/\*\*/
              prev_comment = node.value[0]
            else
              prev_comment = nil
            end
          elsif prev_comment
            @docs << {
              :comment => prev_comment,
              :linenr => 0,
              :code => analyze_code(node),
              :type => :doc_comment,
            }
            prev_comment = nil
          end

          find_doc_comments(node.children)
        end
      end

      def analyze_code(node)
        if node.class == Sass::Tree::VariableNode
          return {
            :tagname => :css_var,
            :name => "$" + node.name,
            :default => node.expr.to_s,
            :type => "*",
          }
        elsif node.class == Sass::Tree::MixinDefNode
          return {
            :tagname => :css_mixin,
            :name => node.name,
            :params => build_params(node.args),
          }
        else
          # Default to property like in Js::Parser.
          return {:tagname => :property}
        end
      end

      def build_params(mixin_args)
        mixin_args.map do |arg|
          {
            :name => "$" + arg[0].name,
            :default => arg[1] ? arg[1].to_s : nil,
            :type => "*",
          }
        end
      end

    end

  end
end
+143 −0
Original line number Diff line number Diff line
require 'jsduck/css/sass_parser'

describe JsDuck::Css::SassParser do

  def parse(string)
    JsDuck::Css::SassParser.new(string).parse
  end

  describe "parsing empty string" do
    let(:docs) { parse("") }

    it "finds no documentation" do
      docs.length.should == 0
    end
  end

  describe "parsing SCSS without doc-comments" do
    let(:docs) do
      parse(<<-EOCSS)
        // some comment
        a:href { color: green; }
        /* Shallalalaaa */
        $foo: 10em !default;
        /*! Goul */
        @mixin goul {
            font-weight: bold;
        }
      EOCSS
    end

    it "finds no documentation" do
      docs.length.should == 0
    end
  end

  describe "parsing SCSS with lots of doc-comments" do
    let(:docs) do
      parse(<<-EOCSS)
        /** some comment */
        a:href { color: green; }
        /** Shallalalaaa */
        $foo: 10em !default;
        /** Goul */
        @mixin goul {
            /** Me too! */
            font-weight: bold;
        }
      EOCSS
    end

    it "finds them all" do
      docs.length.should == 4
    end
  end

  describe "parsing SCSS variable" do
    let(:var) do
      parse(<<-EOCSS)[0]
        /** My variable */
        $foo: 10em !default;
      EOCSS
    end

    it "detects comment" do
      var[:comment].should == "/** My variable */"
    end

    it "detects :css_var type" do
      var[:code][:tagname].should == :css_var
    end

    it "detects name" do
      var[:code][:name].should == "$foo"
    end

    it "detects default value" do
      var[:code][:default].should == "10em"
    end
  end

  describe "parsing SCSS mixin" do
    let(:var) do
      parse(<<-EOCSS)[0]
        /** My mixin */
        @mixin foo($alpha, $beta: 2px) {
            color: $alpha;
        }
      EOCSS
    end

    it "detects comment" do
      var[:comment].should == "/** My mixin */"
    end

    it "detects :css_mixin type" do
      var[:code][:tagname].should == :css_mixin
    end

    it "detects name" do
      var[:code][:name].should == "foo"
    end

    it "detects correct number of parameters" do
      var[:code][:params].length.should == 2
    end

    it "detects name of first param" do
      var[:code][:params][0][:name].should == "$alpha"
    end

    it "detects no default value for first param" do
      var[:code][:params][0][:default].should == nil
    end

    it "detects name of second param" do
      var[:code][:params][1][:name].should == "$beta"
    end

    it "detects default value for second param" do
      var[:code][:params][1][:default].should == "2px"
    end
  end

  describe "parsing other SCSS code" do
    let(:var) do
      parse(<<-EOCSS)[0]
        /** My docs */
        .some-class a:href {
            color: #0f0;
        }
      EOCSS
    end

    it "detects comment" do
      var[:comment].should == "/** My docs */"
    end

    it "detects code as :property" do
      var[:code][:tagname].should == :property
    end
  end

end