;;;;;;;;;;;;;;;;;;;
;; Global Variables

global FILE_PATH           ; project relative path of this file
global PROJECT_NAME = ""   ; project name, used to isolate different projects in the same stack graph

global JUMP_TO_SCOPE_NODE
global ROOT_NODE

;;;;;;;;;;;;;;;;;;;;;;;
;; Attribute Shorthands

attribute node_definition = node        => type = "pop_symbol", node_symbol = node, is_definition
attribute node_reference = node         => type = "push_symbol", node_symbol = node, is_reference
attribute pop_node = node               => type = "pop_symbol", node_symbol = node
attribute pop_scoped_node = node        => type = "pop_scoped_symbol", node_symbol = node
attribute pop_scoped_symbol = symbol    => type = "pop_scoped_symbol", symbol = symbol
attribute pop_symbol = symbol           => type = "pop_symbol", symbol = symbol
attribute push_node = node              => type = "push_symbol", node_symbol = node
attribute push_scoped_node = node       => type = "push_scoped_symbol", node_symbol = node
attribute push_scoped_symbol = symbol   => type = "push_scoped_symbol", symbol = symbol
attribute push_symbol = symbol          => type = "push_symbol", symbol = symbol
attribute scoped_node_definition = node => type = "pop_scoped_symbol", node_symbol = node, is_definition
attribute scoped_node_reference = node  => type = "push_scoped_symbol", node_symbol = node, is_reference
attribute symbol_definition = symbol    => type = "pop_symbol", symbol = symbol, is_definition
attribute symbol_reference = symbol     => type = "push_symbol", symbol = symbol, is_reference

attribute node_symbol = node            => symbol = (source-text node), source_node = node

; ######
; #     # #####   ####   ####  #####    ##   #    #  ####
; #     # #    # #    # #    # #    #  #  #  ##  ## #
; ######  #    # #    # #      #    # #    # # ## #  ####
; #       #####  #    # #  ### #####  ###### #    #      #
; #       #   #  #    # #    # #   #  #    # #    # #    #
; #       #    #  ####   ####  #    # #    # #    #  ####
;
; ########################################################

(program)@prog {
  node @prog.defs
  node @prog.lexical_scope
  edge @prog.lexical_scope -> ROOT_NODE
  edge @prog.lexical_scope -> @prog.defs
}

(program (_)@declaration)@prog {
  edge @prog.defs -> @declaration.defs
  edge @declaration.lexical_scope -> @prog.lexical_scope
}


;; =======================
;;  Top level declarations
;; =======================

[
  (module_declaration)
  (package_declaration)
  (import_declaration)
] @decl
{
  node @decl.defs
  node @decl.lexical_scope
}

(program
  (package_declaration
    (identifier)@pkg_name)? @package) @prog {
  if none @package {
    edge ROOT_NODE -> @prog.defs
  } else {
    node pkg_def
    attr (pkg_def) node_definition = @pkg_name
    edge pkg_def -> @prog.defs
    edge ROOT_NODE -> pkg_def
  }
}

(import_declaration (_) @ref) @import {
  edge @ref.lexical_scope -> @import.lexical_scope
}

;; X
(identifier) @name {
  node @name.lexical_scope
  node @name.type

  node @name.ref
  attr (@name.ref) node_reference = @name
  edge @name.ref -> @name.lexical_scope
}

;; _.X
(scoped_identifier scope: (_) @scope name: (_) @name) @scoped_name {
  node @scoped_name.lexical_scope

  edge @scope.lexical_scope -> @scoped_name.lexical_scope

  edge @name.lexical_scope -> @scope.ref

  node @scoped_name.ref
  edge @scoped_name.ref -> @name.ref
}

[
  (import_declaration                                             (identifier) @_scope @name)
  (import_declaration (scoped_identifier scope: (_) @_scope name: (identifier) @name))
] @import {
  node def
  attr (def) node_definition = @name
  edge def -> @name.ref

  edge @import.defs -> def
}

;;;;;;;;;;;;;;;;;;;;
;; Class Expressions

; Classes (and other declarations with members, e.g. records, enums, interfaces) define a name in their enclosing scope as well as names belonging to the class itself (i.e. statics) and instances of the class.
;
; They additionally implicitly define `this` available in the bodies of instance methods, and subclasses further define `super`. The nodes defining these point at the types of the class and superclass, respectively.

