@marko/tree-sitter 0.3.0 → 0.4.1
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/package.json +9 -9
- package/prebuilds/darwin-arm64/@marko+tree-sitter.node +0 -0
- package/prebuilds/darwin-x64/@marko+tree-sitter.node +0 -0
- package/prebuilds/linux-arm64/@marko+tree-sitter.node +0 -0
- package/prebuilds/linux-x64/@marko+tree-sitter.node +0 -0
- package/prebuilds/win32-x64/@marko+tree-sitter.node +0 -0
- package/queries/highlights.scm +1 -0
- package/src/parser.c +2 -2
- package/src/scanner.c +204 -54
- package/tree-sitter-marko.wasm +0 -0
- package/tree-sitter.json +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@marko/tree-sitter",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"description": "Tree-sitter grammar for the Marko templating language",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"incremental",
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"tree-sitter.json"
|
|
35
35
|
],
|
|
36
36
|
"dependencies": {
|
|
37
|
-
"node-addon-api": "^8.
|
|
37
|
+
"node-addon-api": "^8.9.2",
|
|
38
38
|
"node-gyp-build": "^4.8.4"
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
@@ -42,15 +42,15 @@
|
|
|
42
42
|
"@changesets/cli": "^2.31.0",
|
|
43
43
|
"@types/degit": "^2.8.6",
|
|
44
44
|
"@types/mocha": "^10.0.6",
|
|
45
|
-
"@types/node": "^20.
|
|
45
|
+
"@types/node": "^22.20.4",
|
|
46
46
|
"degit": "^2.8.4",
|
|
47
|
-
"htmljs-parser": "^5.
|
|
48
|
-
"mocha": "^
|
|
49
|
-
"node-gyp": "^
|
|
47
|
+
"htmljs-parser": "^5.20.0",
|
|
48
|
+
"mocha": "^12.0.2",
|
|
49
|
+
"node-gyp": "^13.0.2",
|
|
50
50
|
"prebuildify": "^6.0.1",
|
|
51
|
-
"tree-sitter": "^0.25.
|
|
51
|
+
"tree-sitter": "^0.25.1",
|
|
52
52
|
"tree-sitter-cli": "^0.26.9",
|
|
53
|
-
"tsx": "^4.
|
|
53
|
+
"tsx": "^4.23.15",
|
|
54
54
|
"web-tree-sitter": "^0.26.9"
|
|
55
55
|
},
|
|
56
56
|
"peerDependencies": {
|
|
@@ -81,6 +81,6 @@
|
|
|
81
81
|
"prebuild": "prebuildify --napi --strip",
|
|
82
82
|
"build:wasm": "tree-sitter build --wasm -o tree-sitter-marko.wasm",
|
|
83
83
|
"test:wasm": "tsx tools/check-wasm.mts",
|
|
84
|
-
"version": "changeset version && node tools/sync-tree-sitter-version.mjs && pnpm install --lockfile-only"
|
|
84
|
+
"version": "changeset version && node tools/sync-tree-sitter-version.mjs && tree-sitter generate && pnpm install --lockfile-only"
|
|
85
85
|
}
|
|
86
86
|
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/queries/highlights.scm
CHANGED
package/src/parser.c
CHANGED
|
@@ -7965,8 +7965,8 @@ TS_PUBLIC const TSLanguage *tree_sitter_marko(void) {
|
|
|
7965
7965
|
.max_reserved_word_set_size = 0,
|
|
7966
7966
|
.metadata = {
|
|
7967
7967
|
.major_version = 0,
|
|
7968
|
-
.minor_version =
|
|
7969
|
-
.patch_version =
|
|
7968
|
+
.minor_version = 4,
|
|
7969
|
+
.patch_version = 1,
|
|
7970
7970
|
},
|
|
7971
7971
|
};
|
|
7972
7972
|
return &language;
|
package/src/scanner.c
CHANGED
|
@@ -555,16 +555,57 @@ static bool should_terminate(EStream *es, ExprState *e, int32_t code,
|
|
|
555
555
|
return false;
|
|
556
556
|
}
|
|
557
557
|
|
|
558
|
+
// Only JavaScript has `delete` and `void` operators; in a type `void` is the
|
|
559
|
+
// `void` type. `return`, `throw` and `yield` are left out: JavaScript allows
|
|
560
|
+
// no line break after them, so they never continue an expression.
|
|
558
561
|
static const char *const UNARY_KEYWORDS[] = {
|
|
559
|
-
"async", "await", "class", "function", "new", "typeof", "
|
|
562
|
+
"async", "await", "class", "function", "new", "typeof", "delete", "void",
|
|
563
|
+
NULL,
|
|
560
564
|
};
|
|
561
565
|
static const char *const TS_UNARY_KEYWORDS[] = {
|
|
562
|
-
"async",
|
|
563
|
-
"asserts", "infer",
|
|
566
|
+
"async", "await", "class", "function", "new", "typeof",
|
|
567
|
+
"asserts", "infer", "is", "keyof", "readonly", "unique", NULL,
|
|
564
568
|
};
|
|
565
569
|
static const char *const BINARY_KEYWORDS[] = {
|
|
566
570
|
"as", "extends", "instanceof", "in", "satisfies", NULL,
|
|
567
571
|
};
|
|
572
|
+
static const char *const RELATIONAL_KEYWORDS[] = {"in", "instanceof", NULL};
|
|
573
|
+
|
|
574
|
+
static bool es_peek_matches(EStream *es, uint32_t k, const char *str) {
|
|
575
|
+
for (uint32_t j = 0; str[j]; j++) {
|
|
576
|
+
if (es_peek(es, k + j) != (int32_t)str[j]) return false;
|
|
577
|
+
}
|
|
578
|
+
return true;
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
// prepareScriptlet's type look-ahead from offset `k`: `declare`, `interface`
|
|
582
|
+
// or `type` (after spaces) before a name, or `type` before `{`/`*`, starts a
|
|
583
|
+
// type; `type = 1` and `type in x` stay JavaScript.
|
|
584
|
+
static bool looks_like_type_statement(EStream *es, uint32_t k) {
|
|
585
|
+
static const char *const TYPE_KEYWORDS[] = {"declare", "interface", "type",
|
|
586
|
+
NULL};
|
|
587
|
+
while (is_indent_code(es_peek(es, k))) k++;
|
|
588
|
+
for (int i = 0; TYPE_KEYWORDS[i]; i++) {
|
|
589
|
+
if (!es_peek_matches(es, k, TYPE_KEYWORDS[i])) continue;
|
|
590
|
+
|
|
591
|
+
uint32_t n = k + (uint32_t)strlen(TYPE_KEYWORDS[i]);
|
|
592
|
+
if (!is_indent_code(es_peek(es, n))) return false;
|
|
593
|
+
while (is_indent_code(es_peek(es, n))) n++;
|
|
594
|
+
|
|
595
|
+
int32_t c = es_peek(es, n);
|
|
596
|
+
if (c == '{' || c == '*') return strcmp(TYPE_KEYWORDS[i], "type") == 0;
|
|
597
|
+
if (!is_word_code(c) || (c >= '0' && c <= '9')) return false;
|
|
598
|
+
for (int b = 0; BINARY_KEYWORDS[b]; b++) {
|
|
599
|
+
if (es_peek_matches(es, n, BINARY_KEYWORDS[b]) &&
|
|
600
|
+
!is_word_code(
|
|
601
|
+
es_peek(es, n + (uint32_t)strlen(BINARY_KEYWORDS[b])))) {
|
|
602
|
+
return false;
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
return true;
|
|
606
|
+
}
|
|
607
|
+
return false;
|
|
608
|
+
}
|
|
568
609
|
|
|
569
610
|
static int64_t look_behind_for(EStream *es, int64_t pos, const char *str) {
|
|
570
611
|
int64_t len = (int64_t)strlen(str);
|
|
@@ -586,6 +627,19 @@ static int64_t look_behind_while_ws(EStream *es, int64_t pos) {
|
|
|
586
627
|
return 0;
|
|
587
628
|
}
|
|
588
629
|
|
|
630
|
+
// Returns where a whole keyword ending at `pos` starts, or -1.
|
|
631
|
+
static int64_t look_behind_for_keyword(EStream *es, int64_t pos,
|
|
632
|
+
const char *const *keywords) {
|
|
633
|
+
for (int i = 0; keywords[i]; i++) {
|
|
634
|
+
int64_t keyword_pos = look_behind_for(es, pos, keywords[i]);
|
|
635
|
+
if (keyword_pos != -1) {
|
|
636
|
+
int32_t before = keyword_pos > 0 ? es_at(es, keyword_pos - 1) : -1;
|
|
637
|
+
return is_word_or_period_code(before) ? -1 : keyword_pos;
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
return -1;
|
|
641
|
+
}
|
|
642
|
+
|
|
589
643
|
// lookBehindForOperator: `pos` is a buf index just past the candidate.
|
|
590
644
|
static int64_t look_behind_for_operator(EStream *es, ExprState *e,
|
|
591
645
|
int64_t pos) {
|
|
@@ -593,12 +647,33 @@ static int64_t look_behind_for_operator(EStream *es, ExprState *e,
|
|
|
593
647
|
int32_t code = es_at(es, cur_pos);
|
|
594
648
|
|
|
595
649
|
switch (code) {
|
|
596
|
-
case '&': case '*': case '^': case ':': case '=':
|
|
650
|
+
case '&': case '*': case '^': case ':': case '=':
|
|
597
651
|
case '<': case '%': case '|': case '?': case '~':
|
|
598
652
|
// The operator may sit just before the token start (cur_pos == -1, eg
|
|
599
653
|
// resuming a tag var type after its ":"); callers only test != -1.
|
|
600
654
|
return cur_pos < 0 ? 0 : cur_pos;
|
|
601
655
|
|
|
656
|
+
case '!': {
|
|
657
|
+
// After an operand, `!` is a TypeScript non-null assertion (postfix),
|
|
658
|
+
// as is each `!` of a run after one (`x!!`); after a keyword operator
|
|
659
|
+
// (`typeof!a`, `a in!b`) it is the prefix `!`.
|
|
660
|
+
int64_t operand_end = cur_pos - 1;
|
|
661
|
+
while (es_at(es, operand_end) == '!') operand_end--;
|
|
662
|
+
int32_t operand = es_at(es, operand_end);
|
|
663
|
+
switch (operand) {
|
|
664
|
+
case ')': case ']': case '"': case '\'': case '`':
|
|
665
|
+
return -1;
|
|
666
|
+
default:
|
|
667
|
+
return is_word_code(operand) &&
|
|
668
|
+
look_behind_for_operator(es, e, operand_end + 1) ==
|
|
669
|
+
-1 &&
|
|
670
|
+
look_behind_for_keyword(es, operand_end,
|
|
671
|
+
RELATIONAL_KEYWORDS) == -1
|
|
672
|
+
? -1
|
|
673
|
+
: (cur_pos < 0 ? 0 : cur_pos);
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
|
|
602
677
|
case '>':
|
|
603
678
|
if (es_at(es, cur_pos - 1) == '=') return cur_pos - 1;
|
|
604
679
|
return e->in_type ? -1 : (cur_pos < 0 ? 0 : cur_pos);
|
|
@@ -629,16 +704,8 @@ static int64_t look_behind_for_operator(EStream *es, ExprState *e,
|
|
|
629
704
|
|
|
630
705
|
default: {
|
|
631
706
|
if (code < 'a' || code > 'z') return -1;
|
|
632
|
-
|
|
633
|
-
e->in_type ? TS_UNARY_KEYWORDS : UNARY_KEYWORDS;
|
|
634
|
-
for (int i = 0; keywords[i]; i++) {
|
|
635
|
-
int64_t keyword_pos = look_behind_for(es, cur_pos, keywords[i]);
|
|
636
|
-
if (keyword_pos != -1) {
|
|
637
|
-
int32_t before = keyword_pos > 0 ? es_at(es, keyword_pos - 1) : -1;
|
|
638
|
-
return is_word_or_period_code(before) ? -1 : keyword_pos;
|
|
639
|
-
}
|
|
640
|
-
}
|
|
641
|
-
return -1;
|
|
707
|
+
return look_behind_for_keyword(
|
|
708
|
+
es, cur_pos, e->in_type ? TS_UNARY_KEYWORDS : UNARY_KEYWORDS);
|
|
642
709
|
}
|
|
643
710
|
}
|
|
644
711
|
}
|
|
@@ -743,9 +810,15 @@ static bool check_for_operators(EStream *es, ExprState *e, bool eol) {
|
|
|
743
810
|
}
|
|
744
811
|
|
|
745
812
|
int32_t next_c = es_peek(es, k);
|
|
746
|
-
//
|
|
747
|
-
|
|
748
|
-
|
|
813
|
+
// A "</" close tag (html mode) or a "<!--" html comment is markup rather
|
|
814
|
+
// than a less-than operator, which look_ahead_for_operator would otherwise
|
|
815
|
+
// continue across.
|
|
816
|
+
if (next_c == '<') {
|
|
817
|
+
if (!is_concise(s) && es_peek(es, k + 1) == '/') return false;
|
|
818
|
+
if (es_peek(es, k + 1) == '!' && es_peek(es, k + 2) == '-' &&
|
|
819
|
+
es_peek(es, k + 3) == '-') {
|
|
820
|
+
return false;
|
|
821
|
+
}
|
|
749
822
|
}
|
|
750
823
|
|
|
751
824
|
if (next_c >= 0 && !should_terminate(es, e, next_c, k)) {
|
|
@@ -1643,7 +1716,16 @@ static bool scan_scriptlet_body(Scanner *s, TSLexer *lexer, const bool *valid,
|
|
|
1643
1716
|
memset(&cfg, 0, sizeof(cfg));
|
|
1644
1717
|
cfg.operators = true;
|
|
1645
1718
|
cfg.terminated_by_eol = true;
|
|
1646
|
-
|
|
1719
|
+
EStream es;
|
|
1720
|
+
es_init(&es, s, lexer, true);
|
|
1721
|
+
mark(s, lexer); // empty expression: zero-width token
|
|
1722
|
+
if (looks_like_type_statement(&es, 0)) {
|
|
1723
|
+
cfg.in_type = true;
|
|
1724
|
+
cfg.force_type = true;
|
|
1725
|
+
}
|
|
1726
|
+
bool ok = scan_expr_es(&es, cfg, NULL);
|
|
1727
|
+
es_free(&es);
|
|
1728
|
+
if (!ok) return false;
|
|
1647
1729
|
*result = SCRIPTLET_EXPR;
|
|
1648
1730
|
return true;
|
|
1649
1731
|
}
|
|
@@ -2501,6 +2583,19 @@ static bool scan_frame_content(Scanner *s, TSLexer *lexer, const bool *valid,
|
|
|
2501
2583
|
// Concise mode content (CONCISE_HTML_CONTENT)
|
|
2502
2584
|
// --------------------------------------------------------------------------
|
|
2503
2585
|
|
|
2586
|
+
// In concise mode only whitespace may follow a comment block on its line; the
|
|
2587
|
+
// comment event still fires before the error.
|
|
2588
|
+
static void check_block_comment_tail(Scanner *s, EStream *es) {
|
|
2589
|
+
for (uint32_t k = 0;; k++) {
|
|
2590
|
+
int32_t c = es_peek(es, k);
|
|
2591
|
+
if (c < 0 || is_line(c)) return;
|
|
2592
|
+
if (!is_ws(c)) {
|
|
2593
|
+
s->fail_next = 1; // INVALID_CHARACTER after this token
|
|
2594
|
+
return;
|
|
2595
|
+
}
|
|
2596
|
+
}
|
|
2597
|
+
}
|
|
2598
|
+
|
|
2504
2599
|
static bool scan_concise(Scanner *s, TSLexer *lexer, const bool *valid,
|
|
2505
2600
|
TSSymbol *result) {
|
|
2506
2601
|
// Trivia: newlines and indentation.
|
|
@@ -2574,6 +2669,7 @@ static bool scan_concise(Scanner *s, TSLexer *lexer, const bool *valid,
|
|
|
2574
2669
|
} else if (n1 == '*') {
|
|
2575
2670
|
*result = BLOCK_COMMENT;
|
|
2576
2671
|
ok = es_consume_block_comment(&es) && valid[BLOCK_COMMENT];
|
|
2672
|
+
if (ok) check_block_comment_tail(s, &es);
|
|
2577
2673
|
}
|
|
2578
2674
|
es_free(&es);
|
|
2579
2675
|
return ok;
|
|
@@ -2609,6 +2705,14 @@ static bool scan_concise(Scanner *s, TSLexer *lexer, const bool *valid,
|
|
|
2609
2705
|
}
|
|
2610
2706
|
}
|
|
2611
2707
|
|
|
2708
|
+
if (c == ';') {
|
|
2709
|
+
// An empty line, as after a tag ended by ";": the ";" is trivia and only
|
|
2710
|
+
// comments may follow it.
|
|
2711
|
+
skipc(s, lexer);
|
|
2712
|
+
s->semicolon_line = 1;
|
|
2713
|
+
return scan_concise(s, lexer, valid, result);
|
|
2714
|
+
}
|
|
2715
|
+
|
|
2612
2716
|
if (c == '<') {
|
|
2613
2717
|
// Begin a mixed-mode html block; HTML_CONTENT handles the "<".
|
|
2614
2718
|
s->begin_mixed_mode = 1;
|
|
@@ -2633,12 +2737,17 @@ static bool scan_concise(Scanner *s, TSLexer *lexer, const bool *valid,
|
|
|
2633
2737
|
}
|
|
2634
2738
|
|
|
2635
2739
|
if (c == '-') {
|
|
2636
|
-
|
|
2740
|
+
int32_t n1 = es_peek(&es, 1);
|
|
2741
|
+
if (n1 == '-') {
|
|
2637
2742
|
ok = valid[BLOCK_OPEN] && scan_block_open(s, &es, result);
|
|
2743
|
+
es_free(&es);
|
|
2744
|
+
return ok;
|
|
2638
2745
|
}
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2746
|
+
if ((t && t->type == TAG_TEXT) || !is_word_code(n1)) {
|
|
2747
|
+
es_free(&es);
|
|
2748
|
+
return false; // INVALID_LINE_START: single hyphen
|
|
2749
|
+
}
|
|
2750
|
+
// A tag name starting with "-" (eg "-if"): the open tag below reads it.
|
|
2642
2751
|
}
|
|
2643
2752
|
|
|
2644
2753
|
if (c == '/') {
|
|
@@ -2649,20 +2758,7 @@ static bool scan_concise(Scanner *s, TSLexer *lexer, const bool *valid,
|
|
|
2649
2758
|
ok = valid[LINE_COMMENT];
|
|
2650
2759
|
} else if (n1 == '*') {
|
|
2651
2760
|
ok = es_consume_block_comment(&es);
|
|
2652
|
-
if (ok)
|
|
2653
|
-
// In concise mode, only whitespace may follow a comment block; the
|
|
2654
|
-
// comment event still fires before the error.
|
|
2655
|
-
uint32_t k = 0;
|
|
2656
|
-
while (true) {
|
|
2657
|
-
int32_t c2 = es_peek(&es, k);
|
|
2658
|
-
if (c2 < 0 || is_line(c2)) break;
|
|
2659
|
-
if (!is_ws(c2)) {
|
|
2660
|
-
s->fail_next = 1; // INVALID_CHARACTER after this token
|
|
2661
|
-
break;
|
|
2662
|
-
}
|
|
2663
|
-
k++;
|
|
2664
|
-
}
|
|
2665
|
-
}
|
|
2761
|
+
if (ok) check_block_comment_tail(s, &es);
|
|
2666
2762
|
*result = BLOCK_COMMENT;
|
|
2667
2763
|
ok = ok && valid[BLOCK_COMMENT];
|
|
2668
2764
|
}
|
|
@@ -2703,6 +2799,14 @@ static bool is_tag_name_terminator(Scanner *s, int32_t c, int32_t next) {
|
|
|
2703
2799
|
}
|
|
2704
2800
|
}
|
|
2705
2801
|
|
|
2802
|
+
// Nothing of the tag name is read yet: OPEN_TAG still skips "," and
|
|
2803
|
+
// whitespace, and TAG_NAME starts at the next char.
|
|
2804
|
+
static bool tag_name_pending(Scanner *s) {
|
|
2805
|
+
TagFrame *t = top_tag(s);
|
|
2806
|
+
return t && !s->tag_name_done && !s->in_shorthand && t->name_len == 0 &&
|
|
2807
|
+
!(t->flags & TF_NAME_INTERP);
|
|
2808
|
+
}
|
|
2809
|
+
|
|
2706
2810
|
// Finish the current tag name segment (called when a terminator is seen).
|
|
2707
2811
|
static void finalize_tag_name(Scanner *s, int32_t stop_char) {
|
|
2708
2812
|
if (!s->tag_name_done) {
|
|
@@ -2835,23 +2939,10 @@ static bool scan_statement_tail(Scanner *s, TSLexer *lexer, const bool *valid,
|
|
|
2835
2939
|
cfg.terminated_by_eol = true;
|
|
2836
2940
|
cfg.consume_indented = true;
|
|
2837
2941
|
|
|
2838
|
-
//
|
|
2839
|
-
|
|
2840
|
-
|
|
2841
|
-
|
|
2842
|
-
const char *p = PREFIXES[i];
|
|
2843
|
-
bool match_p = true;
|
|
2844
|
-
for (uint32_t j = 0; p[j]; j++) {
|
|
2845
|
-
if (es_peek(es, 1 + j) != (int32_t)p[j]) {
|
|
2846
|
-
match_p = false;
|
|
2847
|
-
break;
|
|
2848
|
-
}
|
|
2849
|
-
}
|
|
2850
|
-
if (match_p) {
|
|
2851
|
-
cfg.in_type = true;
|
|
2852
|
-
cfg.force_type = true;
|
|
2853
|
-
break;
|
|
2854
|
-
}
|
|
2942
|
+
// prepareStatement: the code starts one past the tag name.
|
|
2943
|
+
if (looks_like_type_statement(es, 1)) {
|
|
2944
|
+
cfg.in_type = true;
|
|
2945
|
+
cfg.force_type = true;
|
|
2855
2946
|
}
|
|
2856
2947
|
|
|
2857
2948
|
if (!scan_expr_es(es, cfg, NULL)) return false;
|
|
@@ -2872,6 +2963,14 @@ static bool scan_open_tag_es(Scanner *s, TSLexer *lexer, const bool *valid,
|
|
|
2872
2963
|
return scan_statement_tail(s, lexer, valid, es);
|
|
2873
2964
|
}
|
|
2874
2965
|
|
|
2966
|
+
// ---- a ";" read before the (empty) name, eg ",;", ended the tag there
|
|
2967
|
+
if (s->semicolon_line) {
|
|
2968
|
+
if (!valid[CONCISE_OPEN_TAG_END]) return false;
|
|
2969
|
+
open_tag_ended(s, false);
|
|
2970
|
+
lexer->result_symbol = CONCISE_OPEN_TAG_END; // zero-width
|
|
2971
|
+
return true;
|
|
2972
|
+
}
|
|
2973
|
+
|
|
2875
2974
|
// ---- grammar-position expressions (no trivia before them)
|
|
2876
2975
|
if (valid[ARGS_EXPR] && !valid[ATTR_NAME]) {
|
|
2877
2976
|
bool empty = false;
|
|
@@ -3105,6 +3204,22 @@ static bool scan_open_tag_es(Scanner *s, TSLexer *lexer, const bool *valid,
|
|
|
3105
3204
|
|
|
3106
3205
|
// ---- trivia loop (only when no lookahead is pending)
|
|
3107
3206
|
while (es_drained(es)) {
|
|
3207
|
+
if (concise && tag_name_pending(s) &&
|
|
3208
|
+
(at_eof(lexer) || is_line(lexer->lookahead) ||
|
|
3209
|
+
lexer->lookahead == ';')) {
|
|
3210
|
+
// The open tag ends before a name (eg a line of just "," or ",;"): it
|
|
3211
|
+
// is named empty where it ends, which is after a ";".
|
|
3212
|
+
if (!valid[TAG_NAME_EMPTY]) return false;
|
|
3213
|
+
if (!at_eof(lexer) && lexer->lookahead == ';') {
|
|
3214
|
+
skipc(s, lexer);
|
|
3215
|
+
s->semicolon_line = 1; // the open tag ends on the next scan
|
|
3216
|
+
}
|
|
3217
|
+
finalize_tag_name(s, -1);
|
|
3218
|
+
mark(s, lexer);
|
|
3219
|
+
lexer->result_symbol = TAG_NAME_EMPTY;
|
|
3220
|
+
return true;
|
|
3221
|
+
}
|
|
3222
|
+
|
|
3108
3223
|
if (at_eof(lexer)) {
|
|
3109
3224
|
if (!s->tag_name_done || s->in_shorthand) finalize_tag_name(s, -1);
|
|
3110
3225
|
s->statement_pending = 0; // an empty statement expression: no events
|
|
@@ -3119,7 +3234,7 @@ static bool scan_open_tag_es(Scanner *s, TSLexer *lexer, const bool *valid,
|
|
|
3119
3234
|
|
|
3120
3235
|
int32_t cc = lexer->lookahead;
|
|
3121
3236
|
|
|
3122
|
-
if ((!s->tag_name_done || s->in_shorthand) &&
|
|
3237
|
+
if ((!s->tag_name_done || s->in_shorthand) && !tag_name_pending(s) &&
|
|
3123
3238
|
(is_ws(cc) || cc == ',')) {
|
|
3124
3239
|
// TAG_NAME terminates at whitespace/comma before any trivia skipping.
|
|
3125
3240
|
finalize_tag_name(s, cc);
|
|
@@ -3163,6 +3278,29 @@ static bool scan_open_tag_es(Scanner *s, TSLexer *lexer, const bool *valid,
|
|
|
3163
3278
|
continue;
|
|
3164
3279
|
}
|
|
3165
3280
|
|
|
3281
|
+
if (cc == '/' && tag_name_pending(s)) {
|
|
3282
|
+
// OPEN_TAG reads comments before TAG_NAME (eg ", /* c */ div"), so the
|
|
3283
|
+
// name starts after them. An open tag comment is a hidden token anyway,
|
|
3284
|
+
// so skip it as trivia; any other "/" ends an empty name here.
|
|
3285
|
+
mark(s, lexer);
|
|
3286
|
+
int32_t n1 = es_peek(es, 1);
|
|
3287
|
+
if (n1 != '/' && n1 != '*') break;
|
|
3288
|
+
es_next(es); // "/": skipping the rest drops it from the token
|
|
3289
|
+
skipc(s, lexer); // "/" or "*"
|
|
3290
|
+
if (n1 == '/') {
|
|
3291
|
+
while (!at_eof(lexer) && !is_line(lexer->lookahead)) skipc(s, lexer);
|
|
3292
|
+
} else {
|
|
3293
|
+
int32_t prev = 0;
|
|
3294
|
+
while (!at_eof(lexer) && !(prev == '*' && lexer->lookahead == '/')) {
|
|
3295
|
+
prev = lexer->lookahead;
|
|
3296
|
+
skipc(s, lexer);
|
|
3297
|
+
}
|
|
3298
|
+
if (at_eof(lexer)) return false; // MALFORMED_COMMENT
|
|
3299
|
+
skipc(s, lexer);
|
|
3300
|
+
}
|
|
3301
|
+
continue;
|
|
3302
|
+
}
|
|
3303
|
+
|
|
3166
3304
|
break;
|
|
3167
3305
|
}
|
|
3168
3306
|
|
|
@@ -3179,6 +3317,18 @@ static bool scan_open_tag_es(Scanner *s, TSLexer *lexer, const bool *valid,
|
|
|
3179
3317
|
|
|
3180
3318
|
// ---- tag name section (TAG_NAME handles its chars before OPEN_TAG does)
|
|
3181
3319
|
if (!s->tag_name_done || s->in_shorthand) {
|
|
3320
|
+
if (concise && c == '-' && tag_name_pending(s)) {
|
|
3321
|
+
// OPEN_TAG reads "-" before TAG_NAME: a name only starts with "-" and a
|
|
3322
|
+
// word char (eg "-if"), and "--" ends the tag before a name.
|
|
3323
|
+
int32_t n1 = es_peek(es, 1);
|
|
3324
|
+
if (n1 == '-') {
|
|
3325
|
+
if (!valid[TAG_NAME_EMPTY]) return false;
|
|
3326
|
+
finalize_tag_name(s, c);
|
|
3327
|
+
lexer->result_symbol = TAG_NAME_EMPTY; // zero-width at the "--"
|
|
3328
|
+
return true;
|
|
3329
|
+
}
|
|
3330
|
+
if (!is_word_code(n1)) return false; // MALFORMED_OPEN_TAG
|
|
3331
|
+
}
|
|
3182
3332
|
TSSymbol name_sym = 0;
|
|
3183
3333
|
int r = scan_tag_name_token(s, valid, es, &name_sym);
|
|
3184
3334
|
if (r == 0) return false;
|
package/tree-sitter-marko.wasm
CHANGED
|
Binary file
|