class JsDuck::Js::RKellyAdapter

Converts RKelly AST into Esprima AST.

Constants

ASSIGNMENT_NODES
BINARY_NODES
FALL_THROUGH_ARRAY_NODES
FALL_THROUGH_NODES
LOGICAL_NODES
STRING_ESCAPES
UNARY_NODES

Public Instance Methods

adapt(node) click to toggle source
# File lib/jsduck/js/rkelly_adapter.rb, line 8
def adapt(node)
  ast = adapt_root(node)
  ast["comments"] = node.comments.map {|c| adapt_comment(c) }
  ast
end

Private Instance Methods

adapt_comment(comment) click to toggle source
# File lib/jsduck/js/rkelly_adapter.rb, line 16
def adapt_comment(comment)
  if comment.value =~ /\A\/\*/
    {
      "type" => "Block",
      "value" => comment.value.sub(/\A\/\*/, "").sub(/\*\/\z/, ""),
      "range" => [comment.range.from.index, comment.range.to.index+1],
    }
  else
    {
      "type" => "Line",
      "value" => comment.value.sub(/\A\/\//, ""),
      "range" => [comment.range.from.index, comment.range.to.index+1],
    }
  end
end
adapt_node(node) click to toggle source
# File lib/jsduck/js/rkelly_adapter.rb, line 39
def adapt_node(node)
  case
  # Empty node
  when node.nil?
    nil

  # Fall-through nodes
  when FALL_THROUGH_NODES[node.class]
    adapt_node(node.value)
  when FALL_THROUGH_ARRAY_NODES[node.class]
    node.value.map {|v| adapt_node(v) }

  # Identifiers
  when RKelly::Nodes::ResolveNode == node.class
    make(node, {
      "type" => "Identifier",
      "name" => node.value,
    })

  # Literals
  when RKelly::Nodes::NumberNode == node.class
    make(node, {
      "type" => "Literal",
      "value" => node.value,
      "raw" => node.value.to_s,
    })
  when RKelly::Nodes::StringNode == node.class
    make(node, {
      "type" => "Literal",
      "value" => string_value(node.value),
      "raw" => node.value,
    })
  when RKelly::Nodes::RegexpNode == node.class
    make(node, {
      "type" => "Literal",
      "value" => node.value,
      "raw" => node.value,
    })
  when RKelly::Nodes::TrueNode == node.class
    make(node, {
      "type" => "Literal",
      "value" => true,
      "raw" => "true",
    })
  when RKelly::Nodes::FalseNode == node.class
    make(node, {
      "type" => "Literal",
      "value" => false,
      "raw" => "false",
    })
  when RKelly::Nodes::NullNode == node.class
    make(node, {
      "type" => "Literal",
      "value" => nil,
      "raw" => "null",
    })

  # Expressions
  when BINARY_NODES[node.class]
    make(node, {
      "type" => "BinaryExpression",
      "operator" => BINARY_NODES[node.class],
      "left" => adapt_node(node.left),
      "right" => adapt_node(node.value),
    })
  when LOGICAL_NODES[node.class]
    make(node, {
      "type" => "LogicalExpression",
      "operator" => LOGICAL_NODES[node.class],
      "left" => adapt_node(node.left),
      "right" => adapt_node(node.value),
    })
  when UNARY_NODES[node.class]
    make(node, {
      "type" => "UnaryExpression",
      "operator" => UNARY_NODES[node.class],
      "argument" => adapt_node(node.value),
    })
  when ASSIGNMENT_NODES[node.class]
    make(node, {
      "type" => "AssignmentExpression",
      "operator" => ASSIGNMENT_NODES[node.class],
      "left" => adapt_node(node.left),
      "right" => adapt_node(node.value),
    })
  when RKelly::Nodes::FunctionExprNode == node.class
    make(node, {
      "type" => "FunctionExpression",
      "id" => node.value == "function" ? nil : {
          "type" => "Identifier",
          "name" => node.value,
          "range" => offset_range(node, :value, "function "),
        },
      "params" => node.arguments.map {|a| adapt_node(a) },
      "body" => adapt_node(node.function_body),
    })
  when RKelly::Nodes::ThisNode == node.class
    make(node, {
      "type" => "ThisExpression",
    })
  when RKelly::Nodes::DotAccessorNode == node.class
    make(node, {
      "type" => "MemberExpression",
      "computed" => false,
      "object" => adapt_node(node.value),
      "property" => {
          "type" => "Identifier",
          "name" => node.accessor,
          "range" => offset_range(node, :accessor),
        },
    })
  when RKelly::Nodes::BracketAccessorNode == node.class
    make(node, {
      "type" => "MemberExpression",
      "computed" => true,
      "object" => adapt_node(node.value),
      "property" => adapt_node(node.accessor),
    })
  when RKelly::Nodes::FunctionCallNode == node.class
    make(node, {
      "type" => "CallExpression",
      "callee" => adapt_node(node.value),
      "arguments" => adapt_node(node.arguments),
    })
  when RKelly::Nodes::NewExprNode == node.class
    make(node, {
      "type" => "NewExpression",
      "callee" => adapt_node(node.value),
      "arguments" => adapt_node(node.arguments),
    })
  when RKelly::Nodes::PrefixNode == node.class
    make(node, {
      "type" => "UpdateExpression",
      "operator" => node.value,
      "argument" => adapt_node(node.operand),
      "prefix" => true,
    })
  when RKelly::Nodes::PostfixNode == node.class
    make(node, {
      "type" => "UpdateExpression",
      "operator" => node.value,
      "argument" => adapt_node(node.operand),
      "prefix" => false,
    })
  when RKelly::Nodes::ParameterNode == node.class
    make(node, {
      "type" => "Identifier",
      "name" => node.value,
    })
  when RKelly::Nodes::ConditionalNode == node.class
    make(node, {
      "type" => "ConditionalExpression",
      "test" => adapt_node(node.conditions),
      "consequent" => adapt_node(node.value),
      "alternate" => adapt_node(node.else),
    })
  when RKelly::Nodes::CaseClauseNode == node.class
    make(node, {
      "type" => "SwitchCase",
      "test" => adapt_node(node.left),
      "consequent" => adapt_node(node.value),
    })
  when RKelly::Nodes::CommaNode == node.class
    make(node, {
      "type" => "SequenceExpression",
      "expressions" => flatten_sequence(node).map {|v| adapt_node(v) },
    })
  when RKelly::Nodes::ArrayNode == node.class
    make(node, {
      "type" => "ArrayExpression",
      "elements" => node.value.map {|v| adapt_node(v) },
    })
  when RKelly::Nodes::ObjectLiteralNode == node.class
    make(node, {
      "type" => "ObjectExpression",
      "properties" => node.value.map {|v| adapt_node(v) },
    })
  when RKelly::Nodes::PropertyNode == node.class
    make(node, {
      "type" => "Property",
      "key" => property_key(node),
      "value" => adapt_node(node.value),
      "kind" => "init",
    })
  when RKelly::Nodes::GetterPropertyNode == node.class
    make(node, {
      "type" => "Property",
      "key" => property_key(node),
      "value" => adapt_node(node.value),
      "kind" => "get",
    })
  when RKelly::Nodes::SetterPropertyNode == node.class
    make(node, {
      "type" => "Property",
      "key" => property_key(node),
      "value" => adapt_node(node.value),
      "kind" => "set",
    })
  # Statements
  when RKelly::Nodes::ExpressionStatementNode == node.class
    make(node, {
      "type" => "ExpressionStatement",
      "expression" => adapt_node(node.value),
    })
  when RKelly::Nodes::IfNode == node.class
    make(node, {
      "type" => "IfStatement",
      "test" => adapt_node(node.conditions),
      "consequent" => adapt_node(node.value),
      "alternate" => adapt_node(node.else),
    })
  when RKelly::Nodes::WhileNode == node.class
    make(node, {
      "type" => "WhileStatement",
      "test" => adapt_node(node.left),
      "body" => adapt_node(node.value),
    })
  when RKelly::Nodes::DoWhileNode == node.class
    make(node, {
      "type" => "DoWhileStatement",
      "test" => adapt_node(node.value),
      "body" => adapt_node(node.left),
    })
  when RKelly::Nodes::ForNode == node.class
    make(node, {
      "type" => "ForStatement",
      "init" => adapt_node(node.init),
      "test" => adapt_node(node.test),
      "update" => adapt_node(node.counter),
      "body" => adapt_node(node.value),
    })
  when RKelly::Nodes::ForInNode == node.class
    make(node, {
      "type" => "ForInStatement",
      "left" =>
        if node.left.class == RKelly::Nodes::VarDeclNode
          make(node.left, {
              "type" => "VariableDeclaration",
              "kind" => "var",
              "declarations" => [adapt_node(node.left)]
            })
        else
          adapt_node(node.left)
        end,
      "right" => adapt_node(node.right),
      "body" => adapt_node(node.value),
      "each" => false,
    })
  when RKelly::Nodes::WithNode == node.class
    make(node, {
      "type" => "WithStatement",
      "object" => adapt_node(node.left),
      "body" => adapt_node(node.value),
    })
  when RKelly::Nodes::SwitchNode == node.class
    make(node, {
      "type" => "SwitchStatement",
      "discriminant" => adapt_node(node.left),
      "cases" => adapt_node(node.value),
    })
  when RKelly::Nodes::ReturnNode == node.class
    make(node, {
      "type" => "ReturnStatement",
      "argument" => adapt_node(node.value),
    })
  when RKelly::Nodes::BreakNode == node.class
    make(node, {
      "type" => "BreakStatement",
      "label" => node.value ? {
          "type" => "Identifier",
          "name" => node.value,
          "range" => offset_range(node, :value, "break "),
        } : nil,
    })
  when RKelly::Nodes::ContinueNode == node.class
    make(node, {
      "type" => "ContinueStatement",
      "label" => node.value ? {
          "type" => "Identifier",
          "name" => node.value,
          "range" => offset_range(node, :value),
        } : nil,
    })
  when RKelly::Nodes::TryNode == node.class
    make(node, {
      "type" => "TryStatement",
      "block" => adapt_node(node.value),
      "guardedHandlers" => [],
      "handlers" => node.catch_block ? [catch_clause(node)] : [],
      "finalizer" => adapt_node(node.finally_block),
    })
  when RKelly::Nodes::ThrowNode == node.class
    make(node, {
      "type" => "ThrowStatement",
      "argument" => adapt_node(node.value),
    })
  when RKelly::Nodes::LabelNode == node.class
    make(node, {
      "type" => "LabeledStatement",
      "label" => {
          "type" => "Identifier",
          "name" => node.name,
          "range" => offset_range(node, :name),
        },
      "body" => adapt_node(node.value),
    })
  when RKelly::Nodes::BlockNode == node.class
    make(node, {
      "type" => "BlockStatement",
      "body" => adapt_node(node.value),
    })
  when RKelly::Nodes::FunctionBodyNode == node.class
    make(node, {
      "type" => "BlockStatement",
      "body" => adapt_node(node.value),
    })
  when RKelly::Nodes::EmptyStatementNode == node.class
    if node.value == "debugger"
      make(node, {
        "type" => "DebuggerStatement",
      })
    else
      make(node, {
        "type" => "EmptyStatement",
      })
    end

  # Declarations
  when RKelly::Nodes::VarStatementNode == node.class
    make(node, {
      "type" => "VariableDeclaration",
      "kind" => "var",
      "declarations" => node.value.map {|v| adapt_node(v) },
    })
  when RKelly::Nodes::ConstStatementNode == node.class
    make(node, {
      "type" => "VariableDeclaration",
      "kind" => "const",
      "declarations" => node.value.map {|v| adapt_node(v) },
    })
  when RKelly::Nodes::VarDeclNode == node.class
    make(node, {
      "type" => "VariableDeclarator",
      "id" => {
          "type" => "Identifier",
          "name" => node.name,
          "range" => offset_range(node, :name),
        },
      "init" => adapt_node(node.value),
    })
  when RKelly::Nodes::FunctionDeclNode == node.class
    make(node, {
      "type" => "FunctionDeclaration",
      "id" => {
          "type" => "Identifier",
          "name" => node.value,
          "range" => offset_range(node, :value, "function "),
        },
      "params" => node.arguments.map {|a| adapt_node(a) },
      "body" => adapt_node(node.function_body),
    })

  else
    # Unexpected node type
    node
  end
end
adapt_root(node) click to toggle source
# File lib/jsduck/js/rkelly_adapter.rb, line 32
def adapt_root(node)
  make(node, {
    "type" => "Program",
    "body" => adapt_node(node),
  })
end
catch_clause(node) click to toggle source
# File lib/jsduck/js/rkelly_adapter.rb, line 432
def catch_clause(node)
  {
    "type" => "CatchClause",
    "param" => {
        "type" => "Identifier",
        "name" => node.catch_var,
        "range" => [
          node.catch_block.range.from.index - (") ".length + node.catch_var.length),
          node.catch_block.range.from.index - (") ".length),
          node.catch_block.range.from.line,
        ]
      },
    "body" => adapt_node(node.catch_block),
    "range" => [
      node.catch_block.range.from.index - ("catch () ".length + node.catch_var.length),
      node.catch_block.range.to.index+1,
      node.catch_block.range.from.line,
    ]
  }
end
flatten_sequence(node) click to toggle source
# File lib/jsduck/js/rkelly_adapter.rb, line 424
def flatten_sequence(node)
  if node.is_a?(RKelly::Nodes::CommaNode)
    [flatten_sequence(node.left), flatten_sequence(node.value)].flatten
  else
    node
  end
end
make(node, config) click to toggle source

augments node data with range info.

# File lib/jsduck/js/rkelly_adapter.rb, line 408
def make(node, config)
  config["range"] = [node.range.from.index, node.range.to.index+1, node.range.from.line]
  config
end
nr_to_str(nr, original) click to toggle source

Converts a latin1 character code to UTF-8 string. When running in Ruby <= 1.8, only converts ASCII chars, others are left as escape sequences.

# File lib/jsduck/js/rkelly_adapter.rb, line 498
def nr_to_str(nr, original)
  str = nr.chr
  if str.respond_to?(:encode)
    str.encode('UTF-8', 'ISO-8859-1')
  elsif nr < 127
    str
  else
    original
  end
end
offset_range(node, field, prefix="") click to toggle source

Calculates “range” array from the start position of the node, its field and given offset prefix (amount of characters to discard from the beginning).

# File lib/jsduck/js/rkelly_adapter.rb, line 416
def offset_range(node, field, prefix="")
  line = node.range.from.line
  i = node.range.from.index
  offset = prefix.length
  length = node.send(field).to_s.length
  return [i + offset, i + offset + length, line]
end
property_key(node) click to toggle source
# File lib/jsduck/js/rkelly_adapter.rb, line 453
def property_key(node)
  if node.name.is_a?(Numeric)
    {
      "type" => "Literal",
      "value" => node.name,
      "raw" => node.name.to_s,
      "range" => offset_range(node, :name),
    }
  elsif node.name =~ /['"]/
    {
      "type" => "Literal",
      "value" => string_value(node.name),
      "raw" => node.name,
      "range" => offset_range(node, :name),
    }
  else
    {
      "type" => "Identifier",
      "name" => node.name,
      "range" => offset_range(node, :name),
    }
  end
end
string_value(string) click to toggle source

Evaluates the actual value of a JavaScript string. Importantly we avoid using Ruby's eval().

# File lib/jsduck/js/rkelly_adapter.rb, line 479
def string_value(string)
  string[1..-2].gsub(/\\([0-9]{1,3}|x[0-9A-F]{1,2}|u[0-9A-F]{4}|.)/) do |s|
    if STRING_ESCAPES[s]
      STRING_ESCAPES[s]
    elsif s =~ /^\\[0-9]/
      nr_to_str(s[1..-1].oct, s)
    elsif s =~ /^\\x[0-9A-F]/
      nr_to_str(s[2..-1].hex, s)
    elsif s =~ /^\\u[0-9A-F]/
      [s[2..-1].hex].pack("U")
    else
      s[1, 1]
    end
  end
end