# Astract Syntax Tree generation
# ------------------------------

# Recursively compare all values of enumerable properties of `expected` with
# those of `actual`. Use `looseArray` helper function to skip array length
# comparison.
deepStrictIncludeExpectedProperties = (actual, expected) ->
  eq actual.length, expected.length if expected instanceof Array and not expected.loose
  for key, val of expected
    if val? and typeof val is 'object'
      fail "Property #{reset}#{key}#{red} expected, but was missing" unless actual[key]
      deepStrictIncludeExpectedProperties actual[key], val
    else
      eq actual[key], val, """
        Property #{reset}#{key}#{red}: expected #{reset}#{actual[key]}#{red} to equal #{reset}#{val}#{red}
          Expected AST output to include:
          #{reset}#{inspect expected}#{red}
          but instead it was:
          #{reset}#{inspect actual}#{red}
      """
  actual

# Flag array for loose comparison. See reference to `.loose` in
# `deepStrictIncludeExpectedProperties` above.
looseArray = (arr) ->
  Object.defineProperty arr, 'loose',
    value: yes
    enumerable: no
  arr

testAgainstExpected = (ast, expected) ->
  if expected?
    deepStrictIncludeExpectedProperties ast, expected
  else
    # Convenience for creating new tests; call `testExpression` with no second
    # parameter to see what the current AST generation is for your input code.
    console.log inspect ast

testExpression = (code, expected) ->
  ast = getAstExpression code
  testAgainstExpected ast, expected

testStatement = (code, expected) ->
  ast = getAstStatement code
  testAgainstExpected ast, expected

testComments = (code, expected) ->
  ast = getAstRoot code
  testAgainstExpected ast.comments, expected

test 'Confirm functionality of `deepStrictIncludeExpectedProperties`', ->
  actual =
    name: 'Name'
    a:
      b: 1
      c: 2
    x: [1, 2, 3]

  check = (message, test, expected) ->
    test (-> deepStrictIncludeExpectedProperties actual, expected), null, message

  check 'Expected property does not match', throws,
    name: '"Name"'

  check 'Array length mismatch', throws,
    x: [1, 2]

  check 'Skip array length check', doesNotThrow,
    x: looseArray [
      1
      2
    ]

  check 'Array length matches', doesNotThrow,
    x: [1, 2, 3]

  check 'Array prop mismatch', throws,
    x: [3, 2, 1]

  check 'Partial object comparison', doesNotThrow,
    a:
      c: 2
    forbidden: undefined

  check 'Actual has forbidden prop', throws,
    a:
      b: 1
      c: undefined

  check 'Check prop for existence only', doesNotThrow,
    name: {}
    a: {}
    x: {}

  check 'Prop is missing', throws,
    missingProp: {}

# Shorthand helpers for common AST patterns.

EMPTY_BLOCK =
  type: 'BlockStatement'
  body: []
  directives: []

ID = (name, additionalProperties = {}) ->
  Object.assign({
    type: 'Identifier'
    name
  }, additionalProperties)

NUMBER = (value) -> {
  type: 'NumericLiteral'
  value
}

STRING = (value) -> {
  type: 'StringLiteral'
  value
}

# Check each node type in the same order as they appear in `nodes.coffee`.
# For nodes that have equivalents in Babel’s AST spec, we’re checking that
# the type and properties match. When relevant, also check that values of
# properties are as expected.

test "AST as expected for Block node", ->
  deepStrictIncludeExpectedProperties CoffeeScript.compile('a', ast: yes),
    type: 'File'
    program:
      type: 'Program'
      # sourceType: 'module'
      body: [
        type: 'ExpressionStatement'
        expression:
          type: 'Identifier'
      ]
      directives: []
    comments: []

  deepStrictIncludeExpectedProperties CoffeeScript.compile('# comment', ast: yes),
    type: 'File'
    program:
      type: 'Program'
      body: []
      directives: []
    comments: [
      type: 'CommentLine'
      value: ' comment'
    ]

  deepStrictIncludeExpectedProperties CoffeeScript.compile('', ast: yes),
    type: 'File'
    program:
      type: 'Program'
      body: []
      directives: []

  deepStrictIncludeExpectedProperties CoffeeScript.compile(' ', ast: yes),
    type: 'File'
    program:
      type: 'Program'
      body: []
      directives: []

test "AST as expected for NumberLiteral node", ->
  testExpression '42',
    type: 'NumericLiteral'
    value: 42
    extra:
      rawValue: 42
      raw: '42'

  testExpression '0xE1',
    type: 'NumericLiteral'
    value: 225
    extra:
      rawValue: 225
      raw: '0xE1'

  testExpression '10_000',
    type: 'NumericLiteral'
    value: 10000
    extra:
      rawValue: 10000
      raw: '10_000'

  testExpression '1_2.34_5e6_7',
    type: 'NumericLiteral'
    value: 12.345e67
    extra:
      rawValue: 12.345e67
      raw: '1_2.34_5e6_7'

  testExpression '0o7_7_7',
    type: 'NumericLiteral'
    value: 0o777
    extra:
      rawValue: 0o777
      raw: '0o7_7_7'

  testExpression '42n',
    type: 'BigIntLiteral'
    value: '42'
    extra:
      rawValue: '42'
      raw: '42n'

  testExpression '2e3_08',
    type: 'NumericLiteral'
    value: Infinity
    extra:
      rawValue: Infinity
      raw: '2e3_08'

test "AST as expected for InfinityLiteral node", ->
  testExpression 'Infinity',
    type: 'Identifier'
    name: 'Infinity'

  testExpression '2e308',
    type: 'NumericLiteral'
    value: Infinity
    extra:
      raw: '2e308'
      rawValue: Infinity

test "AST as expected for NaNLiteral node", ->
  testExpression 'NaN',
    type: 'Identifier'
    name: 'NaN'

test "AST as expected for StringLiteral node", ->
  # Just a standalone string literal would be treated as a directive,
  # so embed the string literal in an enclosing expression (e.g. a call).
  testExpression 'a "string cheese"',
    type: 'CallExpression'
    arguments: [
      type: 'StringLiteral'
      value: 'string cheese'
      extra:
        raw: '"string cheese"'
    ]

  testExpression "b 'cheese string'",
    type: 'CallExpression'
    arguments: [
      type: 'StringLiteral'
      value: 'cheese string'
      extra:
        raw: "'cheese string'"
    ]

  testExpression "'''heredoc'''",
    type: 'TemplateLiteral'
    expressions: []
    quasis: [
      type: 'TemplateElement'
      value:
        raw: 'heredoc'
      tail: yes
    ]
    quote: "'''"

test "AST as expected for PassthroughLiteral node", ->
  code = 'const CONSTANT = "unreassignable!"'
  testExpression "`#{code}`",
    type: 'PassthroughLiteral'
    value: code
    here: no

  code = '\nconst CONSTANT = "unreassignable!"\n'
  testExpression "```#{code}```",
    type: 'PassthroughLiteral'
    value: code
    here: yes

  testExpression "``",
    type: 'PassthroughLiteral'
    value: ''
    here: no

  # escaped backticks
  testExpression "`\\`abc\\``",
    type: 'PassthroughLiteral'
    value: '\\`abc\\`'
    here: no

test "AST as expected for IdentifierLiteral node", ->
  testExpression 'id',
    type: 'Identifier'
    name: 'id'

test "AST as expected for JSXTag node", ->
  testExpression '<CSXY />',
    type: 'JSXElement'
    openingElement:
      type: 'JSXOpeningElement'
      name:
        type: 'JSXIdentifier'
        name: 'CSXY'
      attributes: []
      selfClosing: yes
    closingElement: null
    children: []

  testExpression '<div></div>',
    type: 'JSXElement'
    openingElement:
      type: 'JSXOpeningElement'
      name:
        type: 'JSXIdentifier'
        name: 'div'
      attributes: []
      selfClosing: no
    closingElement:
      type: 'JSXClosingElement'
      name:
        type: 'JSXIdentifier'
        name: 'div'
    children: []

  testExpression '<A.B />',
    type: 'JSXElement'
    openingElement:
      type: 'JSXOpeningElement'
      name:
        type: 'JSXMemberExpression'
        object:
          type: 'JSXIdentifier'
          name: 'A'
        property:
          type: 'JSXIdentifier'
          name: 'B'
      attributes: []
      selfClosing: yes
    closingElement: null
    children: []

  testExpression '<Tag.Name.Here></Tag.Name.Here>',
    type: 'JSXElement'
    openingElement:
      type: 'JSXOpeningElement'
      name:
        type: 'JSXMemberExpression'
        object:
          type: 'JSXMemberExpression'
          object:
            type: 'JSXIdentifier'
            name: 'Tag'
          property:
            type: 'JSXIdentifier'
            name: 'Name'
        property:
          type: 'JSXIdentifier'
          name: 'Here'
      attributes: []
      selfClosing: no
    closingElement:
      type: 'JSXClosingElement'
      name:
        type: 'JSXMemberExpression'
        object:
          type: 'JSXMemberExpression'
          object:
            type: 'JSXIdentifier'
            name: 'Tag'
          property:
            type: 'JSXIdentifier'
            name: 'Name'
        property:
          type: 'JSXIdentifier'
          name: 'Here'
    children: []

  testExpression '<></>',
    type: 'JSXFragment'
    openingFragment:
      type: 'JSXOpeningFragment'
    closingFragment:
      type: 'JSXClosingFragment'
    children: []

  testExpression '<div a b="c" d={e} {...f} />',
    type: 'JSXElement'
    openingElement:
      type: 'JSXOpeningElement'
      name:
        type: 'JSXIdentifier'
        name: 'div'
      attributes: [
        type: 'JSXAttribute'
        name:
          type: 'JSXIdentifier'
          name: 'a'
      ,
        type: 'JSXAttribute'
        name:
          type: 'JSXIdentifier'
          name: 'b'
        value:
          type: 'StringLiteral'
          value: 'c'
      ,
        type: 'JSXAttribute'
        name:
          type: 'JSXIdentifier'
          name: 'd'
        value:
          type: 'JSXExpressionContainer'
          expression:
            type: 'Identifier'
            name: 'e'
      ,
        type: 'JSXSpreadAttribute'
        argument:
          type: 'Identifier'
          name: 'f'
        postfix: no
      ]
      selfClosing: yes
    closingElement: null
    children: []

  testExpression '<div {f...} />',
    type: 'JSXElement'
    openingElement:
      type: 'JSXOpeningElement'
      attributes: [
        type: 'JSXSpreadAttribute'
        argument:
          type: 'Identifier'
          name: 'f'
        postfix: yes
      ]

  testExpression '<div>abc</div>',
    type: 'JSXElement'
    openingElement:
      type: 'JSXOpeningElement'
      name:
        type: 'JSXIdentifier'
        name: 'div'
      attributes: []
      selfClosing: no
    closingElement:
      type: 'JSXClosingElement'
      name:
        type: 'JSXIdentifier'
        name: 'div'
    children: [
      type: 'JSXText'
      extra:
        raw: 'abc'
      value: 'abc'
    ]

  testExpression '''
    <a>
      {b}
      <c />
    </a>
  ''',
    type: 'JSXElement'
    openingElement:
      type: 'JSXOpeningElement'
      name:
        type: 'JSXIdentifier'
        name: 'a'
      attributes: []
      selfClosing: no
    closingElement:
      type: 'JSXClosingElement'
      name:
        type: 'JSXIdentifier'
        name: 'a'
    children: [
      type: 'JSXText'
      extra:
        raw: '\n  '
      value: '\n  '
    ,
      type: 'JSXExpressionContainer'
      expression: ID 'b'
    ,
      type: 'JSXText'
      extra:
        raw: '\n  '
      value: '\n  '
    ,
      type: 'JSXElement'
      openingElement:
        type: 'JSXOpeningElement'
        name:
          type: 'JSXIdentifier'
          name: 'c'
        selfClosing: true
      closingElement: null
      children: []
    ,
      type: 'JSXText'
      extra:
        raw: '\n'
      value: '\n'
    ]

  testExpression '<>abc{}</>',
    type: 'JSXFragment'
    openingFragment:
      type: 'JSXOpeningFragment'
    closingFragment:
      type: 'JSXClosingFragment'
    children: [
      type: 'JSXText'
      extra:
        raw: 'abc'
      value: 'abc'
    ,
      type: 'JSXExpressionContainer'
      expression:
        type: 'JSXEmptyExpression'
    ]

  testExpression '''
    <a>{<b />}</a>
  ''',
    type: 'JSXElement'
    openingElement:
      type: 'JSXOpeningElement'
      name:
        type: 'JSXIdentifier'
        name: 'a'
      attributes: []
      selfClosing: no
    closingElement:
      type: 'JSXClosingElement'
      name:
        type: 'JSXIdentifier'
        name: 'a'
    children: [
      type: 'JSXExpressionContainer'
      expression:
        type: 'JSXElement'
        openingElement:
          type: 'JSXOpeningElement'
          name:
            type: 'JSXIdentifier'
            name: 'b'
          selfClosing: true
        closingElement: null
        children: []
    ]

  testExpression '''
    <div>{
      # comment
    }</div>
  ''',
    type: 'JSXElement'
    openingElement:
      type: 'JSXOpeningElement'
      name:
        type: 'JSXIdentifier'
        name: 'div'
      attributes: []
      selfClosing: no
    closingElement:
      type: 'JSXClosingElement'
      name:
        type: 'JSXIdentifier'
        name: 'div'
    children: [
      type: 'JSXExpressionContainer'
      expression:
        type: 'JSXEmptyExpression'
    ]

  testExpression '''
    <div>{### here ###}</div>
  ''',
    type: 'JSXElement'
    openingElement:
      type: 'JSXOpeningElement'
      name:
        type: 'JSXIdentifier'
        name: 'div'
      attributes: []
      selfClosing: no
    closingElement:
      type: 'JSXClosingElement'
      name:
        type: 'JSXIdentifier'
        name: 'div'
    children: [
      type: 'JSXExpressionContainer'
      expression:
        type: 'JSXEmptyExpression'
    ]

  testExpression '<div:a b:c />',
    type: 'JSXElement'
    openingElement:
      type: 'JSXOpeningElement'
      name:
        type: 'JSXNamespacedName'
        namespace:
          type: 'JSXIdentifier'
          name: 'div'
        name:
          type: 'JSXIdentifier'
          name: 'a'
      attributes: [
        type: 'JSXAttribute'
        name:
          type: 'JSXNamespacedName'
          namespace:
            type: 'JSXIdentifier'
            name: 'b'
          name:
            type: 'JSXIdentifier'
            name: 'c'
      ]
      selfClosing: yes

  testExpression '''
    <div:a>
      {b}
    </div:a>
  ''',
    type: 'JSXElement'
    openingElement:
      type: 'JSXOpeningElement'
      name:
        type: 'JSXNamespacedName'
        namespace:
          type: 'JSXIdentifier'
          name: 'div'
        name:
          type: 'JSXIdentifier'
          name: 'a'
    closingElement:
      type: 'JSXClosingElement'
      name:
        type: 'JSXNamespacedName'
        namespace:
          type: 'JSXIdentifier'
          name: 'div'
        name:
          type: 'JSXIdentifier'
          name: 'a'

  testExpression '''
    <div b={
      c
      d
    } />
  ''',
    type: 'JSXElement'
    openingElement:
      attributes: [
        value:
          type: 'JSXExpressionContainer'
          expression:
            type: 'BlockStatement'
            body: [
              type: 'ExpressionStatement'
            ,
              type: 'ExpressionStatement'
              expression:
                returns: yes
            ]
      ]

