mirror of
https://github.com/github/codeql.git
synced 2025-12-16 16:53:25 +01:00
3483 lines
92 KiB
Plaintext
3483 lines
92 KiB
Plaintext
;;;;;; Part 1: Definining ~all~ most of the nodes
|
||
; This section contains all of the "simple" definitions. All of the places where a single
|
||
; tree-sitter node corresponds to an AST node.
|
||
|
||
; Create the module node first, so it always appears first in the output.
|
||
(module) @mod
|
||
{ let @mod.node = (ast-node @mod "Module") }
|
||
|
||
(_) @anynode
|
||
{
|
||
scan (node-type @anynode) {
|
||
"^(ERROR|MISSING)$" {
|
||
let @anynode.node = (ast-node @anynode "SyntaxErrorNode")
|
||
attr (@anynode.node) source = (source-text @anynode)
|
||
}
|
||
}
|
||
}
|
||
|
||
(parenthesized_expression) @nd
|
||
{ let @nd.node = (ast-node @nd "Expr") }
|
||
|
||
(assignment !type) @assign
|
||
{ let @assign.node = (ast-node @assign "Assign") }
|
||
|
||
[ (expression_list) (tuple) (tuple_pattern) (pattern_list) ] @tuple
|
||
{ let @tuple.node = (ast-node @tuple "Tuple") }
|
||
|
||
(list_pattern) @list
|
||
{ let @list.node = (ast-node @list "List") }
|
||
|
||
(call) @call { let @call.node = (ast-node @call "Call") }
|
||
|
||
(for_statement) @for
|
||
{ let @for.node = (ast-node @for "For") }
|
||
|
||
[ (if_statement) (elif_clause) ] @if
|
||
{ let @if.node = (ast-node @if "If") }
|
||
|
||
(continue_statement) @continue
|
||
{ let @continue.node = (ast-node @continue "Continue") }
|
||
|
||
(break_statement) @break
|
||
{ let @break.node = (ast-node @break "Break") }
|
||
|
||
(pass_statement) @pass
|
||
{ let @pass.node = (ast-node @pass "Pass") }
|
||
|
||
(assert_statement) @assert
|
||
{ let @assert.node = (ast-node @assert "Assert") }
|
||
|
||
(assignment type: (_)) @assign
|
||
{ let @assign.node = (ast-node @assign "AnnAssign") }
|
||
|
||
(augmented_assignment) @assign
|
||
{ let @assign.node = (ast-node @assign "AugAssign") }
|
||
|
||
(delete_statement) @del
|
||
{ let @del.node = (ast-node @del "Delete") }
|
||
|
||
(global_statement) @global
|
||
{ let @global.node = (ast-node @global "Global") }
|
||
|
||
(nonlocal_statement) @nonlocal
|
||
{ let @nonlocal.node = (ast-node @nonlocal "Nonlocal") }
|
||
|
||
[(import_statement) (import_from_statement name: (_))] @import
|
||
{ let @import.node = (ast-node @import "Import") }
|
||
|
||
(import_from_statement (wildcard_import)) @importstar
|
||
{ let @importstar.node = (ast-node @importstar "ImportFrom") }
|
||
|
||
(raise_statement) @raise
|
||
{ let @raise.node = (ast-node @raise "Raise") }
|
||
|
||
(binary_operator) @binop
|
||
{ let @binop.node = (ast-node @binop "BinOp") }
|
||
|
||
(keyword_argument) @kwarg
|
||
{ let @kwarg.node = (ast-node @kwarg "keyword") }
|
||
|
||
[(function_definition) (class_definition) (decorated_definition)] @def
|
||
{ let @def.node = (ast-node @def "Assign") }
|
||
|
||
(decorator) @decorator
|
||
{ let @decorator.node = (ast-node @decorator "Call") }
|
||
|
||
(expression_statement) @stmt
|
||
{ let @stmt.node = (ast-node @stmt "Expr") }
|
||
|
||
[ (integer) (float) ] @num
|
||
{ let @num.node = (ast-node @num "Num") }
|
||
|
||
(identifier) @name
|
||
{ let @name.node = (ast-node @name "Name") }
|
||
|
||
(list) @list
|
||
{ let @list.node = (ast-node @list "List") }
|
||
|
||
[(list_splat) (list_splat_pattern)] @starred
|
||
{ let @starred.node = (ast-node @starred "Starred") }
|
||
|
||
(comment) @comment
|
||
{ let @comment.node = (ast-node @comment "Comment") }
|
||
|
||
[
|
||
(future_import_statement name: (_) @alias)
|
||
(import_from_statement name: (_) @alias)
|
||
(import_statement name: (_) @alias)
|
||
]
|
||
{ let @alias.node = (ast-node @alias "alias") }
|
||
|
||
; A string _without_ interpolations is just a `Str`, _except_ if it's inside a string
|
||
; concatenation, in which case it's a `StringPart`.
|
||
(string !interpolation) @str
|
||
{
|
||
var str_class = "Str"
|
||
if (instance-of (get-parent @str) "concatenated_string") {
|
||
set str_class = "StringPart"
|
||
}
|
||
let @str.node = (ast-node @str str_class)
|
||
}
|
||
|
||
(string interpolation: (_)) @fstring
|
||
{ let @fstring.node = (ast-node @fstring "JoinedStr") }
|
||
|
||
|
||
(string string_content: (_) @part)
|
||
{ let @part.node = (ast-node @part "StringPart") }
|
||
|
||
; A string concatenation that contains no interpolated expressions is just a `Str` (and its children
|
||
; will be `StringPart`s). A string concatenation that contains interpolated expressions is a
|
||
; `JoinedStr`, however.
|
||
(concatenated_string
|
||
(string interpolation: (_))* @interpolations
|
||
) @string
|
||
{
|
||
var string_class = "Str"
|
||
; Check if there are any interpolations in the string.
|
||
; We cannot use an optional match in the above query, since it could match several times,
|
||
; and subsequent definitions of `@string.node` would then fail.
|
||
for _ in @interpolations {
|
||
set string_class = "JoinedStr"
|
||
}
|
||
let @string.node = (ast-node @string string_class)
|
||
}
|
||
|
||
|
||
(string interpolation: (_)) @fstring
|
||
{
|
||
if (not (instance-of (get-parent @fstring) "concatenated_string")) {
|
||
attr (@fstring.node) _fixup = #true
|
||
}
|
||
}
|
||
|
||
(pair) @kvpair
|
||
{ let @kvpair.node = (ast-node @kvpair "KeyValuePair") }
|
||
|
||
(dictionary) @dict
|
||
{ let @dict.node = (ast-node @dict "Dict") }
|
||
|
||
(dictionary_splat) @dictunpacking
|
||
{ let @dictunpacking.node = (ast-node @dictunpacking "DictUnpacking") }
|
||
|
||
(set) @set
|
||
{ let @set.node = (ast-node @set "Set") }
|
||
|
||
(boolean_operator) @boolop
|
||
{ let @boolop.node = (ast-node @boolop "BoolOp") }
|
||
|
||
(comparison_operator) @compop
|
||
{ let @compop.node = (ast-node @compop "Compare") }
|
||
|
||
[ (unary_operator) (not_operator) ] @unaryop
|
||
{ let @unaryop.node = (ast-node @unaryop "UnaryOp") }
|
||
|
||
(exec_statement) @exec
|
||
{ let @exec.node = (ast-node @exec "Exec") }
|
||
|
||
(print_statement) @print
|
||
{ let @print.node = (ast-node @print "Print") }
|
||
|
||
(return_statement) @return
|
||
{ let @return.node = (ast-node @return "Return") }
|
||
|
||
(yield . "from"? @from) @yield
|
||
{
|
||
var yield_node = "Yield"
|
||
if some @from {
|
||
set yield_node = "YieldFrom"
|
||
}
|
||
let @yield.node = (ast-node @yield yield_node)
|
||
}
|
||
|
||
(ellipsis) @ellipsis
|
||
{ let @ellipsis.node = (ast-node @ellipsis "Ellipsis") }
|
||
|
||
(await) @await
|
||
{ let @await.node = (ast-node @await "Await") }
|
||
|
||
(try_statement) @try
|
||
{ let @try.node = (ast-node @try "Try") }
|
||
|
||
(except_clause) @except
|
||
{ let @except.node = (ast-node @except "ExceptStmt") }
|
||
|
||
(except_group_clause) @except
|
||
{ let @except.node = (ast-node @except "ExceptGroupStmt") }
|
||
|
||
(named_expression) @assignexpr
|
||
{ let @assignexpr.node = (ast-node @assignexpr "AssignExpr") }
|
||
|
||
(conditional_expression) @ifexp
|
||
{ let @ifexp.node = (ast-node @ifexp "IfExp") }
|
||
|
||
(subscript) @subscript
|
||
{ let @subscript.node = (ast-node @subscript "Subscript") }
|
||
|
||
(slice) @slice
|
||
{ let @slice.node = (ast-node @slice "Slice") }
|
||
|
||
(attribute) @attribute
|
||
{ let @attribute.node = (ast-node @attribute "Attribute") }
|
||
|
||
(while_statement) @while
|
||
{ let @while.node = (ast-node @while "While") }
|
||
|
||
(generator_expression) @generatorexp
|
||
{ let @generatorexp.node = (ast-node @generatorexp "GeneratorExp") }
|
||
|
||
(for_in_clause) @for
|
||
{ let @for.node = (ast-node @for "For") }
|
||
|
||
(if_clause) @if
|
||
{ let @if.node = (ast-node @if "If") }
|
||
|
||
(list_comprehension) @listcomp
|
||
{ let @listcomp.node = (ast-node @listcomp "ListComp") }
|
||
|
||
(set_comprehension) @setcomp
|
||
{ let @setcomp.node = (ast-node @setcomp "SetComp") }
|
||
|
||
(dictionary_comprehension) @dictcomp
|
||
{ let @dictcomp.node = (ast-node @dictcomp "DictComp") }
|
||
|
||
[ (with_statement) (with_item)] @with
|
||
{ let @with.node = (ast-node @with "With") }
|
||
|
||
(match_statement) @match
|
||
{ let @match.node = (ast-node @match "Match") }
|
||
|
||
; Do not create an AST node for 'cases', we just wire up the children instead.
|
||
|
||
(case_block) @case
|
||
{ let @case.node = (ast-node @case "Case") }
|
||
|
||
(match_as_pattern) @pattern
|
||
{ let @pattern.node = (ast-node @pattern "MatchAsPattern") }
|
||
|
||
(match_or_pattern) @pattern
|
||
{ let @pattern.node = (ast-node @pattern "MatchOrPattern") }
|
||
|
||
(match_literal_pattern) @pattern
|
||
{ let @pattern.node = (ast-node @pattern "MatchLiteralPattern") }
|
||
|
||
(match_capture_pattern) @pattern
|
||
{ let @pattern.node = (ast-node @pattern "MatchCapturePattern") }
|
||
|
||
(match_wildcard_pattern) @pattern
|
||
{ let @pattern.node = (ast-node @pattern "MatchWildcardPattern") }
|
||
|
||
(match_value_pattern) @pattern
|
||
{ let @pattern.node = (ast-node @pattern "MatchValuePattern") }
|
||
|
||
(match_group_pattern) @pattern
|
||
{ let @pattern.node = (ast-node @pattern "MatchGroupPattern") }
|
||
|
||
(match_sequence_pattern) @pattern
|
||
{ let @pattern.node = (ast-node @pattern "MatchSequencePattern") }
|
||
|
||
(match_star_pattern) @pattern
|
||
{ let @pattern.node = (ast-node @pattern "MatchStarPattern") }
|
||
|
||
(match_mapping_pattern) @pattern
|
||
{ let @pattern.node = (ast-node @pattern "MatchMappingPattern") }
|
||
|
||
(match_double_star_pattern) @pattern
|
||
{ let @pattern.node = (ast-node @pattern "MatchDoubleStarPattern") }
|
||
|
||
(match_key_value_pattern) @pattern
|
||
{ let @pattern.node = (ast-node @pattern "MatchKeyValuePattern") }
|
||
|
||
(match_class_pattern) @pattern
|
||
{ let @pattern.node = (ast-node @pattern "MatchClassPattern") }
|
||
|
||
; Do not create AST nodes for 'only_positionals', 'only_keywords',
|
||
; 'partly_positionals', and 'partly_keywords'. We just wire up the children instead.
|
||
|
||
(match_keyword_pattern) @pattern
|
||
{ let @pattern.node = (ast-node @pattern "MatchKeywordPattern") }
|
||
|
||
(guard) @guard
|
||
{ let @guard.node = (ast-node @guard "Guard") }
|
||
|
||
[(parameters) (lambda_parameters)] @params
|
||
{ let @params.node = (ast-node @params "arguments") }
|
||
|
||
[(false) (true) (none)] @const
|
||
{ let @const.node = (ast-node @const "Name") }
|
||
|
||
(lambda) @lambda
|
||
{ let @lambda.node = (ast-node @lambda "Lambda") }
|
||
|
||
(future_import_statement) @import
|
||
{ let @import.node = (ast-node @import "Import") }
|
||
|
||
(typevar_parameter) @typevar
|
||
{ let @typevar.node = (ast-node @typevar "TypeVar") }
|
||
|
||
(typevartuple_parameter) @typevartuple
|
||
{ let @typevartuple.node = (ast-node @typevartuple "TypeVarTuple") }
|
||
|
||
(paramspec_parameter) @paramspec
|
||
{ let @paramspec.node = (ast-node @paramspec "ParamSpec") }
|
||
|
||
(type_alias_statement) @typealias
|
||
{ let @typealias.node = (ast-node @typealias "TypeAlias") }
|
||
|
||
;;;;;; End of part 1.
|
||
|
||
;;;;;; Part 2: The awkward bunch.
|
||
|
||
;;;;;; Workarounds for node locations
|
||
; These are (hopefully temporary) workarounds for the nodes for which the default start and end does
|
||
; not agree with what our internal AST provides.
|
||
; Once the new parser is in place, we can consider getting rid of these workarounds.
|
||
|
||
|
||
;;; If
|
||
; End position is set to the end of the `:` after the condition.
|
||
[
|
||
(if_statement
|
||
condition: (_)
|
||
.
|
||
":" @colon) @if
|
||
(elif_clause
|
||
condition: (_)
|
||
.
|
||
":" @colon) @if
|
||
]
|
||
{
|
||
attr (@if.node) _location_end = (location-end @colon)
|
||
}
|
||
|
||
;;; For
|
||
; Same as with `if`, we must include the `:` in the position.
|
||
(for_statement
|
||
right: (_)
|
||
.
|
||
":" @colon
|
||
) @for
|
||
{
|
||
attr (@for.node) _location_end = (location-end @colon)
|
||
}
|
||
|
||
;;; While
|
||
; Same as with `if`, we must include the `:` in the position.
|
||
(while_statement
|
||
condition: (_)
|
||
.
|
||
":" @colon
|
||
) @while
|
||
{
|
||
attr (@while.node) _location_end = (location-end @colon)
|
||
}
|
||
|
||
;;; Tuples
|
||
; In the Python AST tuple start and end positions are set to the start and end of the first and last
|
||
; elements. In `tree-sitter-python`, the parentheses are included.
|
||
[
|
||
(tuple . (comment)* . element: (_) @first)
|
||
(tuple_pattern . (comment)* . element: (_) @first)
|
||
] @tuple
|
||
{
|
||
attr (@tuple.node) _location_start = (location-start @first)
|
||
}
|
||
|
||
[
|
||
(tuple !trailing_comma element: (_) @last . (comment)* . ")" .)
|
||
(tuple trailing_comma: _ @last)
|
||
(tuple_pattern element: (_) @last .)
|
||
] @tuple
|
||
{
|
||
|
||
attr (@tuple.node) _location_end = (location-end @last)
|
||
}
|
||
|
||
;;; Try
|
||
|
||
(try_statement ":" @colon) @try
|
||
{ attr (@try.node) _location_end = (location-end @colon) }
|
||
|
||
(except_clause ":" @colon) @except
|
||
{ attr (@except.node) _location_end = (location-end @colon) }
|
||
|
||
;;; GeneratorExp
|
||
|
||
(generator_expression . "(" . (comment)* . (_) @start (_) @end . (comment)* . ")" .) @generatorexp
|
||
{
|
||
attr (@generatorexp.node) _location_start = (location-start @start)
|
||
attr (@generatorexp.node) _location_end = (location-end @end)
|
||
}
|
||
|
||
(if_clause (expression) @expr) @if
|
||
{
|
||
attr (@if.node) _location_start = (location-start @expr)
|
||
attr (@if.node) _location_end = (location-end @expr)
|
||
}
|
||
|
||
(generator_expression . "(" . (comment)* . (_) @start (for_in_clause) @child (_) @end . (comment)* . ")" .) @genexpr
|
||
{
|
||
attr (@child.node) _location_start = (location-start @start)
|
||
attr (@child.node) _location_end = (location-end @end)
|
||
}
|
||
|
||
(generator_expression . "(" . (comment)* . (_) @start (for_in_clause) @end . (comment)* . ")" .) @genexpr
|
||
{
|
||
attr (@end.node) _location_start = (location-start @start)
|
||
attr (@end.node) _location_end = (location-end @end)
|
||
}
|
||
|
||
(list_comprehension (for_in_clause) @child) @genexpr
|
||
{
|
||
attr (@child.node) _location_start = (location-start @genexpr)
|
||
attr (@child.node) _location_end = (location-end @genexpr)
|
||
}
|
||
|
||
(set_comprehension (for_in_clause) @child) @genexpr
|
||
{
|
||
attr (@child.node) _location_start = (location-start @genexpr)
|
||
attr (@child.node) _location_end = (location-end @genexpr)
|
||
}
|
||
|
||
(dictionary_comprehension (for_in_clause) @child) @genexpr
|
||
{
|
||
attr (@child.node) _location_start = (location-start @genexpr)
|
||
attr (@child.node) _location_end = (location-end @genexpr)
|
||
}
|
||
|
||
;;; With
|
||
|
||
(with_statement
|
||
"with" @start
|
||
(with_clause . (with_item) @first)
|
||
":" @end
|
||
)
|
||
{
|
||
attr (@first.node) _location_start = (location-start @start)
|
||
attr (@first.node) _location_end = (location-end @end)
|
||
}
|
||
|
||
;;;;;; End of workarounds
|
||
|
||
;;;;;; End of part 2
|
||
|
||
;;;;;; Part 3: All of the simple nodes.
|
||
|
||
;;;;;; Module
|
||
|
||
; Nodes with a `body` field containing statements.
|
||
(module (_) @stmt) @parent
|
||
{
|
||
edge @parent.node -> @stmt.node
|
||
attr (@parent.node -> @stmt.node) body = (named-child-index @stmt)
|
||
}
|
||
|
||
;;;;;; Comments
|
||
|
||
(comment) @comment
|
||
{
|
||
attr (@comment.node) text = (source-text @comment)
|
||
}
|
||
|
||
;;;;;; Expressions
|
||
|
||
(parenthesized_expression
|
||
inner: (_) @inner
|
||
) @outer
|
||
{
|
||
attr (@outer.node) _skip_to = @inner.node
|
||
attr (@inner.node) parenthesised = #true
|
||
}
|
||
|
||
(keyword_argument
|
||
name: (_) @name
|
||
value: (_) @value
|
||
) @kwarg
|
||
{
|
||
attr (@kwarg.node) arg = (source-text @name)
|
||
attr (@kwarg.node) value = @value.node
|
||
}
|
||
|
||
;;;;;; Num
|
||
|
||
[ (integer) (float) ] @num
|
||
{
|
||
; As we must support a large variety of number literals, we simply forward the source string
|
||
; representation to the Python AST reconstruction.
|
||
let source = (source-text @num)
|
||
attr (@num.node) n = source
|
||
attr (@num.node) text = source
|
||
}
|
||
|
||
;;;;;; End of Num
|
||
|
||
;;;;;; Delete
|
||
|
||
(delete_statement
|
||
target: (expression_list
|
||
element: (_) @target
|
||
)
|
||
) @del
|
||
{
|
||
edge @del.node -> @target.node
|
||
attr (@del.node -> @target.node) targets = (named-child-index @target)
|
||
}
|
||
|
||
(delete_statement target: (_) @target) @del
|
||
{
|
||
attr (@target.node) ctx = "del"
|
||
}
|
||
|
||
(delete_statement [(identifier) (subscript) (attribute)] @id) @del
|
||
{
|
||
edge @del.node -> @id.node
|
||
attr (@del.node -> @id.node) targets = 0
|
||
}
|
||
|
||
;;;;;; Name
|
||
|
||
[(identifier) (false) (true) (none)] @id
|
||
{
|
||
attr (@id.node) variable = (source-text @id)
|
||
}
|
||
|
||
;;;;;; End of Name
|
||
|
||
;;;;;; Arguments
|
||
[
|
||
(keyword_argument value: (_) @id)
|
||
(argument_list element: (_) @id)
|
||
]
|
||
{
|
||
attr (@id.node) ctx = "load"
|
||
}
|
||
|
||
[
|
||
(keyword_argument name: (_) @id)
|
||
]
|
||
{
|
||
attr (@id.node) ctx = "store"
|
||
}
|
||
|
||
;;;;;; End of Arguments
|
||
|
||
;;;;;; BinOp
|
||
|
||
(binary_operator
|
||
left: (_) @left
|
||
operator: _ @op
|
||
right: (_) @right
|
||
) @bin
|
||
{
|
||
attr (@bin.node) left = @left.node
|
||
attr (@bin.node) right = @right.node
|
||
attr (@bin.node) op = (source-text @op)
|
||
attr (@left.node) ctx = "load"
|
||
attr (@right.node) ctx = "load"
|
||
}
|
||
|
||
;;;;;; End of BinOp
|
||
|
||
;;;;;; If
|
||
|
||
; If.test
|
||
[
|
||
(if_statement
|
||
condition: (_) @test) @if
|
||
(elif_clause
|
||
condition: (_) @test) @if
|
||
]
|
||
{
|
||
attr (@if.node) test = @test.node
|
||
attr (@test.node) ctx = "load"
|
||
}
|
||
|
||
; If.orelse - first `elif` clause
|
||
(if_statement
|
||
consequence: (_)
|
||
. (comment)* .
|
||
(elif_clause) @elif
|
||
) @if
|
||
{
|
||
edge @if.node -> @elif.node
|
||
attr (@if.node -> @elif.node) orelse = 0
|
||
}
|
||
|
||
; If.orelse - link up adjacent `elif` clauses
|
||
(
|
||
(elif_clause) @elif1
|
||
. (comment)* .
|
||
(elif_clause) @elif2
|
||
)
|
||
{
|
||
edge @elif1.node -> @elif2.node
|
||
attr (@elif1.node -> @elif2.node) orelse = 0
|
||
}
|
||
|
||
; If.orelse - match outer `else` up with last `elif` clause (i.e. innermost `if`)
|
||
(if_statement
|
||
(elif_clause) @elif . (comment)* .
|
||
alternative: (else_clause body: (block (_) @orelse))
|
||
)
|
||
{
|
||
edge @elif.node -> @orelse.node
|
||
attr (@elif.node -> @orelse.node) orelse = (named-child-index @orelse)
|
||
}
|
||
|
||
; If.orelse - when there are no `elif` clauses.
|
||
(if_statement
|
||
consequence: (_)
|
||
. (comment)* .
|
||
alternative: (else_clause body: (block (_) @orelse))
|
||
) @if
|
||
{
|
||
edge @if.node -> @orelse.node
|
||
attr (@if.node -> @orelse.node) orelse = (named-child-index @orelse)
|
||
}
|
||
|
||
; If.body
|
||
|
||
[
|
||
(if_statement
|
||
consequence: (block (_) @stmt)) @parent
|
||
(elif_clause
|
||
consequence: (block (_) @stmt)) @parent
|
||
]
|
||
{
|
||
edge @parent.node -> @stmt.node
|
||
attr (@parent.node -> @stmt.node) body = (named-child-index @stmt)
|
||
}
|
||
|
||
;;;;;; end of If
|
||
|
||
;;;;;; For statements
|
||
|
||
(for_statement
|
||
left: (_) @left
|
||
right: (_) @right
|
||
) @for
|
||
{
|
||
attr (@for.node) target = @left.node
|
||
attr (@left.node) ctx = "store"
|
||
attr (@for.node) iter = @right.node
|
||
attr (@right.node) ctx = "load"
|
||
}
|
||
|
||
(for_statement
|
||
body: (block (_) @body)
|
||
) @for
|
||
{
|
||
edge @for.node -> @body.node
|
||
attr (@for.node -> @body.node) body = (named-child-index @body)
|
||
}
|
||
|
||
(for_statement
|
||
alternative: (else_clause body: (block (_) @orelse))
|
||
) @for
|
||
{
|
||
edge @for.node -> @orelse.node
|
||
attr (@for.node -> @orelse.node) orelse = (named-child-index @orelse)
|
||
}
|
||
|
||
(for_statement "async" "for" @for_keyword) @for
|
||
{
|
||
attr (@for.node) is_async = #true
|
||
attr (@for.node) _location_start = (location-start @for_keyword)
|
||
}
|
||
|
||
;;;;;; end of For
|
||
|
||
;;;;;; Call expressions (`a(b, c, *d, **e)`)
|
||
|
||
(call function: (_) @func) @call
|
||
{
|
||
attr (@call.node) func = @func.node
|
||
attr (@func.node) ctx = "load"
|
||
}
|
||
|
||
; Handle non-keyword arguments
|
||
(call arguments: (argument_list element: (_) @arg)) @call
|
||
{
|
||
if (not (or
|
||
(instance-of @arg "keyword_argument")
|
||
(instance-of @arg "dictionary_splat"))) {
|
||
edge @call.node -> @arg.node
|
||
attr (@call.node -> @arg.node) positional_args = (named-child-index @arg)
|
||
}
|
||
}
|
||
|
||
(call arguments: (argument_list element: (keyword_argument) @arg)) @call
|
||
{
|
||
edge @call.node -> @arg.node
|
||
attr (@call.node -> @arg.node) named_args = (named-child-index @arg)
|
||
}
|
||
|
||
(call arguments: (argument_list element: (dictionary_splat) @arg)) @call
|
||
{
|
||
edge @call.node -> @arg.node
|
||
attr (@call.node -> @arg.node) named_args = (named-child-index @arg)
|
||
}
|
||
|
||
(call arguments: (generator_expression) @gen) @call
|
||
{
|
||
edge @call.node -> @gen.node
|
||
attr (@call.node -> @gen.node) positional_args = 0
|
||
}
|
||
|
||
|
||
;;;;;; end of Call (`a(b, c, *d, **e)`)
|
||
|
||
|
||
;;;;;; End of part 3
|
||
|
||
;;;;;; Part 4: All of the complicated bits (e.g. nodes that need additional synthesis)
|
||
|
||
|
||
;;;;;; ListComp (`[a for b in c if d]`)
|
||
; See GeneratorExp for details.
|
||
|
||
(list_comprehension) @genexpr
|
||
{
|
||
; Synthesize the `genexpr` function
|
||
let @genexpr.fun = (ast-node @genexpr "Function")
|
||
attr (@genexpr.node) function = @genexpr.fun
|
||
attr (@genexpr.fun) name = "listcomp"
|
||
|
||
; Synthesize the `.0` parameter
|
||
let @genexpr.arg = (ast-node @genexpr "Name")
|
||
attr (@genexpr.arg) variable = ".0"
|
||
attr (@genexpr.arg) ctx = "param"
|
||
|
||
edge @genexpr.fun -> @genexpr.arg
|
||
attr (@genexpr.fun -> @genexpr.arg) args = 0
|
||
attr (@genexpr.fun) kwonlyargs = #null
|
||
attr (@genexpr.fun) kwarg = #null
|
||
|
||
; Synthesize the use of `.0` in the outermost `for`. This has a different context than the parameter
|
||
; ("param" vs. "load") hence we must create another node.
|
||
let @genexpr.arg_use = (ast-node @genexpr "Name")
|
||
attr (@genexpr.arg_use) variable = ".0"
|
||
attr (@genexpr.arg_use) ctx = "load"
|
||
}
|
||
|
||
;;;;;; End of ListComp (`[a for b in c if d]`)
|
||
|
||
;;;;;; SetComp (`{a for b in c if d}`)
|
||
; See GeneratorExp for details.
|
||
|
||
(set_comprehension) @genexpr
|
||
{
|
||
; Synthesize the `genexpr` function
|
||
let @genexpr.fun = (ast-node @genexpr "Function")
|
||
attr (@genexpr.node) function = @genexpr.fun
|
||
attr (@genexpr.fun) name = "setcomp"
|
||
|
||
; Synthesize the `.0` parameter
|
||
let @genexpr.arg = (ast-node @genexpr "Name")
|
||
attr (@genexpr.arg) variable = ".0"
|
||
attr (@genexpr.arg) ctx = "param"
|
||
|
||
edge @genexpr.fun -> @genexpr.arg
|
||
attr (@genexpr.fun -> @genexpr.arg) args = 0
|
||
attr (@genexpr.fun) kwonlyargs = #null
|
||
attr (@genexpr.fun) kwarg = #null
|
||
|
||
; Synthesize the use of `.0` in the outermost `for`. This has a different context than the parameter
|
||
; ("param" vs. "load") hence we must create another node.
|
||
let @genexpr.arg_use = (ast-node @genexpr "Name")
|
||
attr (@genexpr.arg_use) variable = ".0"
|
||
attr (@genexpr.arg_use) ctx = "load"
|
||
}
|
||
|
||
|
||
;;;;;; End of SetComp (`{a for b in c if d}`)
|
||
|
||
;;;;;; DictComp (`{a: b for c in d if e}`)
|
||
; See GeneratorExp for details.
|
||
|
||
(dictionary_comprehension
|
||
body: (pair
|
||
key: (_) @key
|
||
value: (_) @value
|
||
)
|
||
) @genexpr
|
||
{
|
||
; Synthesize the `genexpr` function
|
||
let @genexpr.fun = (ast-node @genexpr "Function")
|
||
attr (@genexpr.node) function = @genexpr.fun
|
||
attr (@genexpr.fun) name = "dictcomp"
|
||
|
||
; Synthesize the `.0` parameter
|
||
let @genexpr.arg = (ast-node @genexpr "Name")
|
||
attr (@genexpr.arg) variable = ".0"
|
||
attr (@genexpr.arg) ctx = "param"
|
||
|
||
edge @genexpr.fun -> @genexpr.arg
|
||
attr (@genexpr.fun -> @genexpr.arg) args = 0
|
||
attr (@genexpr.fun) kwonlyargs = #null
|
||
attr (@genexpr.fun) kwarg = #null
|
||
|
||
; Synthesize the use of `.0` in the innermost `yield`. This has a different context than the parameter
|
||
; ("param" vs. "load") hence we must create another node.
|
||
let @genexpr.arg_use = (ast-node @genexpr "Name")
|
||
attr (@genexpr.arg_use) variable = ".0"
|
||
attr (@genexpr.arg_use) ctx = "load"
|
||
}
|
||
|
||
;;;;;; End of DictComp (`{a: b for c in d if e}`)
|
||
|
||
;;;;;; GeneratorExp (`(a for b in c if d)`)
|
||
; The big one. This one will require quite a bit of setup.
|
||
;
|
||
; First of all, we need to explain what the old parser does to generator expressions.
|
||
;
|
||
; The following generator expression
|
||
;
|
||
; (a
|
||
; for b in c
|
||
; if d
|
||
; if e
|
||
; for f in g
|
||
; if h
|
||
; if i
|
||
; )
|
||
;
|
||
; becomes
|
||
;
|
||
; def genexpr(.0):
|
||
; for b in .0:
|
||
; if e:
|
||
; if d:
|
||
; for f in g:
|
||
; if i:
|
||
; if h:
|
||
; yield a
|
||
;
|
||
; where `.0` is a (very oddly named) variable.
|
||
;
|
||
; Note in particular the reversing of the `if`s, the way `c` is replaced with `.0`, and the way
|
||
; `a` is used in the innermost `yield`.
|
||
|
||
; First of all, we need to set up the generated function and its parameter. These both copy the location
|
||
; information for the entire generator expression (yes, it is a wide parameter!) and so we must recreate the logic for
|
||
; setting this location information correctly.
|
||
|
||
(generator_expression . "(" . (comment)* . (_) @start (_) @end . (comment)* . ")" .) @genexpr
|
||
{
|
||
; Synthesize the `genexpr` function
|
||
let @genexpr.fun = (ast-node @genexpr "Function")
|
||
attr (@genexpr.fun) _location_start = (location-start @start)
|
||
attr (@genexpr.fun) _location_end = (location-end @end)
|
||
attr (@genexpr.node) function = @genexpr.fun
|
||
attr (@genexpr.fun) name = "genexpr"
|
||
|
||
; Synthesize the `.0` parameter
|
||
let @genexpr.arg = (ast-node @genexpr "Name")
|
||
attr (@genexpr.arg) _location_start = (location-start @start)
|
||
attr (@genexpr.arg) _location_end = (location-end @end)
|
||
attr (@genexpr.arg) variable = ".0"
|
||
attr (@genexpr.arg) ctx = "param"
|
||
|
||
edge @genexpr.fun -> @genexpr.arg
|
||
attr (@genexpr.fun -> @genexpr.arg) args = 0
|
||
attr (@genexpr.fun) kwonlyargs = #null
|
||
attr (@genexpr.fun) kwarg = #null
|
||
|
||
; Default to true, but we'll set it to false if we're inside a call
|
||
var genexpr_parenthesised = #true
|
||
|
||
if (instance-of (get-parent @genexpr) "call") {
|
||
set genexpr_parenthesised = #null
|
||
}
|
||
attr (@genexpr.node) parenthesised = genexpr_parenthesised
|
||
|
||
; Synthesize the use of `.0` in the outermost `for`. This has a different context than the parameter
|
||
; ("param" vs. "load") hence we must create another node.
|
||
let @genexpr.arg_use = (ast-node @genexpr "Name")
|
||
attr (@genexpr.arg_use) _location_start = (location-start @start)
|
||
attr (@genexpr.arg_use) _location_end = (location-end @end)
|
||
attr (@genexpr.arg_use) variable = ".0"
|
||
attr (@genexpr.arg_use) ctx = "load"
|
||
}
|
||
|
||
; Link up the outermost `for`
|
||
[
|
||
(generator_expression
|
||
body: (_) . (comment)* .
|
||
(for_in_clause
|
||
left: (_) @target
|
||
right: (_) @iterable
|
||
) @forin
|
||
) @genexpr
|
||
(list_comprehension
|
||
body: (_) . (comment)* .
|
||
(for_in_clause
|
||
left: (_) @target
|
||
right: (_) @iterable
|
||
) @forin
|
||
) @genexpr
|
||
(set_comprehension
|
||
body: (_) . (comment)* .
|
||
(for_in_clause
|
||
left: (_) @target
|
||
right: (_) @iterable
|
||
) @forin
|
||
) @genexpr
|
||
(dictionary_comprehension
|
||
body: (_) . (comment)* .
|
||
(for_in_clause
|
||
left: (_) @target
|
||
right: (_) @iterable
|
||
) @forin
|
||
) @genexpr
|
||
]
|
||
{
|
||
attr (@genexpr.node) iterable = @iterable.node
|
||
attr (@iterable.node) ctx = "load"
|
||
edge @genexpr.fun -> @forin.node
|
||
attr (@genexpr.fun -> @forin.node) body = 0
|
||
|
||
attr (@forin.node) target = @target.node
|
||
attr (@target.node) ctx = "store"
|
||
attr (@forin.node) iter = @genexpr.arg_use
|
||
}
|
||
|
||
; Set up all subsequent `for ... in ...`
|
||
[
|
||
(generator_expression
|
||
body: (_)
|
||
[(for_in_clause) (if_clause)]
|
||
(for_in_clause left: (_) @target right: (_) @iter) @forin
|
||
)
|
||
(list_comprehension
|
||
body: (_)
|
||
[(for_in_clause) (if_clause)]
|
||
(for_in_clause left: (_) @target right: (_) @iter) @forin
|
||
)
|
||
(set_comprehension
|
||
body: (_)
|
||
[(for_in_clause) (if_clause)]
|
||
(for_in_clause left: (_) @target right: (_) @iter) @forin
|
||
)
|
||
(dictionary_comprehension
|
||
body: (_)
|
||
[(for_in_clause) (if_clause)]
|
||
(for_in_clause left: (_) @target right: (_) @iter) @forin
|
||
)
|
||
]
|
||
{
|
||
attr (@forin.node) target = @target.node
|
||
attr (@target.node) ctx = "store"
|
||
attr (@forin.node) iter = @iter.node
|
||
attr (@iter.node) ctx = "load"
|
||
}
|
||
|
||
; Set up each `if ...`
|
||
(if_clause (expression) @test) @if
|
||
{
|
||
attr (@if.node) test = @test.node
|
||
attr (@test.node) ctx = "load"
|
||
}
|
||
|
||
; Link adjacent `for` clauses together
|
||
(_
|
||
(for_in_clause) @forin1
|
||
. (comment)* .
|
||
(for_in_clause) @forin2
|
||
)
|
||
{
|
||
edge @forin1.node -> @forin2.node
|
||
attr (@forin1.node -> @forin2.node) body = 0
|
||
}
|
||
|
||
; For the first `if` clause after a `for` clause, record both the `for` and `if` clauses in variables that we
|
||
; will propagate along. That way, when we get to the last `if` clause, we can link it up with the `for`
|
||
; clause, and we can link up the _first_ `if` clause with whatever follows the last `if` clause.
|
||
(_
|
||
(for_in_clause) @forin
|
||
. (comment)* .
|
||
(if_clause) @if
|
||
)
|
||
{
|
||
let @if.for = @forin.node
|
||
let @if.first_if = @if.node
|
||
}
|
||
|
||
; Link up adjacent `if` clauses (note the reversed order!) and propagate the `for` and `first_if` values.
|
||
(_
|
||
(if_clause) @if1
|
||
. (comment)* .
|
||
(if_clause) @if2
|
||
)
|
||
{
|
||
edge @if2.node -> @if1.node
|
||
attr (@if2.node -> @if1.node) body = 0
|
||
let @if2.for = @if1.for
|
||
let @if2.first_if = @if1.first_if
|
||
}
|
||
|
||
; After the last `if` in a chain, we hook it up as the body of its associated `for`, and hook up the _first_
|
||
; `if` as the one that has the following `for` as its body.
|
||
; The case where there is no `for` following the last `if` is handled later.
|
||
(_
|
||
(if_clause) @if
|
||
. (comment)* .
|
||
(for_in_clause) @forin
|
||
)
|
||
{
|
||
edge @if.for -> @if.node
|
||
attr (@if.for -> @if.node) body = 0
|
||
edge @if.first_if -> @forin.node
|
||
attr (@if.first_if -> @forin.node) body = 0
|
||
}
|
||
|
||
; For everything except dictionary comprehensions, the innermost expression is just the `body` of the
|
||
; comprehension.
|
||
[
|
||
(generator_expression body: (_) @body) @genexpr
|
||
(list_comprehension body: (_) @body) @genexpr
|
||
(set_comprehension body: (_) @body) @genexpr
|
||
]
|
||
{
|
||
let @genexpr.result = @body.node
|
||
}
|
||
|
||
; For dict comprehensions, we build an explicit tuple using the key and value pair.
|
||
(dictionary_comprehension
|
||
body: (pair
|
||
key: (_) @key
|
||
value: (_) @value
|
||
) @body
|
||
) @genexpr
|
||
{
|
||
let tuple = (ast-node @body "Tuple")
|
||
edge tuple -> @key.node
|
||
attr (tuple -> @key.node) elts = 1
|
||
edge tuple -> @value.node
|
||
attr (tuple -> @value.node) elts = 0
|
||
; TODO verify that it is correct to use a `(value, key)` tuple, and not a `(key, value)` tuple above.
|
||
; That is what the current parser does...
|
||
attr (tuple) ctx = "load"
|
||
let @genexpr.result = tuple
|
||
}
|
||
|
||
; For the final clause, we need to hook it up with the rest of the expression.
|
||
; If it's an `if` clause, we need to hook it up with the `yield` expression and with its associated
|
||
; `for` clause.
|
||
; If it's a `for` clause, we only need to create and hook it up with the `yield` expression.
|
||
;
|
||
; It would be tempting to use anchors here, but they just don't work. In particular, an anchor of
|
||
; the form `. (comment)* . )` (which would be needed in order to handle the case where there are
|
||
; comments after the last clause) cause the `tree-sitter` query engine to match _all_ clauses, not
|
||
; just the last one.
|
||
; Instead, we gather up all clauses in a list (these will be in the order they appear in the source
|
||
; code), and extract the last element using a custom Rust function.
|
||
[
|
||
(generator_expression
|
||
body: (_) @body
|
||
[(if_clause) (for_in_clause)]+ @last_candidates
|
||
) @genexpr
|
||
(list_comprehension
|
||
body: (_) @body
|
||
[(if_clause) (for_in_clause)]+ @last_candidates
|
||
) @genexpr
|
||
(set_comprehension
|
||
body: (_) @body
|
||
[(if_clause) (for_in_clause)]+ @last_candidates
|
||
) @genexpr
|
||
(dictionary_comprehension
|
||
body: (_) @body
|
||
[(if_clause) (for_in_clause)]+ @last_candidates
|
||
) @genexpr
|
||
]
|
||
{
|
||
let last = (get-last-element @last_candidates)
|
||
|
||
let expr = (ast-node @body "Expr")
|
||
let yield = (ast-node @body "Yield")
|
||
|
||
let @genexpr.expr = expr
|
||
let @genexpr.yield = yield
|
||
|
||
attr (expr) value = yield
|
||
|
||
attr (yield) value = @genexpr.result
|
||
attr (@body.node) ctx = "load"
|
||
|
||
if (instance-of last "if_clause") {
|
||
edge last.first_if -> expr
|
||
attr (last.first_if -> expr) body = 0
|
||
|
||
; Hook up this `if` clause with its `for` clause
|
||
edge last.for -> last.node
|
||
attr (last.for -> last.node) body = 0
|
||
} else {
|
||
; If the last clause is a `for`, we only have to create and hook up the `yield` expression.
|
||
edge last.node -> expr
|
||
attr (last.node -> expr) body = 0
|
||
}
|
||
}
|
||
|
||
; For whatever reason, we do not consider parentheses around the yielded expression if they are present, so
|
||
; we must adapt the location accordingly.
|
||
[
|
||
(generator_expression
|
||
body: (_ . "(" . _ @first)
|
||
)
|
||
(list_comprehension
|
||
body: (_ . "(" . _ @first)
|
||
)
|
||
(set_comprehension
|
||
body: (_ . "(" . _ @first)
|
||
)
|
||
(dictionary_comprehension
|
||
body: (_ . "(" . _ @first)
|
||
)
|
||
] @genexpr
|
||
{
|
||
attr (@genexpr.expr) _location_start = (location-start @first)
|
||
attr (@genexpr.yield) _location_start = (location-start @first)
|
||
}
|
||
|
||
; Annoyingly, setting the end location of the synthesized `Expr` and `Yield` is a big mess,
|
||
; so we have to use mutable variables.
|
||
[
|
||
(generator_expression body: (_) @body)
|
||
(list_comprehension body: (_) @body)
|
||
(set_comprehension body: (_) @body)
|
||
(dictionary_comprehension body: (_) @body)
|
||
] @genexpr
|
||
{
|
||
var @genexpr.body_end = (location-end @body)
|
||
}
|
||
|
||
|
||
; The reason we need to do this mutably is because the query `(_ _ @last . ")" .)`, despite the liberal use
|
||
; of anchors, is broken (due to a bug in `tree-sitter`). Specifically, it will match both `b` and the
|
||
; following `,` in the tuple expression `(a, b,)`. This means we cannot set the attribute in this stanza
|
||
; (since overwriting attributes is not allowed) and so we instead write it to a mutable variable and set it
|
||
; later. Because the order in which the captures are returned results in `b` being matched before `,` this
|
||
; gives the correct behaviour.
|
||
[
|
||
(generator_expression
|
||
body: (_ _ @last . ")" .)
|
||
)
|
||
(list_comprehension
|
||
body: (_ _ @last . ")" .)
|
||
)
|
||
(set_comprehension
|
||
body: (_ _ @last . ")" .)
|
||
)
|
||
(dictionary_comprehension
|
||
body: (_ _ @last . ")" .)
|
||
)
|
||
] @genexpr
|
||
{
|
||
set @genexpr.body_end = (location-end @last)
|
||
}
|
||
|
||
[
|
||
(generator_expression)
|
||
(list_comprehension)
|
||
(set_comprehension)
|
||
(dictionary_comprehension)
|
||
] @genexpr
|
||
{
|
||
attr (@genexpr.expr) _location_end = @genexpr.body_end
|
||
attr (@genexpr.yield) _location_end = @genexpr.body_end
|
||
}
|
||
|
||
|
||
;;;;;; End of GeneratorExp (`(a for b in c if d)`)
|
||
|
||
|
||
|
||
|
||
;;;;;; Class statements
|
||
; A class definition
|
||
;
|
||
; class Foo(*bases, **keywords): body
|
||
;
|
||
; is turned into an actual assignment statement, with the class name as the left-hand side.
|
||
;
|
||
; Foo = $classexpr(name='Foo', bases, keywords, inner_scope=$class(name='Foo', body))
|
||
;
|
||
; (with a suitably magical definition of the `$` prefix).
|
||
;
|
||
; So we have to synthesize both the outer assignment, and also the two representatives of the class.
|
||
|
||
(class_definition
|
||
name: (identifier) @name
|
||
":" @colon
|
||
) @class
|
||
{
|
||
|
||
; To make it clearer that the outer node is an assignment, we create an alias for it.
|
||
let @class.assign = @class.node
|
||
|
||
; We reuse the identifier as the left hand side of the assignment.
|
||
let @class.assign_lhs = @name.node
|
||
|
||
; Synthesized nodes: the class_expr node, and the class node.
|
||
|
||
let @class.class_expr = (ast-node @class "ClassExpr")
|
||
let @class.inner_scope = (ast-node @class "Class")
|
||
|
||
; Setting up the outer assignment
|
||
edge @class.assign -> @class.assign_lhs
|
||
attr (@class.assign -> @class.assign_lhs) targets = 0
|
||
attr (@class.assign) value = @class.class_expr
|
||
attr (@class.assign) _location_end = (location-end @colon)
|
||
|
||
attr (@class.assign_lhs) ctx = "store"
|
||
|
||
let class_name = (source-text @name)
|
||
|
||
; The right hand side of the assignment, a `ClassExpr`.
|
||
attr (@class.class_expr) name = class_name
|
||
attr (@class.class_expr) inner_scope = @class.inner_scope
|
||
; `bases` will be set elsewhere
|
||
; `keywords` will be set elsewhere
|
||
attr (@class.class_expr) _location_end = (location-end @colon)
|
||
|
||
; The inner scope of the class_expr, a `Class`.
|
||
attr (@class.inner_scope) name = class_name
|
||
; body will be set in a separate stanza.
|
||
attr (@class.inner_scope) _location_end = (location-end @colon)
|
||
|
||
}
|
||
|
||
; Class.body
|
||
(class_definition
|
||
body: (block (_) @stmt)
|
||
) @class
|
||
{
|
||
edge @class.inner_scope -> @stmt.node
|
||
attr (@class.inner_scope -> @stmt.node) body = (named-child-index @stmt)
|
||
}
|
||
|
||
; Class.bases - using `(_ !value !name)` as a proxy for all non-keyword arguments.
|
||
; In particular, `keyword_argument` nodes have a `name` field, and `dictionary_splat`
|
||
; nodes have a `value` field.
|
||
(class_definition
|
||
superclasses: (argument_list element: (_ !value !name) @arg)
|
||
) @class
|
||
{
|
||
edge @class.class_expr -> @arg.node
|
||
attr (@class.class_expr -> @arg.node) bases = (named-child-index @arg)
|
||
}
|
||
|
||
; Class.keywords of the form `foo=bar`
|
||
(class_definition
|
||
superclasses: (argument_list element: (keyword_argument) @arg)
|
||
) @class
|
||
{
|
||
edge @class.class_expr -> @arg.node
|
||
attr (@class.class_expr -> @arg.node) keywords = (named-child-index @arg)
|
||
}
|
||
|
||
; Class.keywords of the form `**kwargs`
|
||
(class_definition
|
||
superclasses: (argument_list element: (dictionary_splat) @arg)
|
||
) @class
|
||
{
|
||
edge @class.class_expr -> @arg.node
|
||
attr (@class.class_expr -> @arg.node) keywords = (named-child-index @arg)
|
||
}
|
||
|
||
;;;;;; End of Class
|
||
|
||
;;;;;; Assign statements
|
||
; Assignment statements require a bit of interesting handling, since we represent a chained
|
||
; assignment such as `a = b = 5` as a single `Assign` node with multiple targets and a single
|
||
; right-hand side. This makes it somewhat complicated (but still doable) to determine the index of
|
||
; any single target in the resulting list.
|
||
;
|
||
; The way we handle this is by explicitly propagating two variables inwards. The first variable
|
||
; keeps track of the outermost node in a chain of assignments, and the second variable keeps track of
|
||
; the index of the left-hand side of the current assignment.
|
||
|
||
; Base case, for the outermost assignment we set the outermost node to this node, and the index to zero.
|
||
(expression_statement (assignment !type) @assign) @expr
|
||
{
|
||
let @assign.outermost_assignment = @assign.node
|
||
let @assign.target_index = 0
|
||
}
|
||
|
||
; Propagating the two variables inwards, increasing the index by one. Note that this depends on
|
||
; having the query match from the outside in -- if this evaluation order ever changes, this will break.
|
||
(assignment !type right: (assignment) @inner) @outer
|
||
{
|
||
let @inner.outermost_assignment = @outer.outermost_assignment
|
||
let @inner.target_index = (plus @outer.target_index 1)
|
||
}
|
||
|
||
; Finally, with the above variables set, we can -- for each assignment -- create an edge from the
|
||
; outermost assignment to it, and set its index to the index that we've calculated for this node.
|
||
(assignment !type left: (_) @target) @assign
|
||
{
|
||
edge @assign.outermost_assignment -> @target.node
|
||
attr (@assign.outermost_assignment -> @target.node) targets = @assign.target_index
|
||
attr (@target.node) ctx = "store"
|
||
}
|
||
|
||
; In addition to the above, we must ensure that the `value` attribute of the outermost assignment
|
||
; points to the _innermost_ right-hand side. We do this by first setting the `value` attribute for
|
||
; _all_ assignments...
|
||
(assignment !type right: (_) @value) @assign
|
||
{
|
||
attr (@assign.node) value = @value.node
|
||
attr (@value.node) ctx = "load"
|
||
}
|
||
|
||
; ... and then for assignments that are _inside_ other assigments, we use the `_skip_to` attribute
|
||
; to jump across the outer assignment.
|
||
;
|
||
; Thus, the outermost assignment's `value` will point to its right-hand side, but this one will (if
|
||
; it's an assignment itself) skip to _its_ right-hand side, and so on until we reach a right-hand side
|
||
; that is not an assignment.
|
||
(assignment !type right: (assignment right: (_) @inner) @outer)
|
||
{
|
||
attr (@outer.node) _skip_to = @inner.node
|
||
}
|
||
|
||
;;;;;; End of Assign
|
||
|
||
;;;;;; AnnAssign
|
||
|
||
(assignment
|
||
left: (_) @target
|
||
type: (type (expression) @type)
|
||
) @assign
|
||
{
|
||
attr (@assign.node) target = @target.node
|
||
attr (@target.node) ctx = "store"
|
||
attr (@assign.node) annotation = @type.node
|
||
attr (@type.node) ctx = "load"
|
||
}
|
||
|
||
(assignment
|
||
left: (_) @target
|
||
type: (_)
|
||
right: (_) @value
|
||
) @assign
|
||
{
|
||
attr (@assign.node) value = @value.node
|
||
attr (@value.node) ctx = "load"
|
||
}
|
||
|
||
;;;;;; End of AnnAssign
|
||
|
||
;;;;;; AugAssign
|
||
|
||
(augmented_assignment
|
||
left: (_) @left
|
||
operator: _ @op
|
||
right: (_) @right
|
||
) @augassign
|
||
{
|
||
let binop = (ast-node @augassign "BinOp")
|
||
attr (@augassign.node) operation = binop
|
||
attr (binop) left = @left.node
|
||
attr (@left.node) ctx = "load" ; yes, it really is "load".
|
||
attr (binop) op = (source-text @op)
|
||
attr (binop) right = @right.node
|
||
attr (@right.node) ctx = "load"
|
||
}
|
||
|
||
;;;;;; End of AugAssign
|
||
|
||
;;;;;; Global
|
||
|
||
(global_statement (identifier) @name) @global
|
||
{
|
||
edge @global.node -> @name.node
|
||
attr (@global.node -> @name.node) names = (named-child-index @name)
|
||
attr (@name.node) _is_literal = (source-text @name)
|
||
}
|
||
|
||
;;;;;; End of Global
|
||
|
||
;;;;;; Nonlocal
|
||
|
||
(nonlocal_statement (identifier) @name) @nonlocal
|
||
{
|
||
edge @nonlocal.node -> @name.node
|
||
attr (@nonlocal.node -> @name.node) names = (named-child-index @name)
|
||
attr (@name.node) _is_literal = (source-text @name)
|
||
}
|
||
|
||
;;;;;; End of Nonlocal
|
||
|
||
;;;;;; Import (`import ...`)
|
||
|
||
; `import j1.j2 as j3, j4, ...` becomes
|
||
;
|
||
; Import:
|
||
; names: [
|
||
; alias:
|
||
; value:
|
||
; ImportExpr:
|
||
; level: 0 # always 0 for absolute imports
|
||
; name: 'j1.j2'
|
||
; top: False
|
||
; asname:
|
||
; Name:
|
||
; variable: Variable('j3', None)
|
||
; ctx: Store
|
||
; alias:
|
||
; value:
|
||
; ImportExpr:
|
||
; level: 0 # always 0 for absolute imports
|
||
; name: 'j4'
|
||
; top: True
|
||
; asname:
|
||
; Name:
|
||
; variable: Variable('j4', None)
|
||
; ctx: Store
|
||
; ...
|
||
; ]
|
||
;
|
||
; from
|
||
;
|
||
; module
|
||
; import_statement
|
||
; name: aliased_import
|
||
; name: dotted_name
|
||
; identifier # j1
|
||
; identifier # j2
|
||
; alias: identifier j3
|
||
; name: dotted_name
|
||
; identifier # j4
|
||
;
|
||
; This means we have to hang our `alias` nodes off of the `dotted_name` and
|
||
; `aliased_import` nodes.
|
||
|
||
; Import.names
|
||
(import_statement name: (_) @name) @import
|
||
{
|
||
edge @import.node -> @name.node
|
||
attr (@import.node -> @name.node) names = (named-child-index @name)
|
||
}
|
||
|
||
; Imports without an explicit alias -- extract the root module name
|
||
(import_statement name: (dotted_name . (identifier) @first) @alias)
|
||
{
|
||
let import_expr = (ast-node @alias "ImportExpr")
|
||
attr (import_expr) level = 0
|
||
attr (import_expr) name = (source-text @alias)
|
||
attr (import_expr) top = #true
|
||
|
||
attr (@alias.node) value = import_expr
|
||
|
||
attr (@alias.node) asname = @first.node
|
||
attr (@first.node) ctx = "store"
|
||
}
|
||
|
||
; Not strictly needed (but the AST reconstruction will complain otherwise) we
|
||
; assign a context to each identifier in a dotted name (except the first part,
|
||
; which already gets one elsewhere).
|
||
(dotted_name (identifier) (identifier) @name)
|
||
{
|
||
attr (@name.node) ctx = "load"
|
||
}
|
||
|
||
; For dotted imports `a.b.c` the location for the `Name` corresponding to the
|
||
; `a` part covers the entire expression, so we explicitly match the final
|
||
; element and set the location appropriately. If there is only one element,
|
||
; this stanza doesn't fire, but in that case the location is actually correct
|
||
; already.
|
||
(import_statement
|
||
name: (dotted_name
|
||
.
|
||
(identifier) @first
|
||
(identifier) @last
|
||
.
|
||
)
|
||
)
|
||
{
|
||
attr (@first.node) _location_end = (location-end @last)
|
||
}
|
||
|
||
; Imports with an explicit alias
|
||
(import_statement
|
||
(aliased_import
|
||
name: (dotted_name . (identifier) @first) @name
|
||
alias: (identifier) @asname
|
||
) @alias
|
||
)
|
||
{
|
||
let import_expr = (ast-node @name "ImportExpr")
|
||
attr (import_expr) level = 0
|
||
attr (import_expr) name = (source-text @name)
|
||
attr (import_expr) top = #false
|
||
|
||
attr (@alias.node) value = import_expr
|
||
|
||
attr (@alias.node) asname = @asname.node
|
||
attr (@asname.node) ctx = "store"
|
||
|
||
attr (@first.node) ctx = "load"
|
||
}
|
||
|
||
;;;;;; End of Import (`import ...`)
|
||
|
||
;;;;;; Import (`from ... import ...`)
|
||
|
||
; Oh what a twisty mess these are. First, the prototypical layout of a
|
||
; `from some_module import x1 as y1, x2, ...` statement is as follows:
|
||
;
|
||
; Import:
|
||
; names: [
|
||
; alias:
|
||
; value:
|
||
; ImportMember:
|
||
; module:
|
||
; ImportExpr;
|
||
; level: <number of dots before some_module>
|
||
; name: <name of some_module without dots>
|
||
; top: #false
|
||
; name: <name of x1>
|
||
; asname:
|
||
; Name:
|
||
; variable: Variable(<name of y1>, None)
|
||
; ctx: "store"
|
||
; alias:
|
||
; value:
|
||
; ImportMember:
|
||
; module:
|
||
; ImportExpr:
|
||
; level: <number of dots before some_module>
|
||
; name: <name of some_module without dots>
|
||
; top: #false
|
||
; name: <name of x2>
|
||
; asname:
|
||
; Name:
|
||
; variable: Variable(<name of x2>, None) # Note the reuse!
|
||
; ctx: "store"
|
||
; ...
|
||
; ]
|
||
;
|
||
; In particular, `alias` nodes are used even if no aliasing takes place.
|
||
|
||
; Now, on the flip side we have the `tree-sitter-python` output. Here
|
||
; the corresponding structure for `from ..some_module import x1 as y1, x2`
|
||
; is as follows:
|
||
;
|
||
; module
|
||
; import_from_statement
|
||
; module_name: relative_import
|
||
; import_prefix # `..`
|
||
; dotted_name
|
||
; identifier # some_module
|
||
; name: aliased_import
|
||
; name: dotted_name
|
||
; identifier # x1
|
||
; alias: identifier # y1
|
||
; name: dotted_name
|
||
; identifier # x2
|
||
;
|
||
; Now, we need to pin our `alias` nodes on something, and the only thing we can
|
||
; really rely on is whatever is in the `name` field of the
|
||
; `import_from_statement`
|
||
|
||
|
||
; Import.names
|
||
[
|
||
(import_from_statement
|
||
name: (_) @alias
|
||
)
|
||
(future_import_statement
|
||
name: (_) @alias
|
||
)
|
||
] @import
|
||
{
|
||
edge @import.node -> @alias.node
|
||
attr (@import.node -> @alias.node) names = (named-child-index @alias)
|
||
}
|
||
|
||
; Setting up the synthesized nodes for `ImportMember` and `ImportExpr`
|
||
; when the module name is _not_ a relative import.
|
||
[
|
||
(import_from_statement
|
||
module_name: (dotted_name) @name
|
||
name: (_) @alias
|
||
)
|
||
(future_import_statement
|
||
"__future__" @name
|
||
name: (_) @alias
|
||
)
|
||
]
|
||
{
|
||
let @alias.import_member = (ast-node @alias "ImportMember")
|
||
let @alias.import_expr = (ast-node @name "ImportExpr")
|
||
|
||
attr (@alias.node) value = @alias.import_member
|
||
attr (@alias.import_member) module = @alias.import_expr
|
||
attr (@alias.import_expr) level = 0
|
||
attr (@alias.import_expr) name = (source-text @name)
|
||
attr (@alias.import_expr) top = #false
|
||
}
|
||
|
||
; Setting up the synthesized nodes for `ImportMember` and `ImportExpr`
|
||
; when the module name _is_ a relative import.
|
||
(import_from_statement
|
||
module_name: (relative_import name: (dotted_name) @name) @rel
|
||
name: (_) @alias
|
||
)
|
||
{
|
||
let @alias.import_member = (ast-node @alias "ImportMember")
|
||
let @alias.import_expr = (ast-node @rel "ImportExpr")
|
||
|
||
attr (@alias.node) value = @alias.import_member
|
||
attr (@alias.import_member) module = @alias.import_expr
|
||
; ImportExpr.level is computed elsewhere
|
||
attr (@alias.import_expr) name = (source-text @name)
|
||
attr (@alias.import_expr) top = #false
|
||
}
|
||
|
||
; Setting up the synthesized nodes for `ImportMember` and `ImportExpr`
|
||
; when the module is a relative import with no module name (e.g. `from . import ...`).
|
||
(import_from_statement
|
||
module_name: (relative_import !name) @rel
|
||
name: (_) @alias
|
||
)
|
||
{
|
||
let @alias.import_member = (ast-node @alias "ImportMember")
|
||
let @alias.import_expr = (ast-node @rel "ImportExpr")
|
||
|
||
attr (@alias.node) value = @alias.import_member
|
||
attr (@alias.import_member) module = @alias.import_expr
|
||
; ImportExpr.level is computed elsewhere
|
||
attr (@alias.import_expr) name = #null
|
||
attr (@alias.import_expr) top = #false
|
||
}
|
||
|
||
; Set the level for relative imports
|
||
(import_from_statement
|
||
module_name: (relative_import (import_prefix) @prefix)
|
||
name: (_) @alias
|
||
)
|
||
{
|
||
var level = 0
|
||
|
||
; Figure out the number of `.`s in the prefix.
|
||
scan (source-text @prefix) {
|
||
"\." {
|
||
set level = (plus level 1)
|
||
}
|
||
}
|
||
|
||
attr (@alias.import_expr) level = level
|
||
}
|
||
|
||
; Set aliases for non-aliased imports
|
||
[
|
||
(import_from_statement
|
||
name:
|
||
(dotted_name (identifier) @name) @alias
|
||
)
|
||
(future_import_statement
|
||
name:
|
||
(dotted_name (identifier) @name) @alias
|
||
)
|
||
]
|
||
{
|
||
attr (@alias.node) asname = @name.node
|
||
attr (@alias.import_member) name = (source-text @name)
|
||
attr (@name.node) ctx = "store"
|
||
}
|
||
|
||
; Set aliases for aliased imports
|
||
(import_from_statement
|
||
name:
|
||
(aliased_import
|
||
name: (dotted_name) @first
|
||
alias: (identifier) @asname
|
||
) @alias
|
||
)
|
||
{
|
||
attr (@alias.node) asname = @asname.node
|
||
attr (@alias.import_member) name = (source-text @first)
|
||
attr (@asname.node) ctx = "store"
|
||
}
|
||
|
||
; Fix up remaining identifiers without contexts.
|
||
(import_from_statement
|
||
module_name: (dotted_name . (identifier) @first)
|
||
)
|
||
{
|
||
attr (@first.node) ctx = "load"
|
||
}
|
||
|
||
(import_from_statement
|
||
module_name: (relative_import (dotted_name . (identifier) @first))
|
||
)
|
||
{
|
||
attr (@first.node) ctx = "load"
|
||
}
|
||
|
||
(import_from_statement
|
||
name: (aliased_import (dotted_name (identifier) @first))
|
||
)
|
||
{
|
||
attr (@first.node) ctx = "load"
|
||
}
|
||
|
||
(import_from_statement
|
||
module_name: (_) @name
|
||
(wildcard_import)
|
||
) @importfrom
|
||
{
|
||
let importexpr = (ast-node @name "ImportExpr")
|
||
let @importfrom.importexpr = importexpr
|
||
attr (@importfrom.node) module = importexpr
|
||
attr (importexpr) top = #false
|
||
}
|
||
|
||
; Absolute star import: `from a import *`
|
||
(import_from_statement
|
||
module_name: (dotted_name) @name
|
||
(wildcard_import)
|
||
) @importfrom
|
||
{
|
||
attr (@importfrom.importexpr) name = (source-text @name)
|
||
attr (@importfrom.importexpr) level = 0
|
||
}
|
||
|
||
; Relative star import, with module name: `from ..a import *`
|
||
(import_from_statement
|
||
module_name:
|
||
(relative_import
|
||
(dotted_name) @name
|
||
)
|
||
(wildcard_import)
|
||
) @importfrom
|
||
{
|
||
attr (@importfrom.importexpr) name = (source-text @name)
|
||
}
|
||
|
||
; Relative star import, without module name: `from ... import *`
|
||
(import_from_statement
|
||
module_name:
|
||
(relative_import
|
||
(import_prefix) @prefix
|
||
)
|
||
(wildcard_import)
|
||
) @importfrom
|
||
{
|
||
var level = 0
|
||
|
||
; Figure out the number of `.`s in the prefix.
|
||
scan (source-text @prefix) {
|
||
"\." {
|
||
set level = (plus level 1)
|
||
}
|
||
}
|
||
|
||
attr (@importfrom.importexpr) level = level
|
||
}
|
||
;;;;;; End of Import (`from ... import ...`)
|
||
|
||
;;;;;; Raise (`raise ...`)
|
||
; This one is interesting, since the `tree-sitter-python` grammar doesn't let
|
||
; us distinguish between `raise foo` and `raise foo, bar`. At the level of the
|
||
; `tree-sitter-python` output, both are `raise_statement` nodes with a single
|
||
; child. In the latter case, the child is an `expression_list` but there's
|
||
; currently no way to match _against_ a particular node type in a query.
|
||
|
||
; To get around this, we instead do the matching inside the stanza itself.
|
||
|
||
(raise_statement . (_) @exc) @raise
|
||
{
|
||
if (not (instance-of @exc "expression_list") ) {
|
||
attr (@raise.node) exc = @exc.node
|
||
}
|
||
attr (@exc.node) ctx = "load"
|
||
}
|
||
|
||
; `raise ... from cause`
|
||
(raise_statement
|
||
cause: (_) @cause
|
||
) @raise
|
||
{
|
||
attr (@raise.node) cause = @cause.node
|
||
attr (@cause.node) ctx = "load"
|
||
}
|
||
|
||
; `raise type, inst`
|
||
(raise_statement (expression_list
|
||
. (_) @type
|
||
. (_) @inst
|
||
)) @raise
|
||
{
|
||
attr (@raise.node) type = @type.node
|
||
attr (@raise.node) inst = @inst.node
|
||
}
|
||
|
||
; `raise type, inst, tback`
|
||
(raise_statement (expression_list
|
||
. (_)
|
||
. (_)
|
||
. (_) @tback
|
||
.
|
||
)) @raise
|
||
{
|
||
attr (@raise.node) tback = @tback.node
|
||
}
|
||
|
||
;;;;;; End of Raise (`raise ...`)
|
||
|
||
;;;;;; Assert (`assert ...`)
|
||
|
||
(assert_statement
|
||
. (_) @test
|
||
) @assert
|
||
{
|
||
attr (@assert.node) test = @test.node
|
||
attr (@test.node) ctx = "load"
|
||
}
|
||
|
||
(assert_statement
|
||
. (_)
|
||
. (_) @msg
|
||
) @assert
|
||
{
|
||
attr (@assert.node) msg = @msg.node
|
||
attr (@msg.node) ctx = "load"
|
||
}
|
||
|
||
;;;;;; End of Assert (`assert ...`)
|
||
|
||
;;;;;; String (`"foo"`)
|
||
|
||
; For regular strings, see the handling of `(string !interpolation)` below.
|
||
|
||
; For concatenated strings, the necessary manipulations are quite complicated to express,
|
||
; so we instead move this problem into the Python side of things. Thus, a concatenated
|
||
; string only has to keep track of what its children are.
|
||
(concatenated_string) @string
|
||
{
|
||
attr (@string.node) _prefix = (string-prefix @string)
|
||
attr (@string.node) _fixup = #true
|
||
}
|
||
|
||
(concatenated_string (string) @part) @string
|
||
{
|
||
edge @string.node -> @part.node
|
||
attr (@string.node -> @part.node) _children = (named-child-index @part)
|
||
}
|
||
|
||
;;;;;; End of String (`"foo"`)
|
||
|
||
;;;;;; JoinedStr (`f"foo"`)
|
||
|
||
; f-strings are quite complicated for a variety of reasons. First of all,
|
||
; we need to synthesize empty strings to appear in-between interpolations
|
||
; that are immediately adjacent. Thus, the string `f"{1}{2}"`, which has
|
||
; a `tree-sitter-python` representation of the form
|
||
;
|
||
; (string (interpolation (integer)) (interpolation (integer)))
|
||
;
|
||
; needs to have three empty additional strings synthesized:
|
||
; - `f"{`, before the `1`,
|
||
; - `}{`, between the `1` and `2`, and
|
||
; - `}"`, after the `2`.
|
||
;
|
||
; Because of this, children of an f-string are indexed using triples of integers.
|
||
; The first component is either 0, 1, or 2, indicating whether this string appears at the
|
||
; beginning, in between, or at the end of the f-string. (At the beginning and end, the other
|
||
; two components are irrelevant.) The second component is the index of child, as seen by
|
||
; `tree-sitter`. The third component allows us to insert empty strings between adjacent children
|
||
; of the f-string. Thus, the string `f"{1}{2}"` has the following children at the given indices:
|
||
; `f"{"` at `[0,0,0]`
|
||
; `1` at `[1,1,0]`
|
||
; `}{` at `[1,1,1]`
|
||
; `2` at `[1,2,0]`
|
||
; `}"` at `[2,0,0]`
|
||
|
||
|
||
; First, we add any strings parts that appear either before or after an interpolation:
|
||
[
|
||
(string
|
||
interpolation: (_)
|
||
string_content: (_) @part
|
||
)
|
||
(string
|
||
string_content: (_) @part
|
||
interpolation: (_)
|
||
)
|
||
] @fstring
|
||
{
|
||
edge @fstring.node -> @part.node
|
||
attr (@fstring.node -> @part.node) values = [1, (named-child-index @part), 0]
|
||
let safe_string = (concatenate-strings (string-safe-prefix @fstring) (source-text @part) (string-quotes @fstring))
|
||
attr (@part.node) s = safe_string
|
||
attr (@part.node) text = safe_string
|
||
}
|
||
|
||
; In a similar fashion, any expressions that are interpolated:
|
||
(string interpolation: (interpolation expression: (_) @part) @interp) @fstring
|
||
{
|
||
edge @fstring.node -> @part.node
|
||
attr (@fstring.node -> @part.node) values = [1, (named-child-index @interp), 0]
|
||
attr (@part.node) ctx = "load"
|
||
}
|
||
|
||
; Any expressions inside the format specifier are appended at the end
|
||
(string
|
||
interpolation: (interpolation
|
||
(format_specifier
|
||
(format_expression
|
||
expression: (_) @part
|
||
) @format_expression
|
||
)
|
||
) @interp
|
||
) @fstring
|
||
{
|
||
edge @fstring.node -> @part.node
|
||
attr (@fstring.node -> @part.node) values = [1, (named-child-index @interp), (plus 1 (named-child-index @format_expression))]
|
||
attr (@part.node) ctx = "load"
|
||
}
|
||
|
||
; Next, the empty string before the first interpolation:
|
||
(string
|
||
.
|
||
(interpolation "{" @end)
|
||
) @fstring
|
||
{
|
||
let empty_string = (ast-node @fstring "StringPart")
|
||
edge @fstring.node -> empty_string
|
||
attr (@fstring.node -> empty_string) values = [0, 0, 0]
|
||
attr (empty_string) prefix = (string-prefix @fstring)
|
||
attr (empty_string) s = "\"\""
|
||
let quotes = (string-quotes @fstring)
|
||
attr (empty_string) text = (concatenate-strings quotes quotes)
|
||
|
||
attr (empty_string) _location_end = (location-end @end)
|
||
}
|
||
|
||
; Then, the empty string between two immediately adjacent interpolations:
|
||
(string
|
||
(interpolation "}" @start) @before
|
||
.
|
||
(interpolation "{" @end)
|
||
) @fstring
|
||
{
|
||
let empty_string = (ast-node @fstring "StringPart")
|
||
edge @fstring.node -> empty_string
|
||
attr (@fstring.node -> empty_string) values = [1, (named-child-index @before), 1]
|
||
attr (empty_string) prefix = (string-prefix @fstring)
|
||
attr (empty_string) s = "\"\""
|
||
let quotes = (string-quotes @fstring)
|
||
attr (empty_string) text = (concatenate-strings quotes quotes)
|
||
attr (empty_string) _location_start = (location-start @start)
|
||
attr (empty_string) _location_end = (location-end @end)
|
||
}
|
||
|
||
; And finally, the empty string after the last interpolation:
|
||
(string
|
||
(interpolation "}" @start)
|
||
.
|
||
) @fstring
|
||
{
|
||
let empty_string = (ast-node @fstring "StringPart")
|
||
edge @fstring.node -> empty_string
|
||
attr (@fstring.node -> empty_string) values = [2, 0, 0]
|
||
attr (empty_string) prefix = (string-prefix @fstring)
|
||
attr (empty_string) s = "\"\""
|
||
let quotes = (string-quotes @fstring)
|
||
attr (empty_string) text = (concatenate-strings quotes quotes)
|
||
attr (empty_string) _location_start = (location-start @start)
|
||
}
|
||
|
||
; If the f-string begins with a non-empty string, we must adjust the start and
|
||
; end location of this part:
|
||
(string
|
||
.
|
||
string_content: (_) @part
|
||
.
|
||
interpolation: (interpolation "{" @int_start)
|
||
) @fstring
|
||
{
|
||
attr (@part.node) prefix = (string-prefix @fstring)
|
||
attr (@part.node) _location_start = (location-start @fstring)
|
||
attr (@part.node) _location_end = (location-end @int_start)
|
||
}
|
||
|
||
; And similarly for any string that follows an interpolation:
|
||
(string
|
||
interpolation: (interpolation "}" @int_end)
|
||
.
|
||
string_content: (_) @part) @fstring
|
||
{
|
||
attr (@part.node) prefix = (string-prefix @fstring)
|
||
attr (@part.node) _location_start = (location-start @int_end)
|
||
}
|
||
|
||
; Finally, we must adjust the end of the last part:
|
||
(string
|
||
interpolation: (_)
|
||
string_content: (_) @part
|
||
.
|
||
) @fstring
|
||
{
|
||
attr (@part.node) _location_end = (location-end @fstring)
|
||
}
|
||
|
||
; For f-strings without interpolations, we simply treat them as regular strings (or `StringPart`s if
|
||
; they are part of a concatenation):
|
||
(string !interpolation string_content: (_) @part) @fstring
|
||
{
|
||
let safe_text = (concatenate-strings (string-safe-prefix @fstring) (source-text @part) (string-quotes @fstring))
|
||
if (instance-of (get-parent @fstring) "concatenated_string"){
|
||
; StringPart
|
||
attr (@fstring.node) text = safe_text
|
||
}
|
||
else {
|
||
; regular string
|
||
attr (@fstring.node) implicitly_concatenated_parts = #null
|
||
}
|
||
attr (@fstring.node) s = safe_text
|
||
attr (@fstring.node) prefix = (string-prefix @fstring)
|
||
}
|
||
|
||
; For f-strings without interpolations _or_ string-content, we simply treat them as regular empty strings:
|
||
(string !interpolation !string_content) @fstring
|
||
{
|
||
let empty_text = "\"\""
|
||
if (instance-of (get-parent @fstring) "concatenated_string"){
|
||
; StringPart
|
||
attr (@fstring.node) text = empty_text
|
||
}
|
||
else {
|
||
; regular string
|
||
attr (@fstring.node) implicitly_concatenated_parts = #null
|
||
}
|
||
attr (@fstring.node) s = empty_text
|
||
attr (@fstring.node) prefix = (string-prefix @fstring)
|
||
}
|
||
|
||
|
||
;;;;;; End of JoinedStr (`f"foo"`)
|
||
|
||
|
||
|
||
;;;;;; List (`[...]`)
|
||
|
||
(list element: (_) @elt) @list
|
||
{
|
||
edge @list.node -> @elt.node
|
||
attr (@list.node -> @elt.node) elts = (named-child-index @elt)
|
||
}
|
||
|
||
;;;;;; End of List (`[...]`)
|
||
|
||
;;;;;; Starred (`*some_sequence`)
|
||
|
||
[
|
||
(list_splat (expression) @value)
|
||
(list_splat_pattern vararg: (_) @value)
|
||
] @starred
|
||
{
|
||
attr (@starred.node) value = @value.node
|
||
attr (@value.node) _inherited_ctx = @starred.node
|
||
}
|
||
|
||
;;;;;; End of Starred (`*some_sequence`)
|
||
|
||
;;;;;; Dict (`{... : ..., ...}`)
|
||
|
||
(dictionary element: (_) @item) @dict
|
||
{
|
||
edge @dict.node -> @item.node
|
||
attr (@dict.node -> @item.node) items = (named-child-index @item)
|
||
attr (@item.node) ctx = "load"
|
||
}
|
||
|
||
(pair key: (_) @key value: (_) @value) @item
|
||
{
|
||
attr (@item.node) key = @key.node
|
||
attr (@item.node) value = @value.node
|
||
attr (@key.node) ctx = "load"
|
||
attr (@value.node) ctx = "load"
|
||
}
|
||
|
||
;;;;;; End of Dict (`{... : ..., ...}`)
|
||
|
||
;;;;;; DictUnpacking (`**some_dict`)
|
||
|
||
(dictionary_splat (expression) @value) @dictunpacking
|
||
{
|
||
attr (@dictunpacking.node) value = @value.node
|
||
attr (@value.node) ctx = "load"
|
||
}
|
||
|
||
;;;;;; End of DictUnpacking (`**some_dict`)
|
||
|
||
;;;;;; Set (`{..., ...}`)
|
||
|
||
(set element: (_) @elt) @set
|
||
{
|
||
edge @set.node -> @elt.node
|
||
attr (@set.node -> @elt.node) elts = (named-child-index @elt)
|
||
}
|
||
|
||
;;;;;; End of Set (`{..., ...}`)
|
||
|
||
|
||
;;;;;; BoolOp (`... and ...`, `... or ...`)
|
||
|
||
; This is probably the single most complex thing in this file. Read it slowly.
|
||
|
||
; First of all, the problem is that `tree-sitter-python` represents boolean operators as if they are binary,
|
||
; whereas in Python they are really n-ary. This means we have to collapse nested `and`s and `or`s in order to
|
||
; correctly create the intended AST structure.
|
||
;
|
||
; We have a structure like this:
|
||
;
|
||
; or
|
||
; / \
|
||
; v_0 or
|
||
; / \
|
||
; v_1 ...
|
||
; \
|
||
; or
|
||
; / \
|
||
; v_n-1 v_n
|
||
;
|
||
; where each `v_i` may be a value or a subtree, but not an `or`.
|
||
; From this we will produce a graph of the form:
|
||
;
|
||
; or -0-> v_0
|
||
; -1-> v_1
|
||
; ...
|
||
; -(n-1)-> v_n-1
|
||
; -n-> [or -skip_to->]* v_n
|
||
;
|
||
; where we see that the last node may be found by a series of `skip_to` edges along the nested `or` nodes,
|
||
; if such are present.
|
||
;
|
||
; As an intermediate step, we will decorate the `or` nodes of the tree with a field `index`, and for the outermost
|
||
; `or` node we will also set `last_index`, initially to 1 but we increment it each time we see a nested `or`, so it ends
|
||
; up being `n`:
|
||
;
|
||
; or index:0, last_index: n
|
||
; / \
|
||
; v_0 or index: 1
|
||
; / \
|
||
; v_1 ...
|
||
; \
|
||
; or index: n-1
|
||
; / \
|
||
; v_n-1 v_n
|
||
;
|
||
; This collapsing goes to the outermost operator (`and` or `or`)
|
||
; and so the first step is to correctly identify these.
|
||
|
||
; For the outermost nodes, we can now assign
|
||
; some special variables that we will propagate inwards. Firstly, we record what the outermost node is (in
|
||
; this case just the node itself), next the index of the value in its left argument (initially `0`), and
|
||
; finally the index at which the _innermost_ right-hand-side value should appear in the resulting list of
|
||
; values. This final variable is mutable, and will be updated as we go through the nested sequence of similar
|
||
; operators.
|
||
(boolean_operator operator: _ @op right: (_)) @boolop
|
||
{
|
||
; this binary operator is outermost if it does not have a parent performing the same operation (`and` or `or`)
|
||
if (not (is-boolean-operator (get-parent @boolop) (source-text @op))) {
|
||
let @boolop.outermost = @boolop
|
||
let @boolop.index = 0
|
||
var @boolop.innermost_index = 1
|
||
}
|
||
}
|
||
|
||
; Now, we propagate/modify the variables mentioned in the previous stanza. The `outermost` field is simply
|
||
; propagated, and the `index` and `innermost_index` fields are propagated and updated respectively.
|
||
;
|
||
; We also set the `_skip_to` field on the inner operator, making it point to its right child. That way, the
|
||
; `right` child of the _outermost_ operator will (once resolved) point to the _innermost_ `right` child (i.e. ; the last child in this nested sequence of operators).
|
||
[
|
||
(boolean_operator
|
||
operator: "or"
|
||
right: (boolean_operator
|
||
operator: "or"
|
||
right: (_) @inner_right
|
||
) @inner
|
||
)
|
||
(boolean_operator
|
||
operator: "and"
|
||
right: (boolean_operator
|
||
operator: "and"
|
||
right: (_) @inner_right
|
||
) @inner
|
||
)
|
||
] @outer
|
||
{
|
||
let @inner.outermost = @outer.outermost
|
||
let @inner.index = (plus @outer.index 1)
|
||
attr (@inner.node) _skip_to = @inner_right.node
|
||
let outermost = @outer.outermost
|
||
set outermost.innermost_index = (plus outermost.innermost_index 1)
|
||
}
|
||
|
||
; For each boolean operator, we hook its left child up as a child of the outermost operator, at the index we
|
||
; calculated previously.
|
||
(boolean_operator left: (_) @value) @boolop
|
||
{
|
||
edge @boolop.outermost.node -> @value.node
|
||
attr (@boolop.outermost.node -> @value.node) values = @boolop.index
|
||
attr (@value.node) ctx = "load"
|
||
}
|
||
|
||
; For the outermost boolean operator, we hook up its right child (which ultimately points to the innermost
|
||
; right child) as a child at the index we calculated previously.
|
||
(boolean_operator
|
||
operator: _ @op
|
||
right: (_) @value
|
||
) @boolop
|
||
{
|
||
; this binary operator is outermost if it does not have a parent performing the same operation (`and` or `or`)
|
||
if (not (is-boolean-operator (get-parent @boolop) (source-text @op))) {
|
||
edge @boolop.node -> @value.node
|
||
attr (@boolop.node -> @value.node) values = @boolop.innermost_index
|
||
}
|
||
}
|
||
|
||
(boolean_operator right: (_) @value)
|
||
{ attr (@value.node) ctx = "load" }
|
||
|
||
(boolean_operator ["and" "or"] @op) @boolop
|
||
{
|
||
attr (@boolop.node) op = (source-text @op)
|
||
}
|
||
|
||
;;;;;; End of BoolOp (`... and ...`, `... or ...`)
|
||
|
||
;;;;;; Compare (`... < ...`, `... <= ...`, etc.)
|
||
|
||
(comparison_operator . (primary_expression) @left) @compare
|
||
{
|
||
attr (@compare.node) left = @left.node
|
||
attr (@left.node) ctx = "load"
|
||
}
|
||
|
||
; Hook up all of the compared values. These are simply the named children (except the first one,
|
||
; which was handled above), as the operators are all unnamed.
|
||
(comparison_operator (primary_expression) (primary_expression) @right) @compare
|
||
{
|
||
edge @compare.node -> @right.node
|
||
attr (@compare.node -> @right.node) comparators = (named-child-index @right)
|
||
attr (@right.node) ctx = "load"
|
||
}
|
||
|
||
|
||
; Record the operators in the `ops` fields.
|
||
;
|
||
; A complication here is that we want to construct a field pointing to a list of
|
||
; literals (and not AST nodes as we do almost everywhere else). To get around this,
|
||
; we create a placeholder node for the operation, and then set the `_is_literal` field
|
||
; to override it with a literal value.
|
||
(comparison_operator ["<" "<=" ">" ">=" "==" "!=" "<>" "in" "is"] @op) @compare
|
||
{
|
||
let @op.node = (ast-node @op "cmpop")
|
||
attr (@op.node) _is_literal = (node-type @op)
|
||
edge @compare.node -> @op.node
|
||
attr (@compare.node -> @op.node) ops = (unnamed-child-index @op)
|
||
}
|
||
|
||
; The `not in` and `is not` operators are complicated by the fact that the query
|
||
; `(comparison_operator "not in" @op)`
|
||
; matches _twice_ for each `not in` operator (in effect for both the `not` and `in` parts, even
|
||
; though these should have been aliased to a single token). To avoid producing duplicate operators,
|
||
; we only create an operator for _one_ of these matches, by checking whether the index is even.
|
||
(comparison_operator "not in"+ @op) @compare
|
||
{
|
||
for op in @op {
|
||
let index = (unnamed-child-index op)
|
||
if (eq (mod index 2) 0) {
|
||
let op.node = (ast-node op "cmpop")
|
||
attr (op.node) _is_literal = "not in"
|
||
edge @compare.node -> op.node
|
||
attr (@compare.node -> op.node) ops = index
|
||
}
|
||
}
|
||
}
|
||
|
||
(comparison_operator "is not"+ @op) @compare
|
||
{
|
||
for op in @op {
|
||
let index = (unnamed-child-index op)
|
||
if (eq (mod index 2) 0) {
|
||
let op.node = (ast-node op "cmpop")
|
||
attr (op.node) _is_literal = "is not"
|
||
edge @compare.node -> op.node
|
||
attr (@compare.node -> op.node) ops = index
|
||
}
|
||
}
|
||
}
|
||
|
||
;;;;;; End of Compare (`... < ...`, `... <= ...`, etc.)
|
||
|
||
;;;;;; UnaryOp (`-x`, `~x`, etc.., `not x`)
|
||
|
||
[
|
||
(unary_operator argument: (_) @operand)
|
||
(not_operator argument: (_) @operand)
|
||
] @unaryop
|
||
{
|
||
attr (@unaryop.node) operand = @operand.node
|
||
attr (@operand.node) ctx = "load"
|
||
}
|
||
|
||
(unary_operator "~" @op) @unaryop
|
||
{
|
||
attr (@unaryop.node) op = "~"
|
||
}
|
||
|
||
(unary_operator "+") @unaryop
|
||
{
|
||
attr (@unaryop.node) op = "uadd"
|
||
}
|
||
|
||
(unary_operator "-") @unaryop
|
||
{
|
||
attr (@unaryop.node) op = "usub"
|
||
}
|
||
|
||
|
||
(not_operator) @unaryop
|
||
{
|
||
attr (@unaryop.node) op = "not"
|
||
}
|
||
|
||
;;;;;; End of UnaryOp (`-x`, `not x`)
|
||
|
||
;;;;;; Exec (`exec ...`)
|
||
|
||
(exec_statement (_) @body) @exec
|
||
{
|
||
attr (@exec.node) body = @body.node
|
||
}
|
||
|
||
;;;;;; End of Exec (`exec ...`)
|
||
|
||
;;;;;; Print (`print ...`)
|
||
|
||
(print_statement argument: (_) @value) @print
|
||
{
|
||
edge @print.node -> @value.node
|
||
attr (@print.node -> @value.node) values = (named-child-index @value)
|
||
attr (@value.node) ctx = "load"
|
||
}
|
||
|
||
(print_statement (chevron (_) @dest)) @print
|
||
{
|
||
attr (@print.node) dest = @dest.node
|
||
attr (@dest.node) ctx = "load"
|
||
}
|
||
|
||
(print_statement ","? @comma .) @print
|
||
{
|
||
var nl = #true
|
||
if some @comma
|
||
{
|
||
set nl = #false
|
||
}
|
||
attr (@print.node) nl = nl
|
||
}
|
||
|
||
;;;;;; End of Print (`print ...`)
|
||
|
||
;;;;;; Return (`return ...`)
|
||
|
||
(return_statement (_) @value) @return
|
||
{
|
||
attr (@return.node) value = @value.node
|
||
attr (@value.node) ctx = "load"
|
||
}
|
||
|
||
;;;;;; End of Return (`return ...`)
|
||
|
||
;;;;;; Yield and YieldFrom (`yield ...` and `yield from ...`)
|
||
|
||
(yield (_) @value) @yield
|
||
{
|
||
attr (@yield.node) value = @value.node
|
||
attr (@value.node) ctx = "load"
|
||
}
|
||
|
||
;;;;;; End of Yield and YieldFrom (`yield ...` and `yield from ...`)
|
||
|
||
;;;;;; Await (`await ...`)
|
||
|
||
(await (_) @value) @await
|
||
{
|
||
attr (@await.node) value = @value.node
|
||
attr (@value.node) ctx = "load"
|
||
}
|
||
|
||
;;;;;; End of Await (`await ...`)
|
||
|
||
;;;;;; Try (`try: ... except: ... else: ... finally: ...`)
|
||
|
||
(try_statement body: (block (_) @stmt)) @try
|
||
{
|
||
edge @try.node -> @stmt.node
|
||
attr (@try.node -> @stmt.node) body = (named-child-index @stmt)
|
||
}
|
||
|
||
(try_statement (except_clause) @except) @try
|
||
{
|
||
edge @try.node -> @except.node
|
||
attr (@try.node -> @except.node) handlers = (named-child-index @except)
|
||
}
|
||
|
||
(try_statement (except_group_clause) @except) @try
|
||
{
|
||
edge @try.node -> @except.node
|
||
attr (@try.node -> @except.node) handlers = (named-child-index @except)
|
||
}
|
||
|
||
(try_statement (else_clause body: (block (_) @stmt))) @try
|
||
{
|
||
edge @try.node -> @stmt.node
|
||
attr (@try.node -> @stmt.node) orelse = (named-child-index @stmt)
|
||
}
|
||
|
||
(try_statement (finally_clause body: (block (_) @stmt))) @try
|
||
{
|
||
edge @try.node -> @stmt.node
|
||
attr (@try.node -> @stmt.node) finalbody = (named-child-index @stmt)
|
||
}
|
||
|
||
(except_clause body: (block (_) @stmt)) @except
|
||
{
|
||
edge @except.node -> @stmt.node
|
||
attr (@except.node -> @stmt.node) body = (named-child-index @stmt)
|
||
}
|
||
|
||
(except_clause type: (_) @type) @except
|
||
{
|
||
attr (@except.node) type = @type.node
|
||
attr (@type.node) ctx = "load"
|
||
}
|
||
|
||
(except_clause alias: (_) @name) @except
|
||
{
|
||
attr (@except.node) name = @name.node
|
||
attr (@name.node) ctx = "store"
|
||
}
|
||
|
||
(except_group_clause body: (block (_) @stmt)) @except
|
||
{
|
||
edge @except.node -> @stmt.node
|
||
attr (@except.node -> @stmt.node) body = (named-child-index @stmt)
|
||
}
|
||
|
||
(except_group_clause type: (_) @type) @except
|
||
{
|
||
attr (@except.node) type = @type.node
|
||
attr (@type.node) ctx = "load"
|
||
}
|
||
|
||
(except_group_clause alias: (_) @name) @except
|
||
{
|
||
attr (@except.node) name = @name.node
|
||
attr (@name.node) ctx = "store"
|
||
}
|
||
|
||
;;;;;; End of Try (`try: ... except: ... else: ... finally: ...`)
|
||
|
||
|
||
;;;;;; AssignExpr (`a := b`)
|
||
|
||
(named_expression
|
||
name: (_) @name
|
||
value: (_) @value
|
||
) @assignexpr
|
||
{
|
||
attr (@assignexpr.node) target = @name.node
|
||
attr (@name.node) ctx = "store"
|
||
attr (@assignexpr.node) value = @value.node
|
||
attr (@value.node) ctx = "load"
|
||
}
|
||
|
||
;;;;;; End of AssignExpr (`a := b`)
|
||
|
||
;;;;;; IfExpr (`a if b else c`)
|
||
|
||
(conditional_expression
|
||
(expression) @body
|
||
(expression) @test
|
||
(expression) @orelse
|
||
) @ifexp
|
||
{
|
||
attr (@ifexp.node) body = @body.node
|
||
attr (@body.node) ctx = "load"
|
||
attr (@ifexp.node) test = @test.node
|
||
attr (@test.node) ctx = "load"
|
||
attr (@ifexp.node) orelse = @orelse.node
|
||
attr (@orelse.node) ctx = "load"
|
||
}
|
||
|
||
;;;;;; End of IfExpr (`a if b else c`)
|
||
|
||
;;;;;; Attribute (`a.b`)
|
||
|
||
(attribute
|
||
object: (_) @value
|
||
attribute: (_) @attr
|
||
) @attribute
|
||
{
|
||
attr (@attribute.node) value = @value.node
|
||
attr (@value.node) ctx = "load"
|
||
attr (@attribute.node) attr = (source-text @attr)
|
||
; Not actually used, but we need to set it to something.
|
||
attr (@attr.node) ctx = "load"
|
||
}
|
||
|
||
;;;;;; End of Attribute (`a.b`)
|
||
|
||
;;;;;; Subscript (`a[b]`)
|
||
|
||
(subscript
|
||
value: (_) @value
|
||
) @subscript
|
||
{
|
||
attr (@subscript.node) value = @value.node
|
||
attr (@value.node) ctx = "load"
|
||
}
|
||
|
||
; Single subscript
|
||
(subscript
|
||
value: (_)
|
||
.
|
||
subscript: (_) @index
|
||
.
|
||
) @subscript
|
||
{
|
||
attr (@subscript.node) index = @index.node
|
||
attr (@index.node) ctx = "load"
|
||
}
|
||
|
||
; For expressions of the form `a[b, c]` we must explicitly synthesize an internal tuple node
|
||
; We do this and also hook it up:
|
||
(subscript
|
||
value: (_)
|
||
.
|
||
subscript: (_) @first
|
||
.
|
||
subscript: (_)
|
||
) @subscript
|
||
{
|
||
let @subscript.tuple = (ast-node @first "Tuple")
|
||
attr (@subscript.tuple) ctx = "load"
|
||
attr (@subscript.node) index = @subscript.tuple
|
||
edge @subscript.tuple -> @first.node
|
||
attr (@subscript.tuple -> @first.node) elts = (named-child-index @first)
|
||
attr (@first.node) ctx = "load"
|
||
}
|
||
|
||
(subscript
|
||
value: (_)
|
||
.
|
||
subscript: (_)
|
||
subscript: (_) @elt
|
||
) @subscript
|
||
{
|
||
edge @subscript.tuple -> @elt.node
|
||
attr (@subscript.tuple -> @elt.node) elts = (named-child-index @elt)
|
||
attr (@elt.node) ctx = "load"
|
||
}
|
||
|
||
|
||
; Set the end position correctly
|
||
(subscript
|
||
value: (_)
|
||
.
|
||
subscript: (_)
|
||
subscript: (_) @last
|
||
.
|
||
) @subscript
|
||
{
|
||
attr (@subscript.tuple) _location_end = (location-end @last)
|
||
}
|
||
|
||
|
||
|
||
;;;;;; End of Subscript (`a[b]`)
|
||
|
||
;;;;;; Slice (`a:b:c`)
|
||
|
||
(slice start: (_) @start) @slice
|
||
{
|
||
attr (@slice.node) start = @start.node
|
||
attr (@start.node) ctx = "load"
|
||
}
|
||
|
||
(slice stop: (_) @stop) @slice
|
||
{
|
||
attr (@slice.node) stop = @stop.node
|
||
attr (@stop.node) ctx = "load"
|
||
}
|
||
|
||
|
||
(slice step: (_) @step) @slice
|
||
{
|
||
attr (@slice.node) step = @step.node
|
||
attr (@step.node) ctx = "load"
|
||
}
|
||
|
||
;;;;;; End of Slice (`a:b:c`)
|
||
|
||
;;;;;; While (`while a: ... else: ...`)
|
||
|
||
(while_statement condition: (_) @test) @while
|
||
{
|
||
attr (@while.node) test = @test.node
|
||
attr (@test.node) ctx = "load"
|
||
}
|
||
|
||
(while_statement body: (block (_) @stmt)) @while
|
||
{
|
||
edge @while.node -> @stmt.node
|
||
attr (@while.node -> @stmt.node) body = (named-child-index @stmt)
|
||
}
|
||
|
||
(while_statement alternative: (else_clause (block (_) @stmt))) @while
|
||
{
|
||
edge @while.node -> @stmt.node
|
||
attr (@while.node -> @stmt.node) orelse = (named-child-index @stmt)
|
||
}
|
||
|
||
;;;;;; End of While (`while a: ... else: ...`)
|
||
|
||
;;;;;; With (`with a as b, c as d: ...`)
|
||
|
||
(with_statement (with_clause . (with_item) @first)) @with
|
||
{
|
||
attr (@with.node) _skip_to = @first.node
|
||
let @with.first = @first.node
|
||
}
|
||
|
||
(with_item
|
||
value: (_) @value
|
||
) @with
|
||
{
|
||
attr (@with.node) context_expr = @value.node
|
||
attr (@value.node) ctx = "load"
|
||
}
|
||
|
||
(with_item
|
||
alias: (_) @alias
|
||
) @with
|
||
{
|
||
attr (@with.node) optional_vars = @alias.node
|
||
attr (@alias.node) ctx = "store"
|
||
}
|
||
|
||
|
||
(with_clause
|
||
(with_item) @with1
|
||
. (comment)* .
|
||
(with_item) @with2
|
||
)
|
||
{
|
||
edge @with1.node -> @with2.node
|
||
attr (@with1.node -> @with2.node) body = 0
|
||
}
|
||
|
||
|
||
(with_statement
|
||
(with_clause
|
||
(with_item) @last
|
||
.
|
||
)
|
||
body: (block (_) @stmt)
|
||
)
|
||
{
|
||
edge @last.node -> @stmt.node
|
||
attr (@last.node -> @stmt.node) body = (named-child-index @stmt)
|
||
}
|
||
|
||
|
||
|
||
;;;;;; End of With (`with a as b, c as d: ...`)
|
||
|
||
;;;;;; Match (`match a: ...`)
|
||
|
||
(match_statement
|
||
subject: (_) @subject
|
||
) @match
|
||
{
|
||
attr (@match.node) subject = @subject.node
|
||
attr (@subject.node) ctx = "load"
|
||
}
|
||
|
||
(match_statement
|
||
cases: (cases (case_block) @case)
|
||
) @match
|
||
{
|
||
edge @match.node -> @case.node
|
||
attr (@match.node -> @case.node) cases = (named-child-index @case)
|
||
}
|
||
|
||
(case_block
|
||
pattern: (_) @pattern
|
||
) @case
|
||
{
|
||
attr (@case.node) pattern = @pattern.node
|
||
}
|
||
|
||
(case_block
|
||
guard: (_) @guard
|
||
) @case
|
||
{
|
||
attr (@case.node) guard = @guard.node
|
||
}
|
||
|
||
(guard
|
||
test: (_) @test
|
||
) @guard
|
||
{
|
||
attr (@guard.node) test = @test.node
|
||
attr (@test.node) ctx = "load"
|
||
}
|
||
|
||
(case_block
|
||
body: (block (_) @stmt)
|
||
) @case
|
||
{
|
||
edge @case.node -> @stmt.node
|
||
attr (@case.node -> @stmt.node) body = (named-child-index @stmt)
|
||
}
|
||
|
||
;;; The various pattern shapes need to have their children set up correctly
|
||
|
||
|
||
(match_as_pattern
|
||
pattern: (_) @pattern
|
||
) @match
|
||
{
|
||
attr (@match.node) pattern = @pattern.node
|
||
}
|
||
|
||
(match_as_pattern
|
||
alias: (_) @alias
|
||
) @match
|
||
{
|
||
attr (@match.node) alias = @alias.node
|
||
attr (@alias.node) ctx = "store"
|
||
}
|
||
|
||
(match_or_pattern
|
||
(_) @pattern
|
||
) @match_or_pattern
|
||
{
|
||
edge @match_or_pattern.node -> @pattern.node
|
||
attr (@match_or_pattern.node -> @pattern.node) patterns = (named-child-index @pattern)
|
||
}
|
||
|
||
(match_literal_pattern !real (_) @literal) @match_literal_pattern
|
||
{
|
||
attr (@match_literal_pattern.node) literal = @literal.node
|
||
attr (@literal.node) ctx = "load"
|
||
}
|
||
|
||
(match_literal_pattern
|
||
prefix_operator: _? @prefix_op
|
||
real: (_) @left
|
||
operator: _? @op
|
||
imaginary: (_)? @right
|
||
) @match_literal_pattern
|
||
{
|
||
; Set `left_node` to point to the left hand side (or only part) of the literal,
|
||
; synthesizing it if needed.
|
||
var left_node = #null
|
||
if some @prefix_op {
|
||
set left_node = (ast-node @left "UnaryOp")
|
||
attr (left_node) _start_location = (location-start @prefix_op)
|
||
attr (left_node) operand = @left.node
|
||
attr (left_node) op = "usub"
|
||
} else {
|
||
set left_node = @left.node
|
||
}
|
||
attr (left_node) ctx = "load"
|
||
; Synthesize the binary operator node, if needed.
|
||
var literal_node = #null
|
||
if some @right {
|
||
; Synthesize the node for the binary operation
|
||
set literal_node = (ast-node @match_literal_pattern "BinOp")
|
||
attr (literal_node) left = left_node
|
||
attr (literal_node) right = @right.node
|
||
attr (literal_node) op = (source-text @op)
|
||
attr (@right.node) ctx = "load"
|
||
attr (literal_node) ctx = "load"
|
||
} else {
|
||
set literal_node = left_node
|
||
}
|
||
attr (@match_literal_pattern.node) literal = literal_node
|
||
}
|
||
|
||
(match_capture_pattern (identifier) @pattern) @match_capture_pattern
|
||
{
|
||
attr (@match_capture_pattern.node) variable = @pattern.node
|
||
attr (@pattern.node) ctx = "store"
|
||
}
|
||
|
||
; We have a structure where the match_value_pattern has a child for each
|
||
; step in the attribute access.
|
||
; We will turn each child into an actual attribute access of its predecessor.
|
||
;
|
||
; We start with (@match_value_pattern) -> id_1 .. id_n
|
||
; result is
|
||
; id_1 is a Name
|
||
; for i > 1:
|
||
; @id_i -skip-> Attribute -value-> @id_{i-1}
|
||
; -attr-> #text
|
||
; @match_value_pattern -value-> @id_n
|
||
|
||
(match_value_pattern
|
||
(identifier) @obj
|
||
.
|
||
(identifier) @attr
|
||
) @match_value_pattern
|
||
{
|
||
let attribute = (ast-node @attr "Attribute")
|
||
attr (@attr.node) _skip_to = attribute
|
||
attr (attribute) value = @obj.node
|
||
attr (attribute) attr = (source-text @attr)
|
||
attr (attribute) ctx = "load"
|
||
}
|
||
|
||
; First id
|
||
; this needs a ctx
|
||
(match_value_pattern
|
||
.
|
||
(identifier) @id
|
||
) @match_value_pattern
|
||
{
|
||
attr (@id.node) ctx = "load"
|
||
}
|
||
|
||
; Last id
|
||
; this should be linked from the pattern.
|
||
(match_value_pattern
|
||
(identifier) @attr
|
||
.
|
||
) @match_value_pattern
|
||
{
|
||
attr (@match_value_pattern.node) value = @attr.node
|
||
}
|
||
|
||
; Group patterns only exist in the parser.
|
||
; They are elided from the AST, where the information is
|
||
; instead recorded in the field `parenthesised`.
|
||
(match_group_pattern
|
||
content: (_) @pattern
|
||
) @match_group_pattern
|
||
{
|
||
attr (@match_group_pattern.node) _skip_to = @pattern.node
|
||
attr (@match_group_pattern.node) parenthesised = #true
|
||
}
|
||
|
||
(match_sequence_pattern
|
||
(_) @pattern
|
||
) @match_sequence_pattern
|
||
{
|
||
edge @match_sequence_pattern.node -> @pattern.node
|
||
attr (@match_sequence_pattern.node -> @pattern.node) patterns = (named-child-index @pattern)
|
||
}
|
||
|
||
(match_star_pattern
|
||
target: (_) @target
|
||
) @match_star_pattern
|
||
{
|
||
attr (@match_star_pattern.node) target = @target.node
|
||
}
|
||
|
||
(match_mapping_pattern
|
||
[
|
||
(match_key_value_pattern) @mapping
|
||
(match_double_star_pattern) @mapping
|
||
]
|
||
) @pattern
|
||
{
|
||
edge @pattern.node -> @mapping.node
|
||
attr (@pattern.node -> @mapping.node) mappings = (named-child-index @mapping)
|
||
}
|
||
|
||
(match_double_star_pattern
|
||
target: (_) @target
|
||
) @match_double_star_pattern
|
||
{
|
||
attr (@match_double_star_pattern.node) target = @target.node
|
||
}
|
||
|
||
(match_key_value_pattern
|
||
key: (_) @key
|
||
value: (_) @value
|
||
) @key_value
|
||
{
|
||
attr (@key_value.node) key = @key.node
|
||
attr (@key_value.node) value = @value.node
|
||
}
|
||
|
||
; Similar situation to the match_value_pattern.
|
||
; We have a structure where the match_class_pattern has a child for each
|
||
; step in the attribute access.
|
||
; We will turn each child into an actual attribute access of its predecessor.
|
||
|
||
(pattern_class_name
|
||
(identifier) @obj
|
||
.
|
||
(identifier) @attr
|
||
)
|
||
{
|
||
let attribute = (ast-node @attr "Attribute")
|
||
attr (@attr.node) _skip_to = attribute
|
||
attr (attribute) value = @obj.node
|
||
attr (attribute) attr = (source-text @attr)
|
||
attr (attribute) ctx = "load"
|
||
}
|
||
|
||
; First id
|
||
(pattern_class_name
|
||
.
|
||
(identifier) @id
|
||
)
|
||
{
|
||
attr (@id.node) ctx = "load"
|
||
}
|
||
|
||
; Last id
|
||
; this should be linked from the pattern.
|
||
(match_class_pattern
|
||
class: (pattern_class_name
|
||
(identifier) @attr
|
||
.
|
||
)
|
||
) @match_class_pattern
|
||
{
|
||
attr (@match_class_pattern.node) class_name = @attr.node
|
||
}
|
||
|
||
(match_class_pattern
|
||
(match_positional_pattern (_) @positional) @positional_pattern
|
||
) @match_class_pattern
|
||
{
|
||
edge @match_class_pattern.node -> @positional.node
|
||
attr (@match_class_pattern.node -> @positional.node) positional = (named-child-index @positional_pattern)
|
||
}
|
||
|
||
(match_class_pattern
|
||
(match_keyword_pattern) @keyword
|
||
) @match_class_pattern
|
||
{
|
||
edge @match_class_pattern.node -> @keyword.node
|
||
attr (@match_class_pattern.node -> @keyword.node) keyword = (named-child-index @keyword)
|
||
}
|
||
|
||
(match_keyword_pattern
|
||
attribute: (_) @attribute
|
||
) @match_keyword_pattern
|
||
{
|
||
attr (@match_keyword_pattern.node) attribute = @attribute.node
|
||
attr (@attribute.node) ctx = "load"
|
||
}
|
||
|
||
(match_keyword_pattern
|
||
value: (_) @pattern
|
||
) @match_keyword_pattern
|
||
{ attr (@match_keyword_pattern.node) value = @pattern.node}
|
||
|
||
;;;;;; End of Match (`match a: ...`)
|
||
|
||
;;;;;; Lambda (`lambda a: ...`)
|
||
|
||
; Lambdas are tricky, much like function definitions.
|
||
;
|
||
; One complication is that we need to distinguish the cases where the parameter has a default value and
|
||
; where it does not. This leads to an unfortunate explosion in mostly similar cases...
|
||
|
||
|
||
(lambda body: (_) @body) @lambda
|
||
{
|
||
|
||
; Lambdas contain a `Function` much like regular functions.
|
||
let @lambda.function = (ast-node @lambda "Function")
|
||
attr (@lambda.function) name = "lambda"
|
||
attr (@lambda.node) inner_scope = @lambda.function
|
||
|
||
; The single child of this function is a synthesised return statement.
|
||
let return = (ast-node @body "Return")
|
||
edge @lambda.function -> return
|
||
attr (@lambda.function -> return) body = 0
|
||
|
||
attr (return) value = @body.node
|
||
attr (@body.node) ctx = "load"
|
||
}
|
||
|
||
; Lambdas without parameters just get a dummy `arguments` child.
|
||
(lambda !parameters) @lambda
|
||
{
|
||
attr (@lambda.node) args = (ast-node @lambda "arguments")
|
||
}
|
||
|
||
(lambda parameters: (_) @params) @lambda
|
||
{
|
||
attr (@lambda.node) args = @params.node
|
||
}
|
||
|
||
(lambda
|
||
parameters: (lambda_parameters
|
||
(list_splat_pattern vararg: (_) @vararg) @starred
|
||
)
|
||
) @lambda
|
||
{
|
||
attr (@lambda.function) vararg = @vararg.node
|
||
attr (@starred.node) ctx = "param" ; Not actually used
|
||
attr (@vararg.node) ctx = "param"
|
||
}
|
||
|
||
(lambda
|
||
parameters: (lambda_parameters
|
||
(dictionary_splat_pattern kwarg: (_) @kwarg)
|
||
)
|
||
) @lambda
|
||
{
|
||
attr (@lambda.function) kwarg = @kwarg.node
|
||
attr (@kwarg.node) ctx = "param"
|
||
}
|
||
|
||
(lambda
|
||
parameters: (lambda_parameters
|
||
[(list_splat_pattern) (keyword_separator)]? @is_kwarg
|
||
[
|
||
(identifier) @name
|
||
(default_parameter
|
||
name: (_) @name
|
||
value: (_) @value
|
||
)
|
||
] @param
|
||
) @params
|
||
) @lambda
|
||
{
|
||
let none = (ast-node @params "None")
|
||
attr (none) _is_literal = #null
|
||
attr (none) ctx = "load"
|
||
edge @params.node -> none
|
||
|
||
; Even though lambda parameters cannot have annotations, we must still record this fact.
|
||
if some @is_kwarg {
|
||
attr (@params.node -> none) kw_annotations = (named-child-index @param)
|
||
} else {
|
||
attr (@params.node -> none) annotations = (named-child-index @param)
|
||
}
|
||
|
||
edge @lambda.function -> @name.node
|
||
attr (@name.node) ctx = "param"
|
||
|
||
if some @is_kwarg {
|
||
attr (@lambda.function -> @name.node) kwonlyargs = (named-child-index @param)
|
||
}
|
||
else {
|
||
attr (@lambda.function -> @name.node) args = (named-child-index @param)
|
||
}
|
||
|
||
var default_node = none
|
||
if some @value {
|
||
set default_node = @value.node
|
||
edge @params.node -> default_node
|
||
attr (default_node) ctx = "load"
|
||
}
|
||
if some @is_kwarg {
|
||
attr (@params.node -> default_node) kw_defaults = (named-child-index @param)
|
||
} else {
|
||
attr (@params.node -> default_node) defaults = (named-child-index @param)
|
||
}
|
||
}
|
||
|
||
;;;;;; End of Lambda (`lambda a: ...`)
|
||
|
||
;;;;;; Function (`def a(b, c): ...`)
|
||
|
||
; Much like lambdas, the main difficulty here is that we need to account for the absence of the positional
|
||
; argument separator. We do this using the exact same machinery.
|
||
;
|
||
; Also, all arguments can now also have a type/annotation, so get ready for _twice_ the number of cases.
|
||
|
||
(function_definition
|
||
name: (_) @name
|
||
":" @end
|
||
) @funcdef
|
||
{
|
||
let end = (location-end @end)
|
||
|
||
attr (@funcdef.node) _location_end = end
|
||
|
||
edge @funcdef.node -> @name.node
|
||
attr (@funcdef.node -> @name.node) targets = 0
|
||
attr (@name.node) ctx = "store"
|
||
|
||
let @funcdef.funcexpr = (ast-node @funcdef "FunctionExpr")
|
||
attr (@funcdef.funcexpr) _location_end = end
|
||
attr (@funcdef.node) value = @funcdef.funcexpr
|
||
attr (@funcdef.funcexpr) name = (source-text @name)
|
||
|
||
let @funcdef.function = (ast-node @funcdef "Function")
|
||
attr (@funcdef.function) _location_end = end
|
||
attr (@funcdef.function) name = (source-text @name)
|
||
attr (@funcdef.funcexpr) inner_scope = @funcdef.function
|
||
}
|
||
|
||
(function_definition
|
||
body: (block (_) @stmt)
|
||
) @funcdef
|
||
{
|
||
edge @funcdef.function -> @stmt.node
|
||
attr (@funcdef.function -> @stmt.node) body = (named-child-index @stmt)
|
||
}
|
||
|
||
(function_definition
|
||
parameters: (_) @params
|
||
) @funcdef
|
||
{
|
||
attr (@funcdef.funcexpr) args = @params.node
|
||
}
|
||
|
||
|
||
(function_definition
|
||
parameters: (parameters
|
||
[(list_splat_pattern) (keyword_separator)]? @is_kwarg
|
||
[
|
||
(identifier) @name
|
||
(default_parameter
|
||
name: (_) @name
|
||
value: (_) @value
|
||
)
|
||
(typed_parameter
|
||
(identifier) @name
|
||
.
|
||
type: (type (_) @type)
|
||
)
|
||
(typed_default_parameter
|
||
name: (_) @name
|
||
type: (type (_) @type)
|
||
value: (_) @value
|
||
)
|
||
] @param
|
||
) @params
|
||
) @funcdef
|
||
{
|
||
let none = (ast-node @params "None")
|
||
attr (none) _is_literal = #null
|
||
attr (none) ctx = "load"
|
||
edge @params.node -> none
|
||
|
||
var type_node = none
|
||
if some @type {
|
||
set type_node = @type.node
|
||
edge @params.node -> type_node
|
||
attr (type_node) ctx = "load"
|
||
}
|
||
|
||
if some @is_kwarg {
|
||
attr (@params.node -> type_node) kw_annotations = (named-child-index @param)
|
||
} else {
|
||
attr (@params.node -> type_node) annotations = (named-child-index @param)
|
||
}
|
||
|
||
edge @funcdef.function -> @name.node
|
||
attr (@name.node) ctx = "param"
|
||
|
||
if some @is_kwarg {
|
||
attr (@funcdef.function -> @name.node) kwonlyargs = (named-child-index @param)
|
||
}
|
||
else {
|
||
attr (@funcdef.function -> @name.node) args = (named-child-index @param)
|
||
}
|
||
|
||
var default_node = none
|
||
if some @value {
|
||
set default_node = @value.node
|
||
edge @params.node -> default_node
|
||
attr (default_node) ctx = "load"
|
||
}
|
||
if some @is_kwarg {
|
||
attr (@params.node -> default_node) kw_defaults = (named-child-index @param)
|
||
} else {
|
||
attr (@params.node -> default_node) defaults = (named-child-index @param)
|
||
}
|
||
}
|
||
|
||
; `*args` argument
|
||
(function_definition
|
||
parameters: (parameters
|
||
[
|
||
(list_splat_pattern vararg: (_) @name) @starred
|
||
(typed_parameter
|
||
(list_splat_pattern vararg: (_) @name) @starred
|
||
type: (type (_) @type)
|
||
)
|
||
]
|
||
) @params
|
||
) @funcdef
|
||
{
|
||
attr (@funcdef.function) vararg = @name.node
|
||
attr (@starred.node) ctx = "param" ; Not actually used
|
||
attr (@name.node) ctx = "param"
|
||
if some @type {
|
||
attr (@params.node) varargannotation = @type.node
|
||
attr (@type.node) ctx = "load"
|
||
}
|
||
}
|
||
|
||
; Return type
|
||
(function_definition
|
||
return_type: (type (_) @type)
|
||
) @funcdef
|
||
{
|
||
attr (@funcdef.funcexpr) returns = @type.node
|
||
attr (@type.node) ctx = "load"
|
||
}
|
||
|
||
; `**kwargs` argument
|
||
(function_definition
|
||
(parameters
|
||
[
|
||
(dictionary_splat_pattern kwarg: (identifier) @name)
|
||
(typed_parameter
|
||
(dictionary_splat_pattern kwarg: (identifier) @name)
|
||
type: (type (_) @type)
|
||
)
|
||
]
|
||
) @params
|
||
) @funcdef
|
||
{
|
||
attr (@funcdef.function) kwarg = @name.node
|
||
attr (@name.node) ctx = "param"
|
||
if some @type {
|
||
attr (@params.node) kwargannotation = @type.node
|
||
attr (@type.node) ctx = "load"
|
||
}
|
||
}
|
||
|
||
;;; Decorators
|
||
|
||
(decorated_definition
|
||
. (decorator) @first
|
||
definition: (function_definition name: (_) @name ":" @end) @funcdef
|
||
) @decorator
|
||
{
|
||
attr (@decorator.node) value = @first.node
|
||
attr (@decorator.node) _location_start = (location-start @funcdef)
|
||
attr (@decorator.node) _location_end = (location-end @end)
|
||
edge @decorator.node -> @name.node
|
||
attr (@decorator.node -> @name.node) targets = 0
|
||
}
|
||
|
||
(decorated_definition
|
||
. (decorator) @first
|
||
definition: (class_definition name: (_) @name ":" @end) @funcdef
|
||
) @decorator
|
||
{
|
||
attr (@decorator.node) value = @first.node
|
||
attr (@decorator.node) _location_start = (location-start @funcdef)
|
||
attr (@decorator.node) _location_end = (location-end @end)
|
||
edge @decorator.node -> @name.node
|
||
attr (@decorator.node -> @name.node) targets = 0
|
||
}
|
||
|
||
(decorator (expression) @exp) @decorator
|
||
{
|
||
attr (@decorator.node) _location_start = (location-start @exp)
|
||
attr (@exp.node) ctx = "load"
|
||
}
|
||
|
||
(decorated_definition
|
||
(decorator (expression) @exp1) @dec1
|
||
. (comment)* .
|
||
(decorator (expression) @exp2) @dec2
|
||
) @decorator
|
||
{
|
||
attr (@dec1.node) func = @exp1.node
|
||
edge @dec1.node -> @dec2.node
|
||
attr (@dec1.node -> @dec2.node) positional_args = 0
|
||
}
|
||
|
||
(decorated_definition
|
||
(decorator (expression) @exp) @last
|
||
. (comment)* .
|
||
definition: (function_definition) @funcdef
|
||
) @decorator
|
||
{
|
||
attr (@last.node) func = @exp.node
|
||
edge @last.node -> @funcdef.funcexpr
|
||
attr (@last.node -> @funcdef.funcexpr) positional_args = 0
|
||
attr (@last.node) _location_end = (location-end @exp)
|
||
}
|
||
|
||
(decorated_definition
|
||
(decorator (expression) @exp) @last
|
||
. (comment)* .
|
||
definition: (class_definition) @class
|
||
) @decorator
|
||
{
|
||
attr (@last.node) func = @exp.node
|
||
edge @last.node -> @class.class_expr
|
||
attr (@last.node -> @class.class_expr) positional_args = 0
|
||
attr (@last.node) _location_end = (location-end @exp)
|
||
}
|
||
|
||
;;; Type parameters
|
||
|
||
(function_definition
|
||
type_parameters: (type_parameters type_parameter: (_) @param)
|
||
) @funcdef
|
||
{
|
||
edge @funcdef.function -> @param.node
|
||
attr (@funcdef.function -> @param.node) type_parameters = (named-child-index @param)
|
||
}
|
||
|
||
(class_definition
|
||
type_parameters: (type_parameters type_parameter: (_) @param)
|
||
) @class
|
||
{
|
||
edge @class.class_expr -> @param.node
|
||
attr (@class.class_expr -> @param.node) type_parameters = (named-child-index @param)
|
||
}
|
||
|
||
;;;;;; End of Function (`def a(b, c): ...`)
|
||
|
||
;;;;;; TypeAlias (`type a[...] = ...`)
|
||
|
||
(type_alias_statement
|
||
name: (_) @name
|
||
value: (_) @value
|
||
) @type_alias
|
||
{
|
||
attr (@name.node) ctx = "store"
|
||
attr (@value.node) ctx = "load"
|
||
attr (@type_alias.node) name = @name.node
|
||
attr (@type_alias.node) value = @value.node
|
||
}
|
||
|
||
(type_alias_statement
|
||
type_parameters: (type_parameters type_parameter: (_) @param)
|
||
) @type_alias
|
||
{
|
||
edge @type_alias.node -> @param.node
|
||
attr (@type_alias.node -> @param.node) type_parameters = (named-child-index @param)
|
||
}
|
||
|
||
;;;;;; End of TypeAlias (`type a[...] = ...`)
|
||
|
||
;;;;;; Type parameters (`T: ..., *T, **T`)
|
||
|
||
(typevar_parameter
|
||
name: (_) @name
|
||
bound: (_)? @bound
|
||
default: (_)? @default
|
||
) @typevar
|
||
{
|
||
attr (@name.node) ctx = "store"
|
||
attr (@typevar.node) name = @name.node
|
||
if some @bound {
|
||
attr (@bound.node) ctx = "load"
|
||
attr (@typevar.node) bound = @bound.node
|
||
}
|
||
if some @default {
|
||
attr (@default.node) ctx = "load"
|
||
attr (@typevar.node) default = @default.node
|
||
}
|
||
}
|
||
|
||
(typevartuple_parameter
|
||
name: (_) @name
|
||
default: (_)? @default
|
||
) @typevartuple
|
||
{
|
||
attr (@name.node) ctx = "store"
|
||
attr (@typevartuple.node) name = @name.node
|
||
if some @default {
|
||
attr (@default.node) ctx = "load"
|
||
attr (@typevartuple.node) default = @default.node
|
||
}
|
||
}
|
||
|
||
(paramspec_parameter
|
||
name: (_) @name
|
||
default: (_)? @default
|
||
) @paramspec
|
||
{
|
||
attr (@name.node) ctx = "store"
|
||
attr (@paramspec.node) name = @name.node
|
||
if some @default {
|
||
attr (@default.node) ctx = "load"
|
||
attr (@paramspec.node) default = @default.node
|
||
}
|
||
}
|
||
|
||
;;;;;; End of Type parameters (`T: ..., *T, **T`)
|
||
|
||
; Nodes with an `elts` field
|
||
[
|
||
; Left hand side of an assignment such as `foo, bar = ...`
|
||
(pattern_list element: (_) @elt) @parent
|
||
|
||
; Left hand side of an assignment such as `[foo, bar] = ...`
|
||
(list_pattern element: (_) @elt) @parent
|
||
|
||
; An unadorned tuple (such as in `x = y, z`)
|
||
(expression_list element: (_) @elt) @parent
|
||
|
||
; A regular tuple such as `(x, y, z)`
|
||
(tuple element: (_) @elt) @parent
|
||
|
||
(tuple_pattern element: (_) @elt) @parent
|
||
]
|
||
{
|
||
edge @parent.node -> @elt.node
|
||
attr (@parent.node -> @elt.node) elts = (named-child-index @elt)
|
||
}
|
||
|
||
|
||
|
||
; Expressions that do not produce an `Expr` node in the AST.
|
||
(expression_statement [(assignment) (augmented_assignment)] @inner) @outer
|
||
{
|
||
attr (@outer.node) _skip_to = @inner.node
|
||
}
|
||
|
||
; Expressions that may result in an `Expr` node in the AST
|
||
; ("may" because of the `_skip_to` field).
|
||
(expression_statement . (_) @expr . ) @stmt
|
||
{
|
||
attr (@stmt.node) value = @expr.node
|
||
attr (@expr.node) ctx = "load"
|
||
}
|
||
|
||
|
||
; Sequence expressions where the elements inherit the load/store context
|
||
[
|
||
(list element: (_) @elt)
|
||
(tuple element: (_) @elt)
|
||
(tuple_pattern element: (_) @elt)
|
||
(pattern_list element: (_) @elt)
|
||
(list_pattern element: (_) @elt)
|
||
(expression_list element: (_) @elt)
|
||
(parenthesized_expression inner: (_) @elt)
|
||
(set element: (_) @elt)
|
||
(match_sequence_pattern (_) @elt)
|
||
] @seq
|
||
{
|
||
attr (@elt.node) _inherited_ctx = @seq.node
|
||
}
|
||
|
||
[(tuple element: (_)) (tuple_pattern)] @tup
|
||
{
|
||
attr (@tup.node) parenthesised = #true
|
||
}
|