class JsDuck::Js::Returns

Analyzes the AST of a Function for possible return values.

Constants

BOOLEAN_RETURNING_OPERATORS
CONTROL_FLOW
POSSIBLY_BLOCKING

Public Instance Methods

chainable?(ast) click to toggle source

True when function always finishes with returning this.

# File lib/jsduck/js/returns.rb, line 11
def chainable?(ast)
  detect(ast) == [:this]
end
detect(ast) click to toggle source

Detects possible return types of the given function.

For now there are three possible detected return values:

  • :this - the code contins 'return this;'

  • “undefined” - the code finishes by returning undefined or without explicitly returning anything

  • :other - some other value is returned.

# File lib/jsduck/js/returns.rb, line 26
def detect(ast)
  h = return_types_hash(ast["body"]["body"])

  # Replace the special :void value that signifies possibility of
  # exiting without explicitly returning anything
  if h[:void]
    h["undefined"] = true
    h.delete(:void)
  end

  h.keys
end

Private Instance Methods

boolean?(ast) click to toggle source
# File lib/jsduck/js/returns.rb, line 103
def boolean?(ast)
  if boolean_literal?(ast)
    true
  elsif ast["type"] == "UnaryExpression" || ast["type"] == "BinaryExpression"
    !!BOOLEAN_RETURNING_OPERATORS[ast["operator"]]
  elsif ast["type"] == "LogicalExpression"
    boolean?(ast["left"]) && boolean?(ast["right"])
  elsif ast["type"] == "ConditionalExpression"
    boolean?(ast["consequent"]) && boolean?(ast["alternate"])
  elsif ast["type"] == "AssignmentExpression" && ast["operator"] == "="
    boolean?(ast["right"])
  else
    false
  end
end
boolean_literal?(ast) click to toggle source
# File lib/jsduck/js/returns.rb, line 119
def boolean_literal?(ast)
  ast["type"] == "Literal" && (ast["value"] == true || ast["value"] == false)
end
control_flow?(ast) click to toggle source
# File lib/jsduck/js/returns.rb, line 143
def control_flow?(ast)
  CONTROL_FLOW[ast["type"]]
end
extract_bodies(ast) click to toggle source
# File lib/jsduck/js/returns.rb, line 147
def extract_bodies(ast)
  body = []
  CONTROL_FLOW[ast["type"]].each do |name|
    statements = ast[name]
    if statements.is_a?(Hash)
      body << [statements]
    else
      body << Array(statements)
    end
  end
  body
end
possibly_blocking?(ast) click to toggle source

True if the node is a control structure which will block further program flow when all its branches finish with a return statement.

# File lib/jsduck/js/returns.rb, line 163
def possibly_blocking?(ast)
  if POSSIBLY_BLOCKING[ast["type"]]
    CONTROL_FLOW[ast["type"]].all? {|key| ast[key] }
  else
    false
  end
end
regexp?(ast) click to toggle source
# File lib/jsduck/js/returns.rb, line 139
def regexp?(ast)
  ast["type"] == "Literal" && ast["raw"] =~ /^\//
end
return?(ast) click to toggle source
# File lib/jsduck/js/returns.rb, line 69
def return?(ast)
  ast["type"] == "ReturnStatement"
end
return_types_hash(body) click to toggle source
# File lib/jsduck/js/returns.rb, line 41
def return_types_hash(body)
  rvalues = {}
  body.each do |ast|
    if return?(ast)
      type = value_type(ast["argument"])
      rvalues[type] = true
      return rvalues
    elsif possibly_blocking?(ast)
      extract_bodies(ast).each do |b|
        rvalues.merge!(return_types_hash(b))
      end
      if !rvalues[:void]
        return rvalues
      else
        rvalues.delete(:void)
      end
    elsif control_flow?(ast)
      extract_bodies(ast).each do |b|
        rvalues.merge!(return_types_hash(b))
      end
      rvalues.delete(:void)
    end
  end

  rvalues[:void] = true
  return rvalues
end
string?(ast) click to toggle source
# File lib/jsduck/js/returns.rb, line 123
def string?(ast)
  if string_literal?(ast)
    true
  elsif ast["type"] == "BinaryExpression" && ast["operator"] == "+"
    string?(ast["left"]) || string?(ast["right"])
  elsif ast["type"] == "UnaryExpression" && ast["operator"] == "typeof"
    true
  else
    false
  end
end
string_literal?(ast) click to toggle source
# File lib/jsduck/js/returns.rb, line 135
def string_literal?(ast)
  ast["type"] == "Literal" && ast["value"].is_a?(String)
end
this?(ast) click to toggle source
# File lib/jsduck/js/returns.rb, line 99
def this?(ast)
  ast["type"] == "ThisExpression"
end
undefined?(ast) click to toggle source
# File lib/jsduck/js/returns.rb, line 91
def undefined?(ast)
  ast["type"] == "Identifier" && ast["name"] == "undefined"
end
value_type(ast) click to toggle source
# File lib/jsduck/js/returns.rb, line 73
def value_type(ast)
  if !ast
    :void
  elsif undefined?(ast) || void?(ast)
    "undefined"
  elsif this?(ast)
    :this
  elsif boolean?(ast)
    "Boolean"
  elsif regexp?(ast)
    "RegExp"
  elsif string?(ast)
    "String"
  else
    :other
  end
end
void?(ast) click to toggle source
# File lib/jsduck/js/returns.rb, line 95
def void?(ast)
  ast["type"] == "UnaryExpression" && ast["operator"] == "void"
end