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

Implement Esprima AST serializer.

A helper class that turns Esprima AST back to string.
parent 3f4b46e5
Loading
Loading
Loading
Loading
+185 −0
Original line number Diff line number Diff line
module JsDuck

  # Transforms Esprima AST into string
  class Serializer

    # Turns AST node into string
    def to_s(ast)
      case ast["type"]
      when "Program"
        ast["body"].map {|s| to_s(s) }.join

      # Statements

      when "BlockStatement"
        "{" + ast["body"].map {|s| to_s(s) }.join + "}"

      when "BreakStatement"
        "break" + (ast["label"] ? " " + to_s(ast["label"]) : "") + ";"

      when "ContinueStatement"
        "continue" + (ast["label"] ? " " + to_s(ast["label"]) : "") + ";"

      when "DoWhileStatement"
        "do " + to_s(ast["body"]) + " while (" + to_s(ast["test"]) + ");"

      when "DebuggerStatement"
        "debugger;"

      when "EmptyStatement"
        ";"

      when "ExpressionStatement"
        to_s(ast["expression"]) + ";"

      when "ForStatement"
        init = ast["init"] ? to_s(ast["init"]).sub(/;\Z/, "") : ""
        test = ast["test"] ? to_s(ast["test"]) : ""
        update = ast["update"] ? to_s(ast["update"]) : ""
        "for (" + init + "; " + test + "; " + update + ") " + to_s(ast["body"])

      when "ForInStatement"
        left = to_s(ast["left"]).sub(/;\Z/, "")
        right = to_s(ast["right"])
        "for (" + left + " in " + right + ") " + to_s(ast["body"])

      when "IfStatement"
        alternate = ast["alternate"] ? " else " + to_s(ast["alternate"]) : ""
        "if (" + to_s(ast["test"]) + ") " + to_s(ast["consequent"]) + alternate

      when "LabeledStatement"
        to_s(ast["label"]) + ": " + to_s(ast["body"])

      when "ReturnStatement"
        "return " + to_s(ast["argument"]) + ";"

      when "SwitchStatement"
        "switch (" + to_s(ast["discriminant"]) + ") {" + ast["cases"].map {|c| to_s(c) }.join + "}"

      when "SwitchCase"
        test = ast["test"] ? "case " + to_s(ast["test"]) : "default"
        test + ": " + ast["consequent"].map {|c| to_s(c) }.join

      when "ThrowStatement"
        "throw " + to_s(ast["argument"]) + ";"

      when "TryStatement"
        handlers = ast["handlers"].map {|h| to_s(h) }.join
        finalizer = ast["finalizer"] ? " finally " + to_s(ast["finalizer"]) : ""
        "try " + to_s(ast["block"]) + handlers + finalizer

      when "CatchClause"
        param = ast["param"] ? to_s(ast["param"]) : ""
        " catch (" + param + ") " + to_s(ast["body"])

      when "WhileStatement"
        "while (" + to_s(ast["test"]) + ") " + to_s(ast["body"])

      when "WithStatement"
        "with (" + to_s(ast["object"]) + ") " + to_s(ast["body"])


      # Declarations

      when "FunctionDeclaration"
        function(ast)

      when "VariableDeclaration"
        ast["kind"] + " " + list(ast["declarations"]) + ";"

      when "VariableDeclarator"
        if ast["init"]
          to_s(ast["id"]) + " = " + to_s(ast["init"])
        else
          to_s(ast["id"])
        end

      # Expressions

      when "AssignmentExpression"
        to_s(ast["left"]) + " " + ast["operator"] + " " + to_s(ast["right"])

      when "ArrayExpression"
        "[" + list(ast["elements"]) + "]"

      when "BinaryExpression"
        to_s(ast["left"]) + " " + ast["operator"] + " " + to_s(ast["right"])

      when "CallExpression"
        call(ast)

      when "ConditionalExpression"
        to_s(ast["test"]) + " ? " + to_s(ast["consequent"]) + " : " + to_s(ast["alternate"])

      when "FunctionExpression"
        function(ast)

      when "LogicalExpression"
        to_s(ast["left"]) + " " + ast["operator"] + " " + to_s(ast["right"])

      when "MemberExpression"
        if ast["computed"]
          to_s(ast["object"]) + "[" + to_s(ast["property"]) + "]"
        else
          to_s(ast["object"]) + "." + to_s(ast["property"])
        end

      when "NewExpression"
        "new " + call(ast)

      when "ObjectExpression"
        "{" + list(ast["properties"]) + "}"

      when "Property"
        to_s(ast["key"]) + ": " + to_s(ast["value"])

      when "SequenceExpression"
        list(ast["expressions"])

      when "ThisExpression"
        "this"

      when "UnaryExpression"
        ast["operator"] + to_s(ast["argument"])

      when "UpdateExpression"
        if ast["prefix"]
          ast["operator"] + to_s(ast["argument"])
        else
          to_s(ast["argument"]) + ast["operator"]
        end

      # Basics

      when "Identifier"
        ast["name"]

      when "Literal"
        ast["value"].to_s

      else
        throw "Unknown node type: "+ast["type"]
      end
    end

    # serializes function declaration or expression
    def function(ast)
      params = list(ast["params"])
      id = ast["id"] ? to_s(ast["id"]) : ""
      "function " + id + "(" + params + ") " + to_s(ast["body"])
    end

    # serializes list of comma-separated items
    def list(array)
      array.map {|x| to_s(x) }.join(", ")
    end

    # serializes call- and new-expression
    def call(ast)
      to_s(ast["callee"]) + "(" + list(ast["arguments"]) + ")"
    end

  end