test "AST as expected for ComputedPropertyName node", ->
  testExpression '[fn]: ->',
    type: 'ObjectExpression'
    properties: [
      type: 'ObjectProperty'
      key:
        type: 'Identifier'
        name: 'fn'
      value:
        type: 'FunctionExpression'
      computed: yes
      shorthand: no
      method: no
    ]
    implicit: yes

  testExpression '[a]: b',
    type: 'ObjectExpression'
    properties: [
      type: 'ObjectProperty'
      key:
        type: 'Identifier'
        name: 'a'
      value:
        type: 'Identifier'
        name: 'b'
      computed: yes
      shorthand: no
      method: no
    ]
    implicit: yes

test "AST as expected for StatementLiteral node", ->
  testStatement 'break',
    type: 'BreakStatement'

  testStatement 'continue',
    type: 'ContinueStatement'

  testStatement 'debugger',
    type: 'DebuggerStatement'

test "AST as expected for ThisLiteral node", ->
  testExpression 'this',
    type: 'ThisExpression'
    shorthand: no

  testExpression '@',
    type: 'ThisExpression'
    shorthand: yes

  testExpression '@b',
    type: 'MemberExpression'
    object:
      type: 'ThisExpression'
      shorthand: yes
    property: ID 'b'

  testExpression 'this.b',
    type: 'MemberExpression'
    object:
      type: 'ThisExpression'
      shorthand: no
    property: ID 'b'

test "AST as expected for UndefinedLiteral node", ->
  testExpression 'undefined',
    type: 'Identifier'
    name: 'undefined'

test "AST as expected for NullLiteral node", ->
  testExpression 'null',
    type: 'NullLiteral'

test "AST as expected for BooleanLiteral node", ->
  testExpression 'true',
    type: 'BooleanLiteral'
    value: true
    name: 'true'

  testExpression 'off',
    type: 'BooleanLiteral'
    value: false
    name: 'off'

  testExpression 'yes',
    type: 'BooleanLiteral'
    value: true
    name: 'yes'

test "AST as expected for Return node", ->
  testStatement 'return no',
    type: 'ReturnStatement'
    argument:
      type: 'BooleanLiteral'

  testExpression '''
    (a, b) ->
      return a + b
  ''',
    type: 'FunctionExpression'
    body:
      type: 'BlockStatement'
      body: [
        type: 'ReturnStatement'
        argument:
          type: 'BinaryExpression'
      ]

  testExpression '-> return',
    type: 'FunctionExpression'
    body:
      type: 'BlockStatement'
      body: [
        type: 'ReturnStatement'
        argument: null
      ]

test "AST as expected for YieldReturn node", ->
  testExpression '-> yield return 1',
    type: 'FunctionExpression'
    body:
      type: 'BlockStatement'
      body: [
        type: 'ExpressionStatement'
        expression:
          type: 'YieldExpression'
          argument:
            type: 'ReturnStatement'
            argument: NUMBER 1
          delegate: no
      ]

test "AST as expected for AwaitReturn node", ->
  testExpression '-> await return 2',
    type: 'FunctionExpression'
    body:
      type: 'BlockStatement'
      body: [
        type: 'ExpressionStatement'
        expression:
          type: 'AwaitExpression'
          argument:
            type: 'ReturnStatement'
            argument: NUMBER 2
      ]

test "AST as expected for Call node", ->
  testExpression 'fn()',
    type: 'CallExpression'
    callee:
      type: 'Identifier'
      name: 'fn'
    arguments: []
    optional: no
    implicit: no
    returns: undefined

  testExpression 'new Date()',
    type: 'NewExpression'
    callee:
      type: 'Identifier'
      name: 'Date'
    arguments: []
    optional: no
    implicit: no

  testExpression 'new Date?()',
    type: 'NewExpression'
    callee:
      type: 'Identifier'
      name: 'Date'
    arguments: []
    optional: yes
    implicit: no

  testExpression 'new Old',
    type: 'NewExpression'
    callee:
      type: 'Identifier'
      name: 'Old'
    arguments: []
    optional: no
    implicit: no

  testExpression 'new Old(1)',
    type: 'NewExpression'
    callee:
      type: 'Identifier'
      name: 'Old'
    arguments: [
      type: 'NumericLiteral'
      value: 1
    ]
    optional: no
    implicit: no

  testExpression 'new Old 1',
    type: 'NewExpression'
    callee:
      type: 'Identifier'
      name: 'Old'
    arguments: [
      type: 'NumericLiteral'
      value: 1
    ]
    optional: no
    implicit: yes

  testExpression 'maybe?()',
    type: 'OptionalCallExpression'
    optional: yes
    implicit: no

  testExpression 'maybe?(1 + 1)',
    type: 'OptionalCallExpression'
    arguments: [
      type: 'BinaryExpression'
    ]
    optional: yes
    implicit: no

  testExpression 'maybe? 1 + 1',
    type: 'OptionalCallExpression'
    arguments: [
      type: 'BinaryExpression'
    ]
    optional: yes
    implicit: yes

  testExpression 'goDo this, that',
    type: 'CallExpression'
    arguments: [
      type: 'ThisExpression'
    ,
      type: 'Identifier'
      name: 'that'
    ]
    implicit: yes
    optional: no

  testExpression 'a?().b',
    type: 'OptionalMemberExpression'
    object:
      type: 'OptionalCallExpression'
      optional: yes
    optional: no

  testExpression 'a?.b.c()',
    type: 'OptionalCallExpression'
    callee:
      type: 'OptionalMemberExpression'
      object:
        type: 'OptionalMemberExpression'
        optional: yes
      optional: no
    optional: no

  testExpression 'a?.b?()',
    type: 'OptionalCallExpression'
    callee:
      type: 'OptionalMemberExpression'
      optional: yes
    optional: yes

  testExpression 'a?().b?()',
    type: 'OptionalCallExpression'
    callee:
      type: 'OptionalMemberExpression'
      optional: no
      object:
        type: 'OptionalCallExpression'
        optional: yes
    optional: yes

  testExpression 'a().b?()',
    type: 'OptionalCallExpression'
    callee:
      type: 'MemberExpression'
      optional: no
      object:
        type: 'CallExpression'
        optional: no
    optional: yes

  testExpression 'a?().b()',
    type: 'OptionalCallExpression'
    callee:
      type: 'OptionalMemberExpression'
      optional: no
      object:
        type: 'OptionalCallExpression'
        optional: yes
    optional: no

test "AST as expected for SuperCall node", ->
  testStatement 'class child extends parent then constructor: -> super()',
    type: 'ClassDeclaration'
    body:
      type: 'ClassBody'
      body: [
        body:
          type: 'BlockStatement'
          body: [
            type: 'ExpressionStatement'
            expression:
              type: 'CallExpression'
              callee:
                type: 'Super'
          ]
      ]

test "AST as expected for Super node", ->
  testStatement 'class child extends parent then func: -> super.prop',
    type: 'ClassDeclaration'
    body:
      type: 'ClassBody'
      body: [
        body:
          type: 'BlockStatement'
          body: [
            type: 'ExpressionStatement'
            expression:
              type: 'MemberExpression'
              object:
                type: 'Super'
              property: ID 'prop'
              computed: no
          ]
      ]

  testStatement '''
    class child extends parent
      func: ->
        super[prop]()
  ''',
    type: 'ClassDeclaration'
    body:
      type: 'ClassBody'
      body: [
        body:
          type: 'BlockStatement'
          body: [
            type: 'ExpressionStatement'
            expression:
              type: 'CallExpression'
              callee:
                type: 'MemberExpression'
                object:
                  type: 'Super'
                property: ID 'prop'
                computed: yes
          ]
      ]

