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

Extract Ast class from DocType class.

Give the responsibility of analyzing Esprima AST tree to separate class.
Then in DocType class simply use the simple results from both DocParser
and Ast to do very simple detection of doc item type.
parent 005bd5b0
Loading
Loading
Loading
Loading

lib/jsduck/ast.rb

0 → 100644
+93 −0
Original line number Diff line number Diff line
module JsDuck

  # Analyzes the AST produced by EsprimaParser.
  class Ast
    # Given parsed code, returns the tagname for documentation item.
    #
    # @param ast :code from Result of EsprimaParser
    # @returns One of: :class, :method, :event, :cfg, :property, :css_var
    #
    def detect(ast)
      ast = ast || {}

      exp = expression?(ast) ? ast["expression"] : nil
      var = var?(ast) ? ast["declarations"][0] : nil

      if exp && call?(exp) && ext_define?(exp["callee"])
        :class
      elsif exp && assignment?(exp) && class_name?(exp["left"])
        :class
      elsif var && class_name?(var["id"])
        :class
      elsif function?(ast) && class_name?(ast["id"])
        :class
      elsif function?(ast)
        :method
      elsif exp && assignment?(exp) && function?(exp["right"])
        :method
      elsif var && function?(var["init"])
        :method
      elsif property?(ast) && function?(ast["value"])
        :method
      else
        :property
      end
    end

    private

    def expression?(ast)
      ast["type"] == "ExpressionStatement"
    end

    def call?(ast)
      ast["type"] == "CallExpression"
    end

    def assignment?(ast)
      ast["type"] == "AssignmentExpression"
    end

    def ext_define?(callee)
      ["Ext.define", "Ext.ClassManager.create"].include?(to_s(callee))
    end

    def function?(ast)
      ast["type"] == "FunctionDeclaration" || ast["type"] == "FunctionExpression" || empty_fn?(ast)
    end

    def empty_fn?(ast)
      ast["type"] == "MemberExpression" && to_s(ast) == "Ext.emptyFn"
    end

    def var?(ast)
      ast["type"] == "VariableDeclaration"
    end

    def property?(ast)
      ast["type"] == "Property"
    end

    # Class name begins with upcase char
    def class_name?(ast)
      return to_s(ast).split(/\./).last =~ /\A[A-Z]/
    end

    def to_s(ast)
      case ast["type"]
      when "MemberExpression"
        if ast["computed"]
          to_s(ast["object"]) + "[" + to_s(ast["property"]) + "]"
        else
          to_s(ast["object"]) + "." + to_s(ast["property"])
        end
      when "Identifier"
        ast["name"]
      when "Literal"
        ast["value"]
      end
    end
  end

end
+13 −85
Original line number Diff line number Diff line
@@ -5,13 +5,12 @@ module JsDuck
    # Given parsed documentation and code, returns the tagname for
    # documentation item.
    #
    # @param docs parsing result from DocParser
    # @param ast :code from Result of EsprimaParser
    # @returns One of: :class, :method, :event, :cfg, :property, :css_var
    # @param docs Result from DocParser
    # @param code Result from Ast#detect or CssParser#parse
    # @returns One of: :class, :method, :event, :cfg, :property, :css_var, :css_mixin
    #
    def detect(docs, ast)
    def detect(docs, code)
      doc_map = build_doc_map(docs)
      ast = ast || {}

      if doc_map[:class]
        :class