(class_declaration
  name: (identifier) @name
  body: (class_body) @class_body) @class {

  node def
  attr (def) node_definition = @name

  node ref
  attr (ref) node_reference = @name
  edge ref -> @class.lexical_scope
  edge @class_body.lexical_scope -> ref

  edge @class_body.lexical_scope -> @class.lexical_scope

  edge @class_body.lexical_scope -> @class_body.defs
  attr (@class_body.lexical_scope -> @class_body.defs) precedence = 1
  edge @class.defs -> def
  edge def -> @class_body.defs
  edge def -> @class_body.static_defs

  node this__expr_def
  node @class.type

  edge @class.lexical_scope -> this__expr_def

  attr (this__expr_def) pop_symbol = "this", source_node = @name, empty_source_span
  edge this__expr_def -> @class.type

  attr (@class.type) pop_symbol = ":"

  node def__typeof
  attr (def__typeof) pop_symbol = ":"
  edge def -> def__typeof
  edge def__typeof -> @class_body.static_defs

  edge @class.type -> ref
  attr (@class.type -> ref) precedence = 1
}

(class_declaration
  superclass: (superclass
    (_) @superclass_name)
    body: (class_body) @class_body) @class {

    node ref
    attr (ref) node_reference = @superclass_name

    edge @superclass_name.lexical_scope -> @class.lexical_scope

    edge ref -> @class.lexical_scope
    edge @class_body.lexical_scope -> ref

    node super__expr_def
    node super__expr_def__typeof

    edge @class.lexical_scope -> super__expr_def

    attr (super__expr_def) pop_symbol = "super", source_node = @superclass_name, empty_source_span
    edge super__expr_def -> super__expr_def__typeof

    attr (super__expr_def__typeof) pop_symbol = ":"

    edge super__expr_def__typeof -> ref
    edge @class.type -> ref
}

(class_declaration (type_parameters)) @class {
  node @class.type_parameters
  edge @class.lexical_scope -> @class.type_parameters
}

(class_declaration (type_parameters (type_parameter) @param)) @class {
  edge @class.type_parameters -> @param.def
  edge @param.lexical_scope -> @class.lexical_scope
}

(type_parameter (type_identifier) @name) @this {
  node @this.def
  node @this.lexical_scope

  edge @name.lexical_scope -> @this.lexical_scope

  attr (@this.def) node_definition = @name
}

(spread_parameter) @spread_param {
  node @spread_param.lexical_scope
  node @spread_param.def
}

(class_declaration interfaces: (super_interfaces (type_list (_) @type))) @this {
  edge @type.lexical_scope -> @this.lexical_scope
}

(class_body) @class_body {
  node @class_body.lexical_scope
  node @class_body.defs
  node @class_body.static_defs
}

(class_body (_)@declaration)@class_body {
  edge @class_body.defs -> @declaration.defs
  edge @class_body.static_defs -> @declaration.static_defs
  edge @declaration.lexical_scope -> @class_body.lexical_scope
}

(class_body (block) @block) {
  node @block.defs
  node @block.static_defs
  node @block.lexical_scope
  edge @block.lexical_scope -> @block.before_scope
}

;; =====================
;;  Member Declarations
;; =====================

; Member declarations can occur as members of classes, enums, etc. They propagate lexical scope and usually define one or more names, available in `defs`.

[
  (class_declaration)
  (enum_declaration)
  (field_declaration)
  (interface_declaration)
  (method_declaration)
  (constructor_declaration)
  (annotation_type_declaration)
  (constant_declaration)
  (record_declaration)
] @decl
{
  ; FIXME: can we get away with defining one and only one thing for each of these, and therefore having a `.def` node instead of `.defs`?
  node @decl.defs
  node @decl.lexical_scope
  node @decl.static_defs
}

(annotation_type_declaration
  name: (identifier) @name) @annotation {

  node def
  attr (def) node_definition = @name
  edge def -> @annotation.lexical_scope
  edge @annotation.defs -> def
}

(constructor_declaration body: (constructor_body) @body) @this {
  edge @body.lexical_scope -> @this.lexical_scope
}

(constructor_body) @this {
  node @this.lexical_scope
}

(constructor_body . (_) @first) @this {
  edge @first.before_scope -> @this.lexical_scope
}

(constructor_body (_) @a . (_) @b) {
  edge @b.before_scope -> @a.after_scope
}

(explicit_constructor_invocation) @this {
  node @this.before_scope
  node @this.after_scope
}

(explicit_constructor_invocation constructor: (_) @constructor) @this {
  edge @constructor.lexical_scope -> @this.before_scope
}

(explicit_constructor_invocation object: (_) @object) @this {
  edge @object.lexical_scope -> @this.before_scope
}

(explicit_constructor_invocation arguments: (argument_list (_) @arg)) @this {
  edge @arg.lexical_scope -> @this.before_scope
}