test "AST as expected for RegexWithInterpolations node", ->
  testExpression '///^#{flavor}script$///',
    type: 'InterpolatedRegExpLiteral'
    interpolatedPattern:
      type: 'TemplateLiteral'
      expressions: [
        ID 'flavor'
      ]
      quasis: [
        type: 'TemplateElement'
        value:
          raw: '^'
        tail: no
      ,
        type: 'TemplateElement'
        value:
          raw: 'script$'
        tail: yes
      ]
      quote: '///'
    flags: ''

  testExpression '''
    ///
      a
      #{b}///ig
  ''',
    type: 'InterpolatedRegExpLiteral'
    interpolatedPattern:
      type: 'TemplateLiteral'
      expressions: [
        ID 'b'
      ]
      quasis: [
        type: 'TemplateElement'
        value:
          raw: '\n  a\n  '
        tail: no
      ,
        type: 'TemplateElement'
        value:
          raw: ''
        tail: yes
      ]
      quote: '///'
    flags: 'ig'

  testExpression '''
    ///
      a # first
      #{b} ### second ###
    ///ig
  ''',
    type: 'InterpolatedRegExpLiteral'
    interpolatedPattern:
      type: 'TemplateLiteral'
      expressions: [
        ID 'b'
      ]
      quasis: [
        type: 'TemplateElement'
        value:
          raw: '\n  a # first\n  '
        tail: no
      ,
        type: 'TemplateElement'
        value:
          raw: ' ### second ###\n'
        tail: yes
      ]
      quote: '///'
    flags: 'ig'
    comments: [
      type: 'CommentLine'
      value: ' first'
    ,
      type: 'CommentBlock'
      value: ' second '
    ]

test "AST as expected for TaggedTemplateCall node", ->
  testExpression 'func"tagged"',
    type: 'TaggedTemplateExpression'
    tag: ID 'func'
    quasi:
      type: 'TemplateLiteral'
      expressions: []
      quasis: [
        type: 'TemplateElement'
        value:
          raw: 'tagged'
        tail: yes
      ]

  testExpression 'a"b#{c}"',
    type: 'TaggedTemplateExpression'
    tag: ID 'a'
    quasi:
      type: 'TemplateLiteral'
      expressions: [
        ID 'c'
      ]
      quasis: [
        type: 'TemplateElement'
        value:
          raw: 'b'
        tail: no
      ,
        type: 'TemplateElement'
        value:
          raw: ''
        tail: yes
      ]

  testExpression '''
    a"""
      b#{c}
    """
  ''',
    type: 'TaggedTemplateExpression'
    tag: ID 'a'
    quasi:
      type: 'TemplateLiteral'
      expressions: [
        ID 'c'
      ]
      quasis: [
        type: 'TemplateElement'
        value:
          raw: '\n  b'
        tail: no
      ,
        type: 'TemplateElement'
        value:
          raw: '\n'
        tail: yes
      ]

  testExpression """
    a'''
      b
    '''
  """,
    type: 'TaggedTemplateExpression'
    tag: ID 'a'
    quasi:
      type: 'TemplateLiteral'
      expressions: []
      quasis: [
        type: 'TemplateElement'
        value:
          raw: '\n  b\n'
        tail: yes
      ]

test "AST as expected for Access node", ->
  testExpression 'obj.prop',
    type: 'MemberExpression'
    object:
      type: 'Identifier'
      name: 'obj'
    property:
      type: 'Identifier'
      name: 'prop'
    computed: no
    optional: no
    shorthand: no

  testExpression 'obj?.prop',
    type: 'OptionalMemberExpression'
    object:
      type: 'Identifier'
      name: 'obj'
    property:
      type: 'Identifier'
      name: 'prop'
    computed: no
    optional: yes
    shorthand: no

  testExpression 'a::b',
    type: 'MemberExpression'
    object:
      type: 'MemberExpression'
      object:
        type: 'Identifier'
        name: 'a'
      property:
        type: 'Identifier'
        name: 'prototype'
      computed: no
      optional: no
      shorthand: yes
    property:
      type: 'Identifier'
      name: 'b'
    computed: no
    optional: no
    shorthand: no

  testExpression 'a.prototype.b',
    type: 'MemberExpression'
    object:
      type: 'MemberExpression'
      object:
        type: 'Identifier'
        name: 'a'
      property:
        type: 'Identifier'
        name: 'prototype'
      computed: no
      optional: no
      shorthand: no
    property:
      type: 'Identifier'
      name: 'b'
    computed: no
    optional: no
    shorthand: no

  testExpression 'a?.b.c',
    type: 'OptionalMemberExpression'
    object:
      type: 'OptionalMemberExpression'
      object:
        type: 'Identifier'
        name: 'a'
      property:
        type: 'Identifier'
        name: 'b'
      computed: no
      optional: yes
      shorthand: no
    property:
      type: 'Identifier'
      name: 'c'
    computed: no
    optional: no
    shorthand: no

test "AST as expected for Index node", ->
  testExpression 'a[b]',
    type: 'MemberExpression'
    object:
      type: 'Identifier'
      name: 'a'
    property:
      type: 'Identifier'
      name: 'b'
    computed: yes
    optional: no
    shorthand: no

  testExpression 'a?[b]',
    type: 'OptionalMemberExpression'
    object:
      type: 'Identifier'
      name: 'a'
    property:
      type: 'Identifier'
      name: 'b'
    computed: yes
    optional: yes
    shorthand: no

  testExpression 'a::[b]',
    type: 'MemberExpression'
    object:
      type: 'MemberExpression'
      object:
        type: 'Identifier'
        name: 'a'
      property:
        type: 'Identifier'
        name: 'prototype'
      computed: no
      optional: no
      shorthand: yes
    property:
      type: 'Identifier'
      name: 'b'
    computed: yes
    optional: no
    shorthand: no

  testExpression 'a[b][3]',
    type: 'MemberExpression'
    object:
      type: 'MemberExpression'
      object:
        type: 'Identifier'
        name: 'a'
      property:
        type: 'Identifier'
        name: 'b'
      computed: yes
      optional: no
      shorthand: no
    property:
      type: 'NumericLiteral'
      value: 3
    computed: yes
    optional: no
    shorthand: no

  testExpression 'a[if b then c]',
    type: 'MemberExpression'
    object: ID 'a'
    property:
      type: 'ConditionalExpression'
      test: ID 'b'
      consequent: ID 'c'
    computed: yes
    optional: no
    shorthand: no

test "AST as expected for Range node", ->
  testExpression '[x..y]',
    type: 'Range'
    exclusive: no
    from:
      name: 'x'
    to:
      name: 'y'

  testExpression '[4...2]',
    type: 'Range'
    exclusive: yes
    from:
      value: 4
    to:
      value: 2

test "AST as expected for Slice node", ->
  testExpression 'x[..y]',
    property:
      type: 'Range'
      exclusive: no
      from: null
      to:
        name: 'y'

  testExpression 'x[y...]',
    property:
      type: 'Range'
      exclusive: yes
      from:
        name: 'y'
      to: null

  testExpression 'x[...]',
    property:
      type: 'Range'
      exclusive: yes
      from: null
      to: null

  testExpression '"abc"[...2]',
    type: 'MemberExpression'
    property:
      type: 'Range'
      from: null
      to:
        type: 'NumericLiteral'
        value: 2
      exclusive: yes
    computed: yes
    optional: no
    shorthand: no

  testExpression 'x[...][a..][b...][..c][...d]',
    type: 'MemberExpression'
    object:
      type: 'MemberExpression'
      object:
        type: 'MemberExpression'
        object:
          type: 'MemberExpression'
          object:
            type: 'MemberExpression'
            property:
              type: 'Range'
              from: null
              to: null
              exclusive: yes
          property:
            type: 'Range'
            from:
              name: 'a'
            to: null
            exclusive: no
        property:
          type: 'Range'
          from:
            name: 'b'
          to: null
          exclusive: yes
      property:
        type: 'Range'
        from: null
        to:
          name: 'c'
        exclusive: no
    property:
      type: 'Range'
      from: null
      to:
        name: 'd'
      exclusive: yes

test "AST as expected for Obj node", ->
  testExpression "{a: 1, b, [c], @d, [e()]: f, 'g': 2, ...h, i...}",
    type: 'ObjectExpression'
    properties: [
      type: 'ObjectProperty'
      key:
        type: 'Identifier'
        name: 'a'
      value:
        type: 'NumericLiteral'
        value: 1
      computed: no
      shorthand: no
    ,
      type: 'ObjectProperty'
      key:
        type: 'Identifier'
        name: 'b'
      value:
        type: 'Identifier'
        name: 'b'
      computed: no
      shorthand: yes
    ,
      type: 'ObjectProperty'
      key:
        type: 'Identifier'
        name: 'c'
      value:
        type: 'Identifier'
        name: 'c'
      computed: yes
      shorthand: yes
    ,
      type: 'ObjectProperty'
      key:
        type: 'MemberExpression'
        object:
          type: 'ThisExpression'
        property:
          type: 'Identifier'
          name: 'd'
      value:
        type: 'MemberExpression'
        object:
          type: 'ThisExpression'
        property:
          type: 'Identifier'
          name: 'd'
      computed: no
      shorthand: yes
    ,
      type: 'ObjectProperty'
      key:
        type: 'CallExpression'
        callee:
          type: 'Identifier'
          name: 'e'
        arguments: []
      value:
        type: 'Identifier'
        name: 'f'
      computed: yes
      shorthand: no
    ,
      type: 'ObjectProperty'
      key:
        type: 'StringLiteral'
        value: 'g'
      value:
        type: 'NumericLiteral'
        value: 2
      computed: no
      shorthand: no
    ,
      type: 'SpreadElement'
      argument:
        type: 'Identifier'
        name: 'h'
      postfix: no
    ,
      type: 'SpreadElement'
      argument:
        type: 'Identifier'
        name: 'i'
      postfix: yes
    ]
    implicit: no

  testExpression 'a: 1',
    type: 'ObjectExpression'
    properties: [
      type: 'ObjectProperty'
      key:
        type: 'Identifier'
        name: 'a'
      value:
        type: 'NumericLiteral'
        value: 1
      shorthand: no
      computed: no
    ]
    implicit: yes

  testExpression '''
    a:
      if b then c
  ''',
    type: 'ObjectExpression'
    properties: [
      type: 'ObjectProperty'
      key: ID 'a'
      value:
        type: 'ConditionalExpression'
        test: ID 'b'
        consequent: ID 'c'
    ]
    implicit: yes

  testExpression '''
    a:
      c if b
  ''',
    type: 'ObjectExpression'
    properties: [
      type: 'ObjectProperty'
      key: ID 'a'
      value:
        type: 'ConditionalExpression'
        test: ID 'b'
        consequent: ID 'c'
    ]
    implicit: yes

  testExpression '"#{a}": 1',
    type: 'ObjectExpression'
    properties: [
      type: 'ObjectProperty'
      key:
        type: 'TemplateLiteral'
        expressions: [
          ID 'a'
        ]
      value:
        type: 'NumericLiteral'
        value: 1
      shorthand: no
      computed: yes
    ]
    implicit: yes

test "AST as expected for Arr node", ->
  testExpression '[]',
    type: 'ArrayExpression'
    elements: []

  testExpression '[3, tables, !1]',
    type: 'ArrayExpression'
    elements: [
      {value: 3}
      {name: 'tables'}
      {operator: '!'}
    ]

