Overhaul Tree-sitter scope tests

I realized a few days ago that the `#is?` and `#is-not?` predicates — which
puzzled me upon first investigation back in February, and which I'd ignored ever
since — are exactly what I should've been using all along for scope tests.

They are counterparts to `#set!` in that they are two additional buckets in
which to store arbitrary data. And they remove the need to have both
`test.onlyIfFoo` and `test.onlyIfNotFoo`, since the presence of `test.onlyIfFoo`
in the `refutedProperties` bucket can behave opposite to the its presence in the
`assertedProperties` bucket.

This further means that a test called `test.onlyIfFoo` can be renamed
`test.foo`. The `onlyIf` was my way of making it clear through context that this
`#set!` predicate actually enforced a criterion — but that's implicit now that
it'll be using an `#is?` or `#is-not?` predicate.

I also took the opportunity to move `test.final` and `test.shy` to their own
namespace. `final` is an oddball in the sense that it sets criteria for _other_
captures as well as its own, and they're both oddballs in the sense that they
consider the effects of other captures on the same range. And I don't want to
introduce the idea that a predicate like `#is?` can have side effects.

So they stay on `#set!` and live at `capture.final` and `capture.shy`,
respectively.

To summarize, here's a before and after:

  ((string "\"" @punctuation.start)
    (#set! test.onlyIfFirst true)
    (#set! test.final true)

  ((bar) @baz
    (#set! test.onlyIfNotDescendantOfType thud))

becomes

  ((string "\"" @punctuation.start)
    (#is? test.first true)
    (#set! capture.final true)

  ((bar) @baz
    (#is-not? test.descendantOfType thud))

This PR changes all the built-in grammars. I don't know if there are any
third-party modern-Tree-sitter grammars out there except for mine, but I've
added a simple compatibility layer so that all of the old predicates should
still work for now. But I'm going to remove that code before we ship this stuff
for real.
This commit is contained in:
Andrew Dupont 2023-07-20 15:17:01 -07:00
parent edbb1fae1d
commit c8cbd82518
32 changed files with 681 additions and 541 deletions

View File

@ -17,7 +17,7 @@
; anonymous nodes will match under ideal conditions, but might not be present
; if the parser is flummoxed.
((preproc_directive) @keyword.control.directive.c
(#set! test.shy true))
(#set! capture.shy true))
((preproc_ifdef
(identifier) @entity.name.function.preprocessor.c
@ -25,7 +25,7 @@
(preproc_function_def
(identifier) @entity.name.function.preprocessor.c
(#set! test.final true))
(#set! capture.final true))
(system_lib_string) @string.quoted.other.lt-gt.include.c
((system_lib_string) @punctuation.definition.string.begin.c
@ -41,7 +41,7 @@
; of `type_identifier`. Someone's probably just typing on a new line.
(ERROR
(type_identifier) @_IGNORE_
(#set! test.final true))
(#set! capture.final true))
(primitive_type) @storage.type.builtin.c
(type_identifier) @storage.type.other.c
@ -77,21 +77,21 @@
(string_literal
"\"" @punctuation.definition.string.begin.c
(#set! test.onlyIfFirst true))
(#is? test.first true))
(string_literal
"\"" @punctuation.definition.string.end.c
(#set! test.onlyIfLast true))
(#is? test.last true))
(char_literal "'") @string.quoted.single.c
(char_literal
"'" @punctuation.definition.string.begin.c
(#set! test.onlyIfFirst true))
(#is? test.first true))
(char_literal
"'" @punctuation.definition.string.end.c
(#set! test.onlyIfLast true))
(#is? test.last true))
(string_literal (escape_sequence) @constant.character.escape.c)
(char_literal (escape_sequence) @constant.character.escape.c)
@ -171,17 +171,17 @@
(identifier) @support.function.c99.c
; Regex copied from the TM grammar.
(#match? @support.function.c99.c "^(_Exit|(?:nearbyint|nextafter|nexttoward|netoward|nan)[fl]?|a(?:cos|sin)h?[fl]?|abort|abs|asctime|assert|atan(?:[h2]?[fl]?)?|atexit|ato[ifl]|atoll|bsearch|btowc|cabs[fl]?|cacos|cacos[fl]|cacosh[fl]?|calloc|carg[fl]?|casinh?[fl]?|catanh?[fl]?|cbrt[fl]?|ccosh?[fl]?|ceil[fl]?|cexp[fl]?|cimag[fl]?|clearerr|clock|clog[fl]?|conj[fl]?|copysign[fl]?|cosh?[fl]?|cpow[fl]?|cproj[fl]?|creal[fl]?|csinh?[fl]?|csqrt[fl]?|ctanh?[fl]?|ctime|difftime|div|erfc?[fl]?|exit|fabs[fl]?|exp(?:2[fl]?|[fl]|m1[fl]?)?|fclose|fdim[fl]?|fe[gs]et(?:env|exceptflag|round)|feclearexcept|feholdexcept|feof|feraiseexcept|ferror|fetestexcept|feupdateenv|fflush|fgetpos|fgetw?[sc]|floor[fl]?|fmax?[fl]?|fmin[fl]?|fmod[fl]?|fopen|fpclassify|fprintf|fputw?[sc]|fread|free|freopen|frexp[fl]?|fscanf|fseek|fsetpos|ftell|fwide|fwprintf|fwrite|fwscanf|genv|get[sc]|getchar|gmtime|gwc|gwchar|hypot[fl]?|ilogb[fl]?|imaxabs|imaxdiv|isalnum|isalpha|isblank|iscntrl|isdigit|isfinite|isgraph|isgreater|isgreaterequal|isinf|isless(?:equal|greater)?|isw?lower|isnan|isnormal|isw?print|isw?punct|isw?space|isunordered|isw?upper|iswalnum|iswalpha|iswblank|iswcntrl|iswctype|iswdigit|iswgraph|isw?xdigit|labs|ldexp[fl]?|ldiv|lgamma[fl]?|llabs|lldiv|llrint[fl]?|llround[fl]?|localeconv|localtime|log[2b]?[fl]?|log1[p0][fl]?|longjmp|lrint[fl]?|lround[fl]?|malloc|mbr?len|mbr?towc|mbsinit|mbsrtowcs|mbstowcs|memchr|memcmp|memcpy|memmove|memset|mktime|modf[fl]?|perror|pow[fl]?|printf|puts|putw?c(?:har)?|qsort|raise|rand|remainder[fl]?|realloc|remove|remquo[fl]?|rename|rewind|rint[fl]?|round[fl]?|scalbl?n[fl]?|scanf|setbuf|setjmp|setlocale|setvbuf|signal|signbit|sinh?[fl]?|snprintf|sprintf|sqrt[fl]?|srand|sscanf|strcat|strchr|strcmp|strcoll|strcpy|strcspn|strerror|strftime|strlen|strncat|strncmp|strncpy|strpbrk|strrchr|strspn|strstr|strto[kdf]|strtoimax|strtol[dl]?|strtoull?|strtoumax|strxfrm|swprintf|swscanf|system|tan|tan[fl]|tanh[fl]?|tgamma[fl]?|time|tmpfile|tmpnam|tolower|toupper|trunc[fl]?|ungetw?c|va_arg|va_copy|va_end|va_start|vfw?printf|vfw?scanf|vprintf|vscanf|vsnprintf|vsprintf|vsscanf|vswprintf|vswscanf|vwprintf|vwscanf|wcrtomb|wcscat|wcschr|wcscmp|wcscoll|wcscpy|wcscspn|wcsftime|wcslen|wcsncat|wcsncmp|wcsncpy|wcspbrk|wcsrchr|wcsrtombs|wcsspn|wcsstr|wcsto[dkf]|wcstoimax|wcstol[dl]?|wcstombs|wcstoull?|wcstoumax|wcsxfrm|wctom?b|wmem(?:set|chr|cpy|cmp|move)|wprintf|wscanf)$")
(#set! test.final true))
(#set! capture.final true))
; The "foo" in `thing->troz->foo(...)`.
(call_expression
(field_expression
field: (field_identifier) @support.other.function.c)
(#set! test.final true))
(#set! capture.final true))
(call_expression
(identifier) @support.other.function.c
(#set! test.final true))
(#set! capture.final true))
; NUMBERS
; =======
@ -301,18 +301,18 @@
(parameter_list
"(" @punctuation.definition.parameters.begin.bracket.round.c
")" @punctuation.definition.parameters.end.bracket.round.c
(#set! test.final true))
(#set! capture.final true))
(parenthesized_expression
"(" @punctuation.definition.expression.begin.bracket.round.c
")" @punctuation.definition.expression.end.bracket.round.c
(#set! test.final true))
(#set! capture.final true))
(if_statement
condition: (parenthesized_expression
"(" @punctuation.definition.expression.begin.bracket.round.c
")" @punctuation.definition.expression.end.bracket.round.c
(#set! test.final true)))
(#set! capture.final true)))
"{" @punctuation.definition.block.begin.bracket.curly.c
"}" @punctuation.definition.block.end.bracket.curly.c

View File

@ -8,7 +8,7 @@
; we might have to make this configurable somehow.
(switch_statement
body: (compound_statement "}" @match
(#set! test.onlyIfLast true))
(#is? test.last true))
(#set! indent.matchIndentOf parent.startPosition))
; 'case' and 'default' need to be indented one level more than their containing

View File

@ -17,7 +17,7 @@
; anonymous nodes will match under ideal conditions, but might not be present
; if the parser is flummoxed.
((preproc_directive) @keyword.control.directive.c
(#set! test.shy true))
(#set! capture.shy true))
((preproc_ifdef
(identifier) @entity.name.function.preprocessor.c
@ -25,11 +25,11 @@
(preproc_function_def
(identifier) @entity.name.function.preprocessor.c
(#set! test.final true))
(#set! capture.final true))
(preproc_function_def
(identifier) @entity.name.function.preprocessor.cpp
(#set! test.final true)
(#set! capture.final true)
)
(system_lib_string) @string.quoted.other.lt-gt.include.c
@ -46,14 +46,14 @@
; of `type_identifier`. Someone's probably just typing on a new line.
(ERROR
(type_identifier) @_IGNORE_
(#set! test.final true))
(#set! capture.final true))
(primitive_type) @storage.type.builtin.cpp
(class_specifier
(type_identifier) @entity.name.class.cpp
(#set! test.final true))
(#set! capture.final true))
(type_identifier) @storage.type.other.cpp
; (struct_specifier) @storage.type.cpp
@ -113,17 +113,17 @@
(identifier) @support.function.c99.cpp
; Regex copied from the TM grammar.
(#match? @support.function.c99.cpp "^(_Exit|(?:nearbyint|nextafter|nexttoward|netoward|nan)[fl]?|a(?:cos|sin)h?[fl]?|abort|abs|asctime|assert|atan(?:[h2]?[fl]?)?|atexit|ato[ifl]|atoll|bsearch|btowc|cabs[fl]?|cacos|cacos[fl]|cacosh[fl]?|calloc|carg[fl]?|casinh?[fl]?|catanh?[fl]?|cbrt[fl]?|ccosh?[fl]?|ceil[fl]?|cexp[fl]?|cimag[fl]?|clearerr|clock|clog[fl]?|conj[fl]?|copysign[fl]?|cosh?[fl]?|cpow[fl]?|cproj[fl]?|creal[fl]?|csinh?[fl]?|csqrt[fl]?|ctanh?[fl]?|ctime|difftime|div|erfc?[fl]?|exit|fabs[fl]?|exp(?:2[fl]?|[fl]|m1[fl]?)?|fclose|fdim[fl]?|fe[gs]et(?:env|exceptflag|round)|feclearexcept|feholdexcept|feof|feraiseexcept|ferror|fetestexcept|feupdateenv|fflush|fgetpos|fgetw?[sc]|floor[fl]?|fmax?[fl]?|fmin[fl]?|fmod[fl]?|fopen|fpclassify|fprintf|fputw?[sc]|fread|free|freopen|frexp[fl]?|fscanf|fseek|fsetpos|ftell|fwide|fwprintf|fwrite|fwscanf|genv|get[sc]|getchar|gmtime|gwc|gwchar|hypot[fl]?|ilogb[fl]?|imaxabs|imaxdiv|isalnum|isalpha|isblank|iscntrl|isdigit|isfinite|isgraph|isgreater|isgreaterequal|isinf|isless(?:equal|greater)?|isw?lower|isnan|isnormal|isw?print|isw?punct|isw?space|isunordered|isw?upper|iswalnum|iswalpha|iswblank|iswcntrl|iswctype|iswdigit|iswgraph|isw?xdigit|labs|ldexp[fl]?|ldiv|lgamma[fl]?|llabs|lldiv|llrint[fl]?|llround[fl]?|localeconv|localtime|log[2b]?[fl]?|log1[p0][fl]?|longjmp|lrint[fl]?|lround[fl]?|malloc|mbr?len|mbr?towc|mbsinit|mbsrtowcs|mbstowcs|memchr|memcmp|memcpy|memmove|memset|mktime|modf[fl]?|perror|pow[fl]?|printf|puts|putw?c(?:har)?|qsort|raise|rand|remainder[fl]?|realloc|remove|remquo[fl]?|rename|rewind|rint[fl]?|round[fl]?|scalbl?n[fl]?|scanf|setbuf|setjmp|setlocale|setvbuf|signal|signbit|sinh?[fl]?|snprintf|sprintf|sqrt[fl]?|srand|sscanf|strcat|strchr|strcmp|strcoll|strcpy|strcspn|strerror|strftime|strlen|strncat|strncmp|strncpy|strpbrk|strrchr|strspn|strstr|strto[kdf]|strtoimax|strtol[dl]?|strtoull?|strtoumax|strxfrm|swprintf|swscanf|system|tan|tan[fl]|tanh[fl]?|tgamma[fl]?|time|tmpfile|tmpnam|tolower|toupper|trunc[fl]?|ungetw?c|va_arg|va_copy|va_end|va_start|vfw?printf|vfw?scanf|vprintf|vscanf|vsnprintf|vsprintf|vsscanf|vswprintf|vswscanf|vwprintf|vwscanf|wcrtomb|wcscat|wcschr|wcscmp|wcscoll|wcscpy|wcscspn|wcsftime|wcslen|wcsncat|wcsncmp|wcsncpy|wcspbrk|wcsrchr|wcsrtombs|wcsspn|wcsstr|wcsto[dkf]|wcstoimax|wcstol[dl]?|wcstombs|wcstoull?|wcstoumax|wcsxfrm|wctom?b|wmem(?:set|chr|cpy|cmp|move)|wprintf|wscanf)$")
(#set! test.final true))
(#set! capture.final true))
; The "foo" in `thing->troz->foo(...)`.
(call_expression
(field_expression
field: (field_identifier) @support.other.function.cpp)
(#set! test.final true))
(#set! capture.final true))
(call_expression
(identifier) @support.other.function.cpp
(#set! test.final true))
(#set! capture.final true))
; STRINGS
@ -135,21 +135,21 @@
(string_literal
"\"" @punctuation.definition.string.begin.cpp
(#set! test.onlyIfFirst true))
(#is? test.first true))
(string_literal
"\"" @punctuation.definition.string.end.cpp
(#set! test.onlyIfLast true))
(#is? test.last true))
(char_literal "'") @string.quoted.single.cpp
(char_literal
"'" @punctuation.definition.string.begin.cpp
(#set! test.onlyIfFirst true))
(#is? test.first true))
(char_literal
"'" @punctuation.definition.string.end.cpp
(#set! test.onlyIfLast true))
(#is? test.last true))
(string_literal (escape_sequence) @constant.character.escape.cpp)
(char_literal (escape_sequence) @constant.character.escape.cpp)
@ -369,18 +369,18 @@
(parameter_list
"(" @punctuation.definition.parameters.begin.bracket.round.cpp
")" @punctuation.definition.parameters.end.bracket.round.cpp
(#set! test.final true))
(#set! capture.final true))
(parenthesized_expression
"(" @punctuation.definition.expression.begin.bracket.round.cpp
")" @punctuation.definition.expression.end.bracket.round.cpp
(#set! test.final true))
(#set! capture.final true))
(if_statement
condition: (condition_clause
"(" @punctuation.definition.expression.begin.bracket.round.cpp
")" @punctuation.definition.expression.end.bracket.round.cpp
(#set! test.final true)))
(#set! capture.final true)))
"{" @punctuation.definition.block.begin.bracket.curly.cpp
"}" @punctuation.definition.block.end.bracket.curly.cpp

View File

@ -20,7 +20,7 @@
; we might have to make this configurable somehow.
(switch_statement
body: (compound_statement "}" @match
(#set! test.onlyIfLast true))
(#is? test.last true))
(#set! indent.matchIndentOf parent.startPosition))
; 'case' and 'default' need to be indented one level more than their containing

View File

@ -1,36 +1,36 @@
;; Collections
(list_lit
"(" @punctuation.section.list.begin (#set! test.onlyIfNotDescendantOfNodeWithData "clojure.dismissTag")
"(" @punctuation.section.list.begin (#is-not? test.descendantOfNodeWithData "clojure.dismissTag")
")" @punctuation.section.list.end)
@meta.list
(vec_lit
"[" @punctuation.section.vector.begin (#set! test.onlyIfNotDescendantOfNodeWithData "clojure.dismissTag")
"[" @punctuation.section.vector.begin (#is-not? test.descendantOfNodeWithData "clojure.dismissTag")
"]" @punctuation.section.vector.end)
@meta.vector
(map_lit
"{" @punctuation.section.map.begin (#set! test.onlyIfNotDescendantOfNodeWithData "clojure.dismissTag")
"{" @punctuation.section.map.begin (#is-not? test.descendantOfNodeWithData "clojure.dismissTag")
"}" @punctuation.section.map.end)
@meta.map
(set_lit
("#" "{") @punctuation.section.set.begin (#set! test.onlyIfNotDescendantOfNodeWithData "clojure.dismissTag")
("#" "{") @punctuation.section.set.begin (#is-not? test.descendantOfNodeWithData "clojure.dismissTag")
"}" @punctuation.section.set.end)
@meta.set
((regex_lit) @string.regexp (#set! test.onlyIfNotDescendantOfNodeWithData "clojure.dismissTag"))
((sym_lit) @meta.symbol (#set! test.onlyIfNotDescendantOfNodeWithData "clojure.dismissTag"))
((kwd_lit) @constant.keyword (#set! test.onlyIfNotDescendantOfNodeWithData "clojure.dismissTag"))
((str_lit) @string.quoted.double (#set! test.onlyIfNotDescendantOfNodeWithData "clojure.dismissTag"))
((num_lit) @constant.numeric (#set! test.onlyIfNotDescendantOfNodeWithData "clojure.dismissTag"))
((nil_lit) @constant.language (#set! test.onlyIfNotDescendantOfNodeWithData "clojure.dismissTag"))
((bool_lit) @constant.language (#set! test.onlyIfNotDescendantOfNodeWithData clojure.dismissTag))
((regex_lit) @string.regexp (#is-not? test.descendantOfNodeWithData "clojure.dismissTag"))
((sym_lit) @meta.symbol (#is-not? test.descendantOfNodeWithData "clojure.dismissTag"))
((kwd_lit) @constant.keyword (#is-not? test.descendantOfNodeWithData "clojure.dismissTag"))
((str_lit) @string.quoted.double (#is-not? test.descendantOfNodeWithData "clojure.dismissTag"))
((num_lit) @constant.numeric (#is-not? test.descendantOfNodeWithData "clojure.dismissTag"))
((nil_lit) @constant.language (#is-not? test.descendantOfNodeWithData "clojure.dismissTag"))
((bool_lit) @constant.language (#is-not? test.descendantOfNodeWithData clojure.dismissTag))
(comment) @comment.line.semicolon
((dis_expr)
@comment.block.clojure
(#set! test.onlyIfConfig language-clojure.dismissTag)
(#is? test.config language-clojure.dismissTag)
(#set! clojure.dismissTag true)
(#set! test.final true))
(#set! capture.final true))
("ERROR" @invalid.illegal)

View File

@ -1,38 +1,38 @@
;; Function calls
(anon_fn_lit
"(" @punctuation.section.expression.begin (#set! test.onlyIfNotDescendantOfNodeWithData "clojure.dismissTag")
"(" @punctuation.section.expression.begin (#is-not? test.descendantOfNodeWithData "clojure.dismissTag")
.
(sym_lit) @entity.name.function @meta.expression
")" @punctuation.section.expression.end)
(list_lit
"(" @punctuation.section.expression.begin (#set! test.onlyIfNotDescendantOfNodeWithData "clojure.dismissTag")
"(" @punctuation.section.expression.begin (#is-not? test.descendantOfNodeWithData "clojure.dismissTag")
.
(sym_lit) @entity.name.function @meta.expression
")" @punctuation.section.expression.end)
; NS things like require
((sym_name) @meta.symbol (#eq? @meta.symbol "import") (#set! test.onlyIfNotDescendantOfNodeWithData "clojure.dismissTag")) @keyword.control
((sym_name) @meta.symbol (#eq? @meta.symbol "require") (#set! test.onlyIfNotDescendantOfNodeWithData "clojure.dismissTag")) @keyword.control
((sym_name) @meta.symbol (#eq? @meta.symbol "import") (#is-not? test.descendantOfNodeWithData "clojure.dismissTag")) @keyword.control
((sym_name) @meta.symbol (#eq? @meta.symbol "require") (#is-not? test.descendantOfNodeWithData "clojure.dismissTag")) @keyword.control
;; USE
((sym_name)
@meta.symbol
(#eq? @meta.symbol "use")
(#set! test.onlyIfConfig language-clojure.markDeprecations)
(#set! test.onlyIfNotDescendantOfNodeWithData clojure.dismissTag))
(#is? test.config language-clojure.markDeprecations)
(#is-not? test.descendantOfNodeWithData clojure.dismissTag))
@invalid.deprecated
((sym_name)
@meta.symbol
(#eq? @meta.symbol "use")
(#set! test.onlyIfNotConfig language-clojure.markDeprecations)
(#set! test.onlyIfNotDescendantOfNodeWithData clojure.dismissTag))
(#is-not? test.config language-clojure.markDeprecations)
(#is-not? test.descendantOfNodeWithData clojure.dismissTag))
@keyword.control
;; Namespace declaration
((list_lit
"(" @punctuation.section.expression.begin (#set! test.onlyIfNotDescendantOfNodeWithData "clojure.dismissTag")
"(" @punctuation.section.expression.begin (#is-not? test.descendantOfNodeWithData "clojure.dismissTag")
.
(sym_lit) @meta.definition.global @keyword.control (#eq? @meta.definition.global "ns")
.
@ -45,12 +45,12 @@
"("
.
(kwd_lit) @invalid.deprecated (#eq? @invalid.deprecated ":use")
(#set! test.onlyIfDescendantOfNodeWithData isNamespace)
(#set! test.onlyIfConfig language-clojure.markDeprecations))
(#is? test.descendantOfNodeWithData isNamespace)
(#is? test.config language-clojure.markDeprecations))
;; Definition
(list_lit
"(" @punctuation.section.expression.begin (#set! test.onlyIfNotDescendantOfNodeWithData "clojure.dismissTag")
"(" @punctuation.section.expression.begin (#is-not? test.descendantOfNodeWithData "clojure.dismissTag")
.
(sym_lit) @keyword.control (#match? @keyword.control "^def")
.
@ -64,14 +64,14 @@
(sym_lit) @meta.definition.global @keyword.control (#eq? @keyword.control "comment")
")" @punctuation.section.expression.end)
@comment.block.clojure
(#set! test.onlyIfConfig language-clojure.commentTag)
(#is? test.config language-clojure.commentTag)
(#set! clojure.dismissTag true))
(list_lit
"(" @punctuation.section.expression.begin
.
(sym_lit) @keyword.control (#eq? @keyword.control "comment")
(#set! test.onlyIfNotConfig language-clojure.commentTag)
(#is-not? test.config language-clojure.commentTag)
")" @punctuation.section.expression.end)
;;; COPY-PASTED from edn-highlights.
@ -80,32 +80,32 @@
;; Collections
(vec_lit
"[" @punctuation.section.vector.begin (#set! test.onlyIfNotDescendantOfNodeWithData "clojure.dismissTag")
"[" @punctuation.section.vector.begin (#is-not? test.descendantOfNodeWithData "clojure.dismissTag")
"]" @punctuation.section.vector.end)
@meta.vector
(map_lit
"{" @punctuation.section.map.begin (#set! test.onlyIfNotDescendantOfNodeWithData "clojure.dismissTag")
"{" @punctuation.section.map.begin (#is-not? test.descendantOfNodeWithData "clojure.dismissTag")
"}" @punctuation.section.map.end)
@meta.map
(set_lit
("#" "{") @punctuation.section.set.begin (#set! test.onlyIfNotDescendantOfNodeWithData "clojure.dismissTag")
("#" "{") @punctuation.section.set.begin (#is-not? test.descendantOfNodeWithData "clojure.dismissTag")
"}" @punctuation.section.set.end)
@meta.set
((regex_lit) @string.regexp (#set! test.onlyIfNotDescendantOfNodeWithData "clojure.dismissTag"))
((sym_lit) @meta.symbol (#set! test.onlyIfNotDescendantOfNodeWithData "clojure.dismissTag"))
((kwd_lit) @constant.keyword (#set! test.onlyIfNotDescendantOfNodeWithData "clojure.dismissTag"))
((str_lit) @string.quoted.double (#set! test.onlyIfNotDescendantOfNodeWithData "clojure.dismissTag"))
((num_lit) @constant.numeric (#set! test.onlyIfNotDescendantOfNodeWithData "clojure.dismissTag"))
((nil_lit) @constant.language (#set! test.onlyIfNotDescendantOfNodeWithData "clojure.dismissTag"))
((bool_lit) @constant.language (#set! test.onlyIfNotDescendantOfNodeWithData clojure.dismissTag))
((regex_lit) @string.regexp (#is-not? test.descendantOfNodeWithData "clojure.dismissTag"))
((sym_lit) @meta.symbol (#is-not? test.descendantOfNodeWithData "clojure.dismissTag"))
((kwd_lit) @constant.keyword (#is-not? test.descendantOfNodeWithData "clojure.dismissTag"))
((str_lit) @string.quoted.double (#is-not? test.descendantOfNodeWithData "clojure.dismissTag"))
((num_lit) @constant.numeric (#is-not? test.descendantOfNodeWithData "clojure.dismissTag"))
((nil_lit) @constant.language (#is-not? test.descendantOfNodeWithData "clojure.dismissTag"))
((bool_lit) @constant.language (#is-not? test.descendantOfNodeWithData clojure.dismissTag))
(comment) @comment.line.semicolon
((dis_expr)
@comment.block.clojure
(#set! test.onlyIfConfig language-clojure.dismissTag)
(#is? test.config language-clojure.dismissTag)
(#set! clojure.dismissTag true)
(#set! test.final true))
(#set! capture.final true))
("ERROR" @invalid.illegal)

View File

@ -8,15 +8,15 @@
(descendant_selector
(tag_name) @_IGNORE_
(#set! test.final true))
(#set! capture.final true))
(ERROR
(attribute_name) @_IGNORE_
(#set! test.final true))
(#set! capture.final true))
((ERROR
(attribute_name) @invalid.illegal)
(#set! test.final true))
(#set! capture.final true))
; WORKAROUND:
;
@ -41,8 +41,8 @@
; Claim this range and block it from being scoped as a tag name.
(pseudo_element_selector
(tag_name) @_IGNORE_
(#set! test.onlyIfLast true)
(#set! test.final true))
(#is? test.last true)
(#set! capture.final true))
; COMMENTS
; ========
@ -94,7 +94,7 @@
((pseudo_class_selector (class_name) (arguments) .) @entity.other.attribute-name.pseudo-class.css
(#set! adjust.startAt lastChild.previousSibling.previousSibling.startPosition)
(#set! adjust.endAt lastChild.previousSibling.endPosition)
(#set! test.final true))
(#set! capture.final true))
; Pseudo-classes with arguments: the ":nth-of-type" in `li:nth-of-type(2n-1)`.
((pseudo_class_selector (class_name) .) @entity.other.attribute-name.pseudo-class.css
@ -120,7 +120,7 @@
(declaration
(property_name) @variable.other.assignment.css
(#match? @variable.other.assignment.css "^--" )
(#set! test.final true))
(#set! capture.final true))
; PROPERTIES
; ==========
@ -233,7 +233,7 @@
; The parser is permissive and supports at-rule keywords that don't currently
; exist, so we'll set a fallback scope for those.
((at_keyword) @keyword.control.at-rule.other.css
(#set! test.shy true))
(#set! capture.shy true))
[(to) (from)] @keyword.control._TYPE_.css
(important) @keyword.control.important.css
@ -255,10 +255,10 @@
(rule_set
(block "{" @punctuation.section.property-list.begin.bracket.curly.css)
(#set! test.final true))
(#set! capture.final true))
(rule_set
(block "}" @punctuation.section.property-list.end.bracket.curly.css)
(#set! test.final true))
(#set! capture.final true))
"{" @punctuation.bracket.curly.begin.css
"}" @punctuation.bracket.curly.end.css
@ -269,7 +269,7 @@
[":" "::"] @punctuation.definition.entity.css)
(":" @punctuation.separator.key-value.css
(#set! test.shy true))
(#set! capture.shy true))
; SECTIONS
@ -278,7 +278,7 @@
; Used by `autocomplete-css`.
(rule_set (block) @meta.block.inside-selector.css)
((block) @meta.block.css
(#set! test.shy true))
(#set! capture.shy true))
; Used by `autocomplete-css`. Includes everything before the opening brace so
; that autocompletion of selector segments works even when the selector is not
@ -305,7 +305,7 @@
.
(ERROR) @meta.property-value.css
(#match? @meta.property-value.css "^\s?!i")
(#set! test.final true))
(#set! capture.final true))
(
(declaration) @meta.property-value.css

View File

@ -8,7 +8,7 @@
((comment) @comment.line.double-slash.go
(#match? @comment.line.double-slash.go "^\/\/")
(#set! test.final true))
(#set! capture.final true))
((comment) @punctuation.definition.comment.go
(#match? @punctuation.definition.comment.go "^\/\/")
@ -33,7 +33,7 @@
(type_declaration
(type_spec
name: (type_identifier) @entity.name.type.go)
(#set! test.final true))
(#set! capture.final true))
(type_identifier) @storage.type.other.go
@ -83,7 +83,7 @@
(call_expression
(identifier) @support.function.builtin.go
(#match? @support.function.builtin.go "^(?:append|cap|close|complex|copy|delete|imag|len|make|new|panic|print|println|real|recover)$")
(#set! test.final true))
(#set! capture.final true))
(call_expression
(identifier) @support.other.function.go)
@ -111,7 +111,7 @@
(package_clause
(package_identifier) @entity.name.package.go
(#set! test.final true))
(#set! capture.final true))
(package_identifier) @support.object.package.go
@ -121,11 +121,11 @@
((interpreted_string_literal "\"") @string.quoted.double.go)
(interpreted_string_literal
"\"" @punctuation.definition.string.begin.go
(#set! test.onlyIfFirst true))
(#is? test.first true))
(interpreted_string_literal
"\"" @punctuation.definition.string.end.go
(#set! test.onlyIfLast true))
(#is? test.last true))
(escape_sequence) @constant.character.escape.go
@ -257,13 +257,13 @@
(parameter_list
"(" @punctuation.definition.parameters.begin.bracket.round.go
")" @punctuation.definition.parameters.end.bracket.round.go
(#set! test.final true))
(#set! capture.final true))
(composite_literal
body: (literal_value
"{" @punctuation.definition.struct.begin.bracket.curly.go
"}" @punctuation.definition.struct.end.bracket.curly.go
(#set! test.final true)))
(#set! capture.final true)))
"{" @punctuation.definition.begin.bracket.curly.go
"}" @punctuation.definition.end.bracket.curly.go
@ -277,7 +277,7 @@
(function_declaration
(block) @meta.block.function.go
(#set! test.final true))
(#set! capture.final true))
(block) @meta.block.go

View File

@ -2,11 +2,11 @@
; of consuming all their preceding whitespace, including newlines.
([(comment_directive) (directive) (output_directive)] @meta.embedded.block.ejs
(#set! adjust.startBeforeFirstMatchOf "\\S")
(#set! test.onlyIfNotEndsOnSameRowAs firstChild.endPosition))
(#is-not? test.endsOnSameRowAs firstChild.endPosition))
([(comment_directive) (directive) (output_directive)] @meta.embedded.line.ejs
(#set! adjust.startBeforeFirstMatchOf "\\S")
(#set! test.onlyIfEndsOnSameRowAs firstChild.endPosition))
(#is? test.endsOnSameRowAs firstChild.endPosition))
(comment_directive) @comment.block.ejs

View File

@ -2,17 +2,17 @@
; "%>" @_IGNORE_
; (#match! @_IGNORE_ "^$")
; ) @_IGNORE_
; (#set! test.final true))
; (#set! capture.final true))
; We compare these to `firstChild.endPosition` because directives have a habit
; of consuming all their preceding whitespace, including newlines.
([(comment_directive) (directive) (output_directive)] @meta.embedded.block.erb
(#set! adjust.startBeforeFirstMatchOf "\\S")
(#set! test.onlyIfNotEndsOnSameRowAs firstChild.endPosition))
(#is-not? test.endsOnSameRowAs firstChild.endPosition))
([(comment_directive) (directive) (output_directive)] @meta.embedded.line.erb
(#set! adjust.startBeforeFirstMatchOf "\\S")
(#set! test.onlyIfEndsOnSameRowAs firstChild.endPosition))
(#is? test.endsOnSameRowAs firstChild.endPosition))
(comment_directive) @comment.block.erb

View File

@ -39,32 +39,32 @@
(start_tag
(tag_name) @entity.name.tag.structure._TEXT_.html
(#match? @entity.name.tag.structure._TEXT_.html "^(body|head|html|BODY|HEAD|HTML)$")
(#set! test.final true))
(#set! capture.final true))
(end_tag
(tag_name) @entity.name.tag.structure._TEXT_.html
(#match? @entity.name.tag.structure._TEXT_.html "^(body|head|html|BODY|HEAD|HTML)$")
(#set! test.final true))
(#set! capture.final true))
(start_tag
(tag_name) @entity.name.tag.block._TEXT_.html
(#match? @entity.name.tag.block._TEXT_.html "^(address|blockquote|dd|div|section|article|aside|header|footer|nav|menu|dl|dt|fieldset|form|frame|frameset|h1|h2|h3|h4|h5|h6|iframe|noframes|object|ol|p|ul|applet|center|dir|hr|pre|ADDRESS|BLOCKQUOTE|DD|DIV|SECTION|ARTICLE|ASIDE|HEADER|FOOTER|NAV|MENU|DL|DT|FIELDSET|FORM|FRAME|FRAMESET|H1|H2|H3|H4|H5|H6|IFRAME|NOFRAMES|OBJECT|OL|P|UL|APPLET|CENTER|DIR|HR|PRE)$")
(#set! test.final true))
(#set! capture.final true))
(end_tag
(tag_name) @entity.name.tag.block._TEXT_.html
(#match? @entity.name.tag.block._TEXT_.html "^(address|blockquote|dd|div|section|article|aside|header|footer|nav|menu|dl|dt|fieldset|form|frame|frameset|h1|h2|h3|h4|h5|h6|iframe|noframes|object|ol|p|ul|applet|center|dir|hr|pre|ADDRESS|BLOCKQUOTE|DD|DIV|SECTION|ARTICLE|ASIDE|HEADER|FOOTER|NAV|MENU|DL|DT|FIELDSET|FORM|FRAME|FRAMESET|H1|H2|H3|H4|H5|H6|IFRAME|NOFRAMES|OBJECT|OL|P|UL|APPLET|CENTER|DIR|HR|PRE)$")
(#set! test.final true))
(#set! capture.final true))
(start_tag
(tag_name) @entity.name.tag.inline._TEXT_.html
(#match? @entity.name.tag.inline._TEXT_.html "^(a|abbr|acronym|area|b|base|basefont|bdo|big|br|button|caption|cite|code|col|colgroup|del|dfn|em|font|head|html|i|img|input|ins|isindex|kbd|label|legend|li|link|map|meta|noscript|optgroup|option|param|q|s|samp|script|select|small|span|strike|strong|style|sub|sup|table|tbody|td|textarea|tfoot|th|thead|title|tr|tt|u|var|A|ABBR|ACRONYM|AREA|B|BASE|BASEFONT|BDO|BIG|BR|BUTTON|CAPTION|CITE|CODE|COL|COLGROUP|DEL|DFN|EM|FONT|HEAD|HTML|I|IMG|INPUT|INS|ISINDEX|KBD|LABEL|LEGEND|LI|LINK|MAP|META|NOSCRIPT|OPTGROUP|OPTION|PARAM|Q|S|SAMP|SCRIPT|SELECT|SMALL|SPAN|STRIKE|STRONG|STYLE|SUB|SUP|TABLE|TBODY|TD|TEXTAREA|TFOOT|TH|THEAD|TITLE|TR|TT|U|VAR)$")
(#set! test.final true))
(#set! capture.final true))
(end_tag
(tag_name) @entity.name.tag.inline._TEXT_.html
(#match? @entity.name.tag.inline._TEXT_.html "^(a|abbr|acronym|area|b|base|basefont|bdo|big|br|button|caption|cite|code|col|colgroup|del|dfn|em|font|head|html|i|img|input|ins|isindex|kbd|label|legend|li|link|map|meta|noscript|optgroup|option|param|q|s|samp|script|select|small|span|strike|strong|style|sub|sup|table|tbody|td|textarea|tfoot|th|thead|title|tr|tt|u|var|A|ABBR|ACRONYM|AREA|B|BASE|BASEFONT|BDO|BIG|BR|BUTTON|CAPTION|CITE|CODE|COL|COLGROUP|DEL|DFN|EM|FONT|HEAD|HTML|I|IMG|INPUT|INS|ISINDEX|KBD|LABEL|LEGEND|LI|LINK|MAP|META|NOSCRIPT|OPTGROUP|OPTION|PARAM|Q|S|SAMP|SCRIPT|SELECT|SMALL|SPAN|STRIKE|STRONG|STYLE|SUB|SUP|TABLE|TBODY|TD|TEXTAREA|TFOOT|TH|THEAD|TITLE|TR|TT|U|VAR)$")
(#set! test.final true))
(#set! capture.final true))
; ELEMENTS
@ -109,10 +109,10 @@
; Single- and double-quotes around attribute values.
((quoted_attribute_value ["\"" "'"] @punctuation.definition.string.begin.html)
(#set! test.onlyIfFirst true))
(#is? test.first true))
((quoted_attribute_value ["\"" "'"] @punctuation.definition.string.end.html)
(#set! test.onlyIfLast true))
(#is? test.last true))
; If this matches, the value is double-quoted.
(quoted_attribute_value "\"") @string.quoted.double.html
@ -123,7 +123,7 @@
; Prevent quoted attribute values from having `string.unquoted` applied.
(quoted_attribute_value
(attribute_value) @_IGNORE_
(#set! test.final true))
(#set! capture.final true))
; The "foo" in `<div class=foo>`.
; Because of the preceding rule, if this matches and passes all tests, the

View File

@ -11,7 +11,7 @@
((comment) @comment.block.documentation.javadoc.java
(#match? @comment.block.documentation.javadoc.java "^/\\*\\*")
(#set! test.final true)
(#set! capture.final true)
(#set! highlight.invalidateOnChange true))
((comment) @comment.block.java
@ -36,7 +36,7 @@
(superclass
"extends" @storage.modifier.extends.java
(type_identifier) @entity.other.inherited-class.java
(#set! test.final true))
(#set! capture.final true))
(class_declaration body: (_) @meta.class.body.java)
@ -65,14 +65,14 @@
(extends_interfaces
(interface_type_list
(type_identifier) @entity.other.inherited-class.java)
(#set! test.final true))
(#set! capture.final true))
(super_interfaces "implements" @storage.modifier.implements.java)
(super_interfaces
(interface_type_list
(type_identifier) @entity.other.inherited-class.java)
(#set! test.final true))
(#set! capture.final true))
(static_initializer "static" @storage.modifier.static.java)
@ -101,21 +101,21 @@
(object_creation_expression (type_identifier)
@support.other.class.java
(#set! test.final true))
(#set! capture.final true))
; WORKAROUND: This matches often when the user is typing, so we shouldn't
; highlight it until we know for sure what it is.
(ERROR
(type_identifier) @_IGNORE_
(#set! test.final true))
(#set! capture.final true))
; WORKAROUND: A chain like `System.out.println` shouldn't match at all until
; it's out of an ERROR state; this should catch all references no matter how
; long the chain is.
(scoped_type_identifier
(type_identifier) @_IGNORE
(#set! test.onlyIfDescendantOfType ERROR)
(#set! test.final true))
(#is? test.descendantOfType ERROR)
(#set! capture.final true))
(type_identifier) @storage.type.java
(type_parameter (identifier) @storage.type.java)
@ -182,22 +182,22 @@
(field_access (identifier) @constant.other.java
(#match? @constant.other.java "^[A-Z][A-Z0-9_\\$]+$")
(#set! test.final true))
(#set! capture.final true))
(field_access
object: (identifier) @support.other.class.java
(#match? @support.other.class.java "^[A-Z]")
(#set! test.final true))
(#set! capture.final true))
(field_access
field: (identifier) @support.other.class.java
(#match? @support.other.class.java "^[A-Z]")
(#set! test.final true))
(#set! capture.final true))
(method_invocation (identifier) @constant.other.java
(#match? @constant.other.java "^[A-Z][A-Z0-9_\\$]+$")
(#set! test.final true))
(#set! capture.final true))
; VARIABLES
@ -374,17 +374,17 @@
condition: (parenthesized_expression
"(" @punctuation.definition.expression.begin.bracket.round.java
")" @punctuation.definition.expression.end.bracket.round.java
(#set! test.final true)))
(#set! capture.final true)))
(formal_parameters
"(" @punctuation.definition.parameters.begin.bracket.round.java
")" @punctuation.definition.parameters.end.bracket.round.java
(#set! test.final true))
(#set! capture.final true))
(argument_list
"(" @punctuation.definition.arguments.begin.bracket.round.java
")" @punctuation.definition.arguments.end.bracket.round.java
(#set! test.final true))
(#set! capture.final true))
"{" @punctuation.definition.block.begin.bracket.curly.java

View File

@ -1,7 +1,7 @@
; The closing brace of a switch statement's body should match the indentation of the line where the switch statement starts.
(switch_statement
body: (switch_block "}" @match
(#set! test.onlyIfLast true))
(#is? test.last true))
(#set! indent.matchIndentOf parent.parent.startPosition))
; 'case' and 'default' need to be indented one level more than their containing

View File

@ -7,33 +7,33 @@
(string
"'" @punctuation.definition.string.begin.js
(#set! test.onlyIfFirst true))
(#is? test.first))
(string
"'" @punctuation.definition.string.end.js
(#set! test.onlyIfLast true))
(#is? test.last))
; Double-quoted.
(string "\"") @string.quoted.double.js
(string
"\"" @punctuation.definition.string.begin.js
(#set! test.onlyIfFirst true))
(#is? test.first))
(string
"\"" @punctuation.definition.string.end.js
(#set! test.onlyIfLast true))
(#is? test.last))
; Template string (backticks).
(template_string) @string.quoted.template.js
(template_string
"`" @punctuation.definition.string.begin.js
(#set! test.onlyIfFirst true))
(#is? test.first))
(template_string
"`" @punctuation.definition.string.end.js
(#set! test.onlyIfLast true))
(#is? test.last))
; Interpolations inside of template strings.
(template_substitution) @meta.embedded.line.interpolation.js
@ -41,7 +41,7 @@
(template_substitution
"${" @punctuation.section.embedded.begin.js
"}" @punctuation.section.embedded.end.js
(#set! final true))
(#set! capture.final true))
(string
(escape_sequence) @constant.character.escape.js)
@ -71,8 +71,7 @@
(assignment_expression
left: (identifier) @variable.other.assignment.js)
; A variable object destructuring:
; The "foo" in `let { foo } = something`
; The "bar" in `foo.bar = true`
(assignment_expression
left: (member_expression
property: (property_identifier)) @variable.other.assignment.property.js)
@ -87,6 +86,9 @@
; `object_pattern` appears to only be encountered in assignment expressions, so
; this won't match other uses of object/prop shorthand.
;
; A variable object destructuring:
; The "foo" in `let { foo } = something`
((object_pattern
(shorthand_property_identifier_pattern) @variable.other.assignment.destructuring.js))
@ -112,6 +114,12 @@
value: (assignment_pattern
left: (identifier) @variable.other.assignment.destructuring.js)))
; A "rest" parameter destructuring:
; The "bar" in `let { foo, ...bar } = something`
(object_pattern
(rest_pattern
(identifier) @variable.other.assignment.destructuring.rest.js))
; A variable array destructuring:
; The "foo" and "bar" in `let [foo, bar] = something`
(variable_declarator
@ -142,7 +150,7 @@
(pair_pattern
key: (_) @entity.other.attribute-name.js
value: (identifier) @variable.other.assignment.loop.js)
(#set! test.final true)))
(#set! capture.final true)))
; The "error" in `} catch (error) {`
(catch_clause
@ -221,7 +229,7 @@
(assignment_expression
left: (member_expression
property: (property_identifier) @entity.name.function.definition.js
(#set! test.final true))
(#set! capture.final true))
right: [(arrow_function) (function)])
; Function variable assignment:
@ -262,7 +270,7 @@
(#eq? @support.object.builtin.js "Array")
property: (property_identifier) @support.function.builtin.js
(#match? @support.function.builtin.js "^(from|isArray|of)$")
(#set! test.final true))
(#set! capture.final true))
; Date methods.
(member_expression
@ -270,7 +278,7 @@
(#eq? @support.object.builtin.js "Date")
property: (property_identifier) @support.function.builtin.js
(#match? @support.function.builtin.js "^(now|parse|UTC)$")
(#set! test.final true))
(#set! capture.final true))
; JSON methods.
(member_expression
@ -278,7 +286,7 @@
(#eq? @support.object.builtin.js "JSON")
property: (property_identifier) @support.function.builtin.js
(#match? @support.function.builtin.js "^(parse|stringify)$")
(#set! test.final true))
(#set! capture.final true))
; Math methods.
(member_expression
@ -286,7 +294,7 @@
(#eq? @support.object.builtin.js "Math")
property: (property_identifier) @support.function.builtin.js
(#match? @support.function.builtin.js "^(abs|acos|acosh|asin|asinh|atan|atanh|atan2|cbrt|ceil|clz32|cos|cosh|exp|expm1|floor|fround|hypot|imul|log|log1p|log10|log2|max|min|pow|random|round|sign|sin|sinh|sqrt|tan|tanh|trunc)$")
(#set! test.final true))
(#set! capture.final true))
; Object methods.
(member_expression
@ -294,7 +302,7 @@
(#eq? @support.object.builtin.js "Object")
property: (property_identifier) @support.function.builtin.js
(#match? @support.function.builtin.js "^(assign|create|defineProperty|defineProperties|entries|freeze|fromEntries|getOwnPropertyDescriptor|getOwnPropertyDescriptors|getOwnPropertyNames|getOwnPropertySymbols|getPrototypeOf|is|isExtensible|isFrozen|isSealed|keys|preventExtensions|seal|setPrototypeOf|values)$")
(#set! test.final true))
(#set! capture.final true))
; Reflect methods.
(member_expression
@ -302,7 +310,7 @@
(#eq? @support.object.builtin.js "Reflect")
property: (property_identifier) @support.function.builtin.js
(#match? @support.function.builtin.js "^(apply|construct|defineProperty|deleteProperty|get|getOwnPropertyDescriptor|getPrototypeOf|has|isExtensible|ownKeys|preventExtensions|set|setPrototypeOf)$")
(#set! test.final true))
(#set! capture.final true))
; Intl.X instantiations.
(new_expression
@ -311,25 +319,25 @@
(#eq? @support.object.builtin.js "Intl")
property: (property_identifier) @support.class.builtin.js
(#match? @support.class.builtin.js "^(Collator|DateTimeFormat|DisplayNames|ListFormat|Locale|NumberFormat|PluralRules|Segmenter)$"))
(#set! test.final true))
(#set! capture.final true))
; Built-in class instantiations.
(new_expression
constructor: (identifier) @support.class.builtin.instance.js
(#match? @support.class.builtin.instance.js "^(AggregateError|Array|ArrayBuffer|BigInt64Array|BigUint64Array|Boolean|DataView|Date|Error|EvalError|FinalizationRegistry|Float32Array|Float64Array|Function|ImageCapture|Int8Array|Int16Array|Int32Array|Map|Number|Object|Promise|RangeError|ReferenceError|RegExp|Set|String|SyntaxError|TypeError|Uint8Array|Uint8ClampedArray|Uint16Array|Uint32Array|URIError|URL|WeakMap|WeakRef|WeakSet|XMLHttpRequest)$")
(#set! test.final true))
(#set! capture.final true))
; Built-in constructors that can be invoked without `new`.
(call_expression
(identifier) @support.function.builtin.js
(#match? @support.function.builtin.js "^(AggregateError|Array|ArrayBuffer|Boolean|BigInt|Error|EvalError|Function|Number|Object|Proxy|RangeError|String|Symbol|SyntaxError|URIError)$")
(#set! test.final true))
(#set! capture.final true))
; Built-in functions.
(call_expression
(identifier) @support.function.builtin.js
(#match? @support.function.builtin.js "^(decodeURI|decodeURIComponent|encodeURI|encodeURIComponent|eval|isFinite|isNaN|parseFloat|parseInt)$")
(#set! test.final true))
(#set! capture.final true))
; Built-in `console` functions.
@ -338,7 +346,7 @@
(#eq? @support.class.builtin.console.js "console")
property: (property_identifier) @support.function.builtin.console.js
(#match? @support.function.builtin.console.js "^(assert|clear|count(Reset)?|debug|dir(xml)?|error|group(End)?info|log|profile(End)?|table|time(End|Log|Stamp)?|trace|warn)$")
(#set! test.final true))
(#set! capture.final true))
; Static methods of `Promise`.
(member_expression
@ -346,7 +354,7 @@
(#eq? @support.class.builtin.js "Promise")
property: (property_identifier) @support.function.builtin.js
(#match? @support.function.builtin.js "^(all|allSettled|any|race|resolve|reject)$")
(#set! test.final true))
(#set! capture.final true))
; All “well-known” symbols (as they are referred to in the spec).
(member_expression
@ -354,7 +362,7 @@
property: (property_identifier) @support.property.builtin.js
(#eq? @support.class.builtin.js "Symbol")
(#match? @support.property.builtin.js "^(asyncIterator|hasInstance|isConcatSpreadable|iterator|match|matchAll|replace|search|split|species|toPrimitive|toStringTag|unscopables)$")
(#set! test.final true))
(#set! capture.final true))
; Static methods of `Symbol`.
(member_expression
@ -362,28 +370,28 @@
(#eq? @support.class.builtin.js "Symbol")
property: (property_identifier) @support.function.builtin.js
(#match? @support.function.builtin.js "^(for|keyFor)$")
(#set! test.final true))
(#set! capture.final true))
; Other built-in objects.
((identifier) @support.class.builtin.js
(#match? @support.class.builtin.js "^(Symbol)$")
(#set! test.final true))
(#set! capture.final true))
; Deprecated built-in functions.
(call_expression
(identifier) @invalid.deprecated.function.js
(#match? @invalid.deprecated.function.js "^(escape|unescape)$")
(#set! test.final true))
(#set! capture.final true))
; Built-in DOM classes.
((identifier) @support.class.builtin.js
(#match? @support.class.builtin.js "^(Document|Element|HTMLElement|HTMLDocument|HTML(Select|BR|HR|LI|Div|Map|Mod|Pre|Area|Base|Body|Data|Font|Form|Head|Html|Link|Menu|Meta|Slot|Span|Time|Audio|DList|Embed|Image|Input|Label|Media|Meter|OList|Param|Quote|Style|Table|Title|Track|UList|Video|Anchor|Button|Canvas|Dialog|IFrame|Legend|Object|Option|Output|Script|Source|Content|Details|Heading|Marquee|Picture|Unknown|DataList|FieldSet|FrameSet|MenuItem|OptGroup|Progress|TableCol|TableRow|Template|TextArea|Paragraph|TableCell|Options|TableCaption|TableSection|FormControls))$")
(#set! test.final true))
(#set! capture.final true))
; Deprecated built-in DOM classes.
((identifier) @invalid.deprecated.class.js
(#match? @invalid.deprecated.class.js "^(HTMLShadowElement)$")
(#set! test.final true))
(#set! capture.final true))
; Built-in DOM methods on `document`.
(call_expression
@ -392,7 +400,7 @@
(#eq? @support.object.builtin.js "document")
property: (property_identifier) @support.function.method.builtin.js
(#match? @support.function.method.builtin.js "^(adoptNode|append|caretPositionFromPoint|caretRangeFromPoint|createAttribute(?:NS)?|createCDATASection|createComment|createDocumentFragment|createElement(?:NS)?|createEvent|createNodeIterator|createProcessingInstruction|createRange|createTextNode|createTreeWalker|elementFromPoint|elementsFromPoint|exitFullscreen|exitPictureInPicture|exitPointerLock|getAnimations|getElementById|getElementsByClassName|getElementsByTagName(?:NS)?|getSelection|hasStorageAccess|importNode|prepend|querySelector|querySelectorAll|releaseCapture|replaceChildren|requestStorageAccess|createExpression|createNSResolver|evaluate|getElementsByName|hasFocus|write|writeln|open|close)$")
(#set! test.final true)))
(#set! capture.final true)))
; Built-in DOM methods on nodes. These will show up as builtins on _any_ class, but
; they're distinctive enough that we're OK with that possibility.
@ -400,7 +408,7 @@
function: (member_expression
property: (property_identifier) @support.function.method.builtin.js
(#match? @support.function.method.builtin.js "^(addEventListener|appendChild|cloneNode|compareDocumentPosition|contains|getElementsByClassName|getElementsByTagName(?:NS)?|getRootNode|hasChildNodes|insertBefore|isDefaultNamespace|isEqualNode|isSameNode|lookupPrefix|lookupNamespaceURI|normalize|querySelector|querySelectorAll|removeChild|replaceChild|removeEventListener)$")
(#set! test.final true)))
(#set! capture.final true)))
; FUNCTION CALLS
@ -414,7 +422,7 @@
(call_expression
function: (member_expression
property: (property_identifier) @support.other.function.method.js
(#set! test.final true)))
(#set! capture.final true)))
; OBJECTS
@ -621,23 +629,23 @@
((identifier) @support.object.builtin._TEXT_.js
(#match? @support.object.builtin._TEXT_.js "^(arguments|module|window|document)$")
(#is-not? local)
(#set! test.final true))
(#set! capture.final true))
((identifier) @support.object.builtin.filename.js
(#eq? @support.object.builtin.filename.js "__filename")
(#is-not? local)
(#set! test.final true))
(#set! capture.final true))
((identifier) @support.object.builtin.dirname.js
(#eq? @support.object.builtin.dirname.js "__dirname")
(#is-not? local)
(#set! test.final true))
(#set! capture.final true))
((identifier) @support.function.builtin.require.js
(#eq? @support.function.builtin.require.js "require")
(#is-not? local)
(#set! test.final true))
(#set! capture.final true))
[
(null)
@ -651,7 +659,7 @@
((identifier) @constant.language.infinity.js
(#eq? @constant.language.infinity.js "Infinity")
(#set! test.final true))
(#set! capture.final true))
(arrow_function
"=>" @punctuation.function.arrow.js)
@ -659,7 +667,7 @@
; Things that `LOOK_LIKE_CONSTANTS`.
([(property_identifier) (identifier)] @constant.other.js
(#match? @constant.other.js "^[A-Z_][A-Z0-9_]*$")
(#set! test.shy true))
(#set! capture.shy true))
; TODO: What do we do with computed object keys?
;
@ -683,11 +691,11 @@
(regex) @string.regexp.js
(regex
"/" @punctuation.definition.string.begin.js
(#set! test.onlyIfFirst true))
(#is? test.first))
(regex
"/" @punctuation.definition.string.end.js
(#set! test.onlyIfLast true))
(#is? test.last))
(regex_flags) @keyword.other.js
@ -707,7 +715,7 @@
; The "Foo" in `</Foo>`.
(jsx_closing_element
"/" @punctuation.definition.tag.end.js
(#set! test.final true)
(#set! capture.final true)
name: (identifier) @entity.name.tag.js)
; The "bar" in `<Foo bar={true} />`.
@ -717,7 +725,7 @@
; All JSX expressions/interpolations within braces.
((jsx_expression) @meta.embedded.block.jsx.js
(#match? @meta.embedded.block.jsx.js "\\n")
(#set! test.final true))
(#set! capture.final true))
(jsx_expression) @meta.embedded.line.jsx.js
@ -731,14 +739,14 @@
(jsx_self_closing_element
"<" @punctuation.definition.tag.begin.js
(#set! test.final true))
(#set! capture.final true))
((jsx_self_closing_element
; The "/>" in `<Foo />`, extended to cover both anonymous nodes at once.
"/") @punctuation.definition.tag.end.js
(#set! adjust.startAt lastChild.previousSibling.startPosition)
(#set! adjust.endAt lastChild.endPosition)
(#set! test.final true))
(#set! capture.final true))
; OPERATORS
@ -810,7 +818,7 @@
(#set! prohibitsOptionalChaining true))
((optional_chain) @invalid.illegal.optional-chain.js
(#set! test.onlyIfDescendantOfNodeWithData prohibitsOptionalChaining))
(#is? test.descendantOfNodeWithData prohibitsOptionalChaining))
; PUNCTUATION
@ -819,37 +827,37 @@
(formal_parameters
"(" @punctuation.definition.parameters.begin.bracket.round.js
")"@punctuation.definition.parameters.end.bracket.round.js
(#set! test.final true))
(#set! capture.final true))
(object
"{" @punctuation.definition.object.begin.bracket.curly.js
"}" @punctuation.definition.object.end.bracket.curly.js
(#set! test.final true))
(#set! capture.final true))
(arguments
"(" @punctuation.definition.arguments.begin.bracket.round.js
")" @punctuation.definition.arguments.end.bracket.round.js
(#set! test.final true))
(#set! capture.final true))
(computed_property_name
"[" @punctuation.definition.computed-property.begin.bracket.square.js
"]" @punctuation.definition.computed-property.end.bracket.square.js
(#set! test.final true))
(#set! capture.final true))
(subscript_expression
"[" @punctuation.definition.subscript.begin.bracket.square.js
"]" @punctuation.definition.subscript.end.bracket.square.js
(#set! test.final true))
(#set! capture.final true))
(array
"[" @punctuation.definition.array.begin.bracket.square.js
"]" @punctuation.definition.array.end.bracket.square.js
(#set! test.final true))
(#set! capture.final true))
(array_pattern
"[" @punctuation.definition.array.begin.bracket.square.js
"]" @punctuation.definition.array.end.bracket.square.js
(#set! test.final true))
(#set! capture.final true))
"{" @punctuation.definition.block.begin.bracket.curly.js
"}" @punctuation.definition.block.end.bracket.curly.js
@ -860,15 +868,15 @@
(array
"," @punctuation.separator.array.comma.js
(#set! test.final true))
(#set! capture.final true))
(array_pattern
"," @punctuation.separator.array.comma.js
(#set! test.final true))
(#set! capture.final true))
(pair
":" @punctuation.separator.key-value.colon.js
(#set! test.final true))
(#set! capture.final true))
";" @punctuation.terminator.statement.js
"," @punctuation.separator.comma.js
@ -880,23 +888,23 @@
; The interiors of functions (useful for snippets and commands).
(method_definition
body: (statement_block) @meta.block.function.js
(#set! test.final true))
(#set! capture.final true))
(function_declaration
body: (statement_block) @meta.block.function.js
(#set! test.final true))
(#set! capture.final true))
(generator_function_declaration
body: (statement_block) @meta.block.function.js
(#set! test.final true))
(#set! capture.final true))
(function
body: (statement_block) @meta.block.function.js
(#set! test.final true))
(#set! capture.final true))
(generator_function
body: (statement_block) @meta.block.function.js
(#set! test.final true))
(#set! capture.final true))
; The interior of a class body (useful for snippets and commands).
(class_body) @meta.block.class.js
@ -943,4 +951,4 @@
; TODO: Any identifier not yet scoped might as well be scoped as a variable,
; but that's an opinionated choice. We might want to make this configurable.
; ((identifier) @variable.other.other.js
; (#set! test.shy true))
; (#set! capture.shy true))

View File

@ -1,6 +1,6 @@
; ((template_string) @ignore
; (#set! test.onlyIfNotOnStartingOrEndingRow true))
; (#is-not? test.OnStartingOrEndingRow true))
; STATEMENT BLOCKS
; ================
@ -17,7 +17,7 @@
; of the line where the switch statement starts.
(switch_statement
body: (switch_body "}" @match
(#set! test.onlyIfLast true))
(#is? test.last true))
(#set! indent.matchIndentOf parent.startPosition))
; 'case' and 'default' need to be indented one level more than their containing
@ -33,16 +33,16 @@
; An `if` statement without an opening brace should indent the next line…
(if_statement
condition: (parenthesized_expression ")" @indent
(#set! test.onlyIfLastTextOnRow true)))
(#is? test.lastTextOnRow true)))
; (as should a braceless `else`…)
("else" @indent
(#set! test.onlyIfLastTextOnRow true))
(#is? test.lastTextOnRow true))
; …and keep that indent level if the user types a comment before the
; consequence…
(if_statement
consequence: (empty_statement) @match
(#set! test.onlyIfNotStartsOnSameRowAs parent.startPosition)
(#is-not? test.startsOnSameRowAs parent.startPosition)
(#set! indent.matchIndentOf parent.startPosition)
(#set! indent.offsetIndent 1))
@ -87,24 +87,24 @@
(throw_statement)
(debugger_statement)
] @dedent.next
(#set! test.onlyIfNotStartsOnSameRowAs parent.startPosition))
(#is-not? test.startsOnSameRowAs parent.startPosition))
; HANGING INDENT ON SPLIT LINES
; =============================
; TODO: We might want to make this configurable behavior with the
; `onlyIfConfig` scope test.
; `Config` scope test.
; Any of these at the end of a line indicate the next line should be indented…
(["||" "&&" "?"] @indent
(#set! test.onlyIfLastTextOnRow true))
(#is? test.lastTextOnRow true))
; …and the line after that should be dedented.
(binary_expression
["||" "&&"]
right: (_) @dedent.next
(#set! test.onlyIfNotStartsOnSameRowAs parent.startPosition))
(#is-not? test.startsOnSameRowAs parent.startPosition))
; let foo = this.longTernaryCondition() ?
; consequenceWhichIsItselfRatherLong :
@ -113,7 +113,7 @@
; …followed by a dedent.
(ternary_expression
alternative: (_) @dedent.next
(#set! test.onlyIfNotStartsOnSameRowAs parent.startPosition))
(#is-not? test.startsOnSameRowAs parent.startPosition))
; DEDENT-NEXT IN LIMITED SCENARIOS
@ -125,15 +125,15 @@
; short, forEach, toHave, itsOwn, line);
;
; (arguments ")" @dedent.next
; (#set! test.onlyIfNotStartsOnSameRowAs parent.firstChild.startPosition)
; (#set! test.onlyIfNotFirstTextOnRow true))
; (#is-not? test.startsOnSameRowAs parent.firstChild.startPosition)
; (#is-not? test.firstTextOnRow true))
; GENERAL
; =======
; Weed out `}`s that should not signal dedents.
(template_substitution "}" @_IGNORE_ (#set! test.final true))
(template_substitution "}" @_IGNORE_ (#set! capture.final true))
[
"{"

View File

@ -1,13 +1,13 @@
; Highlight this comment even if it's not “valid” JSDoc.
((ERROR) @comment.block.documentation.js.jsdoc
(#set! test.onlyIfRoot true))
(#is? test.root true))
((ERROR) @punctuation.definition.begin.comment.js.jsdoc
(#set! test.onlyIfRoot true)
(#is? test.root true)
(#set! adjust.startAndEndAroundFirstMatchOf "^/\\*\\*"))
((ERROR) @punctuation.definition.end.comment.js.jsdoc
(#set! test.onlyIfRoot true)
(#is? test.root true)
(#set! adjust.startAndEndAroundFirstMatchOf "(?:\\*)?\\*/$"))

View File

@ -38,7 +38,7 @@
(anonymous_capturing_group
"(" @punctuation.definition.group.begin.bracket.round.regexp
")" @punctuation.definition.group.end.bracket.round.regexp
(#set! test.final true))
(#set! capture.final true))
"|" @keyword.operator.or.regexp
["*" "+"] @keyword.operator.quantifier.regexp

View File

@ -6,11 +6,11 @@
(string
"\"" @punctuation.definition.string.begin.json
(#set! test.onlyIfFirst true))
(#is? test.first true))
(string
"\"" @punctuation.definition.string.end.json
(#set! test.onlyIfLast true))
(#is? test.last true))
(string) @string.quoted.double.json
@ -36,11 +36,11 @@
(object
"," @punctuation.separator.object.comma.json
(#set! test.final true))
(#set! capture.final true))
(array
"," @punctuation.separator.array.comma.json
(#set! test.final true))
(#set! capture.final true))
"," @punctuation.separator.comma.json
":" @punctuation.separator.key-value.colon.json

View File

@ -22,14 +22,14 @@
((identifier) @support.type.exception.python
(#match? @support.type.exception.python "^(BaseException|Exception|TypeError|StopAsyncIteration|StopIteration|ImportError|ModuleNotFoundError|OSError|ConnectionError|BrokenPipeError|ConnectionAbortedError|ConnectionRefusedError|ConnectionResetError|BlockingIOError|ChildProcessError|FileExistsError|FileNotFoundError|IsADirectoryError|NotADirectoryError|InterruptedError|PermissionError|ProcessLookupError|TimeoutError|EOFError|RuntimeError|RecursionError|NotImplementedError|NameError|UnboundLocalError|AttributeError|SyntaxError|IndentationError|TabError|LookupError|IndexError|KeyError|ValueError|UnicodeError|UnicodeEncodeError|UnicodeDecodeError|UnicodeTranslateError|AssertionError|ArithmeticError|FloatingPointError|OverflowError|ZeroDivisionError|SystemError|ReferenceError|BufferError|MemoryError|Warning|UserWarning|DeprecationWarning|PendingDeprecationWarning|SyntaxWarning|RuntimeWarning|FutureWarning|ImportWarning|UnicodeWarning|BytesWarning|ResourceWarning|GeneratorExit|SystemExit|KeyboardInterrupt)$")
(#set! test.final true))
(#set! capture.final true))
; These methods have magic interpretation by python and are generally called
; indirectly through syntactic constructs.
((identifier) @support.function.magic.python
(#match? @support.function.magic.python "^__(abs|add|and|bool|bytes|call|cmp|coerce|complex|contains|del|delattr|delete|delitem|delslice|dir|div|divmod|enter|eq|exit|float|floordiv|format|ge|get|getattr|getattribute|getitem|getslice|gt|hash|hex|iadd|iand|idiv|ifloordiv|ilshift|imatmul|imod|imul|index|init|instancecheck|int|invert|ior|ipow|irshift|isub|iter|itruediv|ixor|le|len|length_hint|long|lshift|lt|matmul|missing|mod|mul|ne|neg|next|new|nonzero|oct|or|pos|pow|radd|rand|rdiv|rdivmod|repr|reversed|rfloordiv|rlshift|rmatmul|rmod|rmul|ror|round|rpow|rrshift|rshift|rsub|rtruediv|rxor|set|setattr|setitem|setslice|str|sub|subclasscheck|truediv|unicode|xor)__$")
(#set! test.onlyIfDescendantOfType call)
(#set! test.final true))
(#is? test.descendantOfType call)
(#set! capture.final true))
; Magic variables which a class/module may have.
((identifier) @support.variable.magic.python
@ -38,30 +38,30 @@
(call
function: (identifier) @support.type.constructor.python
(#match? @support.type.constructor.python "^[A-Z][a-z_]+")
(#set! test.final true))
(#set! capture.final true))
(call
function: (attribute
attribute: (identifier) @support.type.constructor.python)
(#match? @support.type.constructor.python "^[A-Z][a-z_]+")
(#set! test.final true))
(#set! capture.final true))
(call
(identifier) @support.function.builtin.python
(#match? @support.function.builtin.python "^(__import__|abs|all|any|ascii|bin|bool|bytearray|bytes|callable|chr|classmethod|compile|complex|delattr|dict|dir|divmod|enumerate|eval|exec|filter|float|format|frozenset|getattr|globals|hasattr|hash|help|hex|id|input|int|isinstance|issubclass|iter|len|list|locals|map|max|memoryview|min|next|object|oct|open|ord|pow|print|property|range|repr|reversed|round|set|setattr|slice|sorted|staticmethod|str|sum|super|tuple|type|vars|zip|file|long|raw_input|reduce|reload|unichr|unicode|xrange|apply|buffer|coerce|intern|execfile)$")
(#set! test.final true))
(#set! capture.final true))
; `NotImplemented` is a constant, but is not recognized as such by the parser.
((identifier) @constant.language.not-implemented.python
(#eq? @constant.language.not-implemented.python "NotImplemented")
(#set! test.final true))
(#set! capture.final true))
; `Ellipsis` is also a constant, and though there don't seem to be any use
; cases for using it directly instead of `...`, we should at least mark it as a
; constant because that's how it'll be interpreted by Python.
((identifier) @constant.language.ellipsis.python
(#eq? @constant.language.ellipsis.python "Ellipsis")
(#set! test.final true))
(#set! capture.final true))
; CLASSES
@ -114,12 +114,12 @@
(decorator
(call
function: (identifier) @_IGNORE_
(#set! test.final true)))
(#set! capture.final true)))
(call
function: (attribute
attribute: (identifier) @support.other.function.python)
(#set! test.final true))
(#set! capture.final true))
(call
function: (identifier) @support.other.function.python)
@ -208,11 +208,11 @@
(string
_ @punctuation.definition.string.begin.python
(#set! test.onlyIfFirst true))
(#is? test.first true))
(string
_ @punctuation.definition.string.end.python
(#set! test.onlyIfLast true))
(#is? test.last true))
(string prefix: _ @storage.type.string.python
(#match? @storage.type.string.python "^[bBfFrRuU]+")
@ -302,11 +302,11 @@
; `self` and `cls` are just conventions, but they are _strong_ conventions.
((identifier) @variable.language.self.python
(#eq? @variable.language.self.python "self")
(#set! test.final true))
(#set! capture.final true))
((identifier) @variable.language.cls.python
(#eq? @variable.language.cls.python "cls")
(#set! test.final true))
(#set! capture.final true))
(keyword_argument
name: (identifier) @variable.parameter.function.python)
@ -336,7 +336,7 @@
; =========
(list_splat_pattern "*" @keyword.operator.splat.python
(#set! test.final true))
(#set! capture.final true))
"=" @keyword.operator.assignment.python
@ -389,7 +389,7 @@
(call
(identifier) @keyword.other._TEXT_.python
(#match? @keyword.other._TEXT_.python "^(exec|print)$")
(#set! test.final true))
(#set! capture.final true))
(print_statement "print" @keyword.other.print.python)
@ -412,42 +412,42 @@
(function_definition
":" @punctuation.definition.function.colon.python
(#set! test.final true))
(#set! capture.final true))
(dictionary (pair ":" @puncutation.separator.key-value.python))
(parameters
"(" @punctuation.definition.parameters.begin.bracket.round.python
")" @punctuation.definition.parameters.end.bracket.round.python
(#set! test.final true))
(#set! capture.final true))
(parameters
"," @punctuation.separator.parameters.comma.python
(#set! test.final true))
(#set! capture.final true))
(argument_list
"(" @punctuation.definition.arguments.begin.bracket.round.python
")" @punctuation.definition.arguments.end.bracket.round.python
(#set! test.final true))
(#set! capture.final true))
(argument_list
"," @punctuation.separator.arguments.comma.python
(#set! test.final true))
(#set! capture.final true))
(tuple
"(" @punctuation.definition.tuple.begin.bracket.round.python
")" @punctuation.definition.tuple.end.bracket.round.python
(#set! test.final true))
(#set! capture.final true))
(tuple
"," @punctuation.separator.tuple.comma.python
(#set! test.final true))
(#set! capture.final true))
(dictionary
"{" @punctuation.definition.dictionary.begin.bracket.curly.python
"}" @punctuation.definition.dictionary.end.bracket.curly.python
(#set! test.final true))
(#set! capture.final true))
(dictionary
"," @punctuation.separator.dictionary.comma.python
(#set! test.final true))
(#set! capture.final true))

View File

@ -1,9 +1,9 @@
(dictionary
(pair ":" @_IGNORE_
(#set! test.final true)))
(#set! capture.final true)))
((lambda ":" @_IGNORE_)
(#set! test.final true))
(#set! capture.final true))
":" @indent

View File

@ -1,6 +1,6 @@
; NOTES:
;
; (#set! test.final "true") means that any later rule that matches this exact range
; (#set! capture.final "true") means that any later rule that matches this exact range
; will be ignored.
;
; (#set! shy "true") means that this rule will be ignored if any previous rule
@ -14,7 +14,7 @@
method: (identifier) @keyword.other.special-method.new.ruby
(#match? @keyword.other.special-method.new.ruby "new")
(#match? @support.class.builtin.ruby "^(Array|BasicObject|Date|DateTime|Dir|Exception|File|FileUtils|Float|Hash|Integer|Object|Pathname|Process|Range|Rational|Regexp|Set|Struct|Time|Symbol)$")
(#set! test.final true))
(#set! capture.final true))
; Common receivers of "static" method calls. Some of these are gems, but
@ -31,12 +31,12 @@
(
(identifier) @support.function.kernel.ruby
(#match? @support.function.kernel.ruby "^(abort|at_exit|autoload|binding|callcc|caller|caller_locations|chomp|chop|eval|exec|exit|fork|format|gets|global_variables|gsub|lambda|load|local_variables|open|p|print|printf|proc|putc|puts|rand|readline|readlines|select|set_trace_func|sleep|spawn|sprintf|srand|sub|syscall|system|test|trace_var|trap|untrace_var|warn)$")
(#set! test.final "true"))
(#set! capture.final "true"))
(call
method: (identifier) @keyword.other.pseudo-method.ruby
(#match? @keyword.other.pseudo-method.ruby "^(alias_method)$")
(#set! test.final true))
(#set! capture.final true))
(call
method: (identifier) @support.other.function.ruby)
@ -48,18 +48,18 @@
; "Foo" in `class Foo`
(class
name: (constant) @entity.name.type.class.ruby
(#set! test.final true))
(#set! capture.final true))
; "<" in `class Foo < Bar`
(superclass
"<" @punctuation.separator.inheritance.ruby
(constant) @entity.other.inherited-class.ruby
(#set! test.final "true"))
(#set! capture.final "true"))
; "Foo" in `module Foo`
(module
name: (constant) @entity.name.type.module.ruby
(#set! test.final "true"))
(#set! capture.final "true"))
; Mark `new` as a special method in all contexts, from `Foo.new` to
@ -72,7 +72,7 @@
(call
(identifier) @keyword.other.special-method.ruby
(#match? @keyword.other.special-method.ruby "^(loop|include|extend|prepend|raise|fail|attr_reader|attr_writer|attr_accessor|attr|catch|throw|private_class_method|public_class_method|module_function|refine|using)$")
(#set! test.final true))
(#set! capture.final true))
((identifier) @keyword.other.special-method
(#match? @keyword.other.special-method "^(private|protected|public)$"))
@ -86,7 +86,7 @@
(scope_resolution
scope: (constant) @entity.other.inherited-class.ruby
name: (constant) @entity.other.inherited-class.ruby)
(#set! test.final true))
(#set! capture.final true))
; Marks all nodes on the left side of `scope_resolution` with an arbitrary key.
(scope_resolution
@ -97,18 +97,18 @@
; into a Chain::Of::Namespaces they happen to be.
; "Foo" and "Bar" in `Foo::Bar::Baz`.
((constant) @support.other.namespace.ruby
(#set! test.onlyIfDescendantOfNodeWithData isOnLeftSideOfNamespaceChain)
(#set! test.final true))
(#is? test.descendantOfNodeWithData isOnLeftSideOfNamespaceChain)
(#set! capture.final true))
; "::" in `Foo::Bar`.
(scope_resolution
"::" @keyword.operator.namespace.ruby
(#set! test.final true))
(#set! capture.final true))
; "Bar" in `Foo::Bar`, regardless of the length of the chain.
(scope_resolution
name: (constant) @support.other.class.ruby
(#set! test.final true))
(#set! capture.final true))
@ -123,7 +123,7 @@
(method
name: [(identifier) (constant)] @entity.name.function.ruby
(#set! test.final "true"))
(#set! capture.final "true"))
(singleton_method
"." @keyword.operator.accessor.ruby
@ -161,16 +161,16 @@
scope: [(constant) (scope_resolution)]
"::" @keyword.operator.namespace.ruby
name: [(constant)] @support.other.class.ruby
(#set! test.final "true"))
(#set! capture.final "true"))
; (call
; receiver: (constant) @constant.ruby (#match? @constant.ruby "^[A-Z\\d_]+$")
; )
(call
receiver: (constant) @support.other.class.ruby
(#set! test.final "true"))
(#set! capture.final "true"))
(call "." @keyword.operator.accessor.ruby (#set! test.final "true"))
(call "." @keyword.operator.accessor.ruby (#set! capture.final "true"))
((identifier) @constant.builtin.ruby
(#match? @constant.builtin.ruby "^__(FILE|LINE|ENCODING)__$"))
@ -179,7 +179,7 @@
; it as `constant` if it's all uppercase…
((constant) @constant.ruby
(#match? @constant.ruby "^[A-Z\\d_]+$")
(#set! test.final "true"))
(#set! capture.final "true"))
; …otherwise treat it as a variable.
; ((constant) @variable.other.constant.ruby)
@ -200,13 +200,13 @@
((keyword_parameter) @constant.other.symbol.hashkey.parameter.ruby
(#set! adjust.startAt firstChild.startPosition)
(#set! adjust.endAt firstChild.nextSibling.endPosition)
(#set! test.final true))
(#set! capture.final true))
; This scope should span both the key and the adjacent colon.
((pair key: (hash_key_symbol)) @constant.other.symbol.hashkey.ruby
(#set! adjust.startAt firstChild.startPosition)
(#set! adjust.endAt firstChild.nextSibling.endPosition)
(#set! test.final true))
(#set! capture.final true))
(pair
key: (hash_key_symbol)
@ -214,7 +214,7 @@
; separates it from its value.
":" @punctuation.definition.constant.hashkey.ruby
@punctuation.separator.key-value.ruby
(#set! test.final true))
(#set! capture.final true))
(optional_parameter
name: (identifier) @variable.parameter.function.optional.ruby)
@ -240,7 +240,7 @@
@string.quoted.single.ruby
(#match? @string.quoted.single.ruby "^'")
(#match? @string.quoted.single.ruby "'$")
(#set! test.final true))
(#set! capture.final true))
; Double-quoted string "bar"
@ -252,7 +252,7 @@
@string.quoted.double.interpolated.ruby
(#match? @string.quoted.double.interpolated.ruby "^\"")
(#match? @string.quoted.double.interpolated.ruby "\"$")
(#set! test.final true))
(#set! capture.final true))
; "Other" strings
(
@ -262,7 +262,7 @@
"\"" @punctuation.definition.string.end.ruby)
@string.quoted.other.ruby
(#match? @string.quoted.other.ruby "^%q")
(#set! test.final true))
(#set! capture.final true))
(
(string
@ -271,7 +271,7 @@
"\"" @punctuation.definition.string.end.ruby)
@string.quoted.other.interpolated.ruby
(#match? @string.quoted.other.interpolated.ruby "^%Q")
(#set! test.final true))
(#set! capture.final true))
(
@ -280,7 +280,7 @@
(string_content)?
"\"" @punctuation.definition.string.end.ruby)
@string.quoted.other.ruby
(#set! test.final true))
(#set! capture.final true))
; Highlight the interpolation inside of a string.
(
@ -329,7 +329,7 @@
"`" @punctuation.definition.string.begin.ruby
(_)?
"`" @punctuation.definition.string.end.ruby)
(#set! test.final true))
(#set! capture.final true))
(subshell) @meta.embedded.line.subshell.ruby @string.quoted.subshell.interpolation.ruby
@ -359,9 +359,9 @@
(regex) @string.regexp.interpolated.ruby
(regex "/" @punctuation.definition.begin.regexp.ruby
(#set! test.onlyIfFirst true))
(#is? test.first true))
(regex "/" @punctuation.definition.end.regexp.ruby
(#set! test.onlyIfLast true))
(#is? test.last true))
(escape_sequence) @constant.character.escape.ruby
@ -441,16 +441,16 @@
; OPERATORS
; =========
(splat_parameter "*" @keyword.operator.splat.ruby (#set! test.final true))
(splat_argument "*" @keyword.operator.splat.ruby (#set! test.final true))
(rest_assignment "*" @keyword.operator.splat.ruby (#set! test.final true))
(splat_parameter "*" @keyword.operator.splat.ruby (#set! capture.final true))
(splat_argument "*" @keyword.operator.splat.ruby (#set! capture.final true))
(rest_assignment "*" @keyword.operator.splat.ruby (#set! capture.final true))
(hash_splat_argument
"**" @keyword.operator.double-splat.ruby
(#set! test.final true))
(#set! capture.final true))
(hash_splat_parameter
"**" @keyword.operator.double-splat.ruby
(#set! test.final true))
(#set! capture.final true))
(singleton_class
"<<" @keyword.operator.assigment.ruby)
@ -460,15 +460,15 @@
(conditional
["?" ":"] @keyword.operator.conditional.ruby
(#set! test.final "true"))
(#set! capture.final "true"))
(binary
["+" "-" "*" "/" "**"] @keyword.operator.arithmetic.ruby
(#set! test.final true))
(#set! capture.final true))
(unary
["+" "-" "!" "~" "not" "&" "*"] @keyword.operator.unary.ruby
(#set! test.final true))
(#set! capture.final true))
[
"="
@ -521,17 +521,17 @@
; To distinguish them from the bitwise "|" operator.
(block_parameters
"|" @punctuation.separator.parameters.begin.ruby
(#set! test.onlyIfFirst true)
(#set! test.final true))
(#is? test.first true)
(#set! capture.final true))
(block_parameters
"|" @punctuation.separator.parameters.end.ruby
(#set! test.onlyIfLast true)
(#set! test.final true))
(#is? test.last true)
(#set! capture.final true))
(block_parameters
"," @punctuation.separator.parameters.ruby
(#set! test.final true))
(#set! capture.final true))
"=>" @punctuation.separator.key-value.ruby
@ -564,7 +564,7 @@
(#set! adjust.endAt lastChild.startPosition))
((do_block) @meta.block.ruby
(#set! test.onlyIfNotRangeWithData hasBlockParameters)
(#is-not? test.rangeWithData hasBlockParameters)
; Start just after `do`.
(#set! adjust.startAt firstChild.endPosition)
(#set! adjust.endAt lastChild.startPosition))

View File

@ -1,13 +1,13 @@
; Prevent postfix modifiers from triggering indents on the next line.
(unless_modifier "unless" @_IGNORE_
(#set! test.final true))
(#set! capture.final true))
(if_modifier "if" @_IGNORE_
(#set! test.final true))
(#set! capture.final true))
(while_modifier "while" @_IGNORE_
(#set! test.final true))
(#set! capture.final true))
(until_modifier "until" @_IGNORE_
(#set! test.final true))
(#set! capture.final true))
[

View File

@ -38,7 +38,7 @@
(anonymous_capturing_group
"(" @punctuation.definition.group.begin.bracket.round.regexp
")" @punctuation.definition.group.end.bracket.round.regexp
(#set! test.final true))
(#set! capture.final true))
"|" @keyword.operator.or.regexp
["*" "+"] @keyword.operator.quantifier.regexp

View File

@ -120,7 +120,7 @@
((identifier) @constant.other.rust
(#match? @constant.other.rust "^[A-Z_][A-Z\\d_]+$")
(#set! test.final true))
(#set! capture.final true))
(boolean_literal) @constant.language.boolean._TEXT_.rust
(escape_sequence) @constant.character.escape.rust

View File

@ -9,11 +9,11 @@
; Why isn't this a keyword in the parser?
((command_name) @keyword.control.return.shell
(#eq? @keyword.control.return.shell "return")
(#set! test.final true))
(#set! capture.final true))
((command_name) @support.function.builtin.shell
(#match? @support.function.builtin.shell "^(?:alias|bg|bind|break|builtin|caller|cd|command|compgen|complete|dirs|disown|echo|enable|eval|exec|exit|false|fc|fg|getopts|hash|help|history|jobs|kill|let|logout|popd|printf|pushd|pwd|read|readonly|set|shift|shopt|source|suspend|test|times|trap|true|type|ulimit|umask|unalias|unset|wait)$")
(#set! test.final true))
(#set! capture.final true))
(command_name) @support.other.function.shell
@ -49,13 +49,13 @@
((simple_expansion) @variable.other.positional.shell
(#match? @variable.other.positional.shell "^\\$\\d+$")
(#set! test.final true))
(#set! capture.final true))
((simple_expansion) @variable.other.normal.shell)
; Prevent the "foo" in $foo from matching the following rule.
(simple_expansion (variable_name) @_IGNORE_
(#set! test.final true))
(#set! capture.final true))
(variable_name) @variable.other.shell
@ -64,9 +64,9 @@
(string "\"") @string.quoted.double.shell
(string "\"" @punctuation.definition.string.begin.shell
(#set! test.onlyIfFirst true))
(#is? test.first true))
(string "\"" @punctuation.definition.string.end.shell
(#set! test.onlyIfLast true))
(#is? test.last true))
(raw_string) @string.quoted.single.shell
(string

View File

@ -24,9 +24,9 @@
. [(bare_key) (quoted_key) (dotted_key)]) @meta.pair.key.toml
((bare_key) @variable.other.key.toml
(#set! test.onlyIfDescendantOfType pair))
(#is? test.descendantOfType pair))
((quoted_key) @variable.other.key.quoted.toml
(#set! test.onlyIfDescendantOfType pair))
(#is? test.descendantOfType pair))
(dotted_key "." @keyword.operator.accessor.toml)
@ -42,11 +42,11 @@
(string
["\"" "'"] @punctuation.definition.string.begin.toml
(#set! test.onlyIfFirst true))
(#is? test.first true))
(string
["\"" "'"] @punctuation.definition.string.end.toml
(#set! test.onlyIfLast true))
(#is? test.last true))
; WORKAROUND: There seems to be a bug with multi-line strings where only the
; opening delimiters are exposed. Let's use adjustments to mark these
@ -79,7 +79,7 @@
(#match? @constant.numeric.binary.toml "^0b"))
((integer) @constant.numeric.decimal.integer.toml
(#set! test.shy true))
(#set! capture.shy true))
; Not sure why `inf` and `nan` are parsed as `float`s, but there you have it.
((float) @constant.numeric.infinity.toml
@ -89,7 +89,7 @@
(#match? @constant.numeric.nan.toml "^[+-]?nan$"))
((float) @constant.numeric.decimal.float.toml
(#set! test.shy true))
(#set! capture.shy true))
; DATES

View File

@ -54,7 +54,7 @@
((property_identifier) @constant.other.property._LANG_
(#match? @constant.other.property._LANG_ "^[\$A-Z_]+$")
(#set! test.final true))
(#set! capture.final true))
; (property_identifier) @variable.other.object.property._LANG_
@ -73,6 +73,9 @@
(public_field_definition
name: (property_identifier) @variable.declaration.field._LANG_)
(public_field_definition
name: (private_property_identifier) @variable.declaration.field.private._LANG_)
(new_expression
constructor: (identifier) @support.type.class._LANG_)
@ -113,7 +116,7 @@
((literal_type [(null) (undefined)]) @storage.type._TEXT_._LANG_)
((literal_type [(null) (undefined)]) @support.type._TEXT_._LANG_
(#set! test.final true))
(#set! capture.final true))
; TODO: Decide whether other literal types — strings, booleans, and whatnot —
; should be highlighted as they are in JS, or should be highlighted like other
@ -138,7 +141,7 @@
name: (identifier) @entity.other.attribute-name.type._LANG_)
((type_identifier) @storage.type._LANG_
; (#set! test.onlyIfDescendantOfType "type_annotation type_arguments satisfies_expression type_parameter")
; (#is? test.descendantOfType "type_annotation type_arguments satisfies_expression type_parameter")
)
; A capture can satisfy more than one of these criteria, so we need to guard
@ -146,8 +149,8 @@
; two capture names are applied in separate captures — otherwise `test.final`
; would be applied after the first capture.
((type_identifier) @support.type._LANG_
; (#set! test.onlyIfDescendantOfType "type_annotation type_arguments satisfies_expression type_parameter")
(#set! test.final true))
; (#is? test.descendantOfType "type_annotation type_arguments satisfies_expression type_parameter")
(#set! capture.final true))
; OBJECTS
; =======
@ -169,6 +172,8 @@
object: (member_expression
property: (property_identifier) @support.other.object._LANG_))
(method_signature
(property_identifier) @entity.other.attribute-name.method._LANG_)
(property_signature
(property_identifier) @entity.other.attribute-name._LANG_)
@ -213,7 +218,7 @@
(assignment_expression
left: (member_expression
property: (property_identifier) @entity.name.function.definition._LANG_
(#set! test.final true))
(#set! capture.final true))
right: [(arrow_function) (function)])
; Function variable assignment:
@ -257,7 +262,13 @@
(required_parameter
pattern: (object_pattern
(shorthand_property_identifier_pattern) @variable.parameter.destructuring._LANG_)
(#set! test.final true))
(#set! capture.final true))
(optional_parameter
pattern: (identifier) @variable.parameter.optional._LANG_)
(optional_parameter "?" @keyword.operator.type.optional._LANG_)
["var" "const" "let"] @storage.type._TYPE_._LANG_
@ -336,7 +347,7 @@
(pair_pattern
key: (_) @entity.other.attribute-name._LANG_
value: (identifier) @variable.other.assignment.loop._LANG_)
(#set! test.final true)))
(#set! capture.final true)))
; The "error" in `} catch (error) {`
(catch_clause
@ -352,31 +363,31 @@
((identifier) @support.object.builtin._TEXT_._LANG_
(#match? @support.object.builtin._TEXT_._LANG_ "^(arguments|module|window|document)$")
(#is-not? local)
(#set! test.final true))
(#set! capture.final true))
((identifier) @support.object.builtin.filename._LANG_
(#eq? @support.object.builtin.filename._LANG_ "__filename")
(#is-not? local)
(#set! test.final true))
(#set! capture.final true))
((identifier) @support.object.builtin.dirname._LANG_
(#eq? @support.object.builtin.dirname._LANG_ "__dirname")
(#is-not? local)
(#set! test.final true))
(#set! capture.final true))
((identifier) @support.function.builtin.require._LANG_
(#eq? @support.function.builtin.require._LANG_ "require")
(#is-not? local)
(#set! test.final true))
(#set! capture.final true))
((identifier) @constant.language.infinity._LANG_
(#eq? @constant.language.infinity._LANG_ "Infinity")
(#set! test.final true))
(#set! capture.final true))
; Things that `LOOK_LIKE_CONSTANTS`.
([(property_identifier) (identifier)] @constant.other._LANG_
(#match? @constant.other._LANG_ "^[A-Z_][A-Z0-9_]*$")
(#set! test.shy true))
(#set! capture.shy true))
; NUMBERS
@ -390,27 +401,27 @@
((string "\"") @string.quoted.double._LANG_)
((string
"\"" @punctuation.definition.string.begin._LANG_)
(#set! test.onlyIfFirst true))
(#is? test.first true))
((string
"\"" @punctuation.definition.string.end._LANG_)
(#set! test.onlyIfLast true))
(#is? test.last true))
((string "'") @string.quoted.single._LANG_)
((string
"'" @punctuation.definition.string.begin._LANG_)
(#set! test.onlyIfFirst true))
(#is? test.first true))
((string
"'" @punctuation.definition.string.end._LANG_)
(#set! test.onlyIfLast true))
(#is? test.last true))
(template_string) @string.quoted.template._LANG_
((template_string "`" @punctuation.definition.string.begin._LANG_)
(#set! test.onlyIfFirst true))
(#is? test.first true))
((template_string "`" @punctuation.definition.string.end._LANG_)
(#set! test.onlyIfLast true))
(#is? test.last true))
; Interpolations inside of template strings.
(template_substitution
@ -483,6 +494,8 @@
"&="
"^="
"|="
"??="
"||="
] @keyword.operator.assignment.compound._LANG_
[
@ -521,7 +534,13 @@
; The "?" in a `isFoo?: boolean` property type annotation.
(property_signature "?" @keyword.operator.type.optional._LANG_)
; The "?" in a `isFoo()?: boolean` method type annotation.
(method_signature "?" @keyword.operator.type.optional._LANG_)
; The "?" in a `isFoo?: boolean` class field annotation.
(public_field_definition "?" @keyword.operator.type.optional._LANG_)
; The "!" in a `isFoo!: boolean` class field annotation.
(public_field_definition "!" @keyword.operator.type.definite._LANG_)
"..." @keyword.operator.spread._LANG_
"." @keyword.operator.accessor._LANG_
@ -552,23 +571,23 @@
; The interiors of functions (useful for snippets and commands).
(method_definition
body: (statement_block) @meta.block.function._LANG_
(#set! test.final true))
(#set! capture.final true))
(function_declaration
body: (statement_block) @meta.block.function._LANG_
(#set! test.final true))
(#set! capture.final true))
(generator_function_declaration
body: (statement_block) @meta.block.function._LANG_
(#set! test.final true))
(#set! capture.final true))
(function
body: (statement_block) @meta.block.function._LANG_
(#set! test.final true))
(#set! capture.final true))
(generator_function
body: (statement_block) @meta.block.function._LANG_
(#set! test.final true))
(#set! capture.final true))
; The interior of a class body (useful for snippets and commands).
(class_body) @meta.block.class._LANG_

View File

@ -1,7 +1,7 @@
; The closing brace of a switch statement's body should match the indentation of the line where the switch statement starts.
(switch_statement
body: (switch_body "}" @match
(#set! test.onlyIfLast true))
(#is? test.last true))
(#set! indent.matchIndentOf parent.parent.startPosition))
; 'case' and 'default' need to be indented one level more than their containing
@ -22,5 +22,10 @@
"]"
] @dedent
(type_parameters "<" @indent)
(type_parameters ">" @dedent)
(type_arguments "<" @indent)
(type_arguments ">" @dedent)
["case" "default"] @indent

View File

@ -14,7 +14,7 @@
; The "Foo" in `</Foo>`.
(jsx_closing_element
"/" @punctuation.definition.tag.end.ts.tsx
(#set! test.final true)
(#set! capture.final true)
name: (identifier) @entity.name.tag.ts.tsx)
; The "bar" in `<Foo bar={true} />`.
@ -24,17 +24,17 @@
; All JSX expressions/interpolations within braces.
((jsx_expression) @meta.embedded.block.ts.tsx
(#match? @meta.embedded.block.ts.tsx "\\n")
(#set! test.final true))
(#set! capture.final true))
(jsx_expression) @meta.embedded.line.ts.tsx
(jsx_self_closing_element
"<" @punctuation.definition.tag.begin.ts.tsx
(#set! test.final true))
(#set! capture.final true))
((jsx_self_closing_element
; The "/>" in `<Foo />`, extended to cover both anonymous nodes at once.
"/") @punctuation.definition.tag.end.ts.tsx
(#set! adjust.startAt lastChild.previousSibling.startPosition)
(#set! adjust.endAt lastChild.endPosition)
(#set! test.final true))
(#set! capture.final true))

View File

@ -276,7 +276,43 @@ describe('ScopeResolver', () => {
describe('tests', () => {
it('rejects scopes for ranges that have already been claimed by another capture with (#set! test.final true)', async () => {
it('rejects scopes for ranges that have already been claimed by another capture with (#set! capture.final true)', async () => {
await grammar.setQueryForTest('highlightsQuery', `
(comment) @comment
(string) @string0
((string) @string1
(#set! capture.final true))
(string) @string2
"=" @operator
`);
const languageMode = new WASMTreeSitterLanguageMode({ grammar, buffer });
buffer.setLanguageMode(languageMode);
buffer.setText(dedent`
// this is a comment
const foo = "ahaha";
`);
await languageMode.ready;
let { scopeResolver, captures } = await getAllCaptures(grammar, languageMode);
for (let capture of captures) {
let { node, name } = capture;
let result = scopeResolver.store(capture);
if (name === 'string0') {
expect(!!result).toBe(true);
}
if (name === 'string1') {
expect(!!result).toBe(true);
}
if (name === 'string2') {
expect(!!result).toBe(false);
}
}
});
it('temporarily supports the deprecated (#set! test.final true)', async () => {
await grammar.setQueryForTest('highlightsQuery', `
(comment) @comment
(string) @string0
@ -312,12 +348,12 @@ describe('ScopeResolver', () => {
}
});
it('rejects scopes for ranges that have already been claimed by another capture with (#set! test.final true)', async () => {
it('rejects scopes for ranges that have already been claimed by another capture with (#set! capture.final true)', async () => {
await grammar.setQueryForTest('highlightsQuery', `
(comment) @comment
(string) @string0
((string) @string1
(#set! test.final true))
(#set! capture.final true))
(string) @string2
"=" @operator
@ -348,7 +384,39 @@ describe('ScopeResolver', () => {
}
});
it('rejects scopes for ranges that have already been claimed if set with (#set! test.shy true)', async () => {
it('rejects scopes for ranges that have already been claimed if set with (#set! capture.shy true)', async () => {
await grammar.setQueryForTest('highlightsQuery', `
(comment) @comment
(string "\\"") @string.double
((string) @string.other (#set! capture.shy true))
"=" @operator
`);
const languageMode = new WASMTreeSitterLanguageMode({ grammar, buffer });
buffer.setLanguageMode(languageMode);
buffer.setText(dedent`
// this is a comment
const foo = "ahaha";
const bar = 'troz'
`);
await languageMode.ready;
let { scopeResolver, captures } = await getAllCaptures(grammar, languageMode);
let first = true;
for (let capture of captures) {
let { node, name } = capture;
let result = scopeResolver.store(capture);
// First string.other should fail; second should succeed.
if (name === 'string.other') {
let expected = first ? false : true;
first = false;
expect(!!result).toBe(expected);
}
}
});
it('temporarily supports the deprecated (#set! test.shy true)', async () => {
await grammar.setQueryForTest('highlightsQuery', `
(comment) @comment
(string "\\"") @string.double
@ -380,16 +448,16 @@ describe('ScopeResolver', () => {
}
});
it('rejects scopes for ranges that fail onlyIfFirst or onlyIfLast', async () => {
it('rejects scopes for ranges that fail test.first or test.last', async () => {
await grammar.setQueryForTest('highlightsQuery', `
((string_fragment) @impossible.first
(#set! test.onlyIfFirst true))
(#is? test.first true))
((string_fragment) @impossible.last
(#set! test.onlyIfLast true))
(#is? test.last true))
((string) "'" @punctuation.first
(#onlyIfFirst true))
(#is? test.first true))
((string) "'" @punctuation.last
(#onlyIfLast true))
(#is? test.last true))
`);
const languageMode = new WASMTreeSitterLanguageMode({ grammar, buffer });
@ -419,17 +487,56 @@ describe('ScopeResolver', () => {
}
});
it('supports onlyIfFirstOfType and onlyIfLastOfType', async () => {
it('temporarily supports the deprecated (#set! test.onlyIfFirst) and (#set! test.onlyIfLast)', async () => {
await grammar.setQueryForTest('highlightsQuery', `
((string_fragment) @impossible.first
(#is? test.onlyIfFirst true))
((string_fragment) @impossible.last
(#is? test.onlyIfLast true))
((string) "'" @punctuation.first
(#is? test.onlyIfFirst true))
((string) "'" @punctuation.last
(#is? test.onlyIfLast true))
`);
const languageMode = new WASMTreeSitterLanguageMode({ grammar, buffer });
buffer.setLanguageMode(languageMode);
buffer.setText(dedent`
// this is a comment
const foo = "ahaha";
const bar = 'troz'
`);
await languageMode.ready;
let { scopeResolver, captures } = await getAllCaptures(grammar, languageMode);
for (let capture of captures) {
let { node, name } = capture;
let result = scopeResolver.store(capture);
// Impossible for string_fragment to be the first or last child.
if (name.startsWith('impossible')) {
expect(!!result).toBe(false);
}
if (name === 'punctuation.first') {
expect(node.id).toBe(node.parent.lastChild.id);
} else if (name === 'punctuation.last') {
expect(node.id).toBe(node.parent.firstChild.id);
}
}
});
it('supports test.firstOfType and test.lastOfType', async () => {
await grammar.setQueryForTest('highlightsQuery', `
(formal_parameters (identifier) @first-param
(#set! test.onlyIfFirstOfType identifier))
(#is? test.firstOfType identifier))
(formal_parameters (identifier) @last-param
(#set! test.onlyIfLastOfType identifier))
(#is? test.lastOfType identifier))
(formal_parameters "," @first-comma
(#set! test.onlyIfFirstOfType ","))
(#is? test.firstOfType ","))
(formal_parameters "," @last-comma
(#set! test.onlyIfLastOfType ","))
(#is? test.lastOfType ","))
`);
const languageMode = new WASMTreeSitterLanguageMode({ grammar, buffer });
@ -454,10 +561,10 @@ describe('ScopeResolver', () => {
})).toEqual(["first-param", "first-comma", "last-comma", "last-param"]);
});
it('supports onlyIfLastTextOnRow', async () => {
it('supports test.lastTextOnRow', async () => {
await grammar.setQueryForTest('highlightsQuery', `
("||" @hanging-logical-operator
(#set! test.onlyIfLastTextOnRow true))
(#is? test.lastTextOnRow true))
`);
const languageMode = new WASMTreeSitterLanguageMode({ grammar, buffer });
@ -485,10 +592,10 @@ describe('ScopeResolver', () => {
["hanging-logical-operator"]);
});
it('supports onlyIfFirstTextOnRow', async () => {
it('supports test.firstTextOnRow', async () => {
await grammar.setQueryForTest('highlightsQuery', `
("||" @hanging-logical-operator
(#set! test.onlyIfFirstTextOnRow true))
(#is? test.firstTextOnRow true))
`);
const languageMode = new WASMTreeSitterLanguageMode({ grammar, buffer });
@ -516,10 +623,10 @@ describe('ScopeResolver', () => {
["hanging-logical-operator"]);
});
it('supports onlyIfDescendantOfType', async () => {
it('supports test.descendantOfType', async () => {
await grammar.setQueryForTest('highlightsQuery', `
("," @comma-inside-function
(#set! test.onlyIfDescendantOfType function_declaration))
(#is? test.descendantOfType function_declaration))
`);
const languageMode = new WASMTreeSitterLanguageMode({ grammar, buffer });
@ -538,10 +645,10 @@ describe('ScopeResolver', () => {
})).toBe(true);
});
it('supports onlyIfDescendantOfType (multiple values)', async () => {
it('supports test.descendantOfType (multiple values)', async () => {
await grammar.setQueryForTest('highlightsQuery', `
("," @comma-inside-function
(#set! test.onlyIfDescendantOfType "function_declaration generator_function_declaration"))
(#is? test.descendantOfType "function_declaration generator_function_declaration"))
`);
const languageMode = new WASMTreeSitterLanguageMode({ grammar, buffer });
@ -563,10 +670,10 @@ describe('ScopeResolver', () => {
});
it('supports onlyIfAncestorOfType', async () => {
it('supports test.ancestorOfType', async () => {
await grammar.setQueryForTest('highlightsQuery', `
((function_declaration) @function-with-semicolons
(#set! test.onlyIfAncestorOfType ";"))
(#is? test.ancestorOfType ";"))
`);
const languageMode = new WASMTreeSitterLanguageMode({ grammar, buffer });
@ -585,10 +692,10 @@ describe('ScopeResolver', () => {
expect(matched[0].node.text.includes("function bar")).toBe(true);
});
it('supports onlyIfAncestorOfType (multiple values)', async () => {
it('supports test.ancestorOfType (multiple values)', async () => {
await grammar.setQueryForTest('highlightsQuery', `
((function_declaration) @function-with-semicolons-or-booleans
(#set! test.onlyIfAncestorOfType "; false"))
(#is? test.ancestorOfType "; false"))
`);
const languageMode = new WASMTreeSitterLanguageMode({ grammar, buffer });
@ -611,14 +718,14 @@ describe('ScopeResolver', () => {
expect(matched[1].node.text.includes("function ba")).toBe(true);
});
it('supports onlyIfDescendantOfNodeWithData (without value)', async () => {
it('supports test.descendantOfNodeWithData (without value)', async () => {
await grammar.setQueryForTest('highlightsQuery', `
((function_declaration) @_IGNORE_
(#match? @_IGNORE_ "foo")
(#set! isSpecialFunction true))
("," @special-comma
(#set! test.onlyIfDescendantOfNodeWithData isSpecialFunction))
(#is? test.descendantOfNodeWithData isSpecialFunction))
`);
const languageMode = new WASMTreeSitterLanguageMode({ grammar, buffer });
@ -638,14 +745,14 @@ describe('ScopeResolver', () => {
})).toBe(true);
});
it('supports onlyIfDescendantOfNodeWithData (with right value)', async () => {
it('supports test.descendantOfNodeWithData (with right value)', async () => {
await grammar.setQueryForTest('highlightsQuery', `
((function_declaration) @_IGNORE_
(#match? @_IGNORE_ "foo" )
(#set! isSpecialFunction "troz"))
("," @special-comma
(#set! test.onlyIfDescendantOfNodeWithData "isSpecialFunction troz"))
(#is? test.descendantOfNodeWithData "isSpecialFunction troz"))
`);
const languageMode = new WASMTreeSitterLanguageMode({ grammar, buffer });
@ -665,14 +772,14 @@ describe('ScopeResolver', () => {
})).toBe(true);
});
it('supports onlyIfDescendantOfNodeWithData (with wrong value)', async () => {
it('supports test.descendantOfNodeWithData (with wrong value)', async () => {
await grammar.setQueryForTest('highlightsQuery', `
((function_declaration) @_IGNORE_
(#match? @_IGNORE_ "foo")
(#set! isSpecialFunction "troz"))
("," @special-comma
(#set! test.onlyIfDescendantOfNodeWithData "isSpecialFunction zort"))
(#is? test.descendantOfNodeWithData "isSpecialFunction zort"))
`);
const languageMode = new WASMTreeSitterLanguageMode({ grammar, buffer });
@ -689,10 +796,10 @@ describe('ScopeResolver', () => {
expect(matched.length).toBe(0);
});
it('supports onlyIfType', async () => {
it('supports test.type', async () => {
await grammar.setQueryForTest('highlightsQuery', `
(formal_parameters _ @function-comma
(#set! test.onlyIfType ","))
(#is? test.type ","))
`);
const languageMode = new WASMTreeSitterLanguageMode({ grammar, buffer });
@ -710,10 +817,10 @@ describe('ScopeResolver', () => {
})).toBe(true);
});
it('supports onlyIfType with multiple types', async () => {
it('supports test.type with multiple types', async () => {
await grammar.setQueryForTest('highlightsQuery', `
(formal_parameters _ @thing
(#set! test.onlyIfType ", identifier"))
(#is? test.type ", identifier"))
`);
const languageMode = new WASMTreeSitterLanguageMode({ grammar, buffer });
@ -728,10 +835,10 @@ describe('ScopeResolver', () => {
expect(matched.length).toBe(5);
});
it('supports onlyIfHasError', async () => {
it('supports test.hasError', async () => {
await grammar.setQueryForTest('highlightsQuery', `
((statement_block) @messed-up-statement-block
(#set! test.onlyIfHasError true))
(#is? test.hasError true))
`);
const languageMode = new WASMTreeSitterLanguageMode({ grammar, buffer });
@ -751,10 +858,10 @@ describe('ScopeResolver', () => {
})).toBe(true);
});
it('supports onlyIfRoot', async () => {
it('supports test.oot', async () => {
await grammar.setQueryForTest('highlightsQuery', `
((_) @is-root
(#set! test.onlyIfRoot true))
(#is? test.root true))
`);
const languageMode = new WASMTreeSitterLanguageMode({ grammar, buffer });
@ -775,10 +882,10 @@ describe('ScopeResolver', () => {
})).toBe(true);
});
it('supports onlyIfLastTextOnRow', async () => {
it('supports test.lastTextOnRow', async () => {
await grammar.setQueryForTest('highlightsQuery', `
("||" @orphaned-operator
(#set! test.onlyIfLastTextOnRow true))
(#is? test.lastTextOnRow true))
`);
const languageMode = new WASMTreeSitterLanguageMode({ grammar, buffer });
@ -801,11 +908,11 @@ describe('ScopeResolver', () => {
}
});
it('supports onlyIfRangeWithData (without value)', async () => {
it('supports test.rangeWithData (without value)', async () => {
await grammar.setQueryForTest('highlightsQuery', `
((true) @_IGNORE_ (#set! isTrue true))
([ (true) (false) ] @optimistic-boolean
(#set! test.onlyIfRangeWithData isTrue))
(#is? test.rangeWithData isTrue))
`);
const languageMode = new WASMTreeSitterLanguageMode({ grammar, buffer });
@ -827,11 +934,11 @@ describe('ScopeResolver', () => {
}
});
it('supports onlyIfRangeWithData (with right value)', async () => {
it('supports test.rangeWithData (with right value)', async () => {
await grammar.setQueryForTest('highlightsQuery', `
((true) @_IGNORE_ (#set! isTrue "exactly"))
([ (true) (false) ] @optimistic-boolean
(#set! test.onlyIfRangeWithData "isTrue exactly"))
(#is? test.rangeWithData "isTrue exactly"))
`);
const languageMode = new WASMTreeSitterLanguageMode({ grammar, buffer });
@ -853,11 +960,11 @@ describe('ScopeResolver', () => {
}
});
it('supports onlyIfRangeWithData (with wrong value)', async () => {
it('supports test.rangeWithData (with wrong value)', async () => {
await grammar.setQueryForTest('highlightsQuery', `
((true) @_IGNORE_ (#set! isTrue "perhaps"))
([ (true) (false) ] @optimistic-boolean
(#set! test.onlyIfRangeWithData "isTrue exactly"))
(#is? test.rangeWithData "isTrue exactly"))
`);
const languageMode = new WASMTreeSitterLanguageMode({ grammar, buffer });
@ -876,10 +983,10 @@ describe('ScopeResolver', () => {
expect(matched.length).toBe(0);
});
it('supports onlyIfStartsOnSameRowAs', async () => {
it('supports test.startsOnSameRowAs', async () => {
await grammar.setQueryForTest('highlightsQuery', `
((false) @non-hanging-false
(#set! test.onlyIfStartsOnSameRowAs parent.startPosition))
(#is? test.startsOnSameRowAs parent.startPosition))
`);
const languageMode = new WASMTreeSitterLanguageMode({ grammar, buffer });
@ -903,10 +1010,10 @@ describe('ScopeResolver', () => {
}
});
it('supports onlyIfEndsOnSameRowAs', async () => {
it('supports test.endsOnSameRowAs', async () => {
await grammar.setQueryForTest('highlightsQuery', `
((true) @non-hanging-true
(#set! test.onlyIfEndsOnSameRowAs parent.endPosition))
(#is? test.endsOnSameRowAs parent.endPosition))
`);
const languageMode = new WASMTreeSitterLanguageMode({ grammar, buffer });
@ -930,12 +1037,12 @@ describe('ScopeResolver', () => {
}
});
it('supports onlyIfConfig (with no arguments)', async () => {
it('supports test.config (with no arguments)', async () => {
atom.config.set('core.careAboutBooleans', true);
await grammar.setQueryForTest('highlightsQuery', `
([(true) (false)] @boolean
(#set! test.onlyIfConfig core.careAboutBooleans))
(#is? test.config core.careAboutBooleans))
`);
const languageMode = new WASMTreeSitterLanguageMode({ grammar, buffer, config: atom.config });
@ -957,12 +1064,12 @@ describe('ScopeResolver', () => {
expect(matched.length).toBe(0);
});
it('supports onlyIfConfig (with boolean arguments)', async () => {
it('supports test.config (with boolean arguments)', async () => {
atom.config.set('core.careAboutBooleans', true);
await grammar.setQueryForTest('highlightsQuery', `
([(true) (false)] @boolean
(#set! test.onlyIfConfig "core.careAboutBooleans true"))
(#is? test.config "core.careAboutBooleans true"))
`);
const languageMode = new WASMTreeSitterLanguageMode({ grammar, buffer, config: atom.config });
@ -984,12 +1091,12 @@ describe('ScopeResolver', () => {
expect(matched.length).toBe(0);
});
it('supports onlyIfConfig (with number arguments)', async () => {
it('supports test.config (with number arguments)', async () => {
atom.config.set('core.careAboutBooleans', 0);
await grammar.setQueryForTest('highlightsQuery', `
([(true) (false)] @boolean
(#set! test.onlyIfConfig "core.careAboutBooleans 0"))
(#is? test.config "core.careAboutBooleans 0"))
`);
const languageMode = new WASMTreeSitterLanguageMode({ grammar, buffer, config: atom.config });
@ -1011,12 +1118,12 @@ describe('ScopeResolver', () => {
expect(matched.length).toBe(0);
});
it('supports onlyIfConfig (with string arguments)', async () => {
it('supports test.config (with string arguments)', async () => {
atom.config.set('core.careAboutBooleans', "something");
await grammar.setQueryForTest('highlightsQuery', `
([(true) (false)] @boolean
(#set! test.onlyIfConfig "core.careAboutBooleans something"))
(#is? test.config "core.careAboutBooleans something"))
`);
const languageMode = new WASMTreeSitterLanguageMode({ grammar, buffer, config: atom.config });
@ -1038,17 +1145,17 @@ describe('ScopeResolver', () => {
expect(matched.length).toBe(0);
});
it('supports onlyIfInjection', async () => {
it('supports test.injection', async () => {
jasmine.useRealClock();
await grammar.setQueryForTest('highlightsQuery', `
((escape_sequence) @regex-escape
(#set! test.onlyIfInjection true))
(#is? test.injection true))
`);
let regexGrammar = new WASMTreeSitterGrammar(atom.grammars, jsRegexGrammarPath, jsRegexConfig);
await regexGrammar.setQueryForTest('highlightsQuery', `
((control_escape) @regex-escape
(#set! test.onlyIfInjection true))
(#is? test.injection true))
`);
atom.grammars.addGrammar(regexGrammar);

View File

@ -498,13 +498,13 @@ describe('WASMTreeSitterLanguageMode', () => {
((identifier) @constant
(#match? @constant "^[A-Z_]+$")
(#set! test.final true))
(#set! capture.final true))
((identifier) @constructor
(#match? @constructor "^[A-Z]"))
((identifier) @variable
(#set! test.shy true))
(#set! capture.shy true))
`);
buffer.setText(`exports.object = Class(SOME_CONSTANT, x)`);
@ -650,10 +650,10 @@ describe('WASMTreeSitterLanguageMode', () => {
(call_expression
(member_expression
(property_identifier) @method)
(#set! test.final true))
(#set! capture.final true))
((property_identifier) @property
(#set! test.final true))
(#set! capture.final true))
(call_expression (identifier) @function)
`);
@ -1130,7 +1130,7 @@ describe('WASMTreeSitterLanguageMode', () => {
let jsdocGrammar = atom.grammars.grammarForScopeName('source.jsdoc');
await jsdocGrammar.setQueryForTest('highlightsQuery', `
((ERROR) @comment.block.js
(#set! test.onlyIfRoot true))
(#is? test.root true))
(document) @comment.block.js
(tag_name) @storage.type.class.jsdoc
@ -1204,18 +1204,18 @@ describe('WASMTreeSitterLanguageMode', () => {
await rustGrammar.setQueryForTest('highlightsQuery', `
(macro_invocation
macro: (identifier) @macro
(#set! test.final true))
(#set! capture.final true))
(call_expression
(field_expression
(field_identifier) @function)
(#set! test.final true))
(#set! capture.final true))
((field_identifier) @property
(#set! test.final true))
(#set! capture.final true))
((identifier) @variable
(#set! test.shy true))
(#set! capture.shy true))
`);
atom.grammars.addGrammar(rustGrammar);
@ -3231,14 +3231,14 @@ describe('WASMTreeSitterLanguageMode', () => {
(call_expression
(member_expression
(property_identifier) @method)
(#set! test.final true))
(#set! capture.final true))
(call_expression
(identifier) @function
(#set! test.final true))
(#set! capture.final true))
((property_identifier) @property
(#set! test.final true))
(#set! capture.final true))
(identifier) @variable
`);
@ -3395,7 +3395,7 @@ describe('WASMTreeSitterLanguageMode', () => {
await grammar.setQueryForTest('indentsQuery', `
(template_string
"\`" @match
(#set! test.onlyIfLast true)
(#is? test.last true)
(#set! indent.matchIndentOf parent.firstChild.startPosition))
`);
@ -3431,7 +3431,7 @@ describe('WASMTreeSitterLanguageMode', () => {
await grammar.setQueryForTest('indentsQuery', `
(template_string
"\`" @dedent @match
(#set! test.onlyIfLast true)
(#is? test.last true)
(#set! indent.matchIndentOf parent.firstChild.startPosition))
`);
@ -3692,7 +3692,7 @@ describe('WASMTreeSitterLanguageMode', () => {
["{"] @indent
["}"] @dedent
((comment) @indent
(#set! test.onlyIfDescendantOfType class_body))
(#is? test.descendantOfType class_body))
`);
let emptyClassText = dedent`
@ -3742,7 +3742,7 @@ describe('WASMTreeSitterLanguageMode', () => {
["{"] @indent
["}"] @dedent
((comment) @indent
(#set! test.onlyIfDescendantOfType class_body))
(#is? test.descendantOfType class_body))
`);
let emptyClassText = dedent`

View File

@ -152,7 +152,15 @@ class ScopeResolver {
setDataForRange(range, props) {
let key = this._keyForRange(range);
return this.rangeData.set(key, props);
let normalizedProps = { ...props };
// TEMP: No longer needed when we remove support for (#set! test.final
// true).
for (let prop of ['final', 'shy']) {
if (`test.${prop}` in normalizedProps) {
normalizedProps[`capture.${prop}`] = normalizedProps[`test.${prop}`];
}
}
return this.rangeData.set(key, normalizedProps);
}
getDataForRange(syntax) {
@ -205,7 +213,26 @@ class ScopeResolver {
normalizeTestProperty(prop) {
if (prop.startsWith('test.')) {
prop = prop.replace(/^test\./, '');
prop = prop.substring(5);
}
// TEMP: Normalize `onlyIfNotFoo` and `onlyIfFoo` to `foo`.
if (prop.startsWith('onlyIfNot')) {
prop = prop.charAt(9).toLowerCase() + prop.substring(10);
}
if (prop.startsWith('onlyIf')) {
prop = prop.charAt(6).toLowerCase() + prop.substring(7);
}
return prop;
}
normalizeCaptureSettingProperty(prop) {
if (prop.startsWith('capture.')) {
prop = prop.substring(8);
}
// TEMP: Normalize `test.final` and `test.shy` to `final` and `shy`.
if (prop === 'test.final' || prop === 'test.shy') {
prop = prop.substring(5);
}
return prop;
}
@ -215,9 +242,28 @@ class ScopeResolver {
return prop in ScopeResolver.TESTS;
}
capturePropertyIsCaptureSetting(prop) {
// TEMP: Support `test.final` and `test.shy` temporarily.
if (prop === 'test.final' || prop === 'test.shy') {
return true;
}
if (prop.includes('.') && !prop.startsWith('capture.')) {
return false;
}
prop = this.normalizeCaptureSettingProperty(prop);
return prop in ScopeResolver.CAPTURE_SETTINGS;
}
applyTest(prop, ...args) {
let isLegacyNegation = prop.includes('onlyIfNot');
prop = this.normalizeTestProperty(prop);
return ScopeResolver.TESTS[prop](...args);
let result = ScopeResolver.TESTS[prop](...args);
return isLegacyNegation ? !result : result;
}
applyCaptureSettingProperty(prop, ...args) {
prop = this.normalizeCaptureSettingProperty(prop);
return ScopeResolver.CAPTURE_SETTINGS[prop](...args);
}
// Given a capture and possible predicate data, determines the buffer range
@ -265,16 +311,57 @@ class ScopeResolver {
// Given a syntax capture, test whether we should include its scope in the
// document.
test(capture, existingData) {
let { node, setProperties: props = {} } = capture;
if (existingData?.final || existingData?.['test.final']) { return false; }
let {
node,
setProperties: props = {},
assertedProperties: asserted = {},
refutedProperties: refuted = {}
} = capture;
if (existingData?.final || existingData?.['capture.final']) {
return false;
}
// Capture settings (final/shy) are the only keys in `setProperties` that
// matter when testing this capture.
//
// TODO: For compatibility reasons, we're still checking tests of the form
// (#set! test.final) here, but this should be removed before modern
// Tree-sitter ships.
for (let key in props) {
if (!this.capturePropertyIsTest(key)) { continue; }
let value = props[key];
if (!this.applyTest(key, node, value, props, existingData, this)) {
return false;
let isCaptureSettingProperty = this.capturePropertyIsCaptureSetting(key);
let isTest = this.capturePropertyIsTest(key);
if (!(isCaptureSettingProperty || isTest)) { continue; }
let value = props[key] ?? true;
if (isCaptureSettingProperty) {
if (!this.applyCaptureSettingProperty(key, node, existingData, this)) {
return false;
}
} else {
// TODO: Remove this once third-party grammars have had time to adapt to
// the migration of tests to `#is?` and `#is-not?`.
if (!this.applyTest(key, node, value, existingData, this)) {
return false;
}
}
}
// Apply tests of the form `(#is? foo)`.
for (let key in asserted) {
if (!this.capturePropertyIsTest(key)) { continue; }
let value = asserted[key] ?? true;
let result = this.applyTest(key, node, value, props, existingData, this);
if (!result) return false;
}
// Apply tests of the form `(#is-not? foo)`.
for (let key in refuted) {
if (!this.capturePropertyIsTest(key)) { continue; }
let value = refuted[key] ?? true;
let result = this.applyTest(key, node, value, props, existingData, this);
if (result) return false;
}
return true;
}
@ -402,16 +489,41 @@ ScopeResolver.interpolateName = (name, node) => {
return name;
};
// Special `#set!` predicates that work on “claimed” and “unclaimed” ranges.
ScopeResolver.CAPTURE_SETTINGS = {
// Passes only if another capture has not already declared `final` for the
// exact same range. If a capture is the first one to define `final`, then
// all other captures for that same range are ignored, whether they try to
// define `final` or not.
final(node, existingData) {
let final = existingData?.final || existingData?.['capture.final'];
return !(existingData && final);
},
// Passes only if no earlier capture has occurred for the exact same range.
shy(node, existingData) {
return existingData === undefined;
}
};
// These tests are used to define criteria under which the scope should be
// applied. Set them in a query file like so:
//
// (
// (foo) @some.scope.name
// (#set! onlyIfFirst true)
// (#is? test.first)
// )
//
// For boolean rules, the second argument to `#set!` is arbitrary, but must be
// something truthy; `true` is suggested.
// For boolean rules, the second argument to `#is?` can be omitted.
//
// A test can be negated with `#is-not?`:
//
// (
// (foo) @some.scope.name
// (#is-not? test.first)
// )
//
// These tests come in handy for criteria that can't be represented by the
// built-in predicates like `#match?` and `#eq?`.
@ -419,70 +531,37 @@ ScopeResolver.interpolateName = (name, node) => {
// NOTE: Syntax queries will always be run through a `ScopeResolver`, but other
// kinds of queries may or may not, depending on purpose.
//
ScopeResolver.TESTS = {
// Passes only if another capture has not already declared `final` for the
// exact same range. If a capture is the first one to define `final`, then
// all other captures for that same range are ignored, whether they try to
// define `final` or not.
final(node, value, props, existingData) {
let final = existingData?.final || existingData?.['test.final'];
return !(existingData && final);
},
// Passes only if no earlier capture has occurred for the exact same range.
shy(node, value, props, existingData) {
return existingData === undefined;
},
ScopeResolver.TESTS = {
// Passes only if the node is of the given type. Can accept multiple
// space-separated types.
onlyIfType(node, nodeType) {
type(node, nodeType) {
if (!nodeType.includes(' ')) { return node.type === nodeType }
let nodeTypes = nodeType.split(/\s+/);
return nodeTypes.some(t => t === node.type);
},
// Negates `onlyIfType`.
onlyIfNotType(...args) {
let isType = ScopeResolver.TESTS.onlyIfType(...args);
return !isType;
},
// Passes only if the node contains any descendant ERROR nodes.
onlyIfHasError(node) {
hasError(node) {
return node.hasError();
},
// Negates `onlyIfHasError`.
onlyIfNotHasError(node) {
return !node.hasError();
},
// Passes when the node's tree belongs to an injection layer, rather than the
// buffer's root language layer.
onlyIfInjection(node, value, props, existingData, instance) {
injection(node, value, props, existingData, instance) {
return instance.languageLayer.depth > 0;
},
// Negates `onlyIfInjection`.
onlyIfNotInjection(node, value, props, existingData, instance) {
return instance.languageLayer.depth === 0;
},
// Passes when the node has no parent.
onlyIfRoot(node) {
root(node) {
return !node.parent;
},
// Negates `onlyIfRoot`.
onlyIfNotRoot(node) {
return !!node.parent;
},
// Passes only if the given node is the first among its siblings.
//
// Is not guaranteed to pass if descended from an ERROR node.
onlyIfFirst(node) {
first(node) {
// Root nodes are always first.
if (!node.parent) { return true; }
@ -492,31 +571,19 @@ ScopeResolver.TESTS = {
return node?.parent?.firstChild?.id === node.id;
},
// Negates `onlyIfFirst`.
onlyIfNotFirst(node) {
if (!node.parent) { return false; }
return node?.parent?.firstChild?.id !== node.id;
},
// Passes only if the given node is the last among its siblings.
//
// Is not guaranteed to pass if descended from an ERROR node.
onlyIfLast(node) {
last(node) {
// Root nodes are always last.
if (!node.parent) { return true; }
return node?.parent?.lastChild?.id === node.id;
},
// Negates `onlyIfLast`.
onlyIfNotLast(node) {
if (!node.parent) { return false; }
return node?.parent?.lastChild?.id !== node.id;
},
// Passes when the node is the first of its type among its siblings.
//
// Is not guaranteed to pass if descended from an ERROR node.
onlyIfFirstOfType(node) {
firstOfType(node) {
if (!node.parent) { return true; }
let type = node.type;
let parent = node.parent;
@ -532,16 +599,10 @@ ScopeResolver.TESTS = {
return false;
},
// Negates `onlyIfFirstOfType`.
onlyIfNotFirstOfType(node) {
let onlyIfFirstOfType = ScopeResolver.TESTS.onlyIfFirstOfType(node);
return !onlyIfFirstOfType;
},
// Passes when the node is the last of its type among its siblings.
//
// Is not guaranteed to pass if descended from an ERROR node.
onlyIfLastOfType(node) {
lastOfType(node) {
if (!node.parent) { return true; }
let type = node.type;
let parent = node.parent;
@ -555,45 +616,27 @@ ScopeResolver.TESTS = {
return false;
},
// Negates `onlyIfLastOfType`.
onlyIfNotLastOfType(node) {
let onlyIfLastOfType = ScopeResolver.TESTS.onlyIfLastOfType(node);
return !onlyIfLastOfType;
},
// Passes when the node represents the last non-whitespace content on its
// row. Considers the node's ending row.
onlyIfLastTextOnRow(node, value, props, existingData, instance) {
lastTextOnRow(node, value, props, existingData, instance) {
let { buffer } = instance;
let text = buffer.lineForRow(node.endPosition.row);
let textAfterNode = text.slice(node.endPosition.column);
return !/\S/.test(textAfterNode);
},
// Negates `onlyIfLastTextOnRow`.
onlyIfNotLastTextOnRow(...args) {
let isLastTextOnRow = ScopeResolver.TESTS.onlyIfLastTextOnRow(...args);
return !isLastTextOnRow;
},
// Passes when the node represents the first non-whitespace content on its
// row. Considers the node's starting row.
onlyIfFirstTextOnRow(node, value, props, existingData, instance) {
firstTextOnRow(node, value, props, existingData, instance) {
let { buffer } = instance;
let text = buffer.lineForRow(node.startPosition.row);
let textBeforeNode = text.slice(0, node.startPosition.column);
return !/\S/.test(textBeforeNode);
},
// Negates `onlyIfFirstTextOnRow`.
onlyIfNotFirstTextOnRow(...args) {
let isFirstTextOnRow = ScopeResolver.TESTS.onlyIfFirstTextOnRow(...args);
return !isFirstTextOnRow;
},
// Passes if this node has any node of the given type(s) in its ancestor
// chain.
onlyIfDescendantOfType(node, type) {
descendantOfType(node, type) {
let multiple = type.includes(' ');
let target = multiple ? type.split(/\s+/) : type;
let current = node;
@ -605,28 +648,16 @@ ScopeResolver.TESTS = {
return false;
},
// Negates `onlyIfDescendantOfType`.
onlyIfNotDescendantOfType(...args) {
let isDescendantOfType = ScopeResolver.TESTS.onlyIfDescendantOfType(...args);
return !isDescendantOfType;
},
// Passes if this node has at least one descendant of the given type(s).
onlyIfAncestorOfType(node, type) {
ancestorOfType(node, type) {
let target = type.includes(' ') ? type.split(/\s+/) : type;
let descendants = node.descendantsOfType(target);
return descendants.length > 0;
},
// Negates `onlyIfAncestorOfType`.
onlyIfNotAncestorOfType(...args) {
let isAncestorOfType = ScopeResolver.TESTS.onlyIfAncestorOfType(...args);
return !isAncestorOfType;
},
// Passes if this range (after adjustments) has previously had data stored at
// the given key.
onlyIfRangeWithData(node, rawValue, props, existingData) {
rangeWithData(node, rawValue, props, existingData) {
if (existingData === undefined) { return false; }
let [key, value] = interpretPossibleKeyValuePair(rawValue, false);
@ -638,15 +669,9 @@ ScopeResolver.TESTS = {
(key in existingData);
},
// Negates `onlyIfRangeWithData`.
onlyIfNotRangeWithData(...args) {
let isNodeWithData = ScopeResolver.TESTS.onlyIfRangeWithData(...args);
return !isNodeWithData;
},
// Passes if one of this node's ancestors has stored data at the given key
// for its inherent range (ignoring adjustments).
onlyIfDescendantOfNodeWithData(node, rawValue, props, existingData, instance) {
descendantOfNodeWithData(node, rawValue, props, existingData, instance) {
let current = node;
let [key, value] = interpretPossibleKeyValuePair(rawValue, false);
@ -663,42 +688,24 @@ ScopeResolver.TESTS = {
return false;
},
// Negates `onlyIfDescendantOfNodeWithData`.
onlyIfNotDescendantOfNodeWithData(...args) {
let isDescendantOfNodeWithData = ScopeResolver.TESTS.onlyIfDescendantOfNodeWithData(...args);
return !isDescendantOfNodeWithData;
},
// Passes if this node starts on the same row as the one in the described
// position. Accepts a node position descriptor.
onlyIfStartsOnSameRowAs(node, descriptor) {
startsOnSameRowAs(node, descriptor) {
let otherNodePosition = resolveNodePosition(node, descriptor);
return otherNodePosition.row === node.startPosition.row;
},
// Negates `onlyIfStartsOnSameRowAs`.
onlyIfNotStartsOnSameRowAs(node, descriptor) {
let otherNodePosition = resolveNodePosition(node, descriptor);
return otherNodePosition.row !== node.startPosition.row;
},
// Passes if this node ends on the same row as the one in the described
// position. Accepts a node position descriptor.
onlyIfEndsOnSameRowAs(node, descriptor) {
endsOnSameRowAs(node, descriptor) {
let otherNodePosition = resolveNodePosition(node, descriptor);
return otherNodePosition.row === node.endPosition.row;
},
// Negates `onlyIfEndsOnSameRowAs`.
onlyIfNotEndsOnSameRowAs(node, descriptor) {
let otherNodePosition = resolveNodePosition(node, descriptor);
return otherNodePosition.row !== node.endPosition.row;
},
// Passes only when a given config option is present and truthy. Accepts
// either (a) a configuration key or (b) a configuration key and value
// separated by a space.
onlyIfConfig(node, rawValue, props, existingData, instance) {
config(node, rawValue, props, existingData, instance) {
let [key, value] = interpretPossibleKeyValuePair(rawValue, true);
// Invalid predicates should be ignored.
@ -706,12 +713,6 @@ ScopeResolver.TESTS = {
let configValue = instance.getConfig(key) ?? false;
return value === null ? !!configValue : configValue === value;
},
// Negates `onlyIfConfig`.
onlyIfNotConfig(...args) {
let onlyIfConfig = ScopeResolver.TESTS.onlyIfConfig(...args);
return !onlyIfConfig;
}
};