(enum_declaration name: (_) @name) @this {
  node def
  attr (def) node_definition = @name
  edge @this.defs -> def

  node @this.constants
  attr (@this.constants) pop_symbol = "."
  edge def -> @this.constants

  ; allow A.X field accesses to resolve constants
  node def__typeof
  attr (def__typeof) pop_symbol = ":"
  edge def -> def__typeof
  edge def__typeof -> @this.constants
}

(enum_declaration (enum_body (enum_constant name: (_) @name))) @this {
  node def
  attr (def) node_definition = @name
  edge @this.constants -> def
}

(field_declaration
  type: (_) @type
  declarator: (variable_declarator
    name: (_) @name
  )
) @field_decl
{
  edge @type.lexical_scope -> @field_decl.lexical_scope

  node member
  edge @field_decl.defs -> member

  attr (member) pop_symbol = "."

  node def
  attr (def) node_definition = @name
  edge member -> def

  node def__typeof
  attr (def__typeof) pop_symbol = ":"
  edge def__typeof -> @type.type

  edge def -> def__typeof
}

(modifiers) @this {
  node @this.lexical_scope
}

(modifiers (_) @annotation) @this {
  edge @annotation.ref -> @this.lexical_scope
}

(marker_annotation name: (_) @name) @this {
  node @this.ref
  attr (@this.ref) node_reference = @name
}

(annotation name: (_) @name) @this {
  node @this.ref
  node @this.lexical_scope
  attr (@this.ref) node_reference = @name
}

(modifiers (annotation arguments: (annotation_argument_list (_) @value))) @this {
  edge @value.lexical_scope -> @this.lexical_scope
}

(element_value_array_initializer) @this {
  node @this.def
  node @this.lexical_scope
}

(element_value_pair value: (_) @value) @this {
  node @this.lexical_scope
  edge @value.lexical_scope -> @this.lexical_scope
}

(field_declaration (modifiers) @modifiers) @decl {
  edge @modifiers.lexical_scope -> @decl.lexical_scope
}

(interface_declaration name: (_) @name body: (_) @body) @this {
  node def
  attr (def) node_definition = @name
  edge @this.defs -> def

  edge def -> @body.defs
  edge @body.lexical_scope -> @this.lexical_scope
}

(interface_declaration (extends_interfaces (type_list (_) @type))) @this {
  edge @type.lexical_scope -> @this.lexical_scope
}

(interface_declaration
  type_parameters: (type_parameters
    (type_parameter
      (type_identifier) @type_identifier))
  body: (_) @body) @_this {
  node type_ident
  attr (type_ident) node_definition = @type_identifier
  edge @body.lexical_scope -> type_ident
}

(interface_body) @this {
  node @this.defs
  node @this.lexical_scope
}

(interface_body (_) @child) @this {
  edge @this.defs -> @child.defs
  edge @child.lexical_scope -> @this.lexical_scope
}

(method_declaration
  (modifiers "static"?@is_static)?
  type: (_) @type
  name: (identifier) @name
  body: (block) @_block) @method
{
  edge @type.lexical_scope -> @method.lexical_scope

  node member

  if none @is_static {
    edge @method.defs -> member
  } else {
    edge @method.static_defs -> member
  }

  attr (member) pop_symbol = "."

  node def
  attr (def) node_definition = @name
  edge member -> def
}

(method_declaration (formal_parameters (_) @param)) @method
{
  edge @param.lexical_scope -> @method.lexical_scope
  edge @method.lexical_scope -> @param.def
  edge @method.defs -> @param.def
}

(formal_parameter type: (_) @type (_) @name) @param
{
  node @param.def
  node @param.lexical_scope
  node def__typeof

  edge @type.lexical_scope -> @param.lexical_scope

  attr (@param.def) node_definition = @name

  attr (def__typeof) pop_symbol = ":"
  edge def__typeof -> @type.type

  edge @param.def -> def__typeof
}

(formal_parameter (modifiers) @modifiers) @this {
  edge @modifiers.lexical_scope -> @this.lexical_scope
}

(method_declaration
  (modifiers) @modifiers) @this {
  edge @modifiers.lexical_scope -> @this.lexical_scope
}

(method_declaration
  body: (_) @stmt) @method
{
  edge @stmt.before_scope -> @method.lexical_scope
}

(record_declaration name: (_) @name body: (_) @body) @this {
  node def
  attr (def) node_definition = @name
  edge @this.defs -> def

  edge @body.lexical_scope -> @this.lexical_scope
}

(record_declaration parameters: (formal_parameters (_) @param)) @this {
  edge @param.lexical_scope -> @this.lexical_scope
  edge @this.lexical_scope -> @param.def
  edge @this.defs -> @param.def
}

;; ============
;;  Statements
;; ============

