;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Stack graphs definition for JavaScript
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Global Variables
;; ^^^^^^^^^^^^^^^^
global ROOT_NODE
global JUMP_TO_SCOPE_NODE
global FILE_PATH
global PROJECT_NAME = ""
;; 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
;; Stack Graph Rules
;; ^^^^^^^^^^^^^^^^^
;; # JavaScript
;; This file defines StackGraph queries for JavaScript. It is written as a
;; semi-literate file, which is to say, comments starting with `;;` can be
;; treated as Markdown. This file can therefore be converted into a
;; corresponding pure Markdown document by removing the StackGraph comments, and
;; wrapping the uncommented code in Markdown code blocks.
;; This file has a number of sections that it's divided into, and it's useful to
;; provide an overview here. Aside from conventions, the queries are broken up
;; into groups by what kind of syntactic objects the group covers. There are
;; Programs, Statements, Expressions, Patterns, and Special Cases.
;; Within each major section, we break things down by the particular syntactic
;; form under consideration. Some particular syntactic forms are not one of the
;; main forms listed, but are tightly associated with other forms -- for
;; example, the branches of an `if` statement are not statements nor
;; expressions, they're simply components of an `if` statement. In that
;; situation, we group them with the primary statement that they're associated
;; with, where possible, and nearby when not.
;; Additionally, some syntactic constructs have arbitrary numbers of child
;; nodes, which requires us to explain how to relate them as sequences of
;; nodes. In such cases, we have associated queries with the node type in
;; question.
;; ## Design Conventions
;; The general convention for how JavaScript is translated into a Stack Graph is
;; to treat the graph as a reverse Control Flow Graph. There are some corner
;; cases for which that analogy doesn't quite work, but it's sufficient for
;; conveying the overall approach. Many things matter for name resolution in a
;; Control Flow Graph, but one of the only ones we can really encode into a
;; Stack Graph, and arguably the most important one, is the flow of the variable
;; environment, and so we create scope nodes for those.
;; In particular, for each node, during an execution traversal, there is an
;; incoming variable environment that the node will be executed in, and an
;; outgoing variable environment that resulted from executing the node and all
;; the various variable assignments and updates inside it.
;; An example helps, so let's consider a simplified case: `if` statement with a
;; mandatory `else` branch. If we were to implement an execution function for
;; this fictionalized language, there would be some environment that goes into
;; the execution at the whole `if-then-else` statement, an environment that goes
;; into the evaluation of the test, an environment that comes out of the test
;; (because the test could be something like `x++` which changes the
;; environment), an environment which goes into each branch (which happens to be
;; the same one that came out of the test), and an environment that comes out of
;; each of the branches. So each major AST node has a pair of environments
;; associated with it: the incoming one to execute it in, and the outcoming node
;; that resulted from executing.
;; We therefore would implement `if-then-else` statements like so:
;; ``````stgdoc
;; (if_then_else
;; (_)@test
;; (_)@consequent
;; (_)alternative)@if_stmt {
;;
;; edge @test.before_scope -> @if_stmt.before_scope
;; edge @consequent.before_scope -> @test.after_scope
;; edge @alternative.before_scope -> @test.after_scope
;; edge @if_stmt.after_scope -> @consequent.after_scope
;; edge @if_stmt.after_scope -> @alternative.after_scope
;;
;; }
;; ``````
;;
;; Another important way that things build scopes is through values. When an
;; expression is executed and returns a value, that value in many ways acts like
;; a sub-environment, at least in that the value has different parts that can be
;; accessed in various ways. For a simple value, as constructed by a number
;; literal expression, the number itself has no parts. So it has an associated
;; value scope node in the graph, but no edges coming off that:
;; ``````stgdoc
;; (number)@num {
;; attr (@num.value) pop_symbol = "NUM_VAL"
;; }
;; ``````
;; Why we use a `pop` attribute here isn't deeply important so we'll gloss over
;; it.
;; All kinds of expressions have values, what precisely goes into them depends
;; on what the expression is. Objects, for instance, would have corresponding
;; values that point to the values of each of the keys in the object. Arrays
;; would have values that point to the values of each of the indexes.
;; The primary purpose of values is to act as the targets of assignments, so
;; that names can resolve to something in code. Normally we care only about the
;; location of the assignment, of course, but we also need to be able to do
;; further lookup on the values. For instance, in an expression like `x.y.z`,
;; we need to look up `x` of course, but also then further look up `y` on that,
;; and then `z`. So whatever `x` is assigned to has to also be present in the
;; graph as something which can itself have stuff hanging off it that can be
;; used for lookup.
;; If you were to run some JavaScript to test whether or not number literals
;; have parts (really, members/fields), you'd discover that actually they do,
;; but not because of which number they are. Rather, their parts are for builtin
;; functionality like `toString`. To handle this, we can point the value to some
;; shared global variable that represents the prototype for the number, like so:
;; ``````stgdoc
;; (number)@num {
;; attr (@num.value) pop_symbol = "NUM_VAL"
;; edge @num.value -> @num.number_prototype
;; }
;; ``````
;; In the preamble definition for the `program` node type, we could then also
;; want to define number prototype, something like this:
;; ``````stgdoc
;; inherit .number_prototype
;;
;; (program)@prog {
;;
;; ...
;;
;; node @prog.number_prototype
;; edge @prog.number_prototype -> @prog.number_prototype_toString_method_value
;; edge @prog.number_prototype -> @prog.number_prototype_valueOf_method_value
;; ...etc
;;
;; ...
;;
;; }
;; ``````
;; We would then also want to have a bunch of hand-encoded graphs for the values
;; of those fields. That would then allow the number values to point to their
;; prototypes where the fields could be found and used for further lookup on
;; methods.
;; One caveat of this is that we don't want to get too deep into explaining how
;; some method might be implemented. Indeed, sometimes we *can't*, because it's
;; not implementable in JavaScript at all and is instead some primitive function
;; implemented in the host language. What we want to do, instead, is to provide
;; just enough implementation that other data can flow through as much as
;; possible, to prevent calls to primitive methods from blocking downstream name
;; resolution. For instance, it would be unfortunate if calling `toString` on a
;; number did not let you look up string fields on the resultant value of the
;; call, i.e. if `length` in `(5).toString().length` just simply could not be
;; resolved at all. In such cases, the ideal approach is to implement a bare
;; minimum of `toString` so that we can recover the fact that its returned
;; value is a string. E.g.:
;; ``````stgdoc
;; (program)@prog {
;;
;; ...
;;
;; edge @prog.number_prototype -> @prog.number_prototype_toString_method_value
;; edge @prog.number_prototype_toString_method_value -> @prog.number_prototype_toString_method_value_return
;; attr (@prog.number_prototype_toString_method_value_return) pop_symbol = "GUARD:RETURN"
;; edge @prog.number_prototype_toString_method_value_return -> @prog.string_prototype
;;
;; ...
;;
;; }
;; ``````
;; As currently implemented, this document does not contain any rules for
;; builtins because of the scope of that task, but they are currently in the
;; pipeline, using the above approach.
;; Something you may notice in the above code snippet is this pop node labelled
;; `GUARD:RETURN`. This is another part of the design conventions for this
;; library. We often want to use nodes in the graph to constrain name lookup.
;; In this case, we want to be able to pinpoint some of the graph nodes
;; associated with the body of a function as being the values returned by the
;; function. We do this by creating a so-called guard node, which will only be
;; traversed if there is a corresponding push somewhere else. We would generate
;; such a push node precisely when we call a function. This lets us treat the
;; function *ASTs* as if they have parts that we can inspect *in the graph*.
;; By convention, this library of queries prefixes all guard nodes with "GUARD:"
;; to distinguish them from nodes that more directly correspond to aspects of
;; execution such as member lookup (labelled with "GUARD:MEMBER") or variable lookup
;; (labelled with the variable name itself). The current names used for guard
;; nodes are
;; - `GUARD:CONSTRUCTOR` - used for the constructor method inside a `Class`
;; - `GUARD:DEFAULT` - used for the default export value
;; - `GUARD:EXPORTS` - used for the names exported by the module
;; - `GUARD:GANDALF` - used for situations where parts of a graph must be accessible
;; in order to avoid pruning, but which never the less should not be actually reached
;; during any normal traversals. By only ever popping, and never pushing, we can get
;; a connected subgraph that is never the less never traversed.
;; - `GUARD:LABEL` - used for the names of labels
;; - `GUARD:MEMBER` - used for the members/fields/properties of objects
;; - `GUARD:MODULE` - used for module names
;; - `GUARD:RETURN` - used for the AST nodes for values returned by a function
;; in the function body
;; - `GUARD:THIS` - used for the implicit `this` argument of a function inside
;; - `GUARD:PKG_INTERNAL` - used for the package internal structure that should not
;; be accessible directly from the root
; ## Inherited
inherit .builtins_Regex_prototype
inherit .builtins_arguments_prototype
inherit .builtins_boolean
inherit .builtins_empty_object
inherit .builtins_null
inherit .builtins_number
inherit .builtins_string
inherit .builtins_undefined
inherit .class_value
inherit .constructor
inherit .export_statement
inherit .exports
inherit .hoist_point
inherit .closure_point
inherit .import_statement
inherit .pkg_pop
inherit .pkg_push
inherit .return_or_yield
inherit .containing_class_value
;; ██████ ██████ ██████ ██████ ██████ █████ ███ ███ ███████
;; ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ████ ██
;; ██████ ██████ ██ ██ ██ ███ ██████ ███████ ██ ████ ██ ███████
;; ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
;; ██ ██ ██ ██████ ██████ ██ ██ ██ ██ ██ ██ ███████
;; ## Programs
;; ### Attributes Defined on Programs
;; TODO
(program)@prog {
node @prog.after_scope
node @prog.before_scope
let @prog.closure_point = @prog.after_scope
; apparently it's perfectly cromulent to `return` from the top level scope (because control flow happens there too), so we make a top-level return node.
node @prog.return_or_yield
}
;; ### Program Queries
(program)@prog {
node prog_module_pop
node prog_exports_pop
node prog_pkg_pop_guard
node prog_pkg_push_guard
node prog_legacy_qname_guard
node @prog.exports
node @prog.hoist_point
node @prog.pkg_pop
node @prog.pkg_push
attr (prog_pkg_pop_guard) pop_symbol = "GUARD:PKG_INTERNAL"
attr (@prog.pkg_pop) pop_symbol = PROJECT_NAME
edge ROOT_NODE -> prog_pkg_pop_guard
edge prog_pkg_pop_guard -> @prog.pkg_pop
attr (@prog.pkg_push) push_symbol = PROJECT_NAME
attr (prog_pkg_push_guard) push_symbol = "GUARD:PKG_INTERNAL"
edge @prog.pkg_push -> prog_pkg_push_guard
edge prog_pkg_push_guard -> ROOT_NODE
node module_guard_pop
attr (module_guard_pop) pop_symbol = "GUARD:MODULE"
edge @prog.pkg_pop -> module_guard_pop
node prog_module_pop_start
edge module_guard_pop -> prog_module_pop_start
var module_pop_end = prog_module_pop_start
let module_name = (replace FILE_PATH "\.js$" "")
scan module_name {
"([^/]+)/" {
attr (module_pop_end) pop_symbol = $1
node module_pop_end_next
edge module_pop_end -> module_pop_end_next
set module_pop_end = module_pop_end_next
}
"([^/]+)$" {
attr (module_pop_end) symbol_definition = $1, source_node = @prog
attr (module_pop_end) empty_source_span
attr (module_pop_end) definiens_node = @prog
}
}
edge @prog.before_scope -> @prog.pkg_push
edge @prog.before_scope -> @prog.hoist_point
attr (prog_legacy_qname_guard) push_symbol = "GUARD:LEGACY_QNAME"
edge @prog.before_scope -> prog_legacy_qname_guard
edge prog_legacy_qname_guard -> ROOT_NODE
attr (prog_module_pop) empty_source_span
attr (@prog.exports) empty_source_span
attr (prog_exports_pop) empty_source_span
attr (@prog.before_scope) empty_source_span
attr (@prog.after_scope) empty_source_span
attr (prog_exports_pop) pop_symbol = "GUARD:EXPORTS"
edge module_pop_end -> prog_exports_pop
edge prog_exports_pop -> @prog.exports
;; builtin types
node @prog.builtins_number
node @prog.builtins_string
node @prog.builtins_boolean
node @prog.builtins_null
node @prog.builtins_undefined
node @prog.builtins_Regex_prototype
node @prog.builtins_arguments_prototype
node @prog.builtins_empty_object
; !!!! HACK
; stack graphs currently make it impossible to test if an inherited variable
; like this is defined or not
let @prog.containing_class_value = @prog.builtins_null
}
; programs, first statement
(program
.
(_)@first_stmt)@prog {
; scopes flow from the program into the first statement
edge @first_stmt.before_scope -> @prog.before_scope
}
; program, between statements
(program
(_)@left_stmt
.
(_)@right_stmt) {
; scopes flow from the left statement to the right one
edge @right_stmt.before_scope -> @left_stmt.after_scope
}
; program, last statement
(program
(_)@last_stmt
.)@prog {
; scopes flow from the last statement to the program
edge @prog.after_scope -> @last_stmt.after_scope
}
(hash_bang_line)@hashbang {
node @hashbang.after_scope
node @hashbang.before_scope
edge @hashbang.after_scope -> @hashbang.before_scope
}
(comment)@comment {
node @comment.after_scope
node @comment.before_scope
node @comment.value
node @comment.covalue
node @comment.new_bindings ; for object patterns
edge @comment.after_scope -> @comment.before_scope
node @comment.source ; for export clauses with multiple exports
}
(identifier) @identifier {
node @identifier.before_scope
node @identifier.after_scope
node @identifier.value
node @identifier.covalue
node @identifier.new_bindings
edge @identifier.after_scope -> @identifier.before_scope
}
;; ███████ ████████ █████ ████████ ███████ ███ ███ ███████ ███ ██ ████████ ███████
;; ██ ██ ██ ██ ██ ██ ████ ████ ██ ████ ██ ██ ██
;; ███████ ██ ███████ ██ █████ ██ ████ ██ █████ ██ ██ ██ ██ ███████
;; ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
;; ███████ ██ ██ ██ ██ ███████ ██ ██ ███████ ██ ████ ██ ███████
;; ## Statements
;; ### Attributes Defined on Statements
;; TODO
[
(export_statement)
(import_statement)
(debugger_statement)
(expression_statement)
(function_declaration)
(generator_function_declaration)
(class_declaration)
(lexical_declaration)
(variable_declaration)
(statement_block)
(if_statement)
(switch_statement)
(for_statement)
(for_in_statement)
(while_statement)
(do_statement)
(try_statement)
(with_statement)
(break_statement)
(continue_statement)
(return_statement)
(throw_statement)
(empty_statement)
(labeled_statement)
]@stmt {
node @stmt.after_scope
node @stmt.before_scope
}
;; ### Statement Queries
;; ███████ ██ ██ ██████ ██████ ██████ ████████ ███████
;; ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
;; █████ ███ ██████ ██ ██ ██████ ██ ███████
;; ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
;; ███████ ██ ██ ██ ██████ ██ ██ ██ ███████
;; #### Export
(export_statement)@export_stmt {
let @export_stmt.export_statement = @export_stmt
}
; exports of just names
; eg
; export { foo, bar as baz };
(export_statement
(export_clause)@export_clause
!source)@export_stmt {
edge @export_clause.source -> @export_clause.before_scope
; scope flows through the export clause
edge @export_clause.before_scope -> @export_stmt.before_scope
edge @export_stmt.after_scope -> @export_clause.after_scope
}
(export_statement
(export_clause)@export_clause
source:(_)@source)@export_stmt {
edge @export_clause.source -> @source.exports
edge @export_clause.before_scope -> @export_stmt.before_scope
edge @export_stmt.after_scope -> @export_clause.after_scope
}
(export_statement
(declaration)@decl)@export_stmt {
edge @decl.before_scope -> @export_stmt.before_scope
edge @export_stmt.after_scope -> @decl.after_scope
}
(export_statement "default"
(declaration)@decl)@export_stmt {
node pop_guard_default
attr (pop_guard_default) symbol_definition = "GUARD:DEFAULT", source_node = @decl, definiens_node = @export_stmt
edge @export_stmt.exports -> pop_guard_default
;; edge export_stmt_pop_guard_default -> @decl.value ;; FIXME declarations have no .value
; !!!! HACK These detour nodes are a massive hack to allow find all refs land on defs
; for the default values of modules that have useful names like the module name or
; package name
node detour_push
node detour_pop
scan FILE_PATH {
"^(.+/)?([^/]+)/index\.js$" {
let module_name = $2
attr (detour_push) push_symbol = module_name
attr (detour_pop) symbol_definition = module_name, source_node = @decl, definiens_node = @export_stmt
edge pop_guard_default -> detour_push
edge detour_push -> detour_pop
; edge detour_pop -> @decl.value ;; FIXME declarations have no .value
}
"^(.+/)?([^/]+)\.js$" {
let module_name = $2
attr (detour_push) push_symbol = module_name
attr (detour_pop) symbol_definition = module_name, source_node = @decl, definiens_node = @export_stmt
edge pop_guard_default -> detour_push
edge detour_push -> detour_pop
; edge detour_pop -> @decl.value ;; FIXME declarations have no .value
}
}
node default_detour_push
node default_detour_pop
attr (default_detour_push) push_symbol = "default"
attr (default_detour_pop) symbol_definition = "default", source_node = @decl, definiens_node = @export_stmt
edge pop_guard_default -> default_detour_push
edge default_detour_push -> default_detour_pop
; edge default_detour_pop -> @decl.value ;; FIXME declarations have no .value
}
(export_statement "default"
value:(_)@value)@export_stmt {
node @export_stmt.pop_guard_default
attr (@export_stmt.pop_guard_default) symbol_definition = "GUARD:DEFAULT", source_node = @value, definiens_node = @export_stmt
edge @export_stmt.exports -> @export_stmt.pop_guard_default
edge @export_stmt.pop_guard_default -> @value.value
; !!!! HACK These detour nodes are a massive hack to allow find all refs land on defs
; for the default values of modules that have useful names like the module name or
; package name
node detour_push
node @export_stmt.detour_pop
scan FILE_PATH {
"^(.+/)?([^/]+)/index\.js$" {
let module_name = $2
attr (detour_push) push_symbol = module_name
attr (@export_stmt.detour_pop) symbol_definition = module_name, source_node = @value, definiens_node = @export_stmt
edge @export_stmt.pop_guard_default -> detour_push
edge detour_push -> @export_stmt.detour_pop
edge @export_stmt.detour_pop -> @value.value
}
"^(.+/)?([^/]+)\.js$" {
let module_name = $2
attr (detour_push) push_symbol = module_name
attr (@export_stmt.detour_pop) symbol_definition = module_name, source_node = @value, definiens_node = @export_stmt
edge @export_stmt.pop_guard_default -> detour_push
edge detour_push -> @export_stmt.detour_pop
edge @export_stmt.detour_pop -> @value.value
}
}
node default_detour_push
node @export_stmt.default_detour_pop
attr (default_detour_push) push_symbol = "default"
attr (@export_stmt.default_detour_pop) symbol_definition = "default", source_node = @value, definiens_node = @export_stmt
edge @export_stmt.pop_guard_default -> default_detour_push
edge default_detour_push -> @export_stmt.default_detour_pop
edge @export_stmt.default_detour_pop -> @value.value
}
(export_statement
declaration: [
(function_declaration name:(identifier)@name)
(generator_function_declaration name:(identifier)@name)
(class_declaration name:(identifier)@name)
(lexical_declaration (variable_declarator name:(identifier)@name))
(variable_declaration (variable_declarator name:(identifier)@name))
]@_decl)@export_stmt {
; TODO this doesn't support destructuring exports
edge @export_stmt.exports -> @name.pop
}
; TODO
; export let [x,y] = [1,2];
(export_statement
declaration: [
(lexical_declaration
(variable_declarator
name: [
(object_pattern)
(array_pattern)
]@pattern))
(variable_declaration
(variable_declarator
name: [
(object_pattern)
(array_pattern)
]@pattern))
])@export_stmt {
edge @export_stmt.exports -> @pattern.new_bindings
}
(export_clause)@export_clause {
node @export_clause.after_scope
node @export_clause.before_scope
node @export_clause.source
}
(export_clause (_)* @clauses)@export_clause {
if (is-empty @clauses) {
edge @export_clause.after_scope -> @export_clause.before_scope
}
}
(export_clause
(_)@export)@export_clause {
edge @export.source -> @export_clause.source
}
(export_clause
.
(_)@first_export)@export_clause {
edge @first_export.before_scope -> @export_clause.before_scope
}
(export_clause
(_)@left_export
.
(_)@right_export) {
edge @right_export.before_scope -> @left_export.after_scope
}
(export_clause
(_)@last_export
.)@export_clause {
edge @export_clause.after_scope -> @last_export.after_scope
}
;; Export specifiers have several cases:
;; - the reference into the source module can be default or named
;; - the definition from this module can be default or named.
;;
;; We model this using seperate sets of rules, two rules for the reference,
;; and two rules for the definition. The `.def_to_ref` node is used to connect
;; the two.
(export_specifier)@export_specifier {
node @export_specifier.after_scope
node @export_specifier.before_scope
node @export_specifier.def_to_ref
node @export_specifier.source
edge @export_specifier.after_scope -> @export_specifier.before_scope
}
;; Export specifier reference rules
; export { default } from ...
; export { default as ... } from ...
(
(export_specifier
name:(_)@name
)@export_specifier
(#not-eq? @name "default")
) {
node name_push
attr (name_push) node_reference = @name
edge @export_specifier.def_to_ref -> name_push
edge name_push -> @export_specifier.source
}
; export { foo } from ...
; export { foo as ... } from ...
(
(export_specifier
name:(_)@name
)@export_specifier
(#eq? @name "default")
) {
node push_guard_default
attr (push_guard_default) symbol_reference = "GUARD:DEFAULT", source_node = @name
edge @export_specifier.def_to_ref -> push_guard_default
edge push_guard_default -> @export_specifier.source
}
;; Export specifier definition rules
; export { foo } from ...
; export { ... as foo } from ...
( [
(export_specifier
name:(_)@alias
!alias)@export_specifier
(export_specifier
name:(_)
alias:(_)@alias)@export_specifier
]
(#not-eq? @alias "default")
) {
node name_pop
attr (name_pop) node_definition = @alias, definiens_node = @export_specifier.export_statement
edge @export_specifier.exports -> name_pop
edge name_pop -> @export_specifier.def_to_ref
}
; export { default } from ...
; export { ... as default } from ...
( [
(export_specifier
name:(_)@alias
!alias)@export_specifier
(export_specifier
name:(_)
alias:(_)@alias)@export_specifier
]
(#eq? @alias "default")
) {
node pop_guard_default
attr (pop_guard_default) symbol_definition = "GUARD:DEFAULT", source_node = @alias, definiens_node = @export_specifier.export_statement
edge @export_specifier.exports -> pop_guard_default
edge pop_guard_default -> @export_specifier.def_to_ref
}
; simple default exports
; export default ...;
(export_statement
value:(_)@default_expr)@export_stmt {
edge @default_expr.before_scope -> @export_stmt.before_scope
edge @export_stmt.after_scope -> @default_expr.after_scope
}
; aggregated exports
; export * from "foo.js";
(export_statement
.
source:(_)@source)@export_statement {
edge @export_statement.after_scope -> @export_statement.before_scope
edge @export_statement.exports -> @source.exports
}
; namespace exports
; export * as foo from "bar.js";
(export_statement
(namespace_export (_)@alias)
source:(_)@source)@export_statement {
node alias_pop
node alias_pop_dot
node source_push
node source_push_guard_exports
edge @export_statement.after_scope -> @export_statement.before_scope
attr (alias_pop) node_definition = @alias, definiens_node = @export_statement.export_statement
attr (alias_pop_dot) pop_symbol = "GUARD:MEMBER"
edge @export_statement.exports -> alias_pop
edge alias_pop -> alias_pop_dot
edge alias_pop_dot -> @source.exports
}
;; ██ ███ ███ ██████ ██████ ██████ ████████ ███████
;; ██ ████ ████ ██ ██ ██ ██ ██ ██ ██ ██
;; ██ ██ ████ ██ ██████ ██ ██ ██████ ██ ███████
;; ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
;; ██ ██ ██ ██ ██████ ██ ██ ██ ███████
;; #### Import
;; We distinguish two kinds of imports, based on the shape of the path:
;;
;; - Relative imports, whose path starts with `.` or `..`. These are resolved relative
;; to the current module, in the same package.
;; - Non-relative or bare imports, which do not start with `.`, `..`, or `/`. These are
;; resolved as package names. Note that the package definitions are introduced in the
;; Go code based on `package.json`, not in this query file.
;;
;; Import paths may include optional `.js` extensions, but behave the same regardless of
;; whether the extension is present.
;;
;; ## Limitations
;;
;; - Absolute imports, whose paths start with `/`, are not supported.
;; - Non-relative imports can resolve into a package (i.e., start with package name
;; components and then module name components inside that package). However, because
;; we don't detect source roots for JavaScript, this might not always work. For example,
;; a module `mod.js` inside a `src/` directory of package `foo` would be accessible as
;; `foo/src/mod`, while `foo/mod` is probably intended.
;;
;; ## References
;;
;; - ES6: https://nodejs.org/api/esm.html, https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import
;; - CommonJS: https://nodejs.org/api/modules.html
(import_statement)@import_stmt {
let @import_stmt.import_statement = @import_stmt
}
[
(import_statement source:(_)@source)
(export_statement source:(_)@source)
( (call_expression function:(_)@_require arguments:(arguments (string)@source)) (#eq? @_require "require") )
( (call_expression function:(_)@_import arguments:(arguments (string)@source)) (#eq? @_import "import") )
] {
node source_push_guard_exports
node source_push_guard_pkg
node @source.exports
attr (source_push_guard_exports) push_symbol = "GUARD:EXPORTS"
scan (source-text @source) {
"^[\"']((\.|\.\.)/.*)[\"']$" {
; relative import
let name = (replace (path-normalize (path-join (path-dir FILE_PATH) $1)) "\.js$" "")
node module_guard_push
attr (module_guard_push) push_symbol = "GUARD:MODULE"
edge module_guard_push -> @source.pkg_push
node source_push_end
edge source_push_end -> module_guard_push
var push_start = source_push_end
scan name {
"([^/]+)/" {
attr (push_start) push_symbol = $1
node push_start_prev
edge push_start_prev -> push_start
set push_start = push_start_prev
}
"([^/]+)$" {
attr (push_start) push_symbol = $1
}
}
scan $1 {
"/$" {
node push_start_prev
edge push_start_prev -> push_start
set push_start = push_start_prev
attr (push_start) push_symbol = "index"
}
}
attr (push_start) is_reference, source_node = @source
edge source_push_guard_exports -> push_start
}
"^[\"']([^\./].*)[\"']$" {
; package import
let name = (replace $1 "\.js$" "")
node source_push_end
var push_start = source_push_end
scan name {
"([^/]+)/" {
attr (push_start) push_symbol = $1
node push_start_prev
edge push_start_prev -> push_start
set push_start = push_start_prev
}
"([^/]+)$" {
attr (push_start) symbol_reference = $1, source_node = @source
}
}
edge source_push_guard_exports -> push_start
edge source_push_end -> source_push_guard_pkg
attr (source_push_guard_pkg) push_symbol = "GUARD:PKG"
edge source_push_guard_pkg -> @source.pkg_push
}
}
edge @source.exports -> source_push_guard_exports
}
; import "foo.js";
; only used for side effects not imports.
(import_statement . source:(_))@import_stmt {
edge @import_stmt.after_scope -> @import_stmt.before_scope
}
(import_clause)@import_clause {
node @import_clause.after_scope
node @import_clause.before_scope
node @import_clause.source
}
; import * as name from "module-name";
(import_clause
(namespace_import)@namespace_import)@import_clause {
edge @namespace_import.before_scope -> @import_clause.before_scope
edge @import_clause.after_scope -> @namespace_import.after_scope
edge @namespace_import.source -> @import_clause.source
}
(namespace_import (identifier)@imported_as)@namespace_import {
node imported_as_pop
node imported_as_pop_dot
node @namespace_import.after_scope
node @namespace_import.before_scope
node @namespace_import.source
edge @namespace_import.after_scope -> @namespace_import.before_scope
attr (imported_as_pop) node_definition = @imported_as, definiens_node = @namespace_import.import_statement
attr (imported_as_pop_dot) pop_symbol = "GUARD:MEMBER"
edge imported_as_pop -> imported_as_pop_dot
edge imported_as_pop_dot -> @namespace_import.source
edge @namespace_import.after_scope -> imported_as_pop
}
; import { export1 } from "module-name";
; import { export1 as alias1 } from "module-name";
; import { export1 , export2 } from "module-name";
; import { export1 , export2 as alias2 , [...] } from "module-name";
(import_statement
(import_clause)@import_clause
source:(_)@source)@import_stmt {
edge @import_stmt.after_scope -> @import_stmt.before_scope
edge @import_clause.before_scope -> @import_stmt.before_scope
edge @import_stmt.hoist_point -> @import_clause.after_scope
edge @import_clause.source -> @source.exports
}
(import_clause
(named_imports)@named_imports)@import_clause {
edge @named_imports.before_scope -> @import_clause.before_scope
edge @import_clause.after_scope -> @named_imports.after_scope
edge @named_imports.source -> @import_clause.source
}
(named_imports)@named_imports {
node @named_imports.after_scope
node @named_imports.before_scope
node @named_imports.source
}
(named_imports (_)* @specs)@named_imports {
if (is-empty @specs) {
edge @named_imports.after_scope -> @named_imports.before_scope
}
}
(named_imports
(import_specifier)@import_specifier)@named_imports {
edge @import_specifier.source -> @named_imports.source
}
(named_imports
.
(import_specifier)@first_import)@named_imports {
edge @first_import.before_scope -> @named_imports.before_scope
}
(named_imports
(import_specifier)@left_import
.
(import_specifier)@right_import) {
edge @right_import.before_scope -> @left_import.after_scope
}
(named_imports
(import_specifier)@last_import
.)@named_imports {
edge @named_imports.after_scope -> @last_import.after_scope
}
;; Import specifiers have several cases:
;; - the reference into the source module can be default or named
;; - the definition from this module can be default or named.
;; The case where the definition is default instead of named is invalid.
;;
;; We model this using seperate sets of rules, two rules for the reference,
;; and one rules for the definition. The `.def_to_ref` node is used to connect
;; the two.
(import_specifier)@import_specifier {
node @import_specifier.after_scope
node @import_specifier.before_scope
node @import_specifier.def_to_ref
node @import_specifier.source
edge @import_specifier.after_scope -> @import_specifier.before_scope
}
;; Import specifier reference rules
(
(import_specifier
name:(_)@name
)@import_specifier
(#not-eq? @name "default")
) {
node name_push
attr (name_push) node_reference = @name
edge name_push -> @import_specifier.source
edge @import_specifier.def_to_ref -> name_push
}
(
(import_specifier
name:(_)@name
)@import_specifier
(#eq? @name "default")
) {
node push_guard_default
attr (push_guard_default) symbol_reference = "GUARD:DEFAULT", source_node = @name
edge push_guard_default -> @import_specifier.source
edge @import_specifier.def_to_ref -> push_guard_default
}
;; Import specifier definition rules
( [
(import_specifier
name:(_)@alias
!alias)@import_specifier
(import_specifier
name:(_)
alias:(_)@alias)@import_specifier
]
(#not-eq? @alias "default")
) {
node name_pop
attr (name_pop) node_definition = @alias, definiens_node = @import_specifier.import_statement
edge name_pop -> @import_specifier.def_to_ref
edge @import_specifier.after_scope -> name_pop
}
; (import_statement
; (import_clause
; (named_imports
; (import_specifier
; name:(_)@name
; alias:(_)@alias)))
; source: (_)@mod_name)@import_stmt {
;
; ; scope passes through, augmented by the identifier
; scan @mod_name {
; "\"([^/\"]+)\.js\"$" {
; attr (@mod_name.push) symbol_reference = $1, source_node = @mod_name
; }
; }
; edge @mod_name.push -> @import_stmt.pkg_push
;
; attr (@name) node_reference = @name
; attr (name_push_dot) push_symbol = "GUARD:MEMBER"
; edge name_push_dot -> @mod_name.push
; edge name -> @name_push_dot
;
; attr (@alias) node_definition = @alias
; edge @alias -> @name
;
; edge @import_stmt.after_scope -> @alias
; }
; TODO import defaultExport, { export1 [ , [...] ] } from "module-name";
; TODO import defaultExport, * as name from "module-name";
; TODO var promise = import("module-name");
; import defaultExport from "module-name";
(import_clause
(identifier)@default_name)@import_clause {
node default_name_pop
node default_name_push_guard_default
edge @import_clause.after_scope -> @import_clause.before_scope
attr (default_name_pop) node_definition = @default_name, definiens_node = @import_clause.import_statement
attr (default_name_push_guard_default) symbol_reference = "GUARD:DEFAULT", source_node = @default_name
edge default_name_pop -> default_name_push_guard_default
edge default_name_push_guard_default -> @import_clause.source
edge @import_clause.after_scope -> default_name_pop
}
;; ███ ██ ██████ ██████ ███ ███ █████ ██
;; ████ ██ ██ ██ ██ ██ ████ ████ ██ ██ ██
;; ██ ██ ██ ██ ██ ██████ ██ ████ ██ ███████ ██
;; ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
;; ██ ████ ██████ ██ ██ ██ ██ ██ ██ ███████
;; ███████ ████████ █████ ████████ ███████ ███ ███ ███████ ███ ██ ████████ ███████
;; ██ ██ ██ ██ ██ ██ ████ ████ ██ ████ ██ ██ ██
;; ███████ ██ ███████ ██ █████ ██ ████ ██ █████ ██ ██ ██ ██ ███████
;; ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
;; ███████ ██ ██ ██ ██ ███████ ██ ██ ███████ ██ ████ ██ ███████
;; #### Debugger
(debugger_statement)@debugger_stmt {
; scopes flow through unchanged
edge @debugger_stmt.after_scope -> @debugger_stmt.before_scope
}
;; #### Expression
(expression_statement (_)@inner)@expr_stmt {
; scopes flow in then back out
edge @inner.before_scope -> @expr_stmt.before_scope
edge @expr_stmt.after_scope -> @inner.after_scope
}
;; #### Declarations
;; ##### Variable Declarations
(variable_declaration
(variable_declarator
name:(identifier)@name))@variable_decl
{
node @name.pop
attr (@name.pop) node_definition = @name
edge @variable_decl.after_scope -> @name.pop
attr (@variable_decl.after_scope -> @name.pop) precedence = 1
}
(lexical_declaration
(variable_declarator
name:(identifier)@name))@lexical_decl
{
node @name.pop
attr (@name.pop) node_definition = @name
edge @lexical_decl.after_scope -> @name.pop
attr (@lexical_decl.after_scope -> @name.pop) precedence = 1
}
(variable_declaration
(variable_declarator
!value))@decl {
edge @decl.after_scope -> @decl.before_scope
}
(lexical_declaration
(variable_declarator
!value))@decl {
edge @decl.after_scope -> @decl.before_scope
}
(variable_declaration
(variable_declarator
name:(identifier)@name
value:(_)@initializer))@variable_decl
{
edge @name.pop -> @initializer.value
edge @initializer.before_scope -> @variable_decl.before_scope
edge @variable_decl.after_scope -> @initializer.after_scope
}
(lexical_declaration
(variable_declarator
name:(identifier)@name
value:(_)@initializer))@lexical_decl
{
edge @name.pop -> @initializer.value
edge @initializer.before_scope -> @lexical_decl.before_scope
edge @lexical_decl.after_scope -> @initializer.after_scope
attr (@lexical_decl.after_scope -> @initializer.after_scope) precedence = 0
}
(variable_declaration
(variable_declarator
name:[(object_pattern) (array_pattern)]@pat
value:(_)@initializer))@decl {
edge @initializer.before_scope -> @decl.before_scope
edge @pat.before_scope -> @initializer.after_scope
edge @decl.after_scope -> @pat.after_scope
edge @pat.covalue -> @initializer.value
}
(lexical_declaration
(variable_declarator
name:[(object_pattern) (array_pattern)]@pat
value:(_)@initializer))@decl {
edge @initializer.before_scope -> @decl.before_scope
edge @pat.before_scope -> @initializer.after_scope
edge @decl.after_scope -> @pat.after_scope
edge @pat.covalue -> @initializer.value
}
;; ##### Function Declarations
(function_declaration
name:(_)@name
parameters:(_)@call_sig
body:(_)@body)@fun_decl {
node call_sig_arguments_pop
node call_sig_arguments_push
node call_sig_this_pop
node call_sig_this_push
node @name.pop
node fun_decl_function_value
node @fun_decl.return_or_yield
node @fun_decl.value_arg_scope
node fun_decl_value_call
node fun_decl_value_drop
node fun_decl_value_return
node fun_decl_value_this
node fun_decl_value_this_guard
let @body.closure_point = @body.after_scope
attr (@name.pop) syntax_type = "function"
; scope flows across the decl
edge @fun_decl.after_scope -> @fun_decl.before_scope
; with an augmentation for the function
attr (@name.pop) node_definition = @name
edge @fun_decl.hoist_point -> @name.pop
edge @name.pop -> fun_decl_function_value
; function values have drop nodes that handle closures, that points to the
; before scope for the function
attr (fun_decl_value_drop) type = "drop_scopes"
edge fun_decl_value_drop -> @fun_decl.closure_point
; the call sig's before scope comes from the drop node then flows into the body
edge @call_sig.before_scope -> fun_decl_value_drop
attr (call_sig_this_pop) symbol_definition = "this", source_node = @call_sig
attr (call_sig_this_push) push_symbol = "this"
edge call_sig_this_pop -> call_sig_this_push
edge call_sig_this_push -> @fun_decl.value_arg_scope
edge @call_sig.before_scope -> call_sig_this_pop
attr (call_sig_arguments_pop) symbol_definition = "arguments", source_node = @call_sig
attr (call_sig_arguments_push) push_symbol = "arguments"
edge call_sig_arguments_pop -> call_sig_arguments_push
edge call_sig_arguments_push -> @fun_decl.value_arg_scope
edge @call_sig.before_scope -> call_sig_arguments_pop
edge @body.before_scope -> @call_sig.after_scope
; function values have call nodes
attr (fun_decl_value_call) pop_scoped_symbol = "()"
edge fun_decl_function_value -> fun_decl_value_call
; function values have return nodes which need to be visible for returns
attr (fun_decl_value_return) pop_symbol = "GUARD:RETURN"
edge fun_decl_value_call -> fun_decl_value_return
let @body.return_or_yield = fun_decl_value_return
; method values have this nodes which need to be visible for constructor calls
attr (fun_decl_value_this) push_symbol = "this"
attr (fun_decl_value_this_guard) pop_symbol = "GUARD:THIS"
edge fun_decl_value_call -> fun_decl_value_this_guard
edge fun_decl_value_this_guard -> fun_decl_value_this
edge fun_decl_value_this -> @body.after_scope
; function values have a jump node that lets params connect up to actual arguments
edge @fun_decl.value_arg_scope -> JUMP_TO_SCOPE_NODE
}
(function_declaration
parameters:
(formal_parameters (_)@param))@fun_decl {
node param_arg_index
; parameters jump to the pushed argument scope
attr (param_arg_index) push_symbol = (named-child-index @param)
edge @param.covalue -> param_arg_index
edge param_arg_index -> @fun_decl.value_arg_scope
}
;; ##### Generator Function Declarations
(generator_function_declaration
name:(_)@name
parameters:(_)@call_sig
body:(_)@body)@fun_decl {
node call_sig_arguments_pop
node call_sig_arguments_push
node call_sig_this_pop
node call_sig_this_push
node @name.pop
node fun_decl_function_value
node @fun_decl.return_or_yield
node @fun_decl.value_arg_scope
node fun_decl_value_call
node fun_decl_value_drop
node fun_decl_value_return
node fun_decl_value_this
node fun_decl_value_this_guard
let @body.closure_point = @body.after_scope
attr (@name.pop) syntax_type = "function"
; scope flows across the decl
edge @fun_decl.after_scope -> @fun_decl.before_scope
; with an augmentation for the function
attr (@name.pop) node_definition = @name
edge @fun_decl.hoist_point -> @name.pop
edge @name.pop -> fun_decl_function_value
; function values have drop nodes that handle closures, that points to the
; before scope of the declaration
attr (fun_decl_value_drop) type = "drop_scopes"
edge fun_decl_value_drop -> @fun_decl.closure_point
; the call sig's before scope comes from the drop node then flows into the body
edge @call_sig.before_scope -> fun_decl_value_drop
attr (call_sig_this_pop) symbol_definition = "this", source_node = @call_sig
attr (call_sig_this_push) push_symbol = "this"
edge call_sig_this_pop -> call_sig_this_push
edge call_sig_this_push -> @fun_decl.value_arg_scope
edge @call_sig.before_scope -> call_sig_this_pop
attr (call_sig_arguments_pop) symbol_definition = "arguments", source_node = @call_sig
attr (call_sig_arguments_push) push_symbol = "arguments"
edge call_sig_arguments_pop -> call_sig_arguments_push
edge call_sig_arguments_push -> @fun_decl.value_arg_scope
edge @call_sig.before_scope -> call_sig_arguments_pop
edge @body.before_scope -> @call_sig.after_scope
; function values have call nodes
attr (fun_decl_value_call) pop_scoped_symbol = "()"
edge fun_decl_function_value -> fun_decl_value_call
; function values have return nodes which need to be visible for returns
attr (fun_decl_value_return) pop_symbol = "GUARD:RETURN"
edge fun_decl_value_call -> fun_decl_value_return
let @body.return_or_yield = fun_decl_value_return
; method values have this nodes which need to be visible for constructor calls
attr (fun_decl_value_this) push_symbol = "this"
attr (fun_decl_value_this_guard) pop_symbol = "GUARD:THIS"
edge fun_decl_value_call -> fun_decl_value_this_guard
edge fun_decl_value_this_guard -> fun_decl_value_this
edge fun_decl_value_this -> @body.after_scope
; function values have a jump node that lets params connect up to actual arguments
edge @fun_decl.value_arg_scope -> JUMP_TO_SCOPE_NODE
}
(generator_function_declaration
parameters:
(formal_parameters (_)@param))@fun_decl {
node param_arg_index
; parameters jump to the pushed argument scope
attr (param_arg_index) push_symbol = (named-child-index @param)
edge @param.covalue -> param_arg_index
edge param_arg_index -> @fun_decl.value_arg_scope
}
;; #### Classes
(class_declaration
name:(_)@name
body:(_)@body)@class_decl {
node @name.pop
node @class_decl.class_value
let @class_decl.containing_class_value = @class_decl.class_value
node guard_prototype
node @class_decl.prototype
node @class_decl.constructor
attr (@name.pop) syntax_type = "class"
attr (@name.pop) node_definition = @name
attr (guard_prototype) pop_symbol = "GUARD:PROTOTYPE"
edge @class_decl.after_scope -> @name.pop
edge @name.pop -> @class_decl.class_value
edge @class_decl.class_value -> guard_prototype
edge guard_prototype -> @class_decl.prototype
edge @body.before_scope -> @class_decl.closure_point
edge @class_decl.prototype -> @body.after_scope
edge @class_decl.after_scope -> @class_decl.before_scope
}
(class_declaration
(class_heritage (_)@name))@class_decl {
node guard_prototype
node guard_constructor
attr (guard_prototype) push_symbol = "GUARD:PROTOTYPE"
attr (guard_constructor) push_symbol = "GUARD:CONSTRUCTOR"
edge @name.before_scope -> @class_decl.before_scope
edge @class_decl.prototype -> guard_prototype
edge guard_prototype -> @name.value
edge @class_decl.constructor -> guard_constructor
edge guard_constructor -> @name.value
}
(class_body)@class_body {
node @class_body.after_scope
node @class_body.before_scope
}
(class_body (_)* @decls)@class_body {
if (is-empty @decls) {
edge @class_body.after_scope -> @class_body.before_scope
}
}
(class_body
.
(_)@first_decl)@class_body {
edge @first_decl.before_scope -> @class_body.before_scope
}
(class_body
(_)@left_decl
.
(_)@right_decl) {
edge @right_decl.before_scope -> @left_decl.after_scope
}
(class_body
(_)@last_decl
.)@class_body {
edge @class_body.after_scope -> @last_decl.after_scope
}
(method_definition name:(_)@name)@method_def {
node @name.pop
node @method_def.after_scope
node @method_def.before_scope
node @method_def.method_value
}
(
(method_definition
name:(_)@name)@method_def
(#eq? @name "constructor")
) {
; augmentation for the constructor
attr (@name.pop) symbol_definition = "GUARD:CONSTRUCTOR", source_node = @name
edge @method_def.class_value -> @name.pop
edge @name.pop -> @method_def.constructor
edge @method_def.constructor -> @method_def.method_value
}
(
(method_definition
name:(_)@name)@method_def
(#not-eq? @name "constructor")
) {
node name_pop_dot
; augmentation for the method
attr (@name.pop) node_definition = @name
attr (name_pop_dot) pop_symbol = "GUARD:MEMBER"
edge @method_def.after_scope -> name_pop_dot
edge name_pop_dot -> @name.pop
edge @name.pop -> @method_def.method_value
}
(method_definition
name:(_)@name
parameters:(_)@call_sig
body:(_)@body)@method_def {
node call_sig_arguments_pop
node call_sig_arguments_push
node @method_def.method_value_arg_scope
node method_def_method_value_call
node method_def_method_value_drop
node method_def_method_value_return
node method_def_method_value_this
node method_def_method_value_this_guard
attr (@name.pop) syntax_type = "method"
; scope flows across the decl
edge @method_def.after_scope -> @method_def.before_scope
; method values have drop nodes that handle closures, that points to the
; before scope from method def
attr (method_def_method_value_drop) type = "drop_scopes"
edge method_def_method_value_drop -> @method_def.before_scope
; the call sig's before scope comes from the drop node then flows into the body
edge @call_sig.before_scope -> method_def_method_value_drop
attr (call_sig_arguments_pop) symbol_definition = "arguments", source_node = @call_sig
attr (call_sig_arguments_push) push_symbol = "arguments"
edge call_sig_arguments_pop -> call_sig_arguments_push
edge call_sig_arguments_push -> @method_def.method_value_arg_scope
edge @call_sig.before_scope -> call_sig_arguments_pop
edge @body.before_scope -> @call_sig.after_scope
; method values have call nodes
attr (method_def_method_value_call) pop_scoped_symbol = "()"
edge @method_def.method_value -> method_def_method_value_call
; method values have return nodes which need to be visible for returns
attr (method_def_method_value_return) pop_symbol = "GUARD:RETURN"
edge method_def_method_value_call -> method_def_method_value_return
let @body.return_or_yield = method_def_method_value_return
; method values have this nodes which need to be visible for constructor calls
attr (method_def_method_value_this) push_symbol = "this"
attr (method_def_method_value_this_guard) pop_symbol = "GUARD:THIS"
edge method_def_method_value_call -> method_def_method_value_this_guard
edge method_def_method_value_this_guard -> method_def_method_value_this
edge method_def_method_value_this -> @body.after_scope
; method values have a jump node that lets params connect up to actual arguments
edge @method_def.method_value_arg_scope -> JUMP_TO_SCOPE_NODE
}
(method_definition
parameters:
(formal_parameters (_)@param))@method_def {
node param_arg_index
; parameters jump to the pushed argument scope
attr (param_arg_index) push_symbol = (named-child-index @param)
edge @param.covalue -> param_arg_index
edge param_arg_index -> @method_def.method_value_arg_scope
}
(field_definition)@field_def {
node @field_def.after_scope
node @field_def.before_scope
}
(field_definition
property:(_)@property)@field_def {
node @property.pop
node property_pop_dot
attr (@property.pop) node_definition = @property
attr (property_pop_dot) pop_symbol = "GUARD:MEMBER"
edge @field_def.after_scope -> property_pop_dot
edge property_pop_dot -> @property.pop
}
(field_definition
!value)@field_def {
edge @field_def.after_scope -> @field_def.before_scope
}
(field_definition
property:(_)@property
value:(_)@value)@field_def {
edge @value.before_scope -> @field_def.before_scope
edge @field_def.after_scope -> @value.after_scope
edge @property.pop -> @value.value
}
(class_static_block body: (_) @body) @static_block {
node @static_block.before_scope
node @static_block.after_scope
edge @body.before_scope -> @static_block.before_scope
edge @static_block.after_scope -> @static_block.before_scope
}
;; #### Statement Block
(statement_block)@statement_block {
node @statement_block.hoist_point
edge @statement_block.before_scope -> @statement_block.hoist_point
}
(statement_block (_)* @stmts)@statement_block {
if (is-empty @stmts) {
edge @statement_block.after_scope -> @statement_block.before_scope
}
}
; statement block, first statement
(statement_block
.
(_)@first_stmt)@block {
; scope flows from block to first statement
edge @first_stmt.before_scope -> @block.before_scope
}
; statement block, between statements
(statement_block
(_)@left_stmt
.
(_)@right_stmt) {
; scope flows from left to right
edge @right_stmt.before_scope -> @left_stmt.after_scope
}
; statement block, last statement
(statement_block
(_)@last_stmt
.)@block {
; scope flows from last statement to block
edge @block.after_scope -> @last_stmt.after_scope
}
;; #### If
(if_statement
consequence:(_)@consequence)@if_stmt {
let @if_stmt.hoist_point = @consequence.before_scope
}
(if_statement condition:(_)@condition)@if_stmt {
; scopes flow from the if statement to the condition
edge @condition.before_scope -> @if_stmt.before_scope
}
(if_statement
condition:(_)@condition
consequence:(_)@consequence)@if_stmt
{
; scopes flow from the condition to the consequence, then to the if statement
edge @consequence.before_scope -> @condition.after_scope
edge @if_stmt.after_scope -> @consequence.after_scope
}
(if_statement
condition:(_)@condition
alternative:(_)@alternative)@if_stmt
{
; scopes flow from the condition to the alternative, then to the if statement
edge @alternative.before_scope -> @condition.after_scope
edge @if_stmt.after_scope -> @alternative.after_scope
}
(else_clause)@else_clause {
node @else_clause.after_scope
node @else_clause.before_scope
}
(else_clause
. (_)@inner)@else_clause {
let @else_clause.hoist_point = @inner.before_scope
}
(else_clause (_)@inner)@else_clause {
; scopes flow in and right back out
edge @inner.before_scope -> @else_clause.before_scope
edge @else_clause.after_scope -> @inner.after_scope
}
;; #### Switch
(switch_statement
value:(_)@value
body:(switch_body)@body)@switch_stmt
{
; scopes flow into the value then into the body then back out to the switch
edge @value.before_scope -> @switch_stmt.before_scope
edge @body.before_scope -> @value.after_scope
edge @switch_stmt.after_scope -> @body.after_scope
}
(switch_body)@switch_body {
node @switch_body.after_scope
node @switch_body.before_scope
node @switch_body.hoist_point
edge @switch_body.before_scope -> @switch_body.hoist_point
}
(switch_body (_)* @choices)@switch_body {
if (is-empty @choices) {
edge @switch_body.after_scope -> @switch_body.before_scope
}
}
; switch body, first choice
(switch_body
.
(_)@first_choice)@switch_body
{
; scopes flow from the body into the first choice
edge @first_choice.before_scope -> @switch_body.before_scope
}
; switch body, between choices
(switch_body
(_)@left_choice
.
(_)@right_choice)
{
; scopes flow left to right
edge @right_choice.before_scope -> @left_choice.after_scope
}
; switch body, last choice
(switch_body
(_)@last_choice
.)@switch_body
{
; scope flows out to the switch body
edge @switch_body.after_scope -> @last_choice.after_scope
}
(switch_case)@switch_case {
node @switch_case.after_scope
node @switch_case.before_scope
}
(switch_case (_)* @stmts)@switch_case {
if (is-empty @stmts) {
edge @switch_case.after_scope -> @switch_case.before_scope
}
}
; switch case, non-empty statements, first statement
(switch_case
value:(_)@value
.
(_)@first_stmt)@switch_case
{
; scopes flow into the value then into the first statement
edge @value.before_scope -> @switch_case.before_scope
edge @first_stmt.before_scope -> @value.after_scope
}
; switch case, non-empty statements, between statement
(switch_case
value:(_)
(_)@left_stmt
.
(_)@right_stmt)
{
; scopes flow left to right
edge @right_stmt.before_scope -> @left_stmt.after_scope
}
; switch case, non-empty statements, last statement
(switch_case
value:(_)
(_)@last_stmt
.)@switch_case {
; scopes flow out from the last statement to the case
edge @switch_case.after_scope -> @last_stmt.after_scope
}
(switch_default)@switch_default {
node @switch_default.after_scope
node @switch_default.before_scope
}
(switch_default (_)* @defaults)@switch_default {
if (is-empty @defaults) {
edge @switch_default.after_scope -> @switch_default.before_scope
}
}
; switch default, non-empty statements, first statement
(switch_default
.
(_)@first_stmt)@switch_default
{
; scopes flow into the first statement
edge @first_stmt.before_scope -> @switch_default.before_scope
}
; switch default, non-empty statements, between statements
(switch_default
(_)@left_stmt
.
(_)@right_stmt)
{
; scopes flow left to right
edge @right_stmt.before_scope -> @left_stmt.after_scope
}
; switch default, non-empty statements, last statement
(switch_default
(_)@last_stmt
.)@switch_default
{
; scopes flow out to the default
edge @switch_default.after_scope -> @last_stmt.after_scope
}
;; #### For
(for_statement
body:(_)@body)@for_stmt {
let @for_stmt.hoist_point = @body.before_scope
}
(for_statement
initializer:(_)@initializer
condition:(_)@condition
increment:(_)@increment
body:(_)@body)@for_stmt
{
; scopes flow from statement to initializer then test then body then increment
edge @initializer.before_scope -> @for_stmt.before_scope
edge @condition.before_scope -> @initializer.after_scope
edge @body.before_scope -> @condition.after_scope
edge @increment.before_scope -> @body.after_scope
; scopes also from from the body back into the condition
edge @condition.before_scope -> @increment.after_scope
; scopes also flow from condition out to statement
edge @for_stmt.after_scope -> @condition.after_scope
}
;; #### For In
(for_in_statement
body:(_)@body)@for_in_stmt {
let @for_in_stmt.hoist_point = @body.before_scope
}
(for_in_statement
left:(_)@_left
right:(_)@right
body:(_)@body)@for_in_stmt
{
; scopes flow from statement to right then to body then back out
edge @right.before_scope -> @for_in_stmt.before_scope
edge @body.before_scope -> @right.after_scope
edge @for_in_stmt.after_scope -> @body.after_scope
}
(for_in_statement
left:(identifier)@left
right:(_)@right
body:(_)@body)@_for_in_stmt
{
node for_in_stmt_pop
attr (for_in_stmt_pop) node_definition = @left
edge for_in_stmt_pop -> @right.value
edge @body.before_scope -> for_in_stmt_pop
}
;; #### While
(while_statement
body:(_)@body)@while_stmt {
let @while_stmt.hoist_point = @body.before_scope
}
(while_statement
condition:(_)@condition
body:(_)@body)@while_stmt
{
; scopes flow from while to condition then to body then back out
edge @condition.before_scope -> @while_stmt.before_scope
edge @body.before_scope -> @condition.after_scope
edge @while_stmt.after_scope -> @body.after_scope
; scopes also flow back into the condition
edge @condition.before_scope -> @body.after_scope
}
;; #### Do
(do_statement
body:(_)@body)@do_stmt {
let @do_stmt.hoist_point = @body.before_scope
}
(do_statement
body:(_)@body
condition:(_)@condition)@do_stmt
{
; scopes flow from statement to body then condition then back to statement
edge @body.before_scope -> @do_stmt.before_scope
edge @condition.before_scope -> @body.after_scope
edge @do_stmt.after_scope -> @condition.after_scope
; scopes also flow back to the body from the condition
edge @body.before_scope -> @condition.after_scope
}
;; #### Try
(try_statement
body:(_)@body)@try_stmt
{
; scopes flow into the body then back out
edge @body.before_scope -> @try_stmt.before_scope
edge @try_stmt.after_scope -> @body.after_scope
}
(try_statement
body:(_)@body
handler:(_)@handler)@try_stmt
{
; scopes flow from body to handler then back out
edge @handler.before_scope -> @body.after_scope
edge @try_stmt.after_scope -> @handler.after_scope
}
(try_statement
body:(_)@body
finalizer:(_)@finalizer)@_try_stmt
{
; scopes flow from body to finalizer then back out
edge @finalizer.before_scope -> @body.after_scope
}
(try_statement
handler:(_)@handler
finalizer:(_)@finalizer)@try_stmt
{
; scopes flow from handler to finalizer then back out
edge @finalizer.before_scope -> @handler.after_scope
edge @try_stmt.after_scope -> @finalizer.after_scope
}
(catch_clause body:(_)@body)@catch_clause {
node @catch_clause.after_scope
node @catch_clause.before_scope
; scopes flow in then back out
edge @body.before_scope -> @catch_clause.before_scope
edge @catch_clause.after_scope -> @body.after_scope
}
(catch_clause
parameter:(identifier)@name
body:(_)@body)@_catch_clause
{
node catch_clause_pop
attr (catch_clause_pop) node_definition = @name
edge @body.before_scope -> catch_clause_pop
}
(finally_clause body:(_)@body)@finally_clause {
node @finally_clause.after_scope
node @finally_clause.before_scope
; scopes flow in thenback out
edge @body.before_scope -> @finally_clause.before_scope
edge @finally_clause.after_scope -> @body.after_scope
}
;; #### With
(with_statement body:(_)@body)@with_stmt {
let @with_stmt.hoist_point = @body.before_scope
}
(with_statement
object:(_)@object
body:(_)@body)@with_stmt
{
node with_stmt_push_dot
; scopes flow from the statement into the object then into the body then back out
edge @object.before_scope -> @with_stmt.before_scope
edge @body.before_scope -> @object.after_scope
edge @with_stmt.after_scope -> @body.after_scope
edge @with_stmt.after_scope -> @with_stmt.before_scope
attr (@with_stmt.after_scope -> @with_stmt.before_scope) precedence = 1
attr (with_stmt_push_dot) push_symbol = "GUARD:MEMBER"
edge with_stmt_push_dot -> @object.value
edge @body.before_scope -> with_stmt_push_dot
attr (@body.before_scope -> with_stmt_push_dot) precedence = 1
}
;; #### Break
(break_statement)@break_stmt {
; scopes flow through unchanged
edge @break_stmt.after_scope -> @break_stmt.before_scope
}
(break_statement (_)@label)@break_stmt {
node break_stmt_label_guard
node break_stmt_label_push
attr (break_stmt_label_guard) push_symbol = "GUARD:LABEL"
attr (break_stmt_label_push) node_reference = @label
edge break_stmt_label_push -> break_stmt_label_guard
edge break_stmt_label_guard -> @break_stmt.before_scope
}
;; #### Continue
(continue_statement)@continue_stmt {
; scopes flow through unchanged
edge @continue_stmt.after_scope -> @continue_stmt.before_scope
}
(continue_statement (_)@label)@continue_stmt {
node continue_stmt_label_guard
node continue_stmt_label_push
attr (continue_stmt_label_guard) push_symbol = "GUARD:LABEL"
attr (continue_stmt_label_push) node_reference = @label
edge continue_stmt_label_push -> continue_stmt_label_guard
edge continue_stmt_label_guard -> @continue_stmt.before_scope
}
;; #### Return
; LATER-TODO tree sitter doesn't let us express empty returns currently
(return_statement)@return_stmt {
; scopes flow through unchanged
edge @return_stmt.after_scope -> @return_stmt.before_scope
}
(return_statement
(_)@returned_expr)@return_stmt {
; scopes flow through the returned expresssion
edge @returned_expr.before_scope -> @return_stmt.before_scope
edge @return_stmt.after_scope -> @returned_expr.after_scope
; return statements hook up to the call node of the function value
edge @return_stmt.return_or_yield -> @returned_expr.value
}
;; #### Throw
(throw_statement (_)@thrown_expr)@throw_stmt {
; scopes flow through the returned expresssion
edge @thrown_expr.before_scope -> @throw_stmt.before_scope
edge @throw_stmt.after_scope -> @thrown_expr.after_scope
}
;; #### Empty
(empty_statement)@empty_stmt {
; scopes flow through unchaged
edge @empty_stmt.after_scope -> @empty_stmt.before_scope
}
;; #### Labeled
(labeled_statement
label:(_)@label
body:(_)@inner)@labeled_stmt
{
node labeled_stmt_label_guard
node labeled_stmt_label_pop
attr (labeled_stmt_label_guard) pop_symbol = "GUARD:LABEL"
attr (labeled_stmt_label_pop) node_definition = @label
; scopes flow through the inner statement then back out
edge @inner.before_scope -> @labeled_stmt.before_scope
edge @inner.before_scope -> labeled_stmt_label_guard
edge labeled_stmt_label_guard -> labeled_stmt_label_pop
edge @labeled_stmt.after_scope -> @inner.after_scope
}
;; ███████ ██ ██ ██████ ██████ ███████ ███████ ███████ ██ ██████ ███ ██ ███████
;; ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ██ ██
;; █████ ███ ██████ ██████ █████ ███████ ███████ ██ ██ ██ ██ ██ ██ ███████
;; ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
;; ███████ ██ ██ ██ ██ ██ ███████ ███████ ███████ ██ ██████ ██ ████ ███████
;; ## Expressions
;; ### Attributes Defined on Expressions
;; TODO
[
(subscript_expression)
(member_expression)
(parenthesized_expression)
(undefined)
(this)
(super)
(number)
(string)
(template_string)
(template_substitution)
(string_fragment)
(escape_sequence)
(regex)
(true)
(false)
(null)
(sequence_expression)
(import)
(object)
(array)
(function_expression)
(arrow_function)
(generator_function)
(class)
(meta_property)
(call_expression)
(assignment_expression)
(augmented_assignment_expression)
(await_expression)
(unary_expression)
(binary_expression)
(ternary_expression)
(update_expression)
(new_expression)
(yield_expression)
(spread_element)
]@expr {
node @expr.after_scope
node @expr.before_scope
node @expr.value
}
[
(function_expression body:(_)@body)
(arrow_function body:(_)@body)
(generator_function body:(_)@body)
] {
let @body.closure_point = @body.after_scope
}
;; ### Expression Queries
;; #### Parenthesized
(parenthesized_expression (_)@inner)@parens {
; scopes flow right through
edge @inner.before_scope -> @parens.before_scope
edge @parens.after_scope -> @inner.after_scope
; as do values
edge @parens.value -> @inner.value
}
;; #### Strings
(string)@string {
; scopes don't change
edge @string.after_scope -> @string.before_scope
; the value of a string is the string primitive
edge @string.value -> @string.builtins_string
}
;; #### Template Strings
; template_strings w/ no substitutions
(template_string (_)* @parts)@template_string {
if (is-empty @parts) {
edge @template_string.after_scope -> @template_string.before_scope
}
}
; nonempty template string, value
; LATER-TODO this isn't really right, but it gets flows through the template string
; which may be useful
(template_string (_)@part)@template_string {
; the value of a template string is a template string value built from the values of its substitutions
; attr (@template_string.value) "template_string_value"
edge @template_string.value -> @part.value
}
; nonempty template string, first substitution
(template_string . (_)@first)@template_string {
; scopes propagate into the first subtitution of the template string
edge @first.before_scope -> @template_string.before_scope
}
; nonempty template string, between substitutions
(template_string
(_) @left
.
(_) @right) {
; scopes propagate from left substitutions to right substitutions
edge @right.before_scope -> @left.after_scope
}
; nonempty template string, last substitution
(template_string (_) @last .)@template_string {
; scopes propagate out of the last substitution to the template string
edge @template_string.after_scope -> @last.after_scope
}
[
(string_fragment)
(escape_sequence)
]@part {
edge @part.after_scope -> @part.before_scope
}
(template_substitution (_)@expr)@subst {
edge @expr.before_scope -> @subst.before_scope
edge @subst.after_scope -> @expr.after_scope
edge @subst.value -> @expr.value
}
;; #### Numbers
(number)@number {
; scopes don't change
edge @number.after_scope -> @number.before_scope
; the value of a number is the number primitive
edge @number.value -> @number.builtins_number
}
;; #### Variables
[
(primary_expression/identifier)@variable
(member_expression object:(identifier)@variable)
] {
; value is a lookup, ie a push
attr (@variable.value) node_reference = @variable
edge @variable.value -> @variable.before_scope
}
;; #### Booleans
(true)@true {
; scopes don't change
edge @true.after_scope -> @true.before_scope
; the value of true is a boolean primitive
edge @true.value -> @true.builtins_boolean
}
(false)@false {
; scopes don't change
edge @false.after_scope -> @false.before_scope
; the value of false is a boolean primitive
edge @false.value -> @false.builtins_boolean
}
;; #### This
(this)@this {
; scopes don't change
edge @this.after_scope -> @this.before_scope
; this is a lookup, ie a push
attr (@this.value) symbol_reference = "this", source_node = @this
edge @this.value -> @this.before_scope
node pop_this
attr (pop_this) symbol_definition = "this"
node guard_prototype
attr (guard_prototype) push_symbol = "GUARD:PROTOTYPE"
edge @this.value -> pop_this
edge pop_this -> guard_prototype
edge guard_prototype -> @this.containing_class_value
}
;; #### Super
(super)@super {
; scopes don't change
edge @super.after_scope -> @super.before_scope
}
;; #### Null
(null)@null {
; scopes don't change
edge @null.after_scope -> @null.before_scope
; the value of null is the null primitive
edge @null.value -> @null.builtins_null
}
;; #### Undefined
(undefined)@undefined {
; scopes don't change
edge @undefined.after_scope -> @undefined.before_scope
; the value of undefined is the undefined primitive
edge @undefined.value -> @undefined.builtins_undefined
; !!!! HACK: `undefined` is a perfectly cromulent name for a parameter, but the parser thinks it means this node instead. For the moment, work around the problem this causes by giving all `undefined` nodes covalues like parameters enjoy.
node @undefined.covalue
}
;; #### Regular Expressions
(regex)@regex {
; scopes don't change
edge @regex.after_scope -> @regex.before_scope
; the value of a regex is the Regex prototype
edge @regex.value -> @regex.builtins_Regex_prototype
}
;; #### Spread Elements
(spread_element (_)@expr)@spread_elem {
; scopes flow in then right back out
edge @expr.before_scope -> @spread_elem.before_scope
edge @spread_elem.after_scope -> @expr.after_scope
}
;; #### Objects
(object)@object {
node @object.member_pop
attr (@object.member_pop) pop_symbol = "GUARD:MEMBER"
edge @object.value -> @object.member_pop
node @object.class_value
node @object.constructor
}
; empty objects
(object (_)* @entries)@object_expr {
if (is-empty @entries) {
edge @object_expr.after_scope -> @object_expr.before_scope
}
}
; non-empty objects, scopes, first entry
(object
.
(_)@first_entry)@object_expr {
; scopes propagate from the object to the first entry
edge @first_entry.before_scope -> @object_expr.before_scope
}
; non-empty objects, scopes, between entries
(object
(_)@left_entry
.
(_)@right_entry
)@_object_expr {
; scopes propagate from left entries to right entries
edge @right_entry.before_scope -> @left_entry.after_scope
}
; non-empty objects, scopes, last entry
(object
(_)@last_entry
.)@object_expr {
; scopes propagate from the last entry back to the object
edge @object_expr.after_scope -> @last_entry.after_scope
}
; shorthand property identifier
(shorthand_property_identifier)@shorthand_property_identifier {
node @shorthand_property_identifier.after_scope
node @shorthand_property_identifier.before_scope
}
(object (shorthand_property_identifier)@keyval)@object {
node rhsvar_before_scope
node rhsvar_after_scope
node rhsvar_value
node key_pop
attr (rhsvar_value) node_reference = @keyval
attr (key_pop) node_definition = @keyval
; scopes flow into rhsvar, and also straight across b/c they can't be modified
edge rhsvar_before_scope -> @keyval.before_scope
edge rhsvar_after_scope -> rhsvar_before_scope
edge @keyval.after_scope -> rhsvar_after_scope
edge rhsvar_value -> rhsvar_before_scope
; shorthand property identifiers augment the object value with a member binding
edge key_pop -> rhsvar_value
edge @object.member_pop -> key_pop
}
; pairs
(computed_property_name)@computed_property_name {
node @computed_property_name.after_scope
node @computed_property_name.before_scope
}
(computed_property_name (_)@expr)@computed_property_name {
edge @expr.before_scope -> @computed_property_name.before_scope
edge @computed_property_name.after_scope -> @expr.after_scope
}
(object
(pair
key:(_)@_key
value: (_)@value)@pair)@object {
; pairs augment the object value with a member binding
; This is done differently depending on what the key is. See next rules.
; attr @key.pop "pop" = @key, "definition"
node @pair.key_pop
edge @pair.key_pop -> @value.value
edge @object.member_pop -> @pair.key_pop
}
(pair)@pair {
node @pair.after_scope
node @pair.before_scope
}
(pair
key:(property_identifier)@key
value:(_)@value)@pair
{
attr (@pair.key_pop) node_definition = @key
; scopes flow into the value, then back to the pair
edge @value.before_scope -> @pair.before_scope
edge @pair.after_scope -> @value.after_scope
}
(pair
key:(string)@key
value:(_)@value)@pair
{
node @key.pop
attr (@key.pop) pop_symbol = (replace (source-text @key) "\"" "")
; scopes flow into the value, then back to the pair
edge @value.before_scope -> @pair.before_scope
edge @pair.after_scope -> @value.after_scope
}
(pair
key:(number)@key
value:(_)@value)@pair
{
attr (@pair.key_pop) node_definition = @key
; scopes flow into the value, then back to the pair
edge @value.before_scope -> @pair.before_scope
edge @pair.after_scope -> @value.after_scope
}
(pair
key:(computed_property_name)@key
value:(_)@value)@pair
{
; scopes flow into the key, then out to the value, then back to the pair
edge @key.before_scope -> @pair.before_scope
edge @value.before_scope -> @key.after_scope
edge @pair.after_scope -> @value.after_scope
}
;; #### Arrays
(array)@array_expr {
node @array_expr.element_pop_dot
attr (@array_expr.element_pop_dot) pop_symbol = "GUARD:MEMBER"
edge @array_expr.value -> @array_expr.element_pop_dot
}
; empty arrays
(array (_)* @elems)@array_expr {
if (is-empty @elems) {
edge @array_expr.after_scope -> @array_expr.before_scope
}
}
; nonempty arrays, first element
(array
.
(_)@first_element)@array_expr {
; scopes propagate into the first element of the array
edge @first_element.before_scope -> @array_expr.before_scope
}
; nonempty arrays, between elements
(array
(_)@left_element
.
(_)@right_element) {
; scopes propagate from left elements to right elements
edge @right_element.before_scope -> @left_element.after_scope
}
; nonempty arrays, last element
(array
(_)@last_element
.)@array_expr {
; scopes propagate out of the last element to the array
edge @array_expr.after_scope -> @last_element.after_scope
}
; elements at indices
(array (_)@element)@array_expr {
node element_index_pop
attr (element_index_pop) pop_symbol = (named-child-index @element)
edge @array_expr.element_pop_dot -> element_index_pop
edge element_index_pop -> @element.value
}
;; #### Formal Parameters
(formal_parameters)@formal_params {
node @formal_params.after_scope
node @formal_params.before_scope
}
(formal_parameters (_)* @params)@formal_params {
if (is-empty @params) {
edge @formal_params.after_scope -> @formal_params.before_scope
}
}
; first parameter
(formal_parameters
.
(_)@first_param)@formal_params {
edge @first_param.before_scope -> @formal_params.before_scope
}
; between parameters
(formal_parameters
(_)@left_param
.
(_)@right_param) {
; scope flows left to right
edge @right_param.before_scope -> @left_param.after_scope
}
; last parameter
(formal_parameters
(_)@last_param
.)@formal_params {
; scope flows from the param to the call sig
edge @formal_params.after_scope -> @last_param.after_scope
}
;; #### Function Literals
; functions with names
(function_expression
name:(_)@name
parameters:(_)@call_sig)@fun {
node @name.pop
attr (@name.pop) syntax_type = "function"
; if the function has a name, this is bound the callsig's before scope
attr (@name.pop) node_definition = @name
edge @call_sig.before_scope -> @name.pop
edge @name.pop -> @fun.value
}
; function
(function_expression
parameters:(_)@call_sig
body:(_)@body)@fun {
node call_sig_arguments_pop
node call_sig_arguments_push
node call_sig_this_pop
node call_sig_this_push
node @fun.return_or_yield
node @fun.value_arg_scope
node fun_value_call
node fun_value_drop
node fun_value_return
node fun_value_this
node fun_value_this_guard
; scope flows across the decl
edge @fun.after_scope -> @fun.before_scope
; function values have drop nodes that handle closures, that points to the
; before scope from the function
attr (fun_value_drop) type = "drop_scopes"
edge fun_value_drop -> @fun.closure_point
; the call sig's before scope comes from the drop node,
; then flows into the body, and includes a variable binding for "this"
edge @call_sig.before_scope -> fun_value_drop
attr (call_sig_this_pop) symbol_definition = "this", source_node = @call_sig
attr (call_sig_this_push) push_symbol = "this"
edge call_sig_this_pop -> call_sig_this_push
edge call_sig_this_push -> @fun.value_arg_scope
edge @call_sig.before_scope -> call_sig_this_pop
attr (call_sig_arguments_pop) symbol_definition = "arguments", source_node = @call_sig
attr (call_sig_arguments_push) push_symbol = "arguments"
edge call_sig_arguments_pop -> call_sig_arguments_push
edge call_sig_arguments_push -> @fun.value_arg_scope
edge @call_sig.before_scope -> call_sig_arguments_pop
edge @body.before_scope -> @call_sig.after_scope
; function values have call nodes
attr (fun_value_call) pop_scoped_symbol = "()"
edge @fun.value -> fun_value_call
; function values have return nodes which need to be visible for returns
attr (fun_value_return) pop_symbol = "GUARD:RETURN"
edge fun_value_call -> fun_value_return
let @body.return_or_yield = fun_value_return
; function values have this nodes which need to be visible for method calls
attr (fun_value_this) push_symbol = "this"
attr (fun_value_this_guard) pop_symbol = "GUARD:THIS"
edge fun_value_call -> fun_value_this_guard
edge fun_value_this_guard -> fun_value_this
edge fun_value_this -> @body.after_scope
; function values have a jump node that lets params connect up to actual arguments
edge @fun.value_arg_scope -> JUMP_TO_SCOPE_NODE
}
(function_expression
parameters:
(formal_parameters (_)@param))@fun {
node param_arg_index
; parameters jump to the pushed argument scope
attr (param_arg_index) push_symbol = (named-child-index @param)
edge @param.covalue -> param_arg_index
edge param_arg_index -> @fun.value_arg_scope
}
;; #### Arrow Function Literals
; function
[
(arrow_function
parameters:(_)@call_sig
body:(_)@body)@fun
(arrow_function
parameter:(_)@call_sig
body:(_)@body)@fun
] {
node @fun.return_or_yield
node @fun.value_arg_scope
node fun_value_call
node fun_value_drop
node fun_value_return
node fun_value_this
node fun_value_this_guard
; scope flows across the decl
edge @fun.after_scope -> @fun.before_scope
; function values have drop nodes that handle closures, that points to the
; before scope from the function
attr (fun_value_drop) type = "drop_scopes"
edge fun_value_drop -> @fun.closure_point
; the call sig's before scope comes from the drop node then flows into the body
edge @call_sig.before_scope -> fun_value_drop
edge @body.before_scope -> @call_sig.after_scope
; function values have call nodes
attr (fun_value_call) pop_scoped_symbol = "()"
edge @fun.value -> fun_value_call
; function values have return nodes which need to be visible for returns
attr (fun_value_return) pop_symbol = "GUARD:RETURN"
edge fun_value_call -> fun_value_return
edge fun_value_return -> @fun.return_or_yield
; function values have this nodes which need to be visible for method calls
attr (fun_value_this) push_symbol = "this"
attr (fun_value_this_guard) pop_symbol = "GUARD:THIS"
edge fun_value_call -> fun_value_this_guard
edge fun_value_this_guard -> fun_value_this
edge fun_value_this -> @body.after_scope
; function values have a jump node that lets params connect up to actual arguments
edge @fun.value_arg_scope -> JUMP_TO_SCOPE_NODE
}
; arrow functions returning exprs need special rules for getting the return value hooked up
(arrow_function
body:(expression)@return_expr) {
edge @return_expr.return_or_yield -> @return_expr.value
}
(arrow_function
parameter:(_)@param)@fun {
node param_arg_index
node param_pop
; but augmented with a pop, b/c it's not a pattern
attr (param_pop) node_definition = @param
edge param_pop -> @param.covalue
edge @param.after_scope -> param_pop
; parameters jump to the pushed argument scope
attr (param_arg_index) push_symbol = "0"
edge @param.covalue -> param_arg_index
edge param_arg_index -> @fun.value_arg_scope
}
(arrow_function
parameters:
(formal_parameters (_)@param))@fun {
node param_arg_index
; parameters jump to the pushed argument scope
attr (param_arg_index) push_symbol = (named-child-index @param)
edge @param.covalue -> param_arg_index
edge param_arg_index -> @fun.value_arg_scope
}
;; #### Generator Function Literals
; generator functions with names
(generator_function
name:(_)@name
parameters:(_)@call_sig)@fun {
node @name.pop
attr (@name.pop) syntax_type = "function"
; if the function has a name, this is bound the callsig's before scope
attr (@name.pop) node_definition = @name
edge @call_sig.before_scope -> @name.pop
edge @name.pop -> @fun.value
}
; generator function
(generator_function
parameters:(_)@call_sig
body:(_)@body)@fun {
node call_sig_arguments_pop
node call_sig_arguments_push
node call_sig_this_pop
node call_sig_this_push
node @fun.return_or_yield
node @fun.value_arg_scope
node fun_value_call
node fun_value_drop
node fun_value_return
node fun_value_this
node fun_value_this_guard
; scope flows across the decl
edge @fun.after_scope -> @fun.before_scope
; function values have drop nodes that handle closures, that points to the
; before scope from the function
attr (fun_value_drop) type = "drop_scopes"
edge fun_value_drop -> @fun.closure_point
; the call sig's before scope comes from the drop node,
; then flows into the body, and includes a variable binding for "this"
edge @call_sig.before_scope -> fun_value_drop
attr (call_sig_this_pop) symbol_definition = "this", source_node = @call_sig
attr (call_sig_this_push) push_symbol = "this"
edge call_sig_this_pop -> call_sig_this_push
edge call_sig_this_push -> @fun.value_arg_scope
edge @call_sig.before_scope -> call_sig_this_pop
attr (call_sig_arguments_pop) symbol_definition = "arguments", source_node = @call_sig
attr (call_sig_arguments_push) push_symbol = "arguments"
edge call_sig_arguments_pop -> call_sig_arguments_push
edge call_sig_arguments_push -> @fun.value_arg_scope
edge @call_sig.before_scope -> call_sig_arguments_pop
edge @body.before_scope -> @call_sig.after_scope
; function values have call nodes
attr (fun_value_call) pop_scoped_symbol = "()"
edge @fun.value -> fun_value_call
; function values have return nodes which need to be visible for returns
attr (fun_value_return) pop_symbol = "GUARD:RETURN"
edge fun_value_call -> fun_value_return
let @body.return_or_yield = fun_value_return
; function values have this nodes which need to be visible for method calls
attr (fun_value_this) push_symbol = "this"
attr (fun_value_this_guard) pop_symbol = "GUARD:THIS"
edge fun_value_call -> fun_value_this_guard
edge fun_value_this_guard -> fun_value_this
edge fun_value_this -> @body.after_scope
; function values have a jump node that lets params connect up to actual arguments
edge @fun.value_arg_scope -> JUMP_TO_SCOPE_NODE
}
(generator_function
parameters:
(formal_parameters (_)@param))@fun {
node param_arg_index
; parameters jump to the pushed argument scope
attr (param_arg_index) push_symbol = (named-child-index @param)
edge @param.covalue -> param_arg_index
edge param_arg_index -> @fun.value_arg_scope
}
;; #### Function Calls
; calls, functions
(call_expression
function:(_)@function
arguments:(_)@arguments)@call_expr {
; scopes flow from call expressions into the function
edge @function.before_scope -> @call_expr.before_scope
edge @arguments.before_scope -> @function.after_scope
edge @call_expr.after_scope -> @arguments.after_scope
}
; calls, values
(call_expression
function:(_)@function
arguments:(_)@arguments)@call_expr {
node arguments_arg_arguments
node arguments_arg_arguments_dot
node @arguments.arg_scope
attr (@arguments.arg_scope) is_exported
node @arguments.arg_scope_no_this
node @arguments.arg_this
node call_expr_call
node call_expr_return_guard
; value is a call, ie a push "()" node w/ "push-scope" @arguments
attr (call_expr_return_guard) push_symbol = "GUARD:RETURN"
attr (call_expr_call) push_scoped_symbol = "()", scope = @arguments.arg_scope
edge @call_expr.value -> call_expr_return_guard
edge call_expr_return_guard -> call_expr_call
edge call_expr_call -> @function.value
attr (@arguments.arg_this) symbol_definition = "this", source_node = @arguments
edge @arguments.arg_scope -> @arguments.arg_this
edge @arguments.arg_scope -> @arguments.arg_scope_no_this
attr (arguments_arg_arguments) symbol_definition = "arguments", source_node = @arguments
attr (arguments_arg_arguments_dot) pop_symbol = "GUARD:MEMBER"
edge @arguments.arg_scope -> arguments_arg_arguments
edge arguments_arg_arguments -> arguments_arg_arguments_dot
edge arguments_arg_arguments_dot -> @arguments.arg_scope_no_this
edge arguments_arg_arguments -> @call_expr.builtins_arguments_prototype
}
; special case to make `this` bind correctly in calls of the forms `x.f(...)`
; and `x[f](...)`
(call_expression
function:[
(member_expression object:(_)@object)
(subscript_expression object:(_)@object)
]
arguments:(_)@arguments) {
edge @arguments.arg_this -> @object.value
}
; TODO this should eventually be removed and replaced with a version that only
; applies to the negation of (member_expression), but that's not supported by
; tree-sitter currently
(call_expression
function: (_)@_function
arguments:(_)@arguments)@call_expr {
edge @arguments.arg_this -> @call_expr.builtins_null
}
(call_expression
arguments:(arguments (_)@arg)@arguments) {
node arg_arg_index
attr (arg_arg_index) pop_symbol = (named-child-index @arg)
edge @arguments.arg_scope_no_this -> arg_arg_index
edge arg_arg_index -> @arg.value
}
;; #### Arguments
(arguments)@arguments {
node @arguments.after_scope
node @arguments.before_scope
}
(arguments (_)* @args)@arguments {
if (is-empty @args) {
edge @arguments.after_scope -> @arguments.before_scope
}
}
(arguments
.
(_)@first_arg)@arguments {
edge @first_arg.before_scope -> @arguments.before_scope
}
(arguments
(_)@left_arg
.
(_)@right_arg) {
edge @right_arg.before_scope -> @left_arg.after_scope
}
(arguments
(_)@last_arg
.)@arguments {
edge @arguments.after_scope -> @last_arg.after_scope
}
;; #### Property Access
;; ##### Member Expressions
(member_expression
object:(_)@object property:(_)@property)@member_expr
{
node member_push
node property_push
; scopes flow into object then back out
edge @object.before_scope -> @member_expr.before_scope
edge @member_expr.after_scope -> @object.after_scope
; value is a member projection on the value of the object ie. a push then push dot
attr (member_push) push_symbol = "GUARD:MEMBER"
attr (property_push) node_reference = @property
edge property_push -> member_push
edge @member_expr.value -> property_push
edge member_push -> @object.value
; (member_expression) nodes can occur in patterns
node @member_expr.covalue
node @member_expr.new_bindings
}
;; ##### Subscript Expressions
(subscript_expression
object: (_)@object
index: (_)@index)@subscript_expr {
node @subscript_expr.index_push
node subscript_expr_push_dot
; scopes flow left to right
edge @object.before_scope -> @subscript_expr.before_scope
edge @index.before_scope -> @object.after_scope
edge @subscript_expr.after_scope -> @index.after_scope
; value is a subscript lookup, ie a push then push dot
attr (subscript_expr_push_dot) push_symbol = "GUARD:MEMBER"
edge subscript_expr_push_dot -> @object.value
; this is done differently depending on what the index is
edge @subscript_expr.value -> @subscript_expr.index_push
edge @subscript_expr.index_push -> subscript_expr_push_dot
; subscript expressions can appear in array patterns, on the left, and thus require a covalue & bindings
node @subscript_expr.covalue
node @subscript_expr.new_bindings
}
(subscript_expression
object: (_)@_object
index: (string)@index)@subscript_expr
{
attr (@subscript_expr.index_push) symbol_reference = (replace (source-text @index) "[\"\']" ""), source_node = @index
}
(subscript_expression
object: (_)@_object
index: (number)@index)@subscript_expr
{
attr (@subscript_expr.index_push) node_reference = @index
}
;; #### Constructor Calls
(new_expression
constructor:(_)@constructor
arguments:(_)@arguments)@new_expr {
node @arguments.arg_scope
attr (@arguments.arg_scope) is_exported
node @arguments.arg_this
node constructor_constructor
node new_expr_call
node new_expr_guard_this
edge @constructor.before_scope -> @new_expr.before_scope
edge @arguments.before_scope -> @constructor.after_scope
edge @new_expr.after_scope -> @arguments.after_scope
attr (new_expr_call) push_scoped_symbol = "()", scope = @arguments.arg_scope
; we guard for constructors for the case where we have a "true" class
attr (constructor_constructor) push_symbol = "GUARD:CONSTRUCTOR"
edge new_expr_call -> constructor_constructor
edge constructor_constructor -> @constructor.value
; and also just go right to the value incase we have a function-as-constructor
edge new_expr_call -> @constructor.value
; value coming from the constructor call
attr (new_expr_guard_this) push_symbol = "GUARD:THIS"
edge @new_expr.value -> new_expr_guard_this
edge new_expr_guard_this -> new_expr_call
; value also coming from the prototype
node guard_prototype
attr (guard_prototype) push_symbol = "GUARD:PROTOTYPE"
edge @new_expr.value -> guard_prototype
edge guard_prototype -> @constructor.value
attr (@arguments.arg_this) symbol_definition = "this", source_node = @arguments
edge @arguments.arg_scope -> @arguments.arg_this
edge @arguments.arg_this -> @new_expr.builtins_empty_object
}
(new_expression
arguments:(arguments (_)@arg)@arguments) {
node arg_arg_index
attr (arg_arg_index) pop_symbol = (named-child-index @arg)
edge @arguments.arg_scope -> arg_arg_index
edge arg_arg_index -> @arg.value
}
;; #### Await
(await_expression (_)@awaited)@await_expr {
; scopes flow into the inner expression then back out
edge @awaited.before_scope -> @await_expr.before_scope
edge @await_expr.after_scope -> @awaited.after_scope
; value is just propagated up
edge @await_expr.value -> @awaited.value
}
;; #### Update Expressions
(update_expression argument: (_)@argument)@update_expr {
node update_expr_pop
; scope propagates through the operand then is updated by the expr
edge @argument.before_scope -> @update_expr.before_scope
edge @update_expr.after_scope -> @argument.after_scope
; LATER-TODO this isn't quite right because the update argument can't be an arbitrary expr
; eg f(x)++ doesn't make any sense, you have to have something more like
; (update_expression argument: (lvar)@argument)
attr (update_expr_pop) node_definition = @argument
edge @update_expr.value -> @argument.value
edge @update_expr.after_scope -> update_expr_pop
edge update_expr_pop -> @argument.value
}
;; #### Binary Expressions
(binary_expression left: (_)@left right: (_)@right)@binary_expr {
; scopes propagate left to right through the operands unchanged by the binop itself
edge @left.before_scope -> @binary_expr.before_scope
edge @right.before_scope -> @left.after_scope
edge @binary_expr.after_scope -> @right.after_scope
; value is a binary op value built from the operands
; LATER-TODO this isn't quite correct but it permits flow through the expression
; which can be useful
; attr (@binary_expr.value) "binary_operation_value"
edge @binary_expr.value -> @left.value
edge @binary_expr.value -> @right.value
}
;; #### Unary Expressions
(unary_expression argument: (_)@argument)@unary_expr {
; scope propagates through the operand
edge @argument.before_scope -> @unary_expr.before_scope
edge @unary_expr.after_scope -> @argument.after_scope
; value is a unaryop value built from the operand
; LATER-TODO this isn't quite correct but it permits flow through the expression
; which can be useful
; attr (@unary_expr.value) "unary_operation_value"
edge @unary_expr.value -> @argument.value
}
;; #### Assignment Expressions;
; scopes on RHS, values
(assignment_expression
left: (_)@_left
right: (_)@right)@assignment_expr {
; scopes flow into the RHS then back out to the whole expr,
; augmented (in subsequent rules) by the LHS
edge @right.before_scope -> @assignment_expr.before_scope
; value of the whole thing is value of the RHS
edge @assignment_expr.value -> @right.value
}
; augmentation of scope via identifiers
(assignment_expression
left: (identifier)@name
right: (_)@right)@assignment_expr {
node @name.pop
; augments the scope by adding a lookup edge, ie. a pop
attr (@name.pop) node_definition = @name
edge @assignment_expr.after_scope -> @name.pop
edge @name.pop -> @right.value
; ensure the scope flows through the identifier
edge @assignment_expr.after_scope -> @right.after_scope
}
(assignment_expression
left: [
(member_expression)
(subscript_expression)
]@left
right: (_)@right)@assignment_expr {
; scope flows from LHS into pattern then back to assignment
edge @left.before_scope -> @assignment_expr.before_scope
; ensure the scope flows through the identifier
edge @assignment_expr.after_scope -> @right.after_scope
}
; assignment to direct fields on `this`
(assignment_expression
left: (member_expression
object:(this)@_this
property:(_)@property)
right: (_)@right)@assignment_expr {
node property_pop
node this_drop
node this_pop
node this_pop_dot
; HACK
attr (this_drop) type = "drop_scopes"
edge this_drop -> this_pop
attr (this_pop) pop_symbol = "this"
attr (this_pop_dot) pop_symbol = "GUARD:MEMBER"
attr (property_pop) node_definition = @property
edge @assignment_expr.after_scope -> this_drop
edge @assignment_expr.after_scope -> this_pop
edge this_pop -> this_pop_dot
edge this_pop_dot -> property_pop
edge property_pop -> @right.value
}
; augmentation of scope via _destructuring_patterns
(assignment_expression
left: [
(object_pattern)
(array_pattern)
(assignment_pattern)
]@left
right: (_)@right)@assignment_expr {
; scope flows from LHS into pattern then back to assignment
edge @left.before_scope -> @right.after_scope
edge @assignment_expr.after_scope -> @left.after_scope
}
;; #### Augmented Assignment Expressions
(augmented_assignment_expression
left: (_)@_left
right: (_)@right)@augmented_assignment_expr {
; scopes flow into the RHS then back out to the whole expr, augmented by the LHS
edge @right.before_scope -> @augmented_assignment_expr.before_scope
edge @augmented_assignment_expr.after_scope -> @right.after_scope
}
(augmented_assignment_expression
left:(identifier)@left
right:(_)@right)@augmented_assignment_expr {
node augmented_assignment_expr_pop
node augmented_assignment_expr_push
; augment the scope
attr (augmented_assignment_expr_pop) node_definition = @left
attr (augmented_assignment_expr_push) node_reference = @left
edge augmented_assignment_expr_push -> @augmented_assignment_expr.before_scope
edge augmented_assignment_expr_pop -> augmented_assignment_expr_push
edge augmented_assignment_expr_pop -> @right.value
edge @augmented_assignment_expr.after_scope -> augmented_assignment_expr_pop
}
;; #### Comma Operator / Sequence Expressions
(sequence_expression (_)* @elems)@sequence_expr {
if (is-empty @elems) {
edge @sequence_expr.after_scope -> @sequence_expr.before_scope
}
}
(sequence_expression . (_)@first)@sequence_expr {
edge @first.before_scope -> @sequence_expr.before_scope
}
(sequence_expression (_)@left . (_)@right) {
edge @right.before_scope -> @left.after_scope
}
(sequence_expression (_)@last .)@sequence_expr {
edge @sequence_expr.after_scope -> @last.after_scope
edge @sequence_expr.value -> @last.value
}
;; #### Ternary Expression
(ternary_expression
condition: (_)@condition
consequence: (_)@consequence
alternative: (_)@alternative)@ternary_expr {
; scopes propagate into condition, then into each branch
edge @condition.before_scope -> @ternary_expr.before_scope
edge @consequence.before_scope -> @condition.after_scope
edge @alternative.before_scope -> @condition.after_scope
edge @ternary_expr.after_scope -> @consequence.after_scope
edge @ternary_expr.after_scope -> @alternative.after_scope
; value of the whole thing is a conditional value from the operands
edge @ternary_expr.value -> @consequence.value
edge @ternary_expr.value -> @alternative.value
}
;; #### Yield
(yield_expression (_)@yielded_expr)@yield_expr {
; scopes flow in to the yielded expression then back out
edge @yielded_expr.before_scope -> @yield_expr.before_scope
edge @yield_expr.after_scope -> @yielded_expr.after_scope
; yield expressions hook up to the call node of the function value
edge @yield_expr.return_or_yield -> @yielded_expr.value
}
;; #### Class Expressions
(class
body:(_)@body)@class {
node @class.class_value
let @class.containing_class_value = @class.class_value
node guard_prototype
node @class.prototype
node @class.constructor
attr (guard_prototype) pop_symbol = "GUARD:PROTOTYPE"
edge @body.before_scope -> @class.closure_point
edge @class.value -> @class.class_value
edge @class.class_value -> guard_prototype
edge guard_prototype -> @class.prototype
edge @class.class_value -> @class.constructor
edge @class.prototype -> @body.after_scope
edge @class.after_scope -> @class.before_scope
}
(class
name:(_)@name
body:(_)@body)@class {
node @name.pop
attr (@name.pop) syntax_type = "class"
attr (@name.pop) node_definition = @name
edge @body.before_scope -> @name.pop
edge @name.pop -> @class.value
}
(class
(class_heritage (_)@name))@class {
node guard_prototype
node guard_constructor
attr (guard_prototype) push_symbol = "GUARD:PROTOTYPE"
attr (guard_constructor) push_symbol = "GUARD:CONSTRUCTOR"
edge @name.before_scope -> @class.before_scope
edge @class.prototype -> guard_prototype
edge guard_prototype -> @name.value
edge @class.constructor -> guard_constructor
edge guard_constructor -> @name.value
edge @class.after_scope -> @name.after_scope
}
(jsx_element
open_tag:(_)@open_tag
close_tag:(_)@close_tag
(_)*@children)@jsx_element {
node @jsx_element.before_scope
node @jsx_element.after_scope
node @jsx_element.value
edge @open_tag.before_scope -> @jsx_element.before_scope
edge @jsx_element.after_scope -> @close_tag.after_scope
if (is-empty @children) {
edge @close_tag.before_scope -> @open_tag.after_scope
}
}
(jsx_element
open_tag:(_)@open_tag
.
[
(jsx_text)
(jsx_element)
(jsx_self_closing_element)
(jsx_expression)
]@first_child
) {
edge @first_child.before_scope -> @open_tag.after_scope
}
(jsx_element
[
(jsx_text)
(jsx_element)
(jsx_self_closing_element)
(jsx_expression)
]@left_child
.
[
(jsx_text)
(jsx_element)
(jsx_self_closing_element)
(jsx_expression)
]@right_child
) {
edge @right_child.before_scope -> @left_child.after_scope
}
(jsx_element
[
(jsx_text)
(jsx_element)
(jsx_self_closing_element)
(jsx_expression)
]@last_child
.
close_tag:(_)@close_tag
) {
edge @close_tag.before_scope -> @last_child.after_scope
}
(jsx_text)@jsx_text {
node @jsx_text.before_scope
node @jsx_text.after_scope
edge @jsx_text.after_scope -> @jsx_text.before_scope
}
(jsx_opening_element)@jsx_opening_element {
node @jsx_opening_element.before_scope
node @jsx_opening_element.after_scope
}
(jsx_opening_element
name:(_)@element_name)@jsx_opening_element {
edge @element_name.before_scope -> @jsx_opening_element.before_scope
}
(jsx_opening_element
!name)@jsx_opening_element
{
edge @jsx_opening_element.after_scope -> @jsx_opening_element.before_scope
}
(jsx_opening_element
name:(_)@element_name
!attribute)@jsx_opening_element
{
edge @jsx_opening_element.after_scope -> @element_name.after_scope
}
(jsx_opening_element
name:(_)@element_name
.
attribute:(_)@first_attr
) {
edge @first_attr.before_scope -> @element_name.after_scope
}
(jsx_opening_element
attribute:(_)@left_attr
.
attribute:(_)@right_attr
) {
edge @right_attr.before_scope -> @left_attr.after_scope
}
(jsx_opening_element
attribute:(_)@last_attr
.)@jsx_opening_element
{
edge @jsx_opening_element.after_scope -> @last_attr.after_scope
}
(jsx_attribute (_) . (_)?@attr_value)@jsx_attribute {
node @jsx_attribute.before_scope
node @jsx_attribute.after_scope
if none @attr_value {
edge @jsx_attribute.after_scope -> @jsx_attribute.before_scope
} else {
edge @attr_value.before_scope -> @jsx_attribute.before_scope
edge @jsx_attribute.after_scope -> @attr_value.after_scope
}
}
(jsx_namespace_name (_) @lhs (_) @rhs)@name {
node @name.before_scope
node @name.after_scope
edge @lhs.before_scope -> @name.before_scope
edge @rhs.before_scope -> @lhs.after_scope
edge @name.after_scope -> @name.before_scope
}
(jsx_self_closing_element
name:(_)@element_name)@jsx_self_closing_element {
node @jsx_self_closing_element.before_scope
node @jsx_self_closing_element.after_scope
node @jsx_self_closing_element.value
edge @element_name.before_scope -> @jsx_self_closing_element.before_scope
}
(jsx_self_closing_element
!name
!attribute)@jsx_self_closing_element
{
edge @jsx_self_closing_element.after_scope -> @jsx_self_closing_element.before_scope
}
(jsx_self_closing_element
name:(_)@element_name
!attribute)@jsx_self_closing_element
{
edge @jsx_self_closing_element.after_scope -> @element_name.after_scope
}
(jsx_self_closing_element
name:(_)@element_name
.
attribute:(_)@first_attr
) {
edge @first_attr.before_scope -> @element_name.after_scope
}
(jsx_self_closing_element
attribute:(_)@left_attr
.
attribute:(_)@right_attr
) {
edge @right_attr.before_scope -> @left_attr.after_scope
}
(jsx_self_closing_element
attribute:(_)@last_attr
.)@jsx_self_closing_element
{
edge @jsx_self_closing_element.after_scope -> @last_attr.after_scope
}
(jsx_expression)@jsx_expression {
node @jsx_expression.before_scope
node @jsx_expression.after_scope
}
(jsx_expression (_)?@child)@jsx_expression {
if none @child {
edge @jsx_expression.after_scope -> @jsx_expression.before_scope
} else {
edge @child.before_scope -> @jsx_expression.before_scope
edge @jsx_expression.after_scope -> @child.after_scope
}
}
(jsx_closing_element)@jsx_closing_element
{
node @jsx_closing_element.before_scope
node @jsx_closing_element.after_scope
}
(jsx_closing_element
!name)@jsx_closing_element
{
edge @jsx_closing_element.after_scope -> @jsx_closing_element.before_scope
}
(jsx_closing_element
name:(_)@element_name)@jsx_closing_element
{
edge @element_name.before_scope -> @jsx_closing_element.before_scope
edge @jsx_closing_element.after_scope -> @element_name.after_scope
}
[
(jsx_opening_element
name:(identifier)@element_name)
(jsx_self_closing_element
name:(identifier)@element_name)
(jsx_closing_element
name:(identifier)@element_name)
]
{
scan (source-text @element_name) {
; standard HTML elements
"^(a|abbr|acronym|address|applet|area|article|aside|audio|b|base|basefont|bdi|bdo|big|blockquote|body|br|button|canvas|caption|center|cite|code|col|colgroup|data|datalist|dd|del|details|dfn|dialog|dir|div|dl|dt|em|embed|fieldset|figcaption|figure|font|footer|form|frame|frameset|h1|h2|h3|h4|h5|h6|head|header|hgroup|hr|html|i|iframe|input|ins|kbd|label|legend|li|link|main|map|mark|menu|meta|meter|nav|noframes|noscript|object|ol|optgroup|option|output|p|param|picture|pre|progress|q|rp|rt|ruby|s|samp|script|search|section|select|small|source|span|strike|strong|style|sub|summary|sup|svg|table|tbody|td|template|textarea|tfoot|th|thead|time|title|tr|track|tt|u|ul|var|video|wbr)$" {
; do nothing!
}
; everything else
"^.+$" {
node element_name_pop
attr (element_name_pop) node_reference = @element_name
edge element_name_pop -> @element_name.before_scope
}
}
}
;; ██████ █████ ████████ ████████ ███████ ██████ ███ ██ ███████
;; ██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ██ ██
;; ██████ ███████ ██ ██ █████ ██████ ██ ██ ██ ███████
;; ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
;; ██ ██ ██ ██ ██ ███████ ██ ██ ██ ████ ███████
;; ## Patterns
;; Patterns introduce at least two interesting problems to the task of name
;; resolution. On the one hand, patterns by themselves are rich in structure
;; and binding possibilities. On the other, they function to pull apart
;; structure in ways that we would like to make use of in resolving names. Let's
;; look at these in order.
;; ### Binding Possibilities
;; If names could only be bound directly either on the LHS of an assignment, or
;; as the formal parameters to a function, or in related places like as
;; arguments to increment or decrement operators, then there would be little
;; trouble saying where a name is bound. But consider a destructuring assignment
;; such as this:
;; ``````javascript
;; let [x,y,z] = arr;
;; ``````
;; This assignment has variable names on the LHS of an assignment, sure, but not
;; directly as the LHS. Rather they're buried down inside a pattern. Here they
;; aren't very deep, just one node below the root of the LHS, but they can be
;; arbitrarily far down:
;; ``````javascript
;; let [x, [y, [z, w], q], r] = arr;
;; ``````
;; On top of this, patterns in JavaScript permit default assignments, such as
;; ``````javascript
;; let [x, y = 1] = arr;
;; ``````
;; These default values can be the result of a computation itself containing
;; variables, such as
;; ``````javascript
;; let [x, y = 2*z] = arr;
;; ``````
;; Additionally, those variables referenced in the default value are evaluated
;; *after* the RHS of the assignment. In the following code, `z` is incremented
;; and becomes `2`, and then `x` is assigned to that value. The array on the RHS
;; is only one element long, so the `y` variable gets assigned the default value
;; of `2*z`, which is computed *after* the increment, and so is `2*2` or `4`.
;; ``````javascript
;; let z = 1;
;; let [x, y = 2*z] = [z++];
;; ``````
;; To complicated matters further, the default value can reference the *bound
;; variables to its left*. For instance:
;; ``````javascript
;; let [x, y = x+1] = arr;
;; ``````
;; All of this leads to some very interesting and tricky problems for name
;; resolution. The flow of the environment is as follows: First, the environment
;; flows into the RHS of the assignment and is updated by whatever evaluations
;; happen there, then it flows out of the RHS and into the LHS, where it goes
;; through each pattern in the LHS parse tree, in a left-to-right, depth-first
;; traversal. Patterns with no default assignments do nothing to the environment
;; but patterns with a default will pass the environment to the default value,
;; where it may be updated by the evaluation, and then passed further along.
;; Each variable, whether bare or on the left of an assignment, also has to
;; extend the environment because those variables come into scope further along.
;; ### Structure Decomposition
;; Let's now look at how patterns decompose structure. Let's consider the effect
;; of an assignment such as this:
;; ``````javascript
;; let [x,y] = [1,2];
;; ``````
;; This assignment binds `x` to `1` and `y` to `2`. It's equivalent to doing
;; ``````javascript
;; let x = 1, y = 2;
;; ``````
;; Except, unlike the latter, the array destructuring does not have any obvious
;; pairing up of the names with the expressions that give them their values.
;; Now, perhaps there's some clever hack we can perform for this special case
;; where the RHS is a structure like `[1,2]` where it's manifestly clear out to
;; pair things up, but of course the RHS can come from anywhere. It can come
;; from a local variable assignment:
;; ``````javascript
;; let arr = [1,2];
;; let [x,y] = arr;
;; ``````
;; Or a function call:
;; ``````javascript
;; function foo(arr) {
;; let [x,y] = arr;
;; ...
;; }
;; foo([1,2]);
;; ``````
;; Or any other number of places. We would like *all* of these to permit at
;; least *some* amount of name resolution so that we can find that `x` is `1`
;; and `y` is `2`. This should extend also to objects, not just arrays.
;; The approach we take is to recognize a general pattern of equivalence, which
;; the above double-let assignment is related special case. For arrays, all
;; destructuring assignments of the form
;; ``````javascript
;; let [..., pat_i, ...] = arr;
;; ``````
;; are equivalent to
;; ``````javascript
;; let pat_i = arr[i];
;; ``````
;; For any pattern `pat` and expression `arr`. So for instance the simple case
;; of a pattern expression and an array literal
;; ``````javascript
;; let [x,y] = [1,2];
;; ``````
;; is equivalent to a pair of lets
;; ``````javascript
;; let x = [1,2][0];
;; let y = [1,2][1];
;; ``````
;; Modulo any change in side effects from duplicating the array syntactically,
;; these two are equivalent and this would be a valid code transformation. Or if
;; the pattern were an embedded array destructuring like so:
;; ``````javascript
;; let [x, [y,z]] = [1, [2,3]];
;; ``````
;; then this would be equivalent to
;; ``````javascript
;; let x = [1, [2,3]][0];
;; let [y,z] = [1, [2,3]][1];
;; ``````
;; And of course the second let here would similarly unfold to be equivalent to
;; a pair of assignments, giving us
;; ``````javascript
;; let x = [1, [2,3]][0];
;; let y = [1, [2,3]][1][0];
;; let z = [1, [2,3]][1][1];
;; ``````
;; Similarly for objects, we have the following destructuring equivalence that
;; says an assignment like this:
;; ``````javascript
;; let {..., k: pat, ...} = obj;
;; ``````
;; is equivalent to
;; ``````javascript
;; let pat = obj[k];
;; ``````
;; for all patterns `pat` and expressions `obj`.
;; This lets us then conclude that whatever the graphs are that we generate for
;; patterns ought to be equivalent to the graphs we would general for using
;; array indexing and object indexing. Since array and object indexing are fully
;; capable of participating in name resolution, if we can achieve this
;; equivalence, patterns can also fully participate as well, and we'll be able
;; to say that the assignment `let [x,y] = [1,2]` yields the name `x` resolving
;; to `1` and `y` to `2`, as well as many many more complicated cases.
;; As mentioned in the intro to this doc, assignments point to values. The
;; simplest way to do this, in the absence of patterns, is just to have an edge
;; from the after scope of the assignment to a pop node for the variable,
;; and then to the value node of the RHS, so for `let x = 1;` it'd be like so:
;; ``````
;; after_scope ---> POP "x" ---> value_node_for_1
;; ``````
;; But the above discussion of destructuring complicates this. What we do to
;; address this is introduce the notion of a "covalue". Just as we can say that
;; an expression *has* a value, or produces a value, etc., we'll say that a
;; pattern *consumes* a value. Expressions have values going "out", while
;; patterns have values coming "in". And so like expressions have an associated
;; `value` node in the graph, patterns have an associated `covalue` node. The
;; covalue corresponds to an incoming value.
;; We build covalues similar to how we build values. Consider the value for an
;; array such as `["foo", "bar"]`, which will be a scope node with pop nodes
;; going out to the values of the strings, like so:
;; ``````
;; ,---> POP 0 ---> value_node_of_foo
;; value_node_of_the_array ---|
;; `---> POP 1 ---> value_node_of_bar
;; ``````
;; Similarly, a covalue for an array pattern will also have nodes for the
;; sub-patterns and nodes for the first and second indexes. But rather than pop
;; nodes, which show you where the 0th and 1st elements are, they'll be push
;; nodes to establish the lookup of those elements. The edges will therefore
;; go the other way around. So for a pattern like `[x,y]`, we have the graph
;; ``````
;; ,--- PUSH 0 <--- covalue_node_of_x
;; covalue_node_of_the_pattern <---|
;; `--- PUSH 1 <--- covalue_node_of_y
;; ``````
;; For readers familiar with category theory's notion of duality, this explains
;; why these are called "covalues". The general schema here is that where values
;; have pops, covalues have pushes, and all the arrows get flipped.
;; ### Attributes Defined on Patterns
;; TODO
[
(assignment_pattern)@pattern
(object_pattern)@pattern
(array_pattern)@pattern
(rest_pattern)@pattern
(pair_pattern)@pattern
(pattern/property_identifier)@pattern
(object_assignment_pattern)@pattern
(shorthand_property_identifier_pattern)@pattern
] {
node @pattern.after_scope
node @pattern.before_scope
node @pattern.covalue
node @pattern.new_bindings
}
;; ### Pattern Queries
;; #### Variable Patterns
; scope propagation through identifier patterns
(pattern/identifier)@ident_pat {
node ident_pat_pop
; scope flows through, binding via a pop edge that goes to an unknown value
attr (ident_pat_pop) node_definition = @ident_pat
edge ident_pat_pop -> @ident_pat.covalue
edge @ident_pat.after_scope -> ident_pat_pop
edge @ident_pat.new_bindings -> ident_pat_pop
}
;; #### Object Patterns
(object_pattern (_)* @entries)@object_pat {
if (is-empty @entries) {
edge @object_pat.after_scope -> @object_pat.before_scope
}
}
; scope propagation through object patterns, first entry
(object_pattern
.
(_)@first_entry)@object_pat {
; scope propagates from object pattern to entry
edge @first_entry.before_scope -> @object_pat.before_scope
}
; scope propagation through object patterns, between entries
(object_pattern
(_)@left_entry
.
(_)@right_entry) {
; scope propagates from left entry to right entry
edge @right_entry.before_scope -> @left_entry.after_scope
}
; scope propagation through object patterns, last entry
(object_pattern
(_)@last_entry
.)@object_pat {
; scope propagates out from last entry to object pattern
edge @object_pat.after_scope -> @last_entry.after_scope
}
; covalue propagation through object patterns
(object_pattern
(_)@entry)@object_pat {
; covalues flow into entries unchanged
edge @entry.covalue -> @object_pat.covalue
edge @object_pat.new_bindings -> @entry.new_bindings
}
; object entry pair patterns
(pair_pattern
key:(_)@key
value:(_)@value_pat)@pair_pat {
node @key.push
node key_push_dot
; covalues flow in dotted
attr (key_push_dot) push_symbol = "GUARD:MEMBER"
edge @value_pat.covalue -> @key.push
edge @key.push -> key_push_dot
edge key_push_dot -> @pair_pat.covalue
; scope flows into value pattern then back out
edge @value_pat.before_scope -> @pair_pat.before_scope
edge @pair_pat.after_scope -> @value_pat.after_scope
edge @pair_pat.new_bindings -> @value_pat.new_bindings
}
(pair_pattern
key:(property_identifier)@key)@_pair_pattern {
attr (@key.push) node_reference = @key
}
(pair_pattern
key:(string)@key)@_pair_pattern {
attr (@key.push) symbol_reference = (replace (source-text @key) "\"" ""), source_node = @key
}
; LATER-TODO the left pattern has to be a name, it cant be another pattern
; object entry assignment patterns
(object_assignment_pattern
left:(_)@left_pat
right:(_)@right_expr)@object_assignment_pat {
; scope flows both THROUGH and AROUND the RHS, because it's a
; by-passable default not a guaranteed value
; here we go around
edge @left_pat.before_scope -> @object_assignment_pat.before_scope
; and here we go through
edge @right_expr.before_scope -> @object_assignment_pat.before_scope
edge @left_pat.before_scope -> @right_expr.after_scope
; and in either case we come out the LHS
edge @object_assignment_pat.after_scope -> @left_pat.after_scope
; covalues flow both in from the outside and also from the right expression
edge @left_pat.covalue -> @object_assignment_pat.covalue
edge @left_pat.covalue -> @right_expr.value
edge @object_assignment_pat.new_bindings -> @left_pat.new_bindings
}
(shorthand_property_identifier_pattern)@shorthand_prop_pat {
node pat_pop
node pat_push
node pat_push_dot
edge @shorthand_prop_pat.after_scope -> @shorthand_prop_pat.before_scope
attr (pat_push) node_reference = @shorthand_prop_pat
attr (pat_push_dot) push_symbol = "GUARD:MEMBER"
attr (pat_pop) node_definition = @shorthand_prop_pat
edge pat_pop -> pat_push
edge pat_push -> pat_push_dot
edge pat_push_dot -> @shorthand_prop_pat.covalue
edge @shorthand_prop_pat.after_scope -> pat_pop
edge @shorthand_prop_pat.new_bindings -> pat_pop
}
;; #### Array Patterns
(array_pattern (_)* @pats)@array_pat {
if (is-empty @pats) {
edge @array_pat.after_scope -> @array_pat.before_scope
}
}
; scope propagation through array patterns, first element
(array_pattern
.
(_)@first_el_pat)@array_pat {
; scope flows into the first element
edge @first_el_pat.before_scope -> @array_pat.before_scope
}
; scope propagation through array patterns, between element
(array_pattern
(_)@left_el_pat
.
(_)@right_el_pat) {
; scope flows from left to right
edge @right_el_pat.before_scope -> @left_el_pat.after_scope
}
; scope propagation through array patterns, last element
(array_pattern
(_)@last_el_pat
.)@array_pat {
; scope flow out from the last element
edge @array_pat.after_scope -> @last_el_pat.after_scope
}
; array pattern
(array_pattern)@array_pat {
node @array_pat.element_index_push_dot
edge @array_pat.element_index_push_dot -> @array_pat.covalue
attr (@array_pat.element_index_push_dot) push_symbol = "GUARD:MEMBER"
}
; array pattern elements
(array_pattern (_)@element_pat)@array_pat {
node element_pat_element_index_push
attr (element_pat_element_index_push) push_symbol = (named-child-index @element_pat)
edge @element_pat.covalue -> element_pat_element_index_push
edge element_pat_element_index_push -> @array_pat.element_index_push_dot
edge @array_pat.new_bindings -> @element_pat.new_bindings
}
;; #### Assignment Patterns
; scope propagation through assignment patterns
(assignment_pattern
left:(_)@left_pat
right:(_)@right_expr)@assignment_pat {
; scope flows both THROUGH and AROUND the RHS, because it's a
; by-passable default not a guaranteed value
; here we go around
edge @left_pat.before_scope -> @assignment_pat.before_scope
; and here we go through
edge @right_expr.before_scope -> @assignment_pat.before_scope
edge @left_pat.before_scope -> @right_expr.after_scope
; the pattern's covalue is the whole thing's, and also the RHS
edge @left_pat.covalue -> @assignment_pat.covalue
edge @left_pat.covalue -> @right_expr.value
; and in either case we come out the LHS
edge @assignment_pat.after_scope -> @left_pat.after_scope
edge @assignment_pat.new_bindings -> @left_pat.new_bindings
}
;; #### Rest Patterns
(rest_pattern (_)@name)@rest_pat {
node rest_pat_pop
; scope flows through, binding via a pop edge that goes to an unknown value
attr (rest_pat_pop) node_definition = @name
edge @rest_pat.after_scope -> @rest_pat.before_scope
edge @rest_pat.after_scope -> rest_pat_pop
}
;; ███████ ██████ ███████ ██████ ██ █████ ██
;; ██ ██ ██ ██ ██ ██ ██ ██ ██
;; ███████ ██████ █████ ██ ██ ███████ ██
;; ██ ██ ██ ██ ██ ██ ██ ██
;; ███████ ██ ███████ ██████ ██ ██ ██ ███████
;; ## Special Cases
;;
;; There are a number of annoying features that libraries make use of to
;; effectively add features to JavaScript. While they don't technically change
;; the language in any way, they're broad design patterns that are meant to be
;; used *as if* these things were more language level than not. These often make
;; it hard to do analysis without actually running code, and so instead, we
;; define some special case queries that treat these techniques as if they were
;; indeed core features of the language.
;;
;; ### Extend
;;
;; The extend method is a mass assignment of values to keys on objects and gets
;; used a bunch for building module export objects. We special case it here so
;; that we can do lookup on it because the extend method itself is dependent on
;; state and mutability, and has no good analytical explanation within the
;; Stack Graph formalism.
;;
;; Since we can't extend the actual value, but only the syntactic references to
;; it in the SG formalism, we treat extend as a kind of shadowing binder,
;; similar to how we treat `+=` or `*=`.
(
(call_expression
function: (member_expression
object: (identifier)@object
property: (_)@_extend)
arguments: (arguments (object)@new_fields))@call_expr
(#eq? @_extend "extend")
) {
node object_pop
attr (object_pop) node_definition = @object
edge @call_expr.after_scope -> object_pop
edge object_pop -> @new_fields.value
}
;; ### CommonJS-style Exports
;; CommonJS introduced an export style for pre-ES6 JavaScript that permitted
;; modules to export functions using an exports object bound to a top-level
;; variable `exports`. For instance, to export something as `foo`, we would do:
;; ``````javascript
;; exports.foo = 1;
;; ``````
;; If we then imported with `require`, the exports object would have `foo` as
;; a field. Alternatively, we can also specify the entire export object, using
;; ``````javascript
;; module.exports = my_exported_object;
;; ``````
(
(assignment_expression
left: [
( ; exports.foo = ...
(member_expression
object:(_)@exports
property:(_)@property)
(#eq? @exports "exports")
)
( ; module.exports.foo = ...
(member_expression
object:(member_expression
object:(_)@_module
property:(_)@exports)
property:(_)@property)
(#eq? @_module "module")
(#eq? @exports "exports")
)
]
right: (_)@right)@assignment_expr
) {
node pop_default_guard
node pop_dot
node @assignment_expr.pop_name
attr (pop_default_guard) symbol_definition = "GUARD:DEFAULT", source_node = @exports
edge @assignment_expr.exports -> pop_default_guard
attr (pop_dot) pop_symbol = "GUARD:MEMBER"
edge pop_default_guard -> pop_dot
attr (@assignment_expr.pop_name) node_definition = @property
attr (@assignment_expr.pop_name) definiens_node = @assignment_expr
edge pop_dot -> @assignment_expr.pop_name
edge @assignment_expr.pop_name -> @right.value
;; For ES6 interoperability, expose members as named exports
edge @assignment_expr.exports -> @assignment_expr.pop_name
node detour_push
node @assignment_expr.detour_pop
scan FILE_PATH {
"^(.+/)?([^/]+)/index\.js$" {
let module_name = $2
attr (detour_push) push_symbol = module_name
attr (@assignment_expr.detour_pop) symbol_definition = module_name, source_node = @assignment_expr, definiens_node = @assignment_expr
edge pop_default_guard -> detour_push
edge detour_push -> @assignment_expr.detour_pop
edge @assignment_expr.detour_pop -> @right.value
}
"^(.+/)?([^/]+)\.js$" {
let module_name = $2
attr (detour_push) push_symbol = module_name
attr (@assignment_expr.detour_pop) symbol_definition = module_name, source_node = @assignment_expr, definiens_node = @assignment_expr
edge pop_default_guard -> detour_push
edge detour_push -> @assignment_expr.detour_pop
edge @assignment_expr.detour_pop -> @right.value
}
}
node default_detour_push
node @assignment_expr.default_detour_pop
attr (default_detour_push) push_symbol = "default"
attr (@assignment_expr.default_detour_pop) symbol_definition = "default", source_node = @assignment_expr, definiens_node = @assignment_expr
edge pop_default_guard -> default_detour_push
edge default_detour_push -> @assignment_expr.default_detour_pop
edge @assignment_expr.default_detour_pop -> @right.value
}
(
(assignment_expression
left: (member_expression
object:(_)@_module
property:(_)@exports)
right: (_)@right)@assignment_expr
(#eq? @_module "module")
(#eq? @exports "exports")
) {
node @assignment_expr.pop_default_guard
node pop_dot
attr (@assignment_expr.pop_default_guard) symbol_definition = "GUARD:DEFAULT", source_node = @exports
attr (@assignment_expr.pop_default_guard) definiens_node = @assignment_expr
edge @assignment_expr.exports -> @assignment_expr.pop_default_guard
edge @assignment_expr.pop_default_guard -> @right.value
;; For ES6 interoperability, expose members as named exports
attr (pop_dot) pop_symbol = "GUARD:MEMBER"
edge @assignment_expr.exports -> pop_dot
edge pop_dot -> @right.value
node detour_push
node @assignment_expr.detour_pop
scan FILE_PATH {
"^(.+/)?([^/]+)/index\.js$" {
let module_name = $2
attr (detour_push) push_symbol = module_name
attr (@assignment_expr.detour_pop) symbol_definition = module_name, source_node = @assignment_expr, definiens_node = @assignment_expr
edge @assignment_expr.pop_default_guard -> detour_push
edge detour_push -> @assignment_expr.detour_pop
edge @assignment_expr.detour_pop -> @right.value
}
"^(.+/)?([^/]+)\.js$" {
let module_name = $2
attr (detour_push) push_symbol = module_name
attr (@assignment_expr.detour_pop) symbol_definition = module_name, source_node = @assignment_expr, definiens_node = @assignment_expr
edge @assignment_expr.pop_default_guard -> detour_push
edge detour_push -> @assignment_expr.detour_pop
edge @assignment_expr.detour_pop -> @right.value
}
}
node default_detour_push
node @assignment_expr.default_detour_pop
attr (default_detour_push) push_symbol = "default"
attr (@assignment_expr.default_detour_pop) symbol_definition = "default", source_node = @assignment_expr, definiens_node = @assignment_expr
edge @assignment_expr.pop_default_guard -> default_detour_push
edge default_detour_push -> @assignment_expr.default_detour_pop
edge @assignment_expr.default_detour_pop -> @right.value
}
;; ### CommonJS-style Imports
;; Similar to exports, CommonJS also defines a way to do imports. In general,
;; these look like `require(expr)`, but in practice the expression is a string
;; constant, which is the only case we handle.
(
(call_expression
function:(identifier)@_require
arguments:(arguments (string)@source))@call_expr
(#eq? @_require "require")
) {
node default_guard_push
attr (default_guard_push) symbol_reference = "GUARD:DEFAULT", source_node = @source
edge @call_expr.value -> default_guard_push
edge default_guard_push -> @source.exports
}
;; ### Dynamic Imports
;; Both ES6 and CommonJS modules can be imported using an import function.
;; In general, these look like `import(expr)`, but in practice the expression
;; is a string constant, which is the only case we handle.
;;
;; The return value of the import function is an object whose properties are
;; the exports of the module. The default export is assigned to the `default`
;; property.
;;
;; The import function is async and returns a promise. Since we do not support
;; async functions and promises, we only support the case where the function
;; is called as `await import(...)`.
(
(await_expression
(call_expression
function:(_)@_import
arguments:(arguments (string)@source))
)@await_expr
(#eq? @_import "import")
) {
node pop_dot
node pop_default
node push_guard_default
attr (pop_dot) pop_symbol = "GUARD:MEMBER"
edge @await_expr.value -> pop_dot
edge pop_dot -> @source.exports
attr (pop_default) pop_symbol = "default"
edge pop_dot -> pop_default
attr (push_guard_default) symbol_reference = "GUARD:DEFAULT", source_node = @source
edge pop_default -> push_guard_default
edge push_guard_default -> @source.exports
}
;; ### ES6 and CommonJS interoperability
;; Nodes supports some interoperability between ES6 and CommonJS modules.
;;
;; A CommonJS module can be imported in an ES6 module:
;;
;; - `import foo from "cjs_module"` binds `foo` to the value of `module.exports`.
;; - `import { foo } from "cjs_module"` binds `foo` to the value of `module.exports.foo`.
;; - `import("cjs_module")` returns (a promise to) an object with
;; - property `default` bound to the value of `module.exports`, and
;; - other properties bound to the value of those properties in `module.exports`
;; (e.g., `foo` binds `module.exports.foo`).
;;
;; A ES6 module can be import in an CommonJS module:
;;
;; - `import("es6_module")` returns (a promise to) an object with
;; - property default bound to the value of `export default`, and
;; - other properties bound to named exports (e.g., `foo` binds `export { foo }`).
;; - `require("es6_module")` is not supported.
;;
;; References:
;;
;; - https://nodejs.org/api/esm.html#interoperability-with-commonjs
;;
;; ██████ ███████ ███████ ██ ███ ██ ██ ███████ ███ ██ ███████
;; ██ ██ ██ ██ ██ ████ ██ ██ ██ ████ ██ ██
;; ██ ██ █████ █████ ██ ██ ██ ██ ██ █████ ██ ██ ██ ███████
;; ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
;; ██████ ███████ ██ ██ ██ ████ ██ ███████ ██ ████ ███████
;; ## Definiens Rules
;; These rules explain how defined names relate to syntactic definitions
;; of various forms. Sometimes that's declared function names mapping to
;; the entire function declaration, and sometimes that's variables in an
;; assignment being mapped to the thing it's assigned to. The purpose of
;; these is not to augment stack graphs, per se, but to permit syntax
;; oriented tools that need to know about approximate call graphs, etc.
;; ### Basic Definiens Rules
;; These rules are all about declarations and terms that have the names
;; directly in them.
(
(class_declaration
name:(_)@name
body:(class_body
member:(method_definition
name:(_)@_method_name)@constructor
)
)
(#eq? @_method_name "constructor")
) {
attr (@name.pop) definiens_node = @constructor
}
(function_declaration
name:(_)@name
parameters:(_)@_call_sig
body:(_)@_body)@fun_decl {
attr (@name.pop) definiens_node = @fun_decl
}
(generator_function_declaration
name:(_)@name
parameters:(_)@_call_sig
body:(_)@_body)@fun_decl {
attr (@name.pop) definiens_node = @fun_decl
}
(method_definition
name:(_)@name
parameters:(_)@_call_sig
body:(_)@_body)@method_def {
attr (@name.pop) definiens_node = @method_def
}
(function_expression
name:(_)@name
parameters:(_)@_call_sig)@fun {
attr (@name.pop) definiens_node = @fun
}
(generator_function
name:(_)@name
parameters:(_)@_call_sig)@fun {
attr (@name.pop) definiens_node = @fun
}
(
(class
name:(_)@name
body:(class_body
member:(method_definition
name:(_)@_method_name)@constructor
)
)
(#eq? @_method_name "constructor")
) {
attr (@name.pop) definiens_node = @constructor
}
;; ### Assignment-like Rules
;; These rules make up for the fact that JavaScript permits way more
;; kinds of definitions/declarations than just those that show up in
;; syntactic declarations of the thing in question.
;; These rules are currently way less precise than we would like but
;; do provide at least some information about definiens for these
;; kinds of definitions.
(assignment_expression
left: (identifier)@left
right: (_))@assignment_expr {
attr (@left.pop) definiens_node = @assignment_expr
}
(
(assignment_expression
left: (member_expression
object:(identifier)@_object
property:(_)@left
)
right: (_))@assignment_expr
(#not-eq? @_object "module")
(#not-eq? @left "exports")
) {
node left_definiens_hook
node left_ignore_guard
attr (left_ignore_guard) pop_symbol = "GUARD:GANDALF"
attr (left_definiens_hook) node_definition = @left
attr (left_definiens_hook) definiens_node = @assignment_expr
edge @assignment_expr.pkg_pop -> left_ignore_guard
edge left_ignore_guard -> left_definiens_hook
}
(
(assignment_expression
left: (member_expression
object:(identifier)@_object
property:(_)@left
)
right: (_))@assignment_expr
(#eq? @_object "module")
(#eq? @left "exports")
) {
node left_definiens_hook
node left_ignore_guard
attr (left_ignore_guard) pop_symbol = "GUARD:GANDALF"
attr (left_definiens_hook) node_definition = @left
attr (left_definiens_hook) definiens_node = @assignment_expr
edge @assignment_expr.pkg_pop -> left_ignore_guard
edge left_ignore_guard -> left_definiens_hook
}
(variable_declaration
(variable_declarator
name:(identifier)@name))@decl {
attr (@name.pop) definiens_node = @decl
}
(lexical_declaration
(variable_declarator
name:(identifier)@name))@decl {
attr (@name.pop) definiens_node = @decl
}
[
(variable_declaration
(variable_declarator
name:(identifier)@name
value: [
(function_expression)
(generator_function)
(arrow_function)
]))
(lexical_declaration
(variable_declarator
name:(identifier)@name
value: [
(function_expression)
(generator_function)
(arrow_function)
]))
(assignment_expression
left: [
(identifier)@name
; (member_expression property:(_)@name) ; FIXME member expressions are references and have no .pop
]
right: [
(function_expression)
(generator_function)
(arrow_function)
])
] {
attr (@name.pop) syntax_type = "function"
}
(
(assignment_expression
left: [
( ; exports.foo = ...
(member_expression
object:(_)@_exports
property:(_)@_property)
(#eq? @_exports "exports")
)
( ; module.exports.foo = ...
(member_expression
object:(member_expression
object:(_)@_module
property:(_)@_exports)
property:(_)@_property)
(#eq? @_module "module")
(#eq? @_exports "exports")
)
]
right: [
(function_expression)
(generator_function)
(arrow_function)
])@assignment_expr
) {
attr (@assignment_expr.pop_name) syntax_type = "function"
attr (@assignment_expr.detour_pop) syntax_type = "function"
attr (@assignment_expr.default_detour_pop) syntax_type = "function"
}
(
(assignment_expression
left: (member_expression
object:(_)@_module
property:(_)@_exports)
right: [
(function_expression)
(generator_function)
(arrow_function)
])@assignment_expr
(#eq? @_module "module")
(#eq? @_exports "exports")
) {
attr (@assignment_expr.pop_default_guard) syntax_type = "function"
attr (@assignment_expr.detour_pop) syntax_type = "function"
attr (@assignment_expr.default_detour_pop) syntax_type = "function"
}
(export_statement "default"
value:[
(function_expression)
(generator_function)
(arrow_function)
])@export_stmt {
attr (@export_stmt.pop_guard_default) syntax_type = "function"
attr (@export_stmt.detour_pop) syntax_type = "function"
attr (@export_stmt.default_detour_pop) syntax_type = "function"
}
(pair
key: (_)@name
value: [
(function_expression)
(generator_function)
(arrow_function)
]) {
attr (@name.definiens_hook) syntax_type = "function"
}
[
(variable_declaration
(variable_declarator
name:(identifier)@name
value: (class)))
(lexical_declaration
(variable_declarator
name:(identifier)@name
value: (class)))
(assignment_expression
left: [
(identifier)@name
; (member_expression property:(_)@name) ; FIXME member expressions are references and have no .pop
]
right: (class))
] {
attr (@name.pop) syntax_type = "class"
}
(
(assignment_expression
left: [
( ; exports.foo = ...
(member_expression
object:(_)@_exports
property:(_)@_property)
(#eq? @_exports "exports")
)
( ; module.exports.foo = ...
(member_expression
object:(member_expression
object:(_)@_module
property:(_)@_exports)
property:(_)@_property)
(#eq? @_module "module")
(#eq? @_exports "exports")
)
]
right: (class))@assignment_expr
) {
attr (@assignment_expr.pop_name) syntax_type = "class"
attr (@assignment_expr.detour_pop) syntax_type = "class"
attr (@assignment_expr.default_detour_pop) syntax_type = "class"
}
(
(assignment_expression
left: (member_expression
object:(_)@_module
property:(_)@_exports)
right: (class))@assignment_expr
(#eq? @_module "module")
(#eq? @_exports "exports")
) {
attr (@assignment_expr.pop_default_guard) syntax_type = "class"
attr (@assignment_expr.detour_pop) syntax_type = "class"
attr (@assignment_expr.default_detour_pop) syntax_type = "class"
}
(export_statement "default"
value:(class))@export_stmt {
attr (@export_stmt.pop_guard_default) syntax_type = "class"
attr (@export_stmt.detour_pop) syntax_type = "class"
attr (@export_stmt.default_detour_pop) syntax_type = "class"
}
(pair
key: (_)@name
value: (class)) {
attr (@name.definiens_hook) syntax_type = "class"
}
(pair
key: (_)@name
value: (_))@pair_expr {
node @name.definiens_hook
node name_ignore_guard
attr (name_ignore_guard) pop_symbol = "GUARD:GANDALF"
attr (@name.definiens_hook) node_definition = @name
attr (@name.definiens_hook) definiens_node = @pair_expr
edge @pair_expr.pkg_pop -> name_ignore_guard
edge name_ignore_guard -> @name.definiens_hook
}