Commit 6db4e07d authored by Rene Saarsoo's avatar Rene Saarsoo
Browse files

Doc item type detection using EsprimaParser.

A separate DocType class that does the detection and tests for it.
parent da8770af
Loading
Loading
Loading
Loading

lib/jsduck/doc_type.rb

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

  # Detects the type of documentation object: class, method, cfg, etc
  class DocType
    # 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
    #
    def detect(docs, ast)
      doc_map = build_doc_map(docs)
      ast = ast || {}

      if doc_map[:class]
        :class
      elsif doc_map[:event]
        :event
      elsif doc_map[:method]
        :method
      elsif doc_map[:property] || doc_map[:type]
        :property
      elsif doc_map[:css_var]
        :css_var
      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

        if exp && call?(exp) && ext_define?(exp["callee"])
          :class
        elsif exp && assignment?(exp) && class_name?(exp["left"])
          :class
        elsif function?(ast) && class_name?(ast["id"])
          :class
        elsif ast[:type] == :css_mixin
          :css_mixin
        elsif doc_map[:cfg]
          :cfg
        elsif function?(ast)
          :method
        elsif exp && assignment?(exp) && function?(exp["right"])
          :method
        elsif doc_map[:return] || doc_map[:param]
          :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"
    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 = {}
      docs.each do |tag|
        if map[tag[:tagname]]
          map[tag[:tagname]] << tag
        else
          map[tag[:tagname]] = [tag]
        end
      end
      map
    end
  end

end

spec/doc_type_spec.rb

0 → 100644
+109 −0
Original line number Diff line number Diff line
require "jsduck/doc_type"
require "jsduck/esprima_parser"
require "jsduck/css_parser"
require "jsduck/doc_parser"

describe JsDuck::DocType do
  def detect(string, type = :js)
    if type == :css
      node = JsDuck::CssParser.new(string).parse[0]
    else
      node = JsDuck::EsprimaParser.new(string).parse[0]
    end

    doc_parser = JsDuck::DocParser.new
    node[:comment] = doc_parser.parse(node[:comment])
    return JsDuck::DocType.new.detect(node[:comment], node[:code])
  end

  describe "detects as class" do
    it "@class tag" do
      detect("/** @class */").should == :class
    end

    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 "Ext.extend()" do
      detect("/** */ 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 "@method tag" do
      detect("/** @method */").should == :method
    end

    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
  end

  describe "detects as event" do
    it "@event tag" do
      detect("/** @event */").should == :event
    end
  end

  describe "detects as config" do
    it "@cfg tag" do
      detect("/** @cfg */").should == :cfg
    end
  end

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

    it "@type tag" do
      detect("/** @type Foo */").should == :property
    end

    it "empty doc-comment with no code" do
      detect("/** */").should == :property
    end
  end

  describe "detects as css variable" do
    it "@var tag" do
      detect("/** @var */").should == :css_var
    end
  end

  describe "detects as css mixin" do
    it "@mixin in code" do
      detect("/** */ @mixin foo-bar {}", :css).should == :css_mixin
    end
  end

end