; Statements are sequenced one after the next, and some (local variable declarations) bring names into scope/shadow existing names for subsequent statements. Thus, instead of having a lexical scope, they have before and after scopes; the before scope points to the previous statement, or the parent scope for the first statement in a sequence, while the after scope points either at the before scope (if no variable is bound) or at a node referencing any variables the statement binds.

[
  (assert_statement)
  (block)
  (break_statement)
  (continue_statement)
  (declaration)
  (do_statement)
  (expression_statement)
  (enhanced_for_statement)
  (for_statement)
  (if_statement)
  (labeled_statement)
  (local_variable_declaration)
  (return_statement)
  (switch_expression)
  (synchronized_statement)
  (throw_statement)
  (try_statement)
  (try_with_resources_statement)
  (while_statement)
  (yield_statement)
] @stmt
{
  node @stmt.before_scope
  node @stmt.after_scope
}

(assert_statement) @stmt {
  edge @stmt.after_scope -> @stmt.before_scope
}

(assert_statement (expression) @expr) @stmt {
  edge @expr.lexical_scope -> @stmt.before_scope
}

(block
  (_) @left
  .
  (_) @right
)
{
  edge @right.before_scope -> @left.after_scope
}

(block
  .
  (_) @first) @block {
  edge @first.before_scope -> @block.before_scope
}

(block
  (_) @last
  . ) @block {
  edge @block.after_scope -> @last.after_scope
}

(break_statement (identifier) @_name) @this {
  edge @this.after_scope -> @this.before_scope
}

(break_statement (identifier) @name) @stmt {
  node ref
  node ns

  attr (ref) node_reference = @name
  edge ref -> ns

  attr (ns) push_symbol = "%Label"

  edge ns -> @stmt.before_scope
}

(continue_statement) @this {
  edge @this.after_scope -> @this.before_scope
}

(continue_statement (identifier) @name) @this {
  node ref
  node ns

  attr (ref) node_reference = @name
  edge ref -> ns

  attr (ns) push_symbol = "%Label"

  edge ns -> @this.before_scope
}

(declaration) @_decl {}

(do_statement body: (_) @body condition: (_) @cond) @stmt {
  edge @body.before_scope -> @stmt.before_scope
  edge @stmt.after_scope -> @stmt.before_scope
  edge @cond.lexical_scope -> @stmt.before_scope
}

(expression_statement (_) @expr) @expr_stmt
{
  edge @expr.lexical_scope -> @expr_stmt.before_scope
  edge @expr_stmt.after_scope -> @expr_stmt.before_scope
}

(enhanced_for_statement type: (_) @type (_) @name value: (_) @value body: (_) @body) @stmt {
  edge @type.lexical_scope -> @stmt.before_scope
  edge @value.lexical_scope -> @stmt.before_scope
  node def
  node defs
  attr (def) node_definition = @name
  edge defs -> @stmt.before_scope
  edge defs -> def
  edge @body.before_scope -> defs
  edge @stmt.after_scope -> @stmt.before_scope
}

(for_statement) @this {
  edge @this.after_scope -> @this.before_scope
}

(for_statement !init !condition !update body: (_) @body) @this {
  edge @body.before_scope -> @this.before_scope
}

(for_statement init: (expression) @init condition: (_) @condition update: (_) @update body: (_) @body) @stmt {
  edge @init.lexical_scope -> @stmt.before_scope
  edge @condition.lexical_scope -> @stmt.before_scope
  edge @update.lexical_scope -> @stmt.before_scope
  edge @body.before_scope -> @stmt.before_scope
}

(for_statement init: (local_variable_declaration) @init condition: (_) @condition update: (_) @update body: (_) @body) @stmt {
  edge @init.before_scope -> @stmt.before_scope
  edge @condition.lexical_scope -> @init.after_scope
  edge @update.lexical_scope -> @init.after_scope
  edge @body.before_scope -> @init.after_scope
}

(if_statement condition: (_) @condition consequence: (_) @consequence) @stmt {
  edge @condition.lexical_scope -> @stmt.before_scope
  edge @consequence.before_scope -> @stmt.before_scope
  edge @stmt.after_scope -> @stmt.before_scope
}

(if_statement alternative: (_) @alternative) @stmt {
  edge @alternative.before_scope -> @stmt.before_scope
}

(labeled_statement (identifier) @name (statement) @child) @stmt {
  edge @child.before_scope -> @stmt.before_scope
  edge @stmt.after_scope -> @child.after_scope

  node def
  attr (def) node_definition = @name
  node ns
  attr (ns) pop_symbol = "%Label"
  edge ns -> def

  edge @stmt.before_scope -> ns
}

(local_variable_declaration
  type: (_) @type
  declarator: (variable_declarator) @var_decl
) @_local_var
{
  edge @var_decl.def__typeof -> @type.type
}