test "AST as expected for Class node", ->
  testStatement 'class Klass',
    type: 'ClassDeclaration'
    id: ID 'Klass', declaration: yes
    superClass: null
    body:
      type: 'ClassBody'
      body: []

  testStatement 'class child extends parent',
    type: 'ClassDeclaration'
    id: ID 'child', declaration: yes
    superClass: ID 'parent', declaration: no
    body:
      type: 'ClassBody'
      body: []

  testStatement 'class Klass then constructor: -> @a = 1',
    type: 'ClassDeclaration'
    id: ID 'Klass', declaration: yes
    superClass: null
    body:
      type: 'ClassBody'
      body: [
        type: 'ClassMethod'
        static: no
        key: ID 'constructor', declaration: no
        computed: no
        kind: 'constructor'
        id: null
        generator: no
        async: no
        params: []
        body:
          type: 'BlockStatement'
          body: [
            type: 'ExpressionStatement'
            expression:
              type: 'AssignmentExpression'
              returns: undefined
          ]
        bound: no
      ]

  testExpression '''
    a = class A
      b: ->
        c
      d: =>
        e
  ''',
    type: 'AssignmentExpression'
    right:
      type: 'ClassExpression'
      id: ID 'A', declaration: yes
      superClass: null
      body:
        type: 'ClassBody'
        body: [
          type: 'ClassMethod'
          static: no
          key: ID 'b'
          computed: no
          kind: 'method'
          id: null
          generator: no
          async: no
          params: []
          body:
            type: 'BlockStatement'
            body: [
              type: 'ExpressionStatement'
              expression: ID 'c', returns: yes
            ]
          operator: ':'
          bound: no
        ,
          type: 'ClassMethod'
          static: no
          key: ID 'd'
          computed: no
          kind: 'method'
          id: null
          generator: no
          async: no
          params: []
          body:
            type: 'BlockStatement'
            body: [
              type: 'ExpressionStatement'
              expression: ID 'e'
            ]
          operator: ':'
          bound: yes
        ]

  testStatement '''
    class A
      @b: ->
      @c = =>
      @d: 1
      @e = 2
      j = 5
      A.f = 3
      A.g = ->
      this.h = ->
      this.i = 4
  ''',
    type: 'ClassDeclaration'
    id: ID 'A', declaration: yes
    superClass: null
    body:
      type: 'ClassBody'
      body: [
        type: 'ClassMethod'
        static: yes
        key: ID 'b'
        computed: no
        kind: 'method'
        id: null
        generator: no
        async: no
        params: []
        body: EMPTY_BLOCK
        operator: ':'
        staticClassName:
          type: 'ThisExpression'
          shorthand: yes
        bound: no
      ,
        type: 'ClassMethod'
        static: yes
        key: ID 'c'
        computed: no
        kind: 'method'
        id: null
        generator: no
        async: no
        params: []
        body: EMPTY_BLOCK
        operator: '='
        staticClassName:
          type: 'ThisExpression'
          shorthand: yes
        bound: yes
      ,
        type: 'ClassProperty'
        static: yes
        key: ID 'd'
        computed: no
        value: NUMBER 1
        operator: ':'
        staticClassName:
          type: 'ThisExpression'
          shorthand: yes
      ,
        type: 'ClassProperty'
        static: yes
        key: ID 'e'
        computed: no
        value: NUMBER 2
        operator: '='
        staticClassName:
          type: 'ThisExpression'
          shorthand: yes
      ,
        type: 'ExpressionStatement'
        expression:
          type: 'AssignmentExpression'
          left: ID 'j', declaration: yes
          right: NUMBER 5
      ,
        type: 'ClassProperty'
        static: yes
        key: ID 'f'
        computed: no
        value: NUMBER 3
        operator: '='
        staticClassName: ID 'A'
      ,
        type: 'ClassMethod'
        static: yes
        key: ID 'g'
        computed: no
        kind: 'method'
        id: null
        generator: no
        async: no
        params: []
        body: EMPTY_BLOCK
        operator: '='
        staticClassName: ID 'A'
        bound: no
      ,
        type: 'ClassMethod'
        static: yes
        key: ID 'h'
        computed: no
        kind: 'method'
        id: null
        generator: no
        async: no
        params: []
        body: EMPTY_BLOCK
        operator: '='
        staticClassName:
          type: 'ThisExpression'
          shorthand: no
        bound: no
      ,
        type: 'ClassProperty'
        static: yes
        key: ID 'i'
        computed: no
        value: NUMBER 4
        operator: '='
        staticClassName:
          type: 'ThisExpression'
          shorthand: no
      ]

  testStatement '''
    class A
      b: 1
      [c]: 2
      [d]: ->
      @[e]: ->
      @[f]: 3
  ''',
    type: 'ClassDeclaration'
    id: ID 'A', declaration: yes
    superClass: null
    body:
      type: 'ClassBody'
      body: [
        type: 'ClassPrototypeProperty'
        key: ID 'b', declaration: no
        value: NUMBER 1
        computed: no
      ,
        type: 'ClassPrototypeProperty'
        key: ID 'c'
        value: NUMBER 2
        computed: yes
      ,
        type: 'ClassMethod'
        static: no
        key: ID 'd'
        computed: yes
        kind: 'method'
        id: null
        generator: no
        async: no
        params: []
        body: EMPTY_BLOCK
        operator: ':'
        bound: no
      ,
        type: 'ClassMethod'
        static: yes
        key: ID 'e'
        computed: yes
        kind: 'method'
        id: null
        generator: no
        async: no
        params: []
        body: EMPTY_BLOCK
        operator: ':'
        bound: no
        staticClassName:
          type: 'ThisExpression'
          shorthand: yes
      ,
        type: 'ClassProperty'
        static: yes
        key: ID 'f'
        computed: yes
        value: NUMBER 3
        operator: ':'
        staticClassName:
          type: 'ThisExpression'
          shorthand: yes
      ]

  testStatement '''
    class A
      @[b] = ->
      "#{c}": ->
      @[d] = 1
      [e]: 2
      "#{f}": 3
      @[g]: 4
  ''',
    type: 'ClassDeclaration'
    body:
      body: [
        type: 'ClassMethod'
        computed: yes
      ,
        type: 'ClassMethod'
        computed: yes
      ,
        type: 'ClassProperty'
        computed: yes
      ,
        type: 'ClassPrototypeProperty'
        computed: yes
      ,
        type: 'ClassPrototypeProperty'
        computed: yes
      ,
        type: 'ClassProperty'
        computed: yes
      ]

  testStatement '''
    class A.b
  ''',
    type: 'ClassDeclaration'
    id:
      type: 'MemberExpression'
      object: ID 'A', declaration: no
      property: ID 'b', declaration: no

  testStatement '''
    class A
      'constructor': ->
  ''',
    type: 'ClassDeclaration'
    body:
      type: 'ClassBody'
      body: [
        type: 'ClassMethod'
        static: no
        key:
          type: 'StringLiteral'
        computed: no
        kind: 'constructor'
        id: null
        generator: no
        async: no
        params: []
        body: EMPTY_BLOCK
        bound: no
      ]


test "AST as expected for ModuleDeclaration node", ->
  testStatement 'export {X}',
    type: 'ExportNamedDeclaration'
    declaration: null
    specifiers: [
      type: 'ExportSpecifier'
      local:
        type: 'Identifier'
        name: 'X'
        declaration: no
      exported:
        type: 'Identifier'
        name: 'X'
        declaration: no
    ]
    source: null
    exportKind: 'value'

  testStatement 'import X from "."',
    type: 'ImportDeclaration'
    specifiers: [
      type: 'ImportDefaultSpecifier'
      local:
        type: 'Identifier'
        name: 'X'
        declaration: no
    ]
    importKind: 'value'
    source:
      type: 'StringLiteral'
      value: '.'

  testStatement 'import X from "." assert { type: "json" }',
    type: 'ImportDeclaration'
    specifiers: [
      type: 'ImportDefaultSpecifier'
      local:
        type: 'Identifier'
        name: 'X'
        declaration: no
    ]
    importKind: 'value'
    source:
      type: 'StringLiteral'
      value: '.'
    assertions: [
      type: 'ImportAttribute'
      key:
        type: 'Identifier'
        name: 'type'
      value:
        type: 'StringLiteral'
        value: 'json'
        extra:
          raw: '"json"'
    ]

test "AST as expected for ImportDeclaration node", ->
  testStatement 'import React, {Component} from "react"',
    type: 'ImportDeclaration'
    specifiers: [
      type: 'ImportDefaultSpecifier'
      local:
        type: 'Identifier'
        name: 'React'
        declaration: no
    ,
      type: 'ImportSpecifier'
      imported:
        type: 'Identifier'
        name: 'Component'
        declaration: no
      importKind: null
      local:
        type: 'Identifier'
        name: 'Component'
        declaration: no
    ]
    importKind: 'value'
    source:
      type: 'StringLiteral'
      value: 'react'
      extra:
        raw: '"react"'

test "AST as expected for ExportNamedDeclaration node", ->
  testStatement 'export {}',
    type: 'ExportNamedDeclaration'
    declaration: null
    specifiers: []
    source: null
    exportKind: 'value'

  testStatement 'export fn = ->',
    type: 'ExportNamedDeclaration'
    declaration:
      type: 'AssignmentExpression'
      left:
        type: 'Identifier'
        declaration: yes
      right:
        type: 'FunctionExpression'
    specifiers: []
    source: null
    exportKind: 'value'

  testStatement 'export class A',
    type: 'ExportNamedDeclaration'
    declaration:
      type: 'ClassDeclaration'
      id: ID 'A', declaration: yes
      superClass: null
      body:
        type: 'ClassBody'
        body: []
    specifiers: []
    source: null
    exportKind: 'value'

  testStatement 'export {x as y, z as default}',
    type: 'ExportNamedDeclaration'
    declaration: null
    specifiers: [
      type: 'ExportSpecifier'
      local:
        type: 'Identifier'
        name: 'x'
        declaration: no
      exported:
        type: 'Identifier'
        name: 'y'
        declaration: no
    ,
      type: 'ExportSpecifier'
      local:
        type: 'Identifier'
        name: 'z'
        declaration: no
      exported:
        type: 'Identifier'
        name: 'default'
        declaration: no
    ]
    source: null
    exportKind: 'value'

  testStatement 'export {default, default as b} from "./abc"',
    type: 'ExportNamedDeclaration'
    declaration: null
    specifiers: [
      type: 'ExportSpecifier'
      local:
        type: 'Identifier'
        name: 'default'
        declaration: no
      exported:
        type: 'Identifier'
        name: 'default'
        declaration: no
    ,
      type: 'ExportSpecifier'
      local:
        type: 'Identifier'
        name: 'default'
        declaration: no
      exported:
        type: 'Identifier'
        name: 'b'
        declaration: no
    ]
    source:
      type: 'StringLiteral'
      value: './abc'
      extra:
        raw: '"./abc"'
    exportKind: 'value'

test "AST as expected for ExportDefaultDeclaration node", ->
  testStatement 'export default class',
    type: 'ExportDefaultDeclaration'
    declaration:
      type: 'ClassDeclaration'

  testStatement 'export default "abc"',
    type: 'ExportDefaultDeclaration'
    declaration:
      type: 'StringLiteral'
      value: 'abc'
      extra:
        raw: '"abc"'

  testStatement 'export default a = b',
    type: 'ExportDefaultDeclaration'
    declaration:
      type: 'AssignmentExpression'
      left: ID 'a', declaration: yes
      right: ID 'b', declaration: no

test "AST as expected for ExportAllDeclaration node", ->
  testStatement 'export * from "module-name"',
    type: 'ExportAllDeclaration'
    source:
      type: 'StringLiteral'
      value: 'module-name'
      extra:
        raw: '"module-name"'
    exportKind: 'value'

  testStatement 'export * from "module-name" assert { type: "json" }',
    type: 'ExportAllDeclaration'
    source:
      type: 'StringLiteral'
      value: 'module-name'
      extra:
        raw: '"module-name"'
    assertions: [
      type: 'ImportAttribute'
      key:
        type: 'Identifier'
        name: 'type'
      value:
        type: 'StringLiteral'
        value: 'json'
        extra:
          raw: '"json"'
    ]
    exportKind: 'value'

test "AST as expected for ExportSpecifierList node", ->
  testStatement 'export {a, b, c}',
    type: 'ExportNamedDeclaration'
    declaration: null
    specifiers: [
      type: 'ExportSpecifier'
      local:
        type: 'Identifier'
        name: 'a'
        declaration: no
      exported:
        type: 'Identifier'
        name: 'a'
        declaration: no
    ,
      type: 'ExportSpecifier'
      local:
        type: 'Identifier'
        name: 'b'
        declaration: no
      exported:
        type: 'Identifier'
        name: 'b'
        declaration: no
    ,
      type: 'ExportSpecifier'
      local:
        type: 'Identifier'
        name: 'c'
        declaration: no
      exported:
        type: 'Identifier'
        name: 'c'
        declaration: no
    ]

test "AST as expected for ImportDefaultSpecifier node", ->
  testStatement 'import React from "react"',
    type: 'ImportDeclaration'
    specifiers: [
      type: 'ImportDefaultSpecifier'
      local:
        type: 'Identifier'
        name: 'React'
        declaration: no
    ]
    importKind: 'value'
    source:
      type: 'StringLiteral'
      value: 'react'

