@dekobon/tree-sitter-groovy 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +25 -4
- package/grammar.js +627 -129
- package/package.json +1 -1
- package/prebuilds/darwin-arm64/@dekobon+tree-sitter-groovy.node +0 -0
- package/prebuilds/linux-x64/@dekobon+tree-sitter-groovy.node +0 -0
- package/prebuilds/win32-x64/@dekobon+tree-sitter-groovy.node +0 -0
- package/queries/groovy/highlights.scm +10 -4
- package/queries/groovy/locals.scm +7 -0
- package/queries/groovy/tags.scm +32 -0
- package/src/grammar.json +1765 -442
- package/src/node-types.json +593 -3
- package/src/parser.c +194884 -146231
- package/src/scanner.c +275 -24
- package/tree-sitter.json +2 -1
package/src/scanner.c
CHANGED
|
@@ -5,7 +5,16 @@
|
|
|
5
5
|
// start with `/`; combining their dispatch keeps the in-grammar lexer
|
|
6
6
|
// from partially consuming the leading `/`. The third is a look-ahead
|
|
7
7
|
// decision the grammar cannot express: `record` is a keyword only
|
|
8
|
-
// when a record header follows it.
|
|
8
|
+
// when a record header follows it. Also handles §6.8
|
|
9
|
+
// COMMAND_NAMED_KEY, an empty token before the key of a named
|
|
10
|
+
// argument in a parenthesis-free call; §6.9 SUBSCRIPT_OPEN, a `[` on
|
|
11
|
+
// the same line as the token before it; §6.11 CAST_CLOSE, a `)` with
|
|
12
|
+
// something after it on its line; and §6.12 NOT_IN, the `!in`
|
|
13
|
+
// operator. STRING_BODY (§6.10) is never emitted; it is valid only
|
|
14
|
+
// inside a string body, where the scanner emits nothing. ERROR_SENTINEL is never emitted either; the parser marks it
|
|
15
|
+
// valid only during error recovery, where COMMAND_NAMED_KEY,
|
|
16
|
+
// SUBSCRIPT_OPEN, CAST_CLOSE, NOT_IN and the STRING_BODY check are
|
|
17
|
+
// suppressed.
|
|
9
18
|
//
|
|
10
19
|
// Stateless — `create` returns NULL and the runtime calls
|
|
11
20
|
// `destroy` / `serialize` / `deserialize` as no-ops. When a future
|
|
@@ -13,14 +22,113 @@
|
|
|
13
22
|
|
|
14
23
|
#include "tree_sitter/parser.h"
|
|
15
24
|
|
|
25
|
+
#include <string.h>
|
|
26
|
+
|
|
16
27
|
enum TokenType {
|
|
17
28
|
SLASHY_STRING_START,
|
|
18
29
|
LINE_COMMENT,
|
|
19
30
|
BLOCK_COMMENT,
|
|
20
31
|
GROOVYDOC_COMMENT,
|
|
21
32
|
RECORD_KEYWORD,
|
|
33
|
+
COMMAND_NAMED_KEY,
|
|
34
|
+
SUBSCRIPT_OPEN,
|
|
35
|
+
CAST_CLOSE,
|
|
36
|
+
NOT_IN,
|
|
37
|
+
STRING_BODY,
|
|
38
|
+
ERROR_SENTINEL,
|
|
22
39
|
};
|
|
23
40
|
|
|
41
|
+
// Wider than the grammar's `identifier` token (SPECIFICATION.md
|
|
42
|
+
// §5.2): every non-ASCII code point counts as an identifier
|
|
43
|
+
// character, because the scanner has no Unicode tables. That is safe
|
|
44
|
+
// because the scanner never emits a name. It only looks ahead, for
|
|
45
|
+
// the name of a record header and for the key of a named argument,
|
|
46
|
+
// and the in-grammar lexer then reads the name with the real class.
|
|
47
|
+
// On valid input the two agree: outside strings and comments, a
|
|
48
|
+
// non-ASCII character can only be part of a name. On other input the
|
|
49
|
+
// name the lexer reads is shorter, and the parse fails there.
|
|
50
|
+
static inline bool is_identifier_start(int32_t c) {
|
|
51
|
+
return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')
|
|
52
|
+
|| c == '_' || c == '$' || c > 0x7F;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
static inline bool is_identifier_char(int32_t c) {
|
|
56
|
+
return is_identifier_start(c) || (c >= '0' && c <= '9');
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// SPECIFICATION.md §5.10 — a line escape, `\` followed by a line
|
|
60
|
+
// break, is whitespace to Groovy's lexer (GroovyLexer.g4
|
|
61
|
+
// `WS : ([ \t]+ | LineEscape+)`), so the line after it continues the
|
|
62
|
+
// current one. Skips spaces, tabs and line escapes. Returns false at a
|
|
63
|
+
// `\` that does not start a line escape: the `\` is consumed by then
|
|
64
|
+
// and no token starts with one, so the caller must return false to
|
|
65
|
+
// make the lexer rewind.
|
|
66
|
+
static bool skip_same_line_space(TSLexer *lexer, bool skip) {
|
|
67
|
+
for (;;) {
|
|
68
|
+
if (lexer->lookahead == ' ' || lexer->lookahead == '\t') {
|
|
69
|
+
lexer->advance(lexer, skip);
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
if (lexer->lookahead != '\\') {
|
|
73
|
+
return true;
|
|
74
|
+
}
|
|
75
|
+
lexer->advance(lexer, skip);
|
|
76
|
+
if (lexer->lookahead == '\r') {
|
|
77
|
+
lexer->advance(lexer, skip);
|
|
78
|
+
}
|
|
79
|
+
if (lexer->lookahead != '\n') {
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
lexer->advance(lexer, skip);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// SPECIFICATION.md §6.8 — decides that the key of a named argument
|
|
87
|
+
// in a parenthesis-free call (`exclude group: 'a'`) starts here: a
|
|
88
|
+
// name on the current line, followed by `:` and not `::`. The token is
|
|
89
|
+
// empty. `mark_end` fixes it at the start of the name, and the
|
|
90
|
+
// in-grammar lexer reads the name itself, so the key is an identifier
|
|
91
|
+
// by the grammar's own definition, non-ASCII letters included. A
|
|
92
|
+
// newline before the key means a new statement (a Spock `where:`
|
|
93
|
+
// label, say), so the caller does not skip one.
|
|
94
|
+
//
|
|
95
|
+
// The caller has skipped the same-line space and seen an identifier
|
|
96
|
+
// start.
|
|
97
|
+
static bool scan_command_named_key(TSLexer *lexer) {
|
|
98
|
+
lexer->mark_end(lexer);
|
|
99
|
+
// Keep the first few chars to reject `case:` / `default:`, which
|
|
100
|
+
// are switch labels, not keys. Apache's `namedArgLabel` accepts both
|
|
101
|
+
// keywords, so `foo default: 1` diverges on purpose: the one-liner
|
|
102
|
+
// `switch (x) { case 1: foo default: bar }` in statements-switch.txt
|
|
103
|
+
// depends on the rejection. A non-ASCII character is stored as 0,
|
|
104
|
+
// which matches neither word; cut to a `char` it could.
|
|
105
|
+
char word[8];
|
|
106
|
+
unsigned len = 0;
|
|
107
|
+
while (is_identifier_char(lexer->lookahead)) {
|
|
108
|
+
if (len < sizeof(word)) {
|
|
109
|
+
word[len] = lexer->lookahead <= 0x7F ? (char)lexer->lookahead : 0;
|
|
110
|
+
}
|
|
111
|
+
len++;
|
|
112
|
+
lexer->advance(lexer, false);
|
|
113
|
+
}
|
|
114
|
+
if ((len == 4 && memcmp(word, "case", 4) == 0)
|
|
115
|
+
|| (len == 7 && memcmp(word, "default", 7) == 0)) {
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
if (!skip_same_line_space(lexer, false)) {
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
121
|
+
if (lexer->lookahead != ':') {
|
|
122
|
+
return false;
|
|
123
|
+
}
|
|
124
|
+
lexer->advance(lexer, false);
|
|
125
|
+
if (lexer->lookahead == ':') {
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
128
|
+
lexer->result_symbol = COMMAND_NAMED_KEY;
|
|
129
|
+
return true;
|
|
130
|
+
}
|
|
131
|
+
|
|
24
132
|
static inline void advance(TSLexer *lexer) {
|
|
25
133
|
lexer->advance(lexer, false);
|
|
26
134
|
}
|
|
@@ -33,26 +141,25 @@ static inline bool is_inline_space(int32_t c) {
|
|
|
33
141
|
return c == ' ' || c == '\t';
|
|
34
142
|
}
|
|
35
143
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')
|
|
39
|
-
|| c == '_' || c == '$';
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
static inline bool is_identifier_char(int32_t c) {
|
|
43
|
-
return is_identifier_start(c) || (c >= '0' && c <= '9');
|
|
144
|
+
static inline bool is_line_break(int32_t c) {
|
|
145
|
+
return c == '\n' || c == '\r';
|
|
44
146
|
}
|
|
45
147
|
|
|
46
148
|
// Skips the trivia Groovy's lexer hides inside a record header:
|
|
47
|
-
// spaces, tabs and block comments, plus line breaks and
|
|
48
|
-
// when `newlines` is set. Returns false on a `/` that
|
|
49
|
-
// comment kind (a slashy string or division cannot be
|
|
50
|
-
// header) and on an
|
|
149
|
+
// spaces, tabs, line escapes and block comments, plus line breaks and
|
|
150
|
+
// line comments when `newlines` is set. Returns false on a `/` that
|
|
151
|
+
// opens neither comment kind (a slashy string or division cannot be
|
|
152
|
+
// part of a header), on a `\` that is not a line escape, and on an
|
|
153
|
+
// unterminated block comment.
|
|
51
154
|
static bool skip_header_trivia(TSLexer *lexer, bool newlines) {
|
|
52
155
|
for (;;) {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
156
|
+
for (;;) {
|
|
157
|
+
if (!skip_same_line_space(lexer, false)) {
|
|
158
|
+
return false;
|
|
159
|
+
}
|
|
160
|
+
if (!newlines || !is_line_break(lexer->lookahead)) {
|
|
161
|
+
break;
|
|
162
|
+
}
|
|
56
163
|
advance(lexer);
|
|
57
164
|
}
|
|
58
165
|
if (lexer->lookahead != '/') {
|
|
@@ -77,7 +184,7 @@ static bool skip_header_trivia(TSLexer *lexer, bool newlines) {
|
|
|
77
184
|
}
|
|
78
185
|
}
|
|
79
186
|
} else if (newlines && lexer->lookahead == '/') {
|
|
80
|
-
while (!lexer->eof(lexer) && lexer->lookahead
|
|
187
|
+
while (!lexer->eof(lexer) && !is_line_break(lexer->lookahead)) {
|
|
81
188
|
advance(lexer);
|
|
82
189
|
}
|
|
83
190
|
} else {
|
|
@@ -112,7 +219,8 @@ static bool scan_record_header(TSLexer *lexer) {
|
|
|
112
219
|
return false;
|
|
113
220
|
}
|
|
114
221
|
lexer->mark_end(lexer);
|
|
115
|
-
if (!is_inline_space(lexer->lookahead) && lexer->lookahead != '/'
|
|
222
|
+
if (!is_inline_space(lexer->lookahead) && lexer->lookahead != '/'
|
|
223
|
+
&& lexer->lookahead != '\\') {
|
|
116
224
|
return false;
|
|
117
225
|
}
|
|
118
226
|
if (!skip_header_trivia(lexer, false)) {
|
|
@@ -135,6 +243,57 @@ static bool scan_record_header(TSLexer *lexer) {
|
|
|
135
243
|
return lexer->lookahead == '(';
|
|
136
244
|
}
|
|
137
245
|
|
|
246
|
+
// SPECIFICATION.md §6.11 — whether something follows a `)` on its
|
|
247
|
+
// line. The `)` is already consumed when this is called.
|
|
248
|
+
// `castParExpression castOperandExpression` in GroovyParser.g4 has no
|
|
249
|
+
// `nls` between the two, so where a line break can end a statement,
|
|
250
|
+
// `x = (a)` followed by `foo()` on the next line is two statements.
|
|
251
|
+
// The grammar ranks a cast by this answer; it does not forbid one,
|
|
252
|
+
// because inside `(…)` and `[…]` a line break ends nothing. Spaces,
|
|
253
|
+
// tabs, line escapes and block comments that close on the line may
|
|
254
|
+
// come first. A line comment ends the line.
|
|
255
|
+
static bool cast_operand_follows(TSLexer *lexer) {
|
|
256
|
+
lexer->mark_end(lexer);
|
|
257
|
+
for (;;) {
|
|
258
|
+
if (!skip_same_line_space(lexer, false)) {
|
|
259
|
+
return true;
|
|
260
|
+
}
|
|
261
|
+
if (lexer->eof(lexer) || is_line_break(lexer->lookahead)) {
|
|
262
|
+
return false;
|
|
263
|
+
}
|
|
264
|
+
if (lexer->lookahead != '/') {
|
|
265
|
+
return true;
|
|
266
|
+
}
|
|
267
|
+
advance(lexer);
|
|
268
|
+
if (lexer->lookahead == '/') {
|
|
269
|
+
return false;
|
|
270
|
+
}
|
|
271
|
+
if (lexer->lookahead != '*') {
|
|
272
|
+
return true;
|
|
273
|
+
}
|
|
274
|
+
// A block comment that closes on this line is skipped, and the
|
|
275
|
+
// answer depends on what follows it. One that runs past the
|
|
276
|
+
// end of the line counts as something on the line: Groovy's
|
|
277
|
+
// lexer drops the comment with its line breaks, and reading
|
|
278
|
+
// on would scan an unterminated comment to the end of input.
|
|
279
|
+
advance(lexer);
|
|
280
|
+
for (;;) {
|
|
281
|
+
if (lexer->eof(lexer) || is_line_break(lexer->lookahead)) {
|
|
282
|
+
return true;
|
|
283
|
+
}
|
|
284
|
+
if (lexer->lookahead == '*') {
|
|
285
|
+
advance(lexer);
|
|
286
|
+
if (lexer->lookahead == '/') {
|
|
287
|
+
advance(lexer);
|
|
288
|
+
break;
|
|
289
|
+
}
|
|
290
|
+
} else {
|
|
291
|
+
advance(lexer);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
138
297
|
void *tree_sitter_groovy_external_scanner_create(void) {
|
|
139
298
|
return NULL;
|
|
140
299
|
}
|
|
@@ -169,6 +328,53 @@ bool tree_sitter_groovy_external_scanner_scan(
|
|
|
169
328
|
) {
|
|
170
329
|
(void)payload;
|
|
171
330
|
|
|
331
|
+
// SPECIFICATION.md §6.10 — STRING_BODY is valid only between the
|
|
332
|
+
// quotes of a string whose body is lexed in pieces. Comments are
|
|
333
|
+
// extras, so the parser offers them there as well, and this
|
|
334
|
+
// function is called at every piece boundary. Without this check a
|
|
335
|
+
// body that starts with `//` or `/*`, or has one after an
|
|
336
|
+
// interpolation, lost that text to a comment token. The body
|
|
337
|
+
// belongs to the in-grammar lexer.
|
|
338
|
+
if (valid_symbols[STRING_BODY] && !valid_symbols[ERROR_SENTINEL]) {
|
|
339
|
+
return false;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// Same-line tokens: SUBSCRIPT_OPEN (§5.10) and COMMAND_NAMED_KEY
|
|
343
|
+
// (§5.5). Both skip only spaces, tabs and line escapes, the
|
|
344
|
+
// same-line whitespace of Groovy's lexer; at a newline we fall
|
|
345
|
+
// through to the branches below. Their first characters (`[` vs. an
|
|
346
|
+
// identifier start) are disjoint, so one whitespace skip serves
|
|
347
|
+
// both.
|
|
348
|
+
bool wants_subscript = valid_symbols[SUBSCRIPT_OPEN];
|
|
349
|
+
bool wants_named_key = valid_symbols[COMMAND_NAMED_KEY];
|
|
350
|
+
if ((wants_subscript || wants_named_key)
|
|
351
|
+
&& !valid_symbols[ERROR_SENTINEL]) {
|
|
352
|
+
if (!skip_same_line_space(lexer, true)) {
|
|
353
|
+
return false;
|
|
354
|
+
}
|
|
355
|
+
// SPECIFICATION.md §5.10 — a `[` on the current line can continue
|
|
356
|
+
// the expression (subscript or array type). After a newline the
|
|
357
|
+
// in-grammar `[` starts a list or map literal instead.
|
|
358
|
+
if (wants_subscript && lexer->lookahead == '[') {
|
|
359
|
+
lexer->advance(lexer, false);
|
|
360
|
+
lexer->result_symbol = SUBSCRIPT_OPEN;
|
|
361
|
+
return true;
|
|
362
|
+
}
|
|
363
|
+
// An identifier is either the key or, once consumed, not something
|
|
364
|
+
// the code below could start (a comment or slashy string), so stop
|
|
365
|
+
// here. Falling through after the scan advanced past `b` in
|
|
366
|
+
// `foo a, b` and a newline made the comment branch emit `b` as a
|
|
367
|
+
// line_comment, since mark_end had already been called.
|
|
368
|
+
// RECORD_KEYWORD can be valid here too, after a statement-start
|
|
369
|
+
// identifier (`foo` then `record R(…)`). The key wins on the same
|
|
370
|
+
// line: `foo record: 1` is a named argument, while a record header
|
|
371
|
+
// there is not valid Groovy. A `record` on the next line is not
|
|
372
|
+
// reached here, since a newline is not skipped.
|
|
373
|
+
if (wants_named_key && is_identifier_start(lexer->lookahead)) {
|
|
374
|
+
return scan_command_named_key(lexer);
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
172
378
|
// SPECIFICATION.md §6.6 — line / block / groovydoc comments
|
|
173
379
|
// and §6.2 slashy strings. Both start with `/`; combining their
|
|
174
380
|
// dispatch keeps the in-grammar lexer from partially consuming
|
|
@@ -184,12 +390,25 @@ bool tree_sitter_groovy_external_scanner_scan(
|
|
|
184
390
|
|| valid_symbols[GROOVYDOC_COMMENT];
|
|
185
391
|
bool wants_slashy = valid_symbols[SLASHY_STRING_START];
|
|
186
392
|
bool wants_record = valid_symbols[RECORD_KEYWORD];
|
|
187
|
-
|
|
393
|
+
bool wants_cast_close = valid_symbols[CAST_CLOSE]
|
|
394
|
+
&& !valid_symbols[ERROR_SENTINEL];
|
|
395
|
+
bool wants_not_in = valid_symbols[NOT_IN]
|
|
396
|
+
&& !valid_symbols[ERROR_SENTINEL];
|
|
397
|
+
if (!wants_comment && !wants_slashy && !wants_record
|
|
398
|
+
&& !wants_cast_close && !wants_not_in) {
|
|
188
399
|
return false;
|
|
189
400
|
}
|
|
190
401
|
|
|
191
|
-
|
|
192
|
-
|
|
402
|
+
// A line escape is whitespace too, so a comment, a slashy string or
|
|
403
|
+
// `record` may follow one.
|
|
404
|
+
while (is_whitespace(lexer->lookahead) || lexer->lookahead == '\\') {
|
|
405
|
+
if (lexer->lookahead == '\\') {
|
|
406
|
+
if (!skip_same_line_space(lexer, true)) {
|
|
407
|
+
return false;
|
|
408
|
+
}
|
|
409
|
+
} else {
|
|
410
|
+
lexer->advance(lexer, true);
|
|
411
|
+
}
|
|
193
412
|
}
|
|
194
413
|
if (lexer->lookahead == 'r' && wants_record) {
|
|
195
414
|
// An `r` can never open a comment or a slashy string, so
|
|
@@ -209,6 +428,37 @@ bool tree_sitter_groovy_external_scanner_scan(
|
|
|
209
428
|
lexer->result_symbol = RECORD_KEYWORD;
|
|
210
429
|
return true;
|
|
211
430
|
}
|
|
431
|
+
if (lexer->lookahead == '!' && wants_not_in) {
|
|
432
|
+
// SPECIFICATION.md §6.12 — `!in` is an operator only before
|
|
433
|
+
// whitespace or an opening bracket (GroovyLexer.g4 `NOT_IN`),
|
|
434
|
+
// so that `a !inside` is `!` applied to a name. The character
|
|
435
|
+
// after it is read and not taken: `a !in[1, 2]`.
|
|
436
|
+
static const char not_in[] = "!in";
|
|
437
|
+
for (const char *c = not_in; *c != '\0'; c++) {
|
|
438
|
+
if (lexer->lookahead != *c) {
|
|
439
|
+
return false;
|
|
440
|
+
}
|
|
441
|
+
advance(lexer);
|
|
442
|
+
}
|
|
443
|
+
int32_t next = lexer->lookahead;
|
|
444
|
+
if (!is_whitespace(next) && next != '[' && next != '('
|
|
445
|
+
&& next != '{') {
|
|
446
|
+
return false;
|
|
447
|
+
}
|
|
448
|
+
lexer->result_symbol = NOT_IN;
|
|
449
|
+
return true;
|
|
450
|
+
}
|
|
451
|
+
if (lexer->lookahead == ')' && wants_cast_close) {
|
|
452
|
+
// On `false` the runtime rewinds and the in-grammar lexer reads
|
|
453
|
+
// a plain `)`. A cast that closes with one ranks below a
|
|
454
|
+
// parenthesized expression.
|
|
455
|
+
advance(lexer);
|
|
456
|
+
if (!cast_operand_follows(lexer)) {
|
|
457
|
+
return false;
|
|
458
|
+
}
|
|
459
|
+
lexer->result_symbol = CAST_CLOSE;
|
|
460
|
+
return true;
|
|
461
|
+
}
|
|
212
462
|
if (lexer->lookahead != '/') {
|
|
213
463
|
return false;
|
|
214
464
|
}
|
|
@@ -227,8 +477,9 @@ bool tree_sitter_groovy_external_scanner_scan(
|
|
|
227
477
|
advance(lexer);
|
|
228
478
|
// EOF-bounded loop: lexer->eof() guarantees termination on
|
|
229
479
|
// unterminated input. Do not tighten without preserving the
|
|
230
|
-
// EOF guard.
|
|
231
|
-
|
|
480
|
+
// EOF guard. A lone `\r` ends the line too, as in Groovy's
|
|
481
|
+
// lexer (`LineTerminator`), so the comment stops before it.
|
|
482
|
+
while (!lexer->eof(lexer) && !is_line_break(lexer->lookahead)) {
|
|
232
483
|
advance(lexer);
|
|
233
484
|
}
|
|
234
485
|
lexer->result_symbol = LINE_COMMENT;
|
|
@@ -330,7 +581,7 @@ bool tree_sitter_groovy_external_scanner_scan(
|
|
|
330
581
|
found_close = true;
|
|
331
582
|
break;
|
|
332
583
|
}
|
|
333
|
-
if (lexer->lookahead
|
|
584
|
+
if (is_line_break(lexer->lookahead)) {
|
|
334
585
|
break;
|
|
335
586
|
}
|
|
336
587
|
advance(lexer);
|
package/tree-sitter.json
CHANGED
|
@@ -9,11 +9,12 @@
|
|
|
9
9
|
"file-types": ["groovy", "gradle", "gvy", "gy", "jenkinsfile"],
|
|
10
10
|
"injection-regex": "^groovy$",
|
|
11
11
|
"highlights": "queries/groovy/highlights.scm",
|
|
12
|
+
"tags": "queries/groovy/tags.scm",
|
|
12
13
|
"class-name": "TreeSitterGroovy"
|
|
13
14
|
}
|
|
14
15
|
],
|
|
15
16
|
"metadata": {
|
|
16
|
-
"version": "0.
|
|
17
|
+
"version": "0.6.0",
|
|
17
18
|
"license": "Apache-2.0 OR MIT",
|
|
18
19
|
"description": "Apache Groovy grammar for tree-sitter (dekobon fork)",
|
|
19
20
|
"authors": [
|