(local_variable_declaration
  type: (_) @type) @local_var {
  edge @type.lexical_scope -> @local_var.before_scope
}

(variable_declarator value: (_) @value) @this {
  edge @value.lexical_scope -> @this.before_scope
}

(local_variable_declaration
  declarator: (_) @last
  . ) @local_var {
  edge @local_var.after_scope -> @last.after_scope
  attr (@local_var.after_scope -> @last.after_scope) precedence = 1
}

(local_variable_declaration
  type: (_)
  .
  declarator: (_) @first) @local_var {
  edge @first.before_scope -> @local_var.before_scope
}

(local_variable_declaration
  declarator: (_) @left
  .
  declarator: (_) @right
  ) @_local_var {
  edge @right.before_scope -> @left.after_scope
}

(variable_declarator
  name: (_) @name) @var_decl {
  node @var_decl.before_scope
  node @var_decl.after_scope
  node @var_decl.def__typeof
  attr (@var_decl.def__typeof) pop_symbol = ":"

  node def
  attr (def) node_definition = @name
  edge @var_decl.after_scope -> def
  edge @var_decl.after_scope -> @var_decl.before_scope

  edge def -> @var_decl.def__typeof
}

(return_statement (_) @expr) @stmt
{
  edge @expr.lexical_scope -> @stmt.before_scope
  edge @stmt.after_scope -> @stmt.before_scope
}

(switch_expression condition: (_) @condition body: (_) @body) @stmt {
  edge @condition.lexical_scope -> @stmt.before_scope
  edge @body.lexical_scope -> @stmt.before_scope
  edge @stmt.after_scope -> @stmt.before_scope
}

(method_declaration
 parameters:
 (formal_parameters
  (formal_parameter
   type: (generic_type
     (type_arguments
      (type_identifier) @type))))
 body:
 (block
  (switch_expression
   condition: (_)
   body: (switch_block
     (switch_block_statement_group
      (switch_label
       (identifier))
      @label))
  )) @stmt) {

  node ident
  attr (ident) node_reference = @type

  node ref__typeof
  attr(ref__typeof) push_symbol = "."
  node implicit_this__typeof
  attr(implicit_this__typeof) push_symbol = ":"

  edge implicit_this__typeof -> @label.lexical_scope
  edge @label.lexical_scope -> ref__typeof
  edge ref__typeof -> implicit_this__typeof
  edge implicit_this__typeof -> ident

  edge ident -> @stmt.after_scope
}


(switch_block) @this {
  node @this.lexical_scope
}

(switch_block (switch_block_statement_group (switch_label) @label)) @this {
  edge @label.lexical_scope -> @this.lexical_scope
}

(switch_block (switch_block_statement_group (switch_label)+ . (statement) @first)) @this {
  edge @first.before_scope -> @this.lexical_scope
}

(switch_block (switch_block_statement_group (switch_label)+ (statement) @a . (statement) @b)) @_this {
  edge @b.before_scope -> @a.after_scope
}

(switch_block (switch_rule (switch_label) @label (_) @body)) @this {
  edge @label.lexical_scope -> @this.lexical_scope
  edge @body.before_scope -> @this.lexical_scope
}

(switch_label) @label {
  node @label.lexical_scope
}

(switch_label (expression) @expr) @label {
  edge @expr.lexical_scope -> @label.lexical_scope
}

(synchronized_statement (_) @expr body: (_) @body) @stmt {
  edge @expr.lexical_scope -> @stmt.before_scope
  edge @body.before_scope -> @stmt.before_scope
  edge @stmt.after_scope -> @stmt.before_scope
}

(try_statement body: (_) @body) @stmt {
  edge @body.before_scope -> @stmt.before_scope
  edge @stmt.after_scope -> @stmt.before_scope
}

(try_statement (catch_clause (catch_formal_parameter (catch_type) @type (_) @name) body: (_) @body)) @stmt {
  node def
  node defs
  attr (def) node_definition = @name
  edge defs -> def
  edge @body.before_scope -> defs
  edge defs -> @stmt.before_scope

  edge @type.lexical_scope -> @stmt.before_scope

  node def__typeof
  attr (def__typeof) pop_symbol = ":"

  edge def -> def__typeof

  edge def__typeof -> @type.type
}

(catch_type) @catch_type {
  node @catch_type.type
  node @catch_type.lexical_scope
}

(catch_type (_) @type) @catch_type {
  edge @catch_type.type -> @type.type
  edge @type.lexical_scope -> @catch_type.lexical_scope
}

(try_statement (finally_clause (_) @finally)) @stmt {
  edge @finally.before_scope -> @stmt.before_scope
}

(try_with_resources_statement) @stmt {
  edge @stmt.after_scope -> @stmt.before_scope
}