@@ -26,92 +25,21 @@ module JsDuck
      elsif doc_map[:cfg] && doc_map[:cfg].length == 1
        # When just one @cfg, avoid treating it as @class
        :cfg
      else
        exp = expression?(ast) ? ast["expression"] : nil
        var = var?(ast) ? ast["declarations"][0] : nil

        if exp && call?(exp) && ext_define?(exp["callee"])
          :class
        elsif exp && assignment?(exp) && class_name?(exp["left"])
          :class
        elsif var && class_name?(var["id"])
          :class
        elsif function?(ast) && class_name?(ast["id"])
      elsif code[:type] == :class
        :class
        elsif ast[:type] == :css_mixin
      elsif code[:type] == :css_mixin
        :css_mixin
      elsif doc_map[:cfg]
        :cfg
        elsif function?(ast)
          :method
        elsif exp && assignment?(exp) && function?(exp["right"])
          :method
        elsif var && function?(var["init"])
          :method
        elsif property?(ast) && function?(ast["value"])
          :method
        elsif doc_map[:return] || doc_map[:param]
      elsif code[:type] == :method
        :method
      else
        :property
      end
    end
    end

    private

    def expression?(ast)
      ast["type"] == "ExpressionStatement"
    end

    def call?(ast)
      ast["type"] == "CallExpression"
    end

    def assignment?(ast)
      ast["type"] == "AssignmentExpression"
    end

    def ext_define?(callee)
      ["Ext.define", "Ext.ClassManager.create"].include?(to_s(callee))
    end

    def function?(ast)
      ast["type"] == "FunctionDeclaration" || ast["type"] == "FunctionExpression" || empty_fn?(ast)
    end

    def empty_fn?(ast)
      ast["type"] == "MemberExpression" && to_s(ast) == "Ext.emptyFn"
    end

    def var?(ast)
      ast["type"] == "VariableDeclaration"
    end

    def property?(ast)
      ast["type"] == "Property"
    end

    # Class name begins with upcase char
    def class_name?(ast)
      return to_s(ast).split(/\./).last =~ /\A[A-Z]/
    end

    def to_s(ast)
      case ast["type"]
      when "MemberExpression"
        if ast["computed"]
          to_s(ast["object"]) + "[" + to_s(ast["property"]) + "]"
        else
          to_s(ast["object"]) + "." + to_s(ast["property"])
        end
      when "Identifier"
        ast["name"]
      when "Literal"
        ast["value"]
      end
    end

    # Build map of at-tags for quick lookup
    def build_doc_map(docs)
      map = {}

spec/ast_spec.rb

0 → 100644
+112 −0
Original line number Diff line number Diff line
require "jsduck/ast"
require "jsduck/esprima_parser"

describe JsDuck::Ast do
  def detect(string)
    node = JsDuck::EsprimaParser.new(string).parse[0]
    return JsDuck::Ast.new.detect(node[:code])
  end

  describe "detects as class" do
    it "function beginning with uppercase letter" do
      detect("/** */ function MyClass() {}").should == :class
    end

    it "function assignment to uppercase name" do
      detect("/** */ MyClass = function() {}").should == :class
    end

    it "function assignment to uppercase property" do
      detect("/** */ foo.MyClass = function() {}").should == :class
    end

    it "uppercase var initialization with function" do
      detect("/** */ var MyClass = function() {}").should == :class
    end

    it "Ext.extend()" do
      detect("/** */ MyClass = Ext.extend(Your.Class, {  });").should == :class
    end

    it "var initialized with Ext.extend()" do
      detect("/** */ var MyClass = Ext.extend(Your.Class, {  });").should == :class
    end

    it "Ext.define()" do
      detect(<<-EOS).should == :class
        /** */
        Ext.define('MyClass', {
        });
      EOS
    end

    it "Ext.ClassManager.create()" do
      detect(<<-EOS).should == :class
        /** */
        Ext.ClassManager.create('MyClass', {
        });
      EOS
    end
  end

  describe "detects as method" do
    it "function beginning with underscore" do
      detect("/** */ function _Foo() {}").should == :method
    end

    it "lowercase function name" do
      detect("/** */ function foo() {}").should == :method
    end

    it "assignment of function" do
      detect("/** */ foo = function() {}").should == :method
    end

    it "assignment of Ext.emptyFn" do
      detect("/** */ foo = Ext.emptyFn").should == :method
    end

    it "var initialized with function" do
      detect("/** */ var foo = function() {}").should == :method
    end

    it "vari initialized with Ext.emptyFn" do
      detect("/** */ var foo = Ext.emptyFn").should == :method
    end

    it "object property initialized with function" do
      detect(<<-EOS).should == :method
        Foo = {
            /** */
            bar: function(){}
        };
      EOS
    end

    it "object property in comma-first notation initialized with function" do
      detect(<<-EOS).should == :method
        Foo = {
            foo: 5
            /** */
            , bar: function(){}
        };
      EOS
    end

    it "object property initialized with Ext.emptyFn" do
      detect(<<-EOS).should == :method
        Foo = {
            /** */
            bar: Ext.emptyFn
        };
      EOS
    end
  end

  describe "detects as property" do
    it "no code" do
      detect("/** */").should == :property
    end
  end