test "AST as expected for ImportNamespaceSpecifier node", ->
  testStatement 'import * as React from "react"',
    type: 'ImportDeclaration'
    specifiers: [
      type: 'ImportNamespaceSpecifier'
      local:
        type: 'Identifier'
        name: 'React'
        declaration: no
    ]
    importKind: 'value'
    source:
      type: 'StringLiteral'
      value: 'react'

  testStatement 'import React, * as ReactStar from "react"',
    type: 'ImportDeclaration'
    specifiers: [
      type: 'ImportDefaultSpecifier'
      local:
        type: 'Identifier'
        name: 'React'
        declaration: no
    ,
      type: 'ImportNamespaceSpecifier'
      local:
        type: 'Identifier'
        name: 'ReactStar'
        declaration: no
    ]
    importKind: 'value'
    source:
      type: 'StringLiteral'
      value: 'react'

test "AST as expected for Assign node", ->
  testExpression 'a = b',
    type: 'AssignmentExpression'
    left:
      type: 'Identifier'
      name: 'a'
      declaration: yes
    right:
      type: 'Identifier'
      name: 'b'
      declaration: no
    operator: '='

  testExpression 'a += b',
    type: 'AssignmentExpression'
    left:
      type: 'Identifier'
      name: 'a'
      declaration: no
    right:
      type: 'Identifier'
      name: 'b'
      declaration: no
    operator: '+='

  testExpression '[@a = 2, {b: {c = 3} = {}, d...}, ...e] = f',
    type: 'AssignmentExpression'
    left:
      type: 'ArrayPattern'
      elements: [
        type: 'AssignmentPattern'
        left:
          type: 'MemberExpression'
          object:
            type: 'ThisExpression'
          property:
            name: 'a'
            declaration: no
        right:
          type: 'NumericLiteral'
      ,
        type: 'ObjectPattern'
        properties: [
          type: 'ObjectProperty'
          key:
            name: 'b'
            declaration: no
          value:
            type: 'AssignmentPattern'
            left:
              type: 'ObjectPattern'
              properties: [
                type: 'ObjectProperty'
                key:
                  name: 'c'
                value:
                  type: 'AssignmentPattern'
                  left:
                    name: 'c'
                    declaration: yes
                  right:
                    value: 3
                shorthand: yes
              ]
            right:
              type: 'ObjectExpression'
              properties: []
        ,
          type: 'RestElement'
          argument:
            name: 'd'
            declaration: yes
          postfix: yes
        ]
      ,
        type: 'RestElement'
        argument:
          name: 'e'
          declaration: yes
        postfix: no
      ]
    right:
      name: 'f'

  testExpression '{a: [...b]} = c',
    type: 'AssignmentExpression'
    left:
      type: 'ObjectPattern'
      properties: [
        type: 'ObjectProperty'
        key:
          name: 'a'
          declaration: no
        value:
          type: 'ArrayPattern'
          elements: [
            type: 'RestElement'
            argument:
              name: 'b'
              declaration: yes
          ]
      ]
    right:
      name: 'c'
      declaration: no

  testExpression '(a = 1; a ?= b)',
    type: 'SequenceExpression'
    expressions: [
      type: 'AssignmentExpression'
    ,
      type: 'AssignmentExpression'
      left:
        type: 'Identifier'
        name: 'a'
        declaration: no
      right:
        type: 'Identifier'
        name: 'b'
        declaration: no
      operator: '?='
    ]

  testExpression '[a..., b] = c',
    type: 'AssignmentExpression'
    left:
      type: 'ArrayPattern'
      elements: [
        type: 'RestElement'
        argument: ID 'a', declaration: yes
        postfix: yes
      ,
        ID 'b'
      ]
    right:
      ID 'c'

  testExpression '[] = c',
    type: 'AssignmentExpression'
    left:
      type: 'ArrayPattern'
      elements: []
    right:
      ID 'c'

  testExpression '{{a...}...} = b',
    type: 'AssignmentExpression'
    left:
      type: 'ObjectPattern'
      properties: [
        type: 'RestElement'
        argument:
          type: 'ObjectPattern'
          properties: [
            type: 'RestElement'
            argument: ID 'a'
          ]
        postfix: yes
      ]
    right: ID 'b'

  testExpression '{a..., b} = c',
    type: 'AssignmentExpression'
    left:
      type: 'ObjectPattern'
      properties: [
        type: 'RestElement'
        argument: ID 'a'
        postfix: yes
      ,
        type: 'ObjectProperty'
      ]
    right: ID 'c'

  testExpression '{a.b...} = c',
    type: 'AssignmentExpression'
    left:
      type: 'ObjectPattern'
      properties: [
        type: 'RestElement'
        argument:
          type: 'MemberExpression'
        postfix: yes
      ]
    right: ID 'c'

  testExpression '{{a}...} = b',
    type: 'AssignmentExpression'
    left:
      type: 'ObjectPattern'
      properties: [
        type: 'RestElement'
        argument:
          type: 'ObjectPattern'
          properties: [
            type: 'ObjectProperty'
            shorthand: yes
          ]
        postfix: yes
      ]
    right: ID 'b'

  testExpression '[u, [v, ...w, x], ...{...y}, z] = a',
    left:
      type: 'ArrayPattern'

  testExpression '{...{a: [...b, c]}} = d',
    left:
      type: 'ObjectPattern'

  testExpression '{"#{a}": b} = c',
    left:
      type: 'ObjectPattern'
      properties: [
        type: 'ObjectProperty'
        key:
          type: 'TemplateLiteral'
          expressions: [
            ID 'a'
          ]
        computed: yes
      ]

test "AST as expected for Code node", ->
  testExpression '=>',
    type: 'ArrowFunctionExpression'
    params: []
    body: EMPTY_BLOCK
    generator: no
    async: no
    id: null
    hasIndentedBody: no

  testExpression '''
    (a, b = 1) ->
      c
      d()
  ''',
    type: 'FunctionExpression'
    params: [
      type: 'Identifier'
      name: 'a'
      declaration: no
    ,
      type: 'AssignmentPattern'
      left:
        type: 'Identifier'
        name: 'b'
        declaration: no
      right:
        type: 'NumericLiteral'
        value: 1
    ]
    body:
      type: 'BlockStatement'
      body: [
        type: 'ExpressionStatement'
        expression:
          type: 'Identifier'
          name: 'c'
      ,
        type: 'ExpressionStatement'
        expression:
          type: 'CallExpression'
          returns: yes
      ]
      directives: []
    generator: no
    async: no
    id: null
    hasIndentedBody: yes

  testExpression '({a}) ->',
    type: 'FunctionExpression'
    params: [
      type: 'ObjectPattern'
      properties: [
        type: 'ObjectProperty'
        key: ID 'a', declaration: no
        value: ID 'a', declaration: no
        shorthand: yes
      ]
    ]
    body: EMPTY_BLOCK
    generator: no
    async: no
    id: null

  testExpression '([a]) ->',
    type: 'FunctionExpression'
    params: [
      type: 'ArrayPattern'
      elements: [
        ID 'a', declaration: no
      ]
    ]
    body: EMPTY_BLOCK
    generator: no
    async: no
    id: null

  testExpression '({a = 1} = {}) ->',
    type: 'FunctionExpression'
    params: [
      type: 'AssignmentPattern'
      left:
        type: 'ObjectPattern'
        properties: [
          type: 'ObjectProperty'
          key: ID 'a', declaration: no
          value:
            type: 'AssignmentPattern'
            left: ID 'a', declaration: no
            right: NUMBER(1)
          shorthand: yes
        ]
      right:
        type: 'ObjectExpression'
        properties: []
    ]
    body: EMPTY_BLOCK
    generator: no
    async: no
    id: null

  testExpression '([a = 1] = []) ->',
    type: 'FunctionExpression'
    params: [
      type: 'AssignmentPattern'
      left:
        type: 'ArrayPattern'
        elements: [
          type: 'AssignmentPattern'
          left: ID 'a', declaration: no
          right: NUMBER(1)
        ]
      right:
        type: 'ArrayExpression'
        elements: []
    ]
    body: EMPTY_BLOCK
    generator: no
    async: no
    id: null

  testExpression '() ->',
    type: 'FunctionExpression'
    params: []
    body: EMPTY_BLOCK
    generator: no
    async: no
    id: null

  testExpression '(@a) ->',
    type: 'FunctionExpression'
    params: [
      type: 'MemberExpression'
      object:
        type: 'ThisExpression'
        shorthand: yes
      property: ID 'a', declaration: no
    ]
    body: EMPTY_BLOCK
    generator: no
    async: no
    id: null

  testExpression '(@a = 1) ->',
    type: 'FunctionExpression'
    params: [
      type: 'AssignmentPattern'
      left:
        type: 'MemberExpression'
      right: NUMBER 1
    ]
    body: EMPTY_BLOCK
    generator: no
    async: no
    id: null

  testExpression '({@a}) ->',
    type: 'FunctionExpression'
    params: [
      type: 'ObjectPattern'
      properties: [
        type: 'ObjectProperty'
        key:
          type: 'MemberExpression'
        value:
          type: 'MemberExpression'
        shorthand: yes
        computed: no
      ]
    ]
    body: EMPTY_BLOCK
    generator: no
    async: no
    id: null

  testExpression '({[a]}) ->',
    type: 'FunctionExpression'
    params: [
      type: 'ObjectPattern'
      properties: [
        type: 'ObjectProperty'
        key:   ID 'a', declaration: no
        value: ID 'a', declaration: no
        shorthand: yes
        computed: yes
      ]
    ]
    body: EMPTY_BLOCK
    generator: no
    async: no
    id: null

  testExpression '(...a) ->',
    type: 'FunctionExpression'
    params: [
      type: 'RestElement'
      argument: ID 'a', declaration: no
      postfix: no
    ]
    body: EMPTY_BLOCK
    generator: no
    async: no
    id: null

  testExpression '(a...) ->',
    type: 'FunctionExpression'
    params: [
      type: 'RestElement'
      argument: ID 'a'
      postfix: yes
    ]
    body: EMPTY_BLOCK
    generator: no
    async: no
    id: null

  testExpression '(..., a) ->',
    type: 'FunctionExpression'
    params: [
      type: 'RestElement'
      argument: null
    ,
      ID 'a'
    ]
    body: EMPTY_BLOCK
    generator: no
    async: no
    id: null

  testExpression '-> a',
    type: 'FunctionExpression'
    params: []
    body:
      type: 'BlockStatement'
      body: [
        type: 'ExpressionStatement'
        expression: ID 'a', returns: yes
      ]
    generator: no
    async: no
    id: null
    hasIndentedBody: no

  testExpression '-> await 3',
    type: 'FunctionExpression'
    params: []
    body:
      type: 'BlockStatement'
      body: [
        type: 'ExpressionStatement'
        expression:
          type: 'AwaitExpression'
          argument: NUMBER 3
          returns: yes
      ]
    generator: no
    async: yes
    id: null

  testExpression '-> yield 4',
    type: 'FunctionExpression'
    params: []
    body:
      type: 'BlockStatement'
      body: [
        type: 'ExpressionStatement'
        expression:
          type: 'YieldExpression'
          argument: NUMBER 4
          delegate: no
      ]
    generator: yes
    async: no
    id: null

  testExpression '-> yield',
    type: 'FunctionExpression'
    params: []
    body:
      type: 'BlockStatement'
      body: [
        type: 'ExpressionStatement'
        expression:
          type: 'YieldExpression'
          argument: null
          delegate: no
      ]
    generator: yes
    async: no
    id: null

  testExpression '(a) -> a = 1',
    type: 'FunctionExpression'
    body:
      type: 'BlockStatement'
      body: [
        type: 'ExpressionStatement'
        expression:
          type: 'AssignmentExpression'
          left:
            ID 'a', declaration: no
      ]

  testExpression '(...a) -> a = 1',
    type: 'FunctionExpression'
    body:
      type: 'BlockStatement'
      body: [
        type: 'ExpressionStatement'
        expression:
          type: 'AssignmentExpression'
          left:
            ID 'a', declaration: no
      ]

  testExpression '({a}) -> a = 1',
    type: 'FunctionExpression'
    body:
      type: 'BlockStatement'
      body: [
        type: 'ExpressionStatement'
        expression:
          type: 'AssignmentExpression'
          left:
            ID 'a', declaration: no
      ]

  testExpression '([a]) -> a = 1',
    type: 'FunctionExpression'
    body:
      type: 'BlockStatement'
      body: [
        type: 'ExpressionStatement'
        expression:
          type: 'AssignmentExpression'
          left:
            ID 'a', declaration: no
      ]

  testExpression '(a = 1) -> a = 1',
    type: 'FunctionExpression'
    body:
      type: 'BlockStatement'
      body: [
        type: 'ExpressionStatement'
        expression:
          type: 'AssignmentExpression'
          left:
            ID 'a', declaration: no
      ]
    generator: no
    async: no
    id: null

  testExpression '({a} = 1) -> a = 1',
    type: 'FunctionExpression'
    body:
      type: 'BlockStatement'
      body: [
        type: 'ExpressionStatement'
        expression:
          type: 'AssignmentExpression'
          left:
            ID 'a', declaration: no
      ]
    generator: no
    async: no
    id: null