(try_with_resources_statement resources: (resource_specification . (resource) @first)) @stmt {
  edge @first.before_scope -> @stmt.before_scope
}

(try_with_resources_statement resources: (resource_specification (resource) @a . (resource) @b)) @_stmt {
  edge @b.before_scope -> @a.after_scope
}

(try_with_resources_statement resources: (resource_specification (resource) @last .) body: (_) @body) @_stmt {
  edge @body.before_scope -> @last.after_scope
}

(resource) @this {
  node @this.before_scope
  node @this.after_scope
}

(resource type: (_) @type (_) @name value: (_) @value) @this {
  edge @type.lexical_scope -> @this.before_scope
  edge @value.lexical_scope -> @this.before_scope

  node def
  attr (def) node_definition = @name

  node def__typeof
  attr (def__typeof) pop_symbol = ":"

  edge def -> def__typeof

  edge def__typeof -> @type.type

  edge @this.after_scope -> def
  edge @this.after_scope -> @this.before_scope
}

(resource . (identifier) @name .) @this {
  node ref
  node ref__typeof

  attr (ref) node_reference = @name

  edge ref -> @name.lexical_scope
  edge @name.lexical_scope -> @this.before_scope

  edge @name.type -> ref__typeof

  attr (ref__typeof) push_symbol = ":"
  edge ref__typeof -> ref

  edge @this.after_scope -> @this.before_scope
}

(resource (field_access) @field_access) @this {
  edge @field_access.lexical_scope -> @this.before_scope
  edge @this.after_scope -> @this.before_scope
}

(try_with_resources_statement (catch_clause (catch_formal_parameter (catch_type) @type (_) @name) body: (_) @body)) @stmt {
  node def
  node defs
  attr (def) node_definition = @name
  edge defs -> def
  edge @body.before_scope -> defs
  edge defs -> @stmt.before_scope

  edge @type.lexical_scope -> @stmt.before_scope

  node def__typeof
  attr (def__typeof) pop_symbol = ":"

  edge def -> def__typeof

  edge def__typeof -> @type.type
}

(try_with_resources_statement (finally_clause (_) @finally)) @stmt {
  edge @finally.before_scope -> @stmt.before_scope
}

(while_statement condition: (_) @condition body: (_) @body) @stmt {
  edge @condition.lexical_scope -> @stmt.before_scope
  edge @body.before_scope -> @stmt.before_scope
  edge @stmt.after_scope -> @stmt.before_scope
}

(yield_statement (_) @expr) @stmt {
  edge @expr.lexical_scope -> @stmt.before_scope
  edge @stmt.after_scope -> @stmt.before_scope
}

;; =====================
;;  Primary Expressions
;; =====================

(array_access (primary_expression) @array (expression) @index) @this {
  edge @array.lexical_scope -> @this.lexical_scope
  edge @index.lexical_scope -> @this.lexical_scope
}

(array_creation_expression type: (_) @type) @this {
  edge @type.lexical_scope -> @this.lexical_scope
}

(array_creation_expression (dimensions_expr (_) @expr)) @this {
  edge @expr.lexical_scope -> @this.lexical_scope
}

(array_initializer (_) @expr) @this {
  edge @expr.lexical_scope -> @this.lexical_scope
}

(class_literal (_) @type) @this {
  edge @type.lexical_scope -> @this.lexical_scope
}

(primary_expression/identifier) @name
{
  node member
  node implicit_this
  node implicit_this__typeof

  node object__typeof

  attr (implicit_this) symbol_reference = "this", empty_source_span
  edge implicit_this__typeof -> implicit_this
  attr (implicit_this__typeof) push_symbol = ":"
  edge implicit_this -> @name.lexical_scope

  node ref
  attr (ref) node_reference = @name
  edge ref -> @name.lexical_scope
  attr (ref -> @name.lexical_scope) precedence = 1
  edge ref -> member

  attr (member) push_symbol = "."

  edge member -> object__typeof
  edge object__typeof -> implicit_this__typeof

  node ref__typeof
  edge @name.type -> ref__typeof

  attr (ref__typeof) push_symbol = ":"
  edge ref__typeof -> ref
}

(field_access
  object: (_) @object
  field: (identifier) @name) @field_access {
  node member

  edge @object.lexical_scope -> @field_access.lexical_scope

  node ref
  attr (ref) node_reference = @name
  edge ref -> member

  attr (member) push_symbol = "."
  edge member -> @object.type

  node ref__typeof
  edge @field_access.type -> ref__typeof

  attr (ref__typeof) push_symbol = ":"
  edge ref__typeof -> ref
}

(method_invocation) @method_invocation
{
  node @method_invocation.before_scope
  node @method_invocation.after_scope
}