end
+4 −78
Original line number Diff line number Diff line
require "jsduck/ast"
require "jsduck/doc_type"
require "jsduck/esprima_parser"
require "jsduck/css_parser"
@@ -9,6 +10,7 @@ describe JsDuck::DocType do
      node = JsDuck::CssParser.new(string).parse[0]
    else
      node = JsDuck::EsprimaParser.new(string).parse[0]
      node[:code] = { :type => JsDuck::Ast.new.detect(node[:code]) }
    end

    doc_parser = JsDuck::DocParser.new
@@ -21,30 +23,10 @@ describe JsDuck::DocType do
      detect("/** @class */").should == :class
    end

    it "function beginning with uppercase letter" do
    it "class-like function" do
      detect("/** */ function MyClass() {}").should == :class
    end

    it "function assignment to uppercase name" do
      detect("/** */ MyClass = function() {}").should == :class
    end

    it "function assignment to uppercase property" do
      detect("/** */ foo.MyClass = function() {}").should == :class
    end

    it "uppercase var initialization with function" do
      detect("/** */ var MyClass = function() {}").should == :class
    end

    it "Ext.extend()" do
      detect("/** */ MyClass = Ext.extend(Your.Class, {  });").should == :class
    end

    it "var initialized with Ext.extend()" do
      detect("/** */ var MyClass = Ext.extend(Your.Class, {  });").should == :class
    end

    it "Ext.define()" do
      detect(<<-EOS).should == :class
        /** */
@@ -52,14 +34,6 @@ describe JsDuck::DocType do
        });
      EOS
    end

    it "Ext.ClassManager.create()" do
      detect(<<-EOS).should == :class
        /** */
        Ext.ClassManager.create('MyClass', {
        });
      EOS
    end
  end

  describe "detects as method" do
@@ -67,57 +41,9 @@ describe JsDuck::DocType do
      detect("/** @method */").should == :method
    end

    it "function beginning with underscore" do
      detect("/** */ function _Foo() {}").should == :method
    end

    it "lowercase function name" do
    it "function declaration" do
      detect("/** */ function foo() {}").should == :method
    end

    it "assignment of function" do
      detect("/** */ foo = function() {}").should == :method
    end

    it "assignment of Ext.emptyFn" do
      detect("/** */ foo = Ext.emptyFn").should == :method
    end

    it "var initialized with function" do
      detect("/** */ var foo = function() {}").should == :method
    end

    it "vari initialized with Ext.emptyFn" do
      detect("/** */ var foo = Ext.emptyFn").should == :method
    end

    it "object property initialized with function" do
      detect(<<-EOS).should == :method
        Foo = {
            /** */
            bar: function(){}
        };
      EOS
    end

    it "object property in comma-first notation initialized with function" do
      detect(<<-EOS).should == :method
        Foo = {
            foo: 5
            /** */
            , bar: function(){}
        };
      EOS
    end

    it "object property initialized with Ext.emptyFn" do
      detect(<<-EOS).should == :method
        Foo = {
            /** */
            bar: Ext.emptyFn
        };
      EOS
    end
  end

  describe "detects as event" do