test "AST as expected for Splat node", ->
  testExpression '[a...]',
    type: 'ArrayExpression'
    elements: [
      type: 'SpreadElement'
      argument:
        type: 'Identifier'
        name: 'a'
        declaration: no
      postfix: yes
    ]

  testExpression '[b, ...c]',
    type: 'ArrayExpression'
    elements: [
      name: 'b'
      declaration: no
    ,
      type: 'SpreadElement'
      argument:
        type: 'Identifier'
        name: 'c'
        declaration: no
      postfix: no
    ]

test "AST as expected for Expansion node", ->
  testExpression '(..., b) ->',
    type: 'FunctionExpression'
    params: [
      type: 'RestElement'
      argument: null
    ,
      ID 'b'
    ]

  testExpression '[..., b] = c',
    type: 'AssignmentExpression'
    left:
      type: 'ArrayPattern'
      elements: [
        type: 'RestElement'
        argument: null
      ,
        type: 'Identifier'
      ]

test "AST as expected for Elision node", ->
  testExpression '[,,,a,,,b]',
    type: 'ArrayExpression'
    elements: [
      null, null, null
      name: 'a'
      null, null
      name: 'b'
    ]

  testExpression '[,,,a,,,b] = "asdfqwer"',
    type: 'AssignmentExpression'
    left:
      type: 'ArrayPattern'
      elements: [
        null, null, null
      ,
        type: 'Identifier'
        name: 'a'
      ,
        null, null
      ,
        type: 'Identifier'
        name: 'b'
      ]
    right:
      type: 'StringLiteral'
      value: 'asdfqwer'

test "AST as expected for While node", ->
  testStatement 'loop 1',
    type: 'WhileStatement'
    test:
      type: 'BooleanLiteral'
      value: true
    body:
      type: 'BlockStatement'
      body: [
        type: 'ExpressionStatement'
        expression: NUMBER 1
      ]
    guard: null
    inverted: no
    postfix: no
    loop: yes

  testStatement 'while 1 < 2 then',
    type: 'WhileStatement'
    test:
      type: 'BinaryExpression'
    body:
      type: 'BlockStatement'
      body: []
    guard: null
    inverted: no
    postfix: no
    loop: no

  testStatement 'while 1 < 2 then fn()',
    type: 'WhileStatement'
    test:
      type: 'BinaryExpression'
    body:
      type: 'BlockStatement'
      body: [
        type: 'ExpressionStatement'
        expression:
          type: 'CallExpression'
      ]
    guard: null
    inverted: no
    postfix: no
    loop: no

  testStatement '''
    x() until y
  ''',
    type: 'WhileStatement'
    test: ID 'y'
    body:
      type: 'BlockStatement'
      body: [
        type: 'ExpressionStatement'
        expression:
          type: 'CallExpression'
          returns: undefined
      ]
    guard: null
    inverted: yes
    postfix: yes
    loop: no

  testStatement '''
    until x when y
      z++
  ''',
    type: 'WhileStatement'
    test: ID 'x'
    body:
      type: 'BlockStatement'
      body: [
        type: 'ExpressionStatement'
        expression:
          type: 'UpdateExpression'
      ]
    guard: ID 'y'
    inverted: yes
    postfix: no
    loop: no

  testStatement '''
    x while y when z
  ''',
    type: 'WhileStatement'
    test: ID 'y'
    body:
      type: 'BlockStatement'
      body: [
        type: 'ExpressionStatement'
        expression: ID 'x'
      ]
    guard: ID 'z'
    inverted: no
    postfix: yes
    loop: no

  testStatement '''
    loop
      a()
      b++
  ''',
    type: 'WhileStatement'
    test:
      type: 'BooleanLiteral'
    body:
      type: 'BlockStatement'
      body: [
        type: 'ExpressionStatement'
        expression:
          type: 'CallExpression'
      ,
        type: 'ExpressionStatement'
        expression:
          type: 'UpdateExpression'
      ]
    guard: null
    inverted: no
    postfix: no
    loop: yes

  testExpression '''
    x = (z() while y)
  ''',
    type: 'AssignmentExpression'
    right:
      type: 'WhileStatement'
      body:
        type: 'BlockStatement'
        body: [
          type: 'ExpressionStatement'
          expression:
            type: 'CallExpression'
            returns: yes
        ]

test "AST as expected for Op node", ->
  testExpression 'a <= 2',
    type: 'BinaryExpression'
    operator: '<='
    left:
      type: 'Identifier'
      name: 'a'
    right:
      type: 'NumericLiteral'
      value: 2

  testExpression 'a is 2',
    type: 'BinaryExpression'
    operator: 'is'
    left:
      type: 'Identifier'
      name: 'a'
    right:
      type: 'NumericLiteral'
      value: 2

  testExpression 'a // 2',
    type: 'BinaryExpression'
    operator: '//'
    left:
      type: 'Identifier'
      name: 'a'
    right:
      type: 'NumericLiteral'
      value: 2

  testExpression 'a << 2',
    type: 'BinaryExpression'
    operator: '<<'
    left:
      type: 'Identifier'
      name: 'a'
    right:
      type: 'NumericLiteral'
      value: 2

  testExpression 'typeof x',
    type: 'UnaryExpression'
    operator: 'typeof'
    prefix: yes
    argument:
      type: 'Identifier'
      name: 'x'

  testExpression 'delete x.y',
    type: 'UnaryExpression'
    operator: 'delete'
    prefix: yes
    argument:
      type: 'MemberExpression'

  testExpression 'do x',
    type: 'UnaryExpression'
    operator: 'do'
    prefix: yes
    argument:
      type: 'Identifier'
      name: 'x'

  testExpression 'do ->',
    type: 'UnaryExpression'
    operator: 'do'
    prefix: yes
    argument:
      type: 'FunctionExpression'

  testExpression '!x',
    type: 'UnaryExpression'
    operator: '!'
    prefix: yes
    argument:
      type: 'Identifier'
      name: 'x'

  testExpression 'not x',
    type: 'UnaryExpression'
    operator: 'not'
    prefix: yes
    argument:
      type: 'Identifier'
      name: 'x'

  testExpression '--x',
    type: 'UpdateExpression'
    operator: '--'
    prefix: yes
    argument:
      type: 'Identifier'
      name: 'x'

  testExpression 'x++',
    type: 'UpdateExpression'
    operator: '++'
    prefix: no
    argument:
      type: 'Identifier'
      name: 'x'

  testExpression 'x && y',
    type: 'LogicalExpression'
    operator: '&&'
    left:
      type: 'Identifier'
      name: 'x'
    right:
      type: 'Identifier'
      name: 'y'

  testExpression 'x or y',
    type: 'LogicalExpression'
    operator: 'or'
    left:
      type: 'Identifier'
      name: 'x'
    right:
      type: 'Identifier'
      name: 'y'

  testExpression 'x ? y',
    type: 'LogicalExpression'
    operator: '?'
    left:
      type: 'Identifier'
      name: 'x'
    right:
      type: 'Identifier'
      name: 'y'

  testExpression 'x in y',
    type: 'BinaryExpression'
    operator: 'in'
    left:
      type: 'Identifier'
      name: 'x'
    right:
      type: 'Identifier'
      name: 'y'

  testExpression 'x not in y',
    type: 'BinaryExpression'
    operator: 'not in'
    left:
      type: 'Identifier'
      name: 'x'
    right:
      type: 'Identifier'
      name: 'y'

  testExpression 'x + y * z',
    type: 'BinaryExpression'
    operator: '+'
    left:
      type: 'Identifier'
      name: 'x'
    right:
      type: 'BinaryExpression'
      operator: '*'
      left:
        type: 'Identifier'
        name: 'y'
      right:
        type: 'Identifier'
        name: 'z'

  testExpression '(x + y) * z',
    type: 'BinaryExpression'
    operator: '*'
    left:
      type: 'BinaryExpression'
      operator: '+'
      left:
        type: 'Identifier'
        name: 'x'
      right:
        type: 'Identifier'
        name: 'y'
    right:
      type: 'Identifier'
      name: 'z'

test "AST as expected for Try node", ->
  testStatement 'try cappuccino',
    type: 'TryStatement'
    block:
      type: 'BlockStatement'
      body: [
        type: 'ExpressionStatement'
        expression:
          type: 'Identifier'
          name: 'cappuccino'
      ]
    handler: null
    finalizer: null

  testStatement '''
    try
      x = 1
      y()
    catch e
      d()
    finally
      f + g
  ''',
    type: 'TryStatement'
    block:
      type: 'BlockStatement'
      body: [
        type: 'ExpressionStatement'
        expression:
          type: 'AssignmentExpression'
      ,
        type: 'ExpressionStatement'
        expression:
          type: 'CallExpression'
      ]
    handler:
      type: 'CatchClause'
      param:
        type: 'Identifier'
        name: 'e'
        declaration: yes
      body:
        type: 'BlockStatement'
        body: [
          type: 'ExpressionStatement'
          expression:
            type: 'CallExpression'
        ]
    finalizer:
      type: 'BlockStatement'
      body: [
        type: 'ExpressionStatement'
        expression:
          type: 'BinaryExpression'
      ]

  testStatement '''
    try
    catch
    finally
  ''',
    type: 'TryStatement'
    block:
      type: 'BlockStatement'
      body: []
    handler:
      type: 'CatchClause'
      param: null
      body:
        type: 'BlockStatement'
        body: []
    finalizer:
      type: 'BlockStatement'
      body: []

  testStatement '''
    try
    catch {e}
      f
  ''',
    type: 'TryStatement'
    block:
      type: 'BlockStatement'
      body: []
    handler:
      type: 'CatchClause'
      param:
        type: 'ObjectPattern'
        properties: [
          type: 'ObjectProperty'
          key: ID 'e', declaration: no
          value: ID 'e', declaration: yes
        ]
      body:
        type: 'BlockStatement'
        body: [
          type: 'ExpressionStatement'
        ]
    finalizer: null

test "AST as expected for Throw node", ->
  testStatement 'throw new BallError "catch"',
    type: 'ThrowStatement'
    argument:
      type: 'NewExpression'

test "AST as expected for Existence node", ->
  testExpression 'Ghosts?',
    type: 'UnaryExpression',
    argument:
      name: 'Ghosts'
    operator: '?'
    prefix: no

test "AST as expected for Parens node", ->
  testExpression '(hmmmmm)',
    type: 'Identifier'
    name: 'hmmmmm'

  testExpression '(a + b) / c',
    type: 'BinaryExpression'
    operator: '/'
    left:
      type: 'BinaryExpression'
      operator: '+'
      left: ID 'a'
      right: ID 'b'
    right: ID 'c'

  testExpression '(((1)))',
    type: 'NumericLiteral'
    value: 1