(method_invocation arguments: (argument_list (expression) @expr)) @method_invocation {
  edge @expr.lexical_scope -> @method_invocation.lexical_scope
}

; method calls with implicit receiver
(method_invocation
  !object
  name: (identifier) @method_name) @method_invocation {
  node member
  node implicit_this
  node implicit_this__typeof

  node object__typeof

  ; attr (implicit_this) node_reference = @method_invocation
  attr (implicit_this) symbol_reference = "this", empty_source_span

  ; receiver is implicitly "this"
  edge implicit_this__typeof -> implicit_this
  attr (implicit_this__typeof) push_symbol = ":"
  edge implicit_this -> @method_invocation.lexical_scope

  node expr_ref
  attr (expr_ref) node_reference = @method_name
  edge expr_ref -> member

  attr (member) push_symbol = "."

  edge member -> object__typeof
  edge object__typeof -> implicit_this__typeof

  node expr_ref__typeof
  edge @method_invocation.type -> expr_ref__typeof

  attr (expr_ref__typeof) push_symbol = ":"
  edge expr_ref__typeof -> expr_ref
}

(method_reference . (_) @lhs) @this {
  edge @lhs.lexical_scope -> @this.lexical_scope
}

(method_reference . (_) @lhs (identifier) @name) @this {
  node @this.member

  node expr_ref
  attr (expr_ref) node_reference = @name
  edge expr_ref -> @this.member

  attr (@this.member) push_symbol = "."
  edge @this.member -> @lhs.type

  node expr_ref__typeof
  ; edge @this.type -> expr_ref__typeof ; FIXME: function types

  attr (expr_ref__typeof) push_symbol = ":"
  edge expr_ref__typeof -> expr_ref
}

(method_reference . (identifier) @lhs (identifier) @_name) @this {
  ; @lhs could be a type name
  node ref
  attr (ref) node_reference = @lhs
  edge @this.member -> ref
  edge ref -> @this.lexical_scope
}

(parenthesized_expression (_) @child) @expr {
  edge @child.lexical_scope -> @expr.lexical_scope
}

(condition (_) @child) @expr {
  edge @child.lexical_scope -> @expr.lexical_scope
}

;; =============
;;  Expressions
;; =============

; Expressions have a lexical scope (propagated to child expressions) and a type. The lexical scope is used for lexical name lookup, while types enable type-based name lookup (for field accesses, method lookup, and so forth).

[
  (array_initializer)
  (assignment_expression)
  (binary_expression)
  (instanceof_expression)
  (lambda_expression)
  (ternary_expression)
  (update_expression)
  (decimal_integer_literal)
  (hex_integer_literal)
  (octal_integer_literal)
  (binary_integer_literal)
  (decimal_floating_point_literal)
  (hex_floating_point_literal)
  (true)
  (false)
  (character_literal)
  (string_literal)
  (null_literal)
  (class_literal)
  (this)
  ; (identifier)
  (parenthesized_expression)
  (condition)
  (object_creation_expression)
  (field_access)
  (array_access)
  (method_invocation)
  (method_reference)
  (array_creation_expression)
  (unary_expression)
  (cast_expression)
  (switch_expression)
  (super)
] @expr
{
  node @expr.type
  node @expr.lexical_scope
}

(assignment_expression left: (identifier) @name) @this {
  node member
  node implicit_this
  node implicit_this__typeof

  node object__typeof

  attr (implicit_this) symbol_reference = "this", empty_source_span
  edge implicit_this__typeof -> implicit_this
  attr (implicit_this__typeof) push_symbol = ":"
  edge implicit_this -> @name.lexical_scope

  node ref
  attr (ref) node_reference = @name
  edge ref -> @name.lexical_scope
  attr (ref -> @name.lexical_scope) precedence = 1
  edge ref -> member

  attr (member) push_symbol = "."

  edge member -> object__typeof
  edge object__typeof -> implicit_this__typeof

  node ref__typeof
  edge @name.type -> ref__typeof

  attr (ref__typeof) push_symbol = ":"
  edge ref__typeof -> ref

  edge @name.lexical_scope -> @this.lexical_scope
}

(assignment_expression left: (field_access) @access) @this {
  edge @access.lexical_scope -> @this.lexical_scope
}

(assignment_expression right: (_) @right) @this {
  edge @right.lexical_scope -> @this.lexical_scope
}

(binary_expression left: (_) @lhs right: (_) @rhs) @this {
  edge @lhs.lexical_scope -> @this.lexical_scope
  edge @rhs.lexical_scope -> @this.lexical_scope
}

(cast_expression type: (_) @type) @this {
  edge @type.lexical_scope -> @this.lexical_scope
}

