Commit 4a1c740c authored by Rene Saarsoo's avatar Rene Saarsoo
Browse files

Start implementing FunctionAst class.

To auto-detect return types of functions from source code.

This version is able to auto-detect if a simple function without any
control flow statements returns this.
parent 564f7871
Loading
Loading
Loading
Loading
+72 −0
Original line number Diff line number Diff line
require "jsduck/serializer"
require "jsduck/evaluator"

module JsDuck

  # Analyzes the AST of a FunctionDeclaration or FunctionExpression.
  class FunctionAst
    # Detects from function body what the function returns.
    def returns(ast)
      if ast && function?(ast)
        body_returns(ast["body"]["body"])
      else
        nil
      end
    end

    private

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

    def body_returns(body)
      body = skip_non_control_flow_statements(body)

      if body.length > 0 && return_this?(body[0])
        "this"
      else
        nil
      end
    end

    def return_this?(ast)
      return?(ast) && this?(ast["argument"])
    end

    def return?(ast)
      ast["type"] == "ReturnStatement"
    end

    def this?(ast)
      ast["type"] == "ThisExpression"
    end

    def skip_non_control_flow_statements(statements)
      i = statements.find_index {|s| control_flow?(s) }
      if i
        statements.slice(i, statements.length)
      else
        []
      end
    end

    def control_flow?(ast)
      [
        "IfStatement",
        "SwitchStatement",
        "ForStatement",
        "ForInStatement",
        "WhileStatement",
        "DoWhileStatement",
        "ReturnStatement",
        "TryStatement",
        "WithStatement",
        "LabeledStatement",
        "BlockStatement",
      ].include?(ast["type"])
    end
  end

end
+50 −0
Original line number Diff line number Diff line
require "jsduck/js_parser"
require "jsduck/function_ast"

describe "JsDuck::FunctionAst#returns" do
  def returns(string)
    node = JsDuck::JsParser.new(string).parse[0]
    return JsDuck::FunctionAst.new.returns(node[:code])
  end

  it "fails when no AST given at all" do
    returns("/** */").should == nil
  end

  it "fails when no function AST given" do
    returns("/** */ Ext.emptyFn;").should == nil
  end

  it "fails when body has no return statement." do
    returns("/** */ function foo() {}").should == nil
  end

  it "returns this when single return this statement in body" do
    returns("/** */ function foo() {return this;}").should == "this"
  end

  it "returns this when return this after a few expression statements" do
    returns(<<-EOJS).should == "this"
      /** */
      function foo() {
          doSomething();
          i++;
          truthy ? foo() : bar();
          return this;
      }
    EOJS
  end

  it "returns this when return this after a few declarations" do
    returns(<<-EOJS).should == "this"
      /** */
      function foo() {
          var x = 10;
          function blah() {
          }
          return this;
      }
    EOJS
  end

end