test "AST as expected for StringWithInterpolations node", ->
  testExpression '"a#{b}c"',
    type: 'TemplateLiteral'
    expressions: [
      ID 'b'
    ]
    quasis: [
      type: 'TemplateElement'
      value:
        raw: 'a'
      tail: no
    ,
      type: 'TemplateElement'
      value:
        raw: 'c'
      tail: yes
    ]
    quote: '"'

  testExpression '"""a#{b}c"""',
    type: 'TemplateLiteral'
    expressions: [
      ID 'b'
    ]
    quasis: [
      type: 'TemplateElement'
      value:
        raw: 'a'
      tail: no
    ,
      type: 'TemplateElement'
      value:
        raw: 'c'
      tail: yes
    ]
    quote: '"""'

  testExpression '"#{b}"',
    type: 'TemplateLiteral'
    expressions: [
      ID 'b'
    ]
    quasis: [
      type: 'TemplateElement'
      value:
        raw: ''
      tail: no
    ,
      type: 'TemplateElement'
      value:
        raw: ''
      tail: yes
    ]
    quote: '"'

  testExpression '''
    " a
      #{b}
      c
    "
  ''',
    type: 'TemplateLiteral'
    expressions: [
      ID 'b'
    ]
    quasis: [
      type: 'TemplateElement'
      value:
        raw: ' a\n  '
      tail: no
    ,
      type: 'TemplateElement'
      value:
        raw: '\n  c\n'
      tail: yes
    ]
    quote: '"'

  testExpression '''
    """
      a
        b#{
        c
      }d
    """
  ''',
    type: 'TemplateLiteral'
    expressions: [
      ID 'c'
    ]
    quasis: [
      type: 'TemplateElement'
      value:
        raw: '\n  a\n    b'
      tail: no
    ,
      type: 'TemplateElement'
      value:
        raw: 'd\n'
      tail: yes
    ]
    quote: '"""'

  # empty interpolation
  testExpression '"#{}"',
    type: 'TemplateLiteral'
    expressions: [
      type: 'EmptyInterpolation'
    ]
    quasis: [
      type: 'TemplateElement'
      value:
        raw: ''
      tail: no
    ,
      type: 'TemplateElement'
      value:
        raw: ''
      tail: yes
    ]
    quote: '"'

  testExpression '''
    "#{
      # comment
     }"
    ''',
    type: 'TemplateLiteral'
    expressions: [
      type: 'EmptyInterpolation'
    ]
    quasis: [
      type: 'TemplateElement'
      value:
        raw: ''
      tail: no
    ,
      type: 'TemplateElement'
      value:
        raw: ''
      tail: yes
    ]
    quote: '"'

  testExpression '"#{ ### here ### }"',
    type: 'TemplateLiteral'
    expressions: [
      type: 'EmptyInterpolation'
    ]
    quasis: [
      type: 'TemplateElement'
      value:
        raw: ''
      tail: no
    ,
      type: 'TemplateElement'
      value:
        raw: ''
      tail: yes
    ]
    quote: '"'

  testExpression '''
    a "#{
      b
      c
    }"
  ''',
    type: 'CallExpression'
    arguments: [
      type: 'TemplateLiteral'
      expressions: [
        type: 'BlockStatement'
        body: [
          type: 'ExpressionStatement'
        ,
          type: 'ExpressionStatement'
          expression:
            returns: yes
        ]
      ]
    ]

test "AST as expected for For node", ->
  testStatement 'for x, i in arr when x? then return',
    type: 'For'
    name: ID 'x', declaration: yes
    index: ID 'i', declaration: yes
    guard:
      type: 'UnaryExpression'
    source: ID 'arr', declaration: no
    body:
      type: 'BlockStatement'
      body: [
        type: 'ReturnStatement'
      ]
    style: 'in'
    own: no
    postfix: no
    await: no
    step: null

  testStatement 'for k, v of obj then return',
    type: 'For'
    name: ID 'v', declaration: yes
    index: ID 'k', declaration: yes
    guard: null
    source: ID 'obj', declaration: no
    body:
      type: 'BlockStatement'
      body: [
        type: 'ReturnStatement'
      ]
    style: 'of'
    own: no
    postfix: no
    await: no
    step: null

  testStatement 'for x from iterable then',
    type: 'For'
    name: ID 'x', declaration: yes
    index: null
    guard: null
    body: EMPTY_BLOCK
    source: ID 'iterable', declaration: no
    style: 'from'
    own: no
    postfix: no
    await: no
    step: null

  testStatement 'for i in [0...42] by step when not (i % 2) then',
    type: 'For'
    name: ID 'i', declaration: yes
    index: null
    body: EMPTY_BLOCK
    source:
      type: 'Range'
    guard:
      type: 'UnaryExpression'
    step: ID 'step', declaration: no
    style: 'in'
    own: no
    postfix: no
    await: no

  testExpression 'a = (x for x in y)',
    type: 'AssignmentExpression'
    right:
      type: 'For'
      name: ID 'x', declaration: yes
      index: null
      body:
        type: 'BlockStatement'
        body: [
          type: 'ExpressionStatement'
          expression: ID 'x', declaration: no, returns: yes
        ]
      source: ID 'y', declaration: no
      guard: null
      step: null
      style: 'in'
      own: no
      postfix: yes
      await: no

  testStatement 'x for [0...1]',
    type: 'For'
    name: null
    index: null
    body:
      type: 'BlockStatement'
      body: [
        type: 'ExpressionStatement'
        expression: ID 'x', declaration: no, returns: undefined
      ]
    source:
      type: 'Range'
    guard: null
    step: null
    style: 'range'
    own: no
    postfix: yes
    await: no

  testStatement '''
    for own x, y of z
      c()
      d
  ''',
    type: 'For'
    name: ID 'y', declaration: yes
    index: ID 'x', declaration: yes
    body:
      type: 'BlockStatement'
      body: [
        type: 'ExpressionStatement'
        expression:
          type: 'CallExpression'
          returns: undefined
      ,
        type: 'ExpressionStatement'
        expression: ID 'd', declaration: no, returns: undefined
      ]
    source: ID 'z', declaration: no
    guard: null
    step: null
    style: 'of'
    own: yes
    postfix: no
    await: no

  testExpression '''
    ->
      for await x from y
        z
  ''',
    type: 'FunctionExpression'
    body:
      type: 'BlockStatement'
      body: [
        type: 'For'
        name: ID 'x', declaration: yes
        index: null
        body:
          type: 'BlockStatement'
          body: [
            type: 'ExpressionStatement'
            expression: ID 'z', declaration: no, returns: yes
          ]
        source: ID 'y', declaration: no
        guard: null
        step: null
        style: 'from'
        own: no
        postfix: no
        await: yes
      ]

  testStatement '''
    for {x} in y
      z
  ''',
    type: 'For'
    name:
      type: 'ObjectPattern'
      properties: [
        type: 'ObjectProperty'
        key: ID 'x', declaration: no
        value: ID 'x', declaration: yes
        shorthand: yes
        computed: no
      ]
    index: null
    body:
      type: 'BlockStatement'
      body: [
        type: 'ExpressionStatement'
        expression: ID 'z'
      ]
    source: ID 'y'
    guard: null
    step: null
    style: 'in'
    postfix: no
    await: no

  testStatement '''
    for [x] in y
      z
  ''',
    type: 'For'
    name:
      type: 'ArrayPattern'
      elements: [
        ID 'x', declaration: yes
      ]
    index: null
    body:
      type: 'BlockStatement'
      body: [
        type: 'ExpressionStatement'
        expression: ID 'z'
      ]
    source: ID 'y'
    guard: null
    step: null
    style: 'in'
    postfix: no
    await: no

  testStatement '''
    for [x..., y] in z
      y()
  ''',
    type: 'For'
    name:
      type: 'ArrayPattern'

test "AST as expected for Switch node", ->
  testStatement '''
    switch x
      when a then a
      when b, c then c
      else 42
  ''',
    type: 'SwitchStatement'
    discriminant:
      type: 'Identifier'
      name: 'x'
    cases: [
      type: 'SwitchCase'
      test:
        type: 'Identifier'
        name: 'a'
      consequent: [
        type: 'ExpressionStatement'
        expression:
          type: 'Identifier'
          name: 'a'
      ]
      trailing: yes
    ,
      type: 'SwitchCase'
      test:
        type: 'Identifier'
        name: 'b'
      consequent: []
      trailing: no
    ,
      type: 'SwitchCase'
      test:
        type: 'Identifier'
        name: 'c'
      consequent: [
        type: 'ExpressionStatement'
        expression:
          type: 'Identifier'
          name: 'c'
      ]
      trailing: yes
    ,
      type: 'SwitchCase'
      test: null
      consequent: [
        type: 'ExpressionStatement'
        expression:
          type: 'NumericLiteral'
          value: 42
      ]
    ]

  testStatement '''
    switch
      when some(condition)
        doSomething()
        andThenSomethingElse
  ''',
    type: 'SwitchStatement'
    discriminant: null
    cases: [
      type: 'SwitchCase'
      test:
        type: 'CallExpression'
      consequent: [
        type: 'ExpressionStatement'
        expression:
          type: 'CallExpression'
      ,
        type: 'ExpressionStatement'
        expression:
          type: 'Identifier'
      ]
      trailing: yes
    ]

  testStatement '''
    switch a
      when 1, 2, 3, 4
        b
      else
        c
        d
  ''',
    type: 'SwitchStatement'
    discriminant:
      type: 'Identifier'
    cases: [
      type: 'SwitchCase'
      test:
        type: 'NumericLiteral'
        value: 1
      consequent: []
      trailing: no
    ,
      type: 'SwitchCase'
      test:
        type: 'NumericLiteral'
        value: 2
      consequent: []
      trailing: no
    ,
      type: 'SwitchCase'
      test:
        type: 'NumericLiteral'
        value: 3
      consequent: []
      trailing: no
    ,
      type: 'SwitchCase'
      test:
        type: 'NumericLiteral'
        value: 4
      consequent: [
        type: 'ExpressionStatement'
        expression:
          type: 'Identifier'
      ]
      trailing: yes
    ,
      type: 'SwitchCase'
      test: null
      consequent: [
        type: 'ExpressionStatement'
        expression:
          type: 'Identifier'
      ,
        type: 'ExpressionStatement'
        expression:
          type: 'Identifier'
      ]
    ]

test "AST as expected for If node", ->
  testStatement 'if maybe then yes',
    type: 'IfStatement'
    test: ID 'maybe'
    consequent:
      type: 'BlockStatement'
      body: [
        type: 'ExpressionStatement'
        expression:
          type: 'BooleanLiteral'
      ]
    alternate: null
    postfix: no
    inverted: no

  testStatement 'yes if maybe',
    type: 'IfStatement'
    test: ID 'maybe'
    consequent:
      type: 'BlockStatement'
      body: [
        type: 'ExpressionStatement'
        expression:
          type: 'BooleanLiteral'
      ]
    alternate: null
    postfix: yes
    inverted: no

  testStatement 'unless x then x else if y then y else z',
    type: 'IfStatement'
    test: ID 'x'
    consequent:
      type: 'BlockStatement'
      body: [
        type: 'ExpressionStatement'
        expression: ID 'x'
      ]
    alternate:
      type: 'IfStatement'
      test: ID 'y'
      consequent:
        type: 'BlockStatement'
        body: [
          type: 'ExpressionStatement'
          expression: ID 'y'
        ]
      alternate:
        type: 'BlockStatement'
        body: [
          type: 'ExpressionStatement'
          expression: ID 'z'
        ]
      postfix: no
      inverted: no
    postfix: no
    inverted: yes

  testStatement '''
    if a
      b
    else
      if c
        d
  ''',
    type: 'IfStatement'
    test: ID 'a'
    consequent:
      type: 'BlockStatement'
      body: [
        type: 'ExpressionStatement'
        expression: ID 'b'
      ]
    alternate:
      type: 'BlockStatement'
      body: [
        type: 'IfStatement'
        test: ID 'c'
        consequent:
          type: 'BlockStatement'
          body: [
            type: 'ExpressionStatement'
            expression: ID 'd'
          ]
        alternate: null
        postfix: no
        inverted: no
      ]
    postfix: no
    inverted: no

  testExpression '''
    a =
      if b then c else if d then e
  ''',
    type: 'AssignmentExpression'
    right:
      type: 'ConditionalExpression'
      test: ID 'b'
      consequent: ID 'c'
      alternate:
        type: 'ConditionalExpression'
        test: ID 'd'
        consequent: ID 'e'
        alternate: null
        postfix: no
        inverted: no
      postfix: no
      inverted: no

  testExpression '''
    f(
      if b
        c
        d
    )
  ''',
    type: 'CallExpression'
    arguments: [
      type: 'ConditionalExpression'
      test: ID 'b'
      consequent:
        type: 'BlockStatement'
        body: [
          type: 'ExpressionStatement'
          expression:
            ID 'c'
        ,
          type: 'ExpressionStatement'
          expression:
            ID 'd'
        ]
      alternate: null
      postfix: no
      inverted: no
    ]

  testStatement 'a unless b',
    type: 'IfStatement'
    test: ID 'b'
    consequent:
      type: 'BlockStatement'
      body: [
        type: 'ExpressionStatement'
        expression: ID 'a'
      ]
    alternate: null
    postfix: yes
    inverted: yes

  testExpression '''
    f(
      if b
        c
      else
        d
    )
  ''',
      type: 'CallExpression'
      arguments: [
        type: 'ConditionalExpression'
        test: ID 'b'
        consequent: ID 'c'
        alternate: ID 'd'
        postfix: no
        inverted: no
      ]