(cast_expression value: (_) @expr) @this {
  edge @expr.lexical_scope -> @this.lexical_scope
}

(instanceof_expression left: (_) @expr right: (_) @type) @this {
  edge @expr.lexical_scope -> @this.lexical_scope
  edge @type.lexical_scope -> @this.lexical_scope
}

(lambda_expression) @this {
  node @this.params
}

(lambda_expression body: (expression) @body) @this {
  edge @body.lexical_scope -> @this.lexical_scope
  edge @body.lexical_scope -> @this.params
}

(lambda_expression body: (block) @body) @this {
  edge @body.before_scope -> @this.lexical_scope
  edge @body.before_scope -> @this.params
}

(lambda_expression parameters: (_) @param) @this {
  node param
  attr (param) node_definition = @param

  edge @this.params -> param
}

[
  (super)
  (this)
] @expr
{
  node expr_ref
  node expr_ref__typeof

  attr (expr_ref) node_reference = @expr
  edge expr_ref -> @expr.lexical_scope

  edge @expr.type -> expr_ref__typeof

  attr (expr_ref__typeof) push_symbol = ":"
  edge expr_ref__typeof -> expr_ref
}

(method_invocation
  object: (_) @object
  name: (identifier) @method_name) @method_invocation {
    node member

    edge @object.lexical_scope -> @method_invocation.lexical_scope

    node expr_ref
    attr (expr_ref) node_reference = @method_name
    edge expr_ref -> member

    attr (member) push_symbol = "."
    edge member -> @object.type

    node expr_ref__typeof
    edge @method_invocation.type -> expr_ref__typeof

    attr (expr_ref__typeof) push_symbol = ":"
    edge expr_ref__typeof -> expr_ref
}

(object_creation_expression (primary_expression) @child) @this {
  edge @child.lexical_scope -> @this.lexical_scope
}

(object_creation_expression type_arguments: (type_arguments (_) @type)) @this {
  edge @type.lexical_scope -> @this.lexical_scope
}

(object_creation_expression type: (_) @type) @this {
  edge @type.lexical_scope -> @this.lexical_scope
}

(object_creation_expression arguments: (argument_list (expression) @expr)) @this {
  edge @expr.lexical_scope -> @this.lexical_scope
}

(ternary_expression (expression) @expr) @this {
  edge @expr.lexical_scope -> @this.lexical_scope
}

(unary_expression (expression) @expr) @this {
  edge @expr.lexical_scope -> @this.lexical_scope
}

(update_expression (expression) @expr) @this {
  edge @expr.lexical_scope -> @this.lexical_scope
}

;; ==========
;;  Types
;; ==========

; Types have a lexical scope for type name lookup.

[
  (annotated_type)
  (array_type)
  (boolean_type)
  (floating_point_type)
  (generic_type)
  (integral_type)
  (scoped_type_identifier)
  (type_identifier)
  (void_type)
] @type
{
  node @type.lexical_scope
  node @type.type ; FIXME: is this actually needed?
}

[
  (boolean_type)
  (floating_point_type)
  (integral_type)
  (type_identifier)
  (void_type)
] @type
{
  attr (@type.type) node_reference = @type
}

(array_type element: (_) @child) @this {
  edge @child.lexical_scope -> @this.lexical_scope
  ; FIXME: use some sort of subscripting relation for .type
}

(generic_type . (_) @name) @this {
  edge @name.lexical_scope -> @this.lexical_scope
}

(generic_type (type_arguments (_) @type)) @this {
  edge @type.lexical_scope -> @this.lexical_scope
}

(scoped_type_identifier . (_) @name) @this {
  edge @name.lexical_scope -> @this.lexical_scope
}

(wildcard) @this
{
  node @this.lexical_scope
}

(type_identifier) @this
{
  edge @this.type -> @this.lexical_scope
}

(scoped_type_identifier
  (type_identifier) @imported_class_name (type_identifier) @method_name) {

  node member
  attr (member) push_symbol = "."

  edge @method_name.type -> member
  edge member -> @imported_class_name.type
}

;; ==========
;;  Comments
;; ==========

(line_comment)@line_comment {
  node @line_comment.before_scope
  node @line_comment.after_scope
  node @line_comment.lexical_defs
  node @line_comment.lexical_scope
  node @line_comment.defs
  node @line_comment.ref
  node @line_comment.static_defs

  edge @line_comment.after_scope -> @line_comment.before_scope
}

(block_comment)@block_comment {
  node @block_comment.before_scope
  node @block_comment.after_scope
  node @block_comment.defs
  node @block_comment.ref
  node @block_comment.static_defs
  node @block_comment.lexical_scope

  edge @block_comment.after_scope -> @block_comment.before_scope
}