end
+137 −0
Original line number Diff line number Diff line
require "jsduck/esprima_core"
require "jsduck/serializer"

describe JsDuck::Serializer do
  def to_s(string)
    ast = JsDuck::EsprimaCore.instance.parse(string)
    return JsDuck::Serializer.new.to_s(ast)
  end

  def test(string)
    to_s(string).should == string
  end

  describe "serializes" do
    it "empty program" do
      test("")
    end

    it "function declaration" do
      test("function foo(a, b, c) {return 0;}")
    end

    it "variable declaration" do
      test("var foo, bar = 5;")
    end

    it "variable declaration with let" do
      test("let foo = true;")
    end

    it "assignment of function expression" do
      test("foo = function (a) {};")
    end

    it "member expression with this" do
      test("this.foo.bar;")
    end

    it "computed member expression" do
      test("foo[bar];")
    end

    it "+= assignment" do
      test("x += 15;")
    end

    it "method call on array" do
      test("[1, 2, 3].map(max);")
    end

    it "object expression" do
      test("x = {foo: 10, bar: 42};")
    end

    it "new expression" do
      test("new Item();")
    end

    it "logical expression" do
      test("true || false;")
    end

    it "binary expression" do
      test("1 + 2 - 3;")
    end

    it "sequence expression" do
      test("foo, bar;")
    end

    it "update expression" do
      test("++c + c--;")
    end

    it "if statement" do
      test("if (true) alert(5);")
    end

    it "if else statement" do
      test("if (true) {} else {foo();}")
    end

    it "else if statement" do
      test("if (true) {} else if (false) {}")
    end

    it "switch statement" do
      test("switch (foo) {case 1: case 2: alert(1);break;default: alert(2);}")
    end

    it "for statement" do
      test("for (var i = 0; i < 10; i++) {}")
    end

    it "empty for statement" do
      test("for (; ; ) ;")
    end

    it "for in statement" do
      test("for (var key in obj) {}")
    end

    it "while statement" do
      test("while (true) {break;continue;}")
    end

    it "do while statement" do
      test("do {alert();} while (true);")
    end

    it "labeled statement" do
      test("foo: while (1) {break foo;continue foo;}")
    end

    it "debugger statement" do
      test("debugger;")
    end

    it "try catch statement" do
      test("try {throw foo;} catch (e) {alert(e);}")
    end

    it "try catch statement with empty catch" do
      test("try {} catch () {}")
    end

    it "try finally statement" do
      test("try {} finally {}")
    end

    it "with statement" do
      test("with (obj) {}")
    end

  end

end