test "AST as expected for `new.target` MetaProperty node", ->
  testExpression '''
    -> new.target
  ''',
    type: 'FunctionExpression'
    body:
      type: 'BlockStatement'
      body: [
        type: 'ExpressionStatement'
        expression:
          type: 'MetaProperty'
          meta: ID 'new'
          property: ID 'target'
      ]

  testExpression '''
    -> new.target.name
  ''',
    type: 'FunctionExpression'
    body:
      type: 'BlockStatement'
      body: [
        type: 'ExpressionStatement'
        expression:
          type: 'MemberExpression'
          object:
            type: 'MetaProperty'
            meta: ID 'new'
            property: ID 'target'
          property: ID 'name'
          computed: no
      ]

test "AST as expected for `import.meta` MetaProperty node", ->
  testExpression '''
    import.meta
  ''',
    type: 'MetaProperty'
    meta: ID 'import'
    property: ID 'meta'

  testExpression '''
    import.meta.name
  ''',
    type: 'MemberExpression'
    object:
      type: 'MetaProperty'
      meta: ID 'import'
      property: ID 'meta'
    property: ID 'name'
    computed: no

test "AST as expected for dynamic import", ->
  testExpression '''
    import('a')
  ''',
    type: 'CallExpression'
    callee:
      type: 'Import'
    arguments: [STRING 'a']

test "AST as expected for RegexLiteral node", ->
  testExpression '/a/ig',
    type: 'RegExpLiteral'
    pattern: 'a'
    originalPattern: 'a'
    flags: 'ig'
    delimiter: '/'
    value: undefined
    extra:
      raw: "/a/ig"
      originalRaw: "/a/ig"
      rawValue: undefined

  testExpression '''
    ///
      a
    ///i
  ''',
    type: 'RegExpLiteral'
    pattern: 'a'
    originalPattern: '\n  a\n'
    flags: 'i'
    delimiter: '///'
    value: undefined
    extra:
      raw: "/a/i"
      originalRaw: "///\n  a\n///i"
      rawValue: undefined

  testExpression '/a\\w\\u1111\\u{11111}/',
    type: 'RegExpLiteral'
    pattern: 'a\\w\\u1111\\ud804\\udd11'
    originalPattern: 'a\\w\\u1111\\u{11111}'
    flags: ''
    delimiter: '/'
    value: undefined
    extra:
      raw: "/a\\w\\u1111\\ud804\\udd11/"
      originalRaw: "/a\\w\\u1111\\u{11111}/"
      rawValue: undefined

  testExpression '''
    ///
      a
      \\w\\u1111\\u{11111}
    ///
  ''',
    type: 'RegExpLiteral'
    pattern: 'a\\w\\u1111\\ud804\\udd11'
    originalPattern: '\n  a\n  \\w\\u1111\\u{11111}\n'
    flags: ''
    delimiter: '///'
    value: undefined
    extra:
      raw: "/a\\w\\u1111\\ud804\\udd11/"
      originalRaw: "///\n  a\n  \\w\\u1111\\u{11111}\n///"
      rawValue: undefined

  testExpression '''
    ///
      /
      (.+)
      /
    ///
  ''',
    type: 'RegExpLiteral'
    pattern: '\\/(.+)\\/'
    originalPattern: '\n  /\n  (.+)\n  /\n'
    flags: ''
    delimiter: '///'
    value: undefined
    extra:
      raw: "/\\/(.+)\\//"
      originalRaw: "///\n  /\n  (.+)\n  /\n///"
      rawValue: undefined

  testExpression '''
    ///
      a # first
      b ### second ###
    ///
  ''',
    type: 'RegExpLiteral'
    pattern: 'ab'
    originalPattern: '\n  a # first\n  b ### second ###\n'
    comments: [
      type: 'CommentLine'
      value: ' first'
    ,
      type: 'CommentBlock'
      value: ' second '
    ]

test "AST as expected for directives", ->
  deepStrictIncludeExpectedProperties CoffeeScript.compile('''
    'directive 1'
    'use strict'
    f()
  ''', ast: yes),
    type: 'File'
    program:
      type: 'Program'
      body: [
        type: 'ExpressionStatement'
        expression:
          type: 'CallExpression'
      ]
      directives: [
        type: 'Directive'
        value:
          type: 'DirectiveLiteral'
          value: 'directive 1'
          extra:
            raw: "'directive 1'"
      ,
        type: 'Directive'
        value:
          type: 'DirectiveLiteral'
          value: 'use strict'
          extra:
            raw: "'use strict'"
      ]

  testExpression '''
    ->
      'use strict'
      f()
      'not a directive'
      g
  ''',
    type: 'FunctionExpression'
    body:
      type: 'BlockStatement'
      body: [
        type: 'ExpressionStatement'
        expression:
          type: 'CallExpression'
      ,
        type: 'ExpressionStatement'
        expression: STRING 'not a directive'
      ,
        type: 'ExpressionStatement'
        expression: ID 'g'
      ]
      directives: [
        type: 'Directive'
        value:
          type: 'DirectiveLiteral'
          value: 'use strict'
          extra:
            raw: "'use strict'"
      ]

  testExpression '''
    ->
      "not a directive because it's implicitly returned"
  ''',
    type: 'FunctionExpression'
    body:
      type: 'BlockStatement'
      body: [
        type: 'ExpressionStatement'
        expression: STRING "not a directive because it's implicitly returned"
      ]
      directives: []

  deepStrictIncludeExpectedProperties CoffeeScript.compile('''
    'use strict'
  ''', ast: yes),
    type: 'File'
    program:
      type: 'Program'
      body: []
      directives: [
        type: 'Directive'
        value:
          type: 'DirectiveLiteral'
          value: 'use strict'
          extra:
            raw: "'use strict'"
      ]

  testStatement '''
    class A
      'classes can have directives too'
      a: ->
  ''',
    type: 'ClassDeclaration'
    body:
      type: 'ClassBody'
      body: [
        type: 'ClassMethod'
      ]
      directives: [
        type: 'Directive'
        value:
          type: 'DirectiveLiteral'
          value: 'classes can have directives too'
      ]

  testStatement '''
    if a
      "but other blocks can't"
      b
  ''',
    type: 'IfStatement'
    consequent:
      type: 'BlockStatement'
      body: [
        type: 'ExpressionStatement'
        expression: STRING "but other blocks can't"
      ,
        type: 'ExpressionStatement'
        expression: ID 'b'
      ]
      directives: []

  testExpression '''
    ->
      """not a directive"""
      b
  ''',
    type: 'FunctionExpression'
    body:
      type: 'BlockStatement'
      body: [
        type: 'ExpressionStatement'
        expression:
          type: 'TemplateLiteral'
      ,
        type: 'ExpressionStatement'
        expression: ID 'b'
      ]
      directives: []

  testExpression '''
    ->
      # leading comment
      'use strict'
      b
  ''',
    type: 'FunctionExpression'
    body:
      type: 'BlockStatement'
      body: [
        type: 'ExpressionStatement'
        expression: ID 'b'
      ]
      directives: [
        type: 'Directive'
        value:
          type: 'DirectiveLiteral'
          value: 'use strict'
          extra:
            raw: "'use strict'"
      ]

test "AST as expected for comments", ->
  testComments '''
    a # simple line comment
  ''', [
    type: 'CommentLine'
    value: ' simple line comment'
  ]

  testComments '''
    a ### simple here comment ###
  ''', [
    type: 'CommentBlock'
    value: ' simple here comment '
  ]

  testComments '''
    # just a line comment
  ''', [
    type: 'CommentLine'
    value: ' just a line comment'
  ]

  testComments '''
    ### just a here comment ###
  ''', [
    type: 'CommentBlock'
    value: ' just a here comment '
  ]

  testComments '''
    "#{
      # empty interpolation line comment
     }"
  ''', [
    type: 'CommentLine'
    value: ' empty interpolation line comment'
  ]

  testComments '''
    "#{
      ### empty interpolation block comment ###
     }"
  ''', [
    type: 'CommentBlock'
    value: ' empty interpolation block comment '
  ]

  testComments '''
    # multiple line comments
    # on consecutive lines
  ''', [
    type: 'CommentLine'
    value: ' multiple line comments'
  ,
    type: 'CommentLine'
    value: ' on consecutive lines'
  ]

  testComments '''
    # multiple line comments

    # with blank line
  ''', [
    type: 'CommentLine'
    value: ' multiple line comments'
  ,
    type: 'CommentLine'
    value: ' with blank line'
  ]

  testComments '''
    #no whitespace line comment
  ''', [
    type: 'CommentLine'
    value: 'no whitespace line comment'
  ]

  testComments '''
    ###no whitespace here comment###
  ''', [
    type: 'CommentBlock'
    value: 'no whitespace here comment'
  ]

  testComments '''
    ###
    # multiline
    # here comment
    ###
  ''', [
    type: 'CommentBlock'
    value: '\n# multiline\n# here comment\n'
  ]

  testComments '''
    if b
      ###
      # multiline
      # indented here comment
      ###
      c
  ''', [
    type: 'CommentBlock'
    value: '\n  # multiline\n  # indented here comment\n  '
  ]

  testComments '''
    if foo
      ;
      ### empty ###
  ''', [
    type: 'CommentBlock'
    value: ' empty '
  ]

test "AST as expected for chained comparisons", ->
  testExpression '''
    a < b < c
  ''',
    type: 'ChainedComparison'
    operands: [
      ID 'a'
      ID 'b'
      ID 'c'
    ]
    operators: [
      '<'
      '<'
    ]

  testExpression '''
    a isnt b is c isnt d
  ''',
    type: 'ChainedComparison'
    operands: [
      ID 'a'
      ID 'b'
      ID 'c'
      ID 'd'
    ]
    operators: [
      'isnt'
      'is'
      'isnt'
    ]

  testExpression '''
    a >= b < c
  ''',
    type: 'ChainedComparison'
    operands: [
      ID 'a'
      ID 'b'
      ID 'c'
    ]
    operators: [
      '>='
      '<'
    ]

test "AST as expected for Sequence", ->
  testExpression '''
    (a; b)
  ''',
    type: 'SequenceExpression'
    expressions: [
      ID 'a'
      ID 'b'
    ]

  testExpression '''
    (a; b)""
  ''',
    type: 'TaggedTemplateExpression'
    tag:
      type: 'SequenceExpression'
      expressions: [
        ID 'a'
        ID 'b'
      ]
