@marko/tree-sitter 0.0.0 → 0.2.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/LICENSE +21 -0
- package/README.md +280 -0
- package/binding.gyp +19 -0
- package/bindings/node/binding.cc +22 -0
- package/bindings/node/index.d.ts +28 -0
- package/bindings/node/index.js +13 -0
- package/grammar.js +248 -0
- package/package.json +86 -1
- 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 +61 -0
- package/queries/injections.scm +123 -0
- package/src/grammar.json +1073 -0
- package/src/node-types.json +924 -0
- package/src/parser.c +7875 -0
- package/src/scanner.c +3584 -0
- package/src/tree_sitter/alloc.h +54 -0
- package/src/tree_sitter/array.h +330 -0
- package/src/tree_sitter/parser.h +286 -0
- package/tree-sitter-marko.wasm +0 -0
- package/tree-sitter.json +34 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 JS Foundation and contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
# @marko/tree-sitter
|
|
2
|
+
|
|
3
|
+
A [tree-sitter](https://tree-sitter.github.io/) grammar for the
|
|
4
|
+
[Marko](https://markojs.com/) templating language.
|
|
5
|
+
|
|
6
|
+
It covers the full language: both authoring modes (HTML and
|
|
7
|
+
concise/indentation-based), `${placeholders}`, tag variables, arguments and
|
|
8
|
+
parameters, shorthand `#id`/`.class` and method attributes, attribute
|
|
9
|
+
groups, statement tags, raw-text tags, `--` content blocks, and `$`
|
|
10
|
+
scriptlets, all with the exact source ranges the Marko compiler sees, so the
|
|
11
|
+
tree is reliable for highlighting, folding, structural editing, and tooling.
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
With the node bindings:
|
|
16
|
+
|
|
17
|
+
```js
|
|
18
|
+
const Parser = require("tree-sitter");
|
|
19
|
+
const Marko = require("@marko/tree-sitter");
|
|
20
|
+
|
|
21
|
+
const parser = new Parser();
|
|
22
|
+
parser.setLanguage(Marko);
|
|
23
|
+
|
|
24
|
+
const tree = parser.parse("<button onClick() { count++ }>${count}</button>");
|
|
25
|
+
console.log(tree.rootNode.toString());
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
In the browser (or anywhere native bindings are unwanted), use the wasm
|
|
29
|
+
build with [web-tree-sitter](https://github.com/tree-sitter/tree-sitter/tree/master/lib/binding_web):
|
|
30
|
+
|
|
31
|
+
```js
|
|
32
|
+
import { Language, Parser } from "web-tree-sitter";
|
|
33
|
+
|
|
34
|
+
await Parser.init();
|
|
35
|
+
const Marko = await Language.load("tree-sitter-marko.wasm");
|
|
36
|
+
const parser = new Parser();
|
|
37
|
+
parser.setLanguage(Marko);
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Building from a checkout:
|
|
41
|
+
|
|
42
|
+
```sh
|
|
43
|
+
npm install
|
|
44
|
+
npm run build # tree-sitter generate && node-gyp rebuild
|
|
45
|
+
npm run build:wasm # tree-sitter-marko.wasm (the CLI fetches wasi-sdk itself)
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
For editors, the grammar ships ready-to-use queries: `queries/highlights.scm`
|
|
49
|
+
for syntax highlighting captures and `queries/injections.scm` for
|
|
50
|
+
embedded-language injections (described below).
|
|
51
|
+
|
|
52
|
+
## Editors and tools
|
|
53
|
+
|
|
54
|
+
Every integration consumes the same artifacts: the generated parser
|
|
55
|
+
(`src/parser.c` + `src/scanner.c`), the queries in `queries/`, and, for
|
|
56
|
+
embedded highlighting, the `typescript`, `css`, and `scss` grammars installed
|
|
57
|
+
in the host tool like any other language.
|
|
58
|
+
|
|
59
|
+
### tree-sitter CLI
|
|
60
|
+
|
|
61
|
+
From this directory:
|
|
62
|
+
|
|
63
|
+
```sh
|
|
64
|
+
npx tree-sitter playground # interactive tree in the browser
|
|
65
|
+
npx tree-sitter parse file.marko # print the syntax tree
|
|
66
|
+
npx tree-sitter highlight file.marko # ANSI highlighting (--html for a page)
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
`highlight` resolves the injected languages through the CLI config: run
|
|
70
|
+
`tree-sitter init-config`, then make sure one of the `parser-directories`
|
|
71
|
+
in `~/.config/tree-sitter/config.json` holds clones of
|
|
72
|
+
`tree-sitter-typescript` (run `npm install --ignore-scripts` inside it, as
|
|
73
|
+
its queries reference its `tree-sitter-javascript` dependency),
|
|
74
|
+
`tree-sitter-css`, and `tree-sitter-scss`. Note the loader only discovers
|
|
75
|
+
grammars that have a `tree-sitter.json`.
|
|
76
|
+
|
|
77
|
+
### Neovim
|
|
78
|
+
|
|
79
|
+
Neovim (0.10+) needs no plugins. Compile the parser onto the runtime path
|
|
80
|
+
and start treesitter for the filetype:
|
|
81
|
+
|
|
82
|
+
```sh
|
|
83
|
+
# from this directory
|
|
84
|
+
mkdir -p ~/.local/share/nvim/site/parser ~/.local/share/nvim/site/queries
|
|
85
|
+
npx tree-sitter build -o ~/.local/share/nvim/site/parser/marko.so
|
|
86
|
+
cp -R queries ~/.local/share/nvim/site/queries/marko
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
```lua
|
|
90
|
+
-- init.lua
|
|
91
|
+
vim.filetype.add({ extension = { marko = "marko" } })
|
|
92
|
+
vim.api.nvim_create_autocmd("FileType", {
|
|
93
|
+
pattern = "marko",
|
|
94
|
+
callback = function()
|
|
95
|
+
vim.treesitter.start()
|
|
96
|
+
end,
|
|
97
|
+
})
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Embedded highlighting uses whatever parsers are installed; with
|
|
101
|
+
nvim-treesitter that's `:TSInstall typescript css scss`.
|
|
102
|
+
|
|
103
|
+
### Helix
|
|
104
|
+
|
|
105
|
+
Register the grammar and language in `~/.config/helix/languages.toml`:
|
|
106
|
+
|
|
107
|
+
```toml
|
|
108
|
+
[[language]]
|
|
109
|
+
name = "marko"
|
|
110
|
+
scope = "source.marko"
|
|
111
|
+
file-types = ["marko"]
|
|
112
|
+
comment-token = "//"
|
|
113
|
+
block-comment-tokens = { start = "<!--", end = "-->" }
|
|
114
|
+
injection-regex = "^marko$"
|
|
115
|
+
|
|
116
|
+
[[grammar]]
|
|
117
|
+
name = "marko"
|
|
118
|
+
source = { git = "https://github.com/marko-js/tree-sitter", rev = "<commit>" }
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
(or `source = { path = "/path/to/tree-sitter" }` for a local
|
|
122
|
+
checkout), then:
|
|
123
|
+
|
|
124
|
+
```sh
|
|
125
|
+
hx --grammar fetch && hx --grammar build
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
and copy the queries to `~/.config/helix/runtime/queries/marko/`.
|
|
129
|
+
|
|
130
|
+
### Zed
|
|
131
|
+
|
|
132
|
+
Marko support in Zed is provided by the
|
|
133
|
+
[marko-js/zed](https://github.com/marko-js/zed) extension, which bundles this
|
|
134
|
+
grammar (via its `[grammars.marko]` entry pointing here), the language config,
|
|
135
|
+
the editor queries, and the
|
|
136
|
+
[Marko language server](https://github.com/marko-js/language-server). Install
|
|
137
|
+
it from Zed's extension registry, or run `zed: install dev extension` on a
|
|
138
|
+
local checkout of that repo. Its `highlights.scm`/`injections.scm` are copies
|
|
139
|
+
of the `queries/` here, used as-is: Zed evaluates the
|
|
140
|
+
`#eq?`/`#any-of?`/`#not-any-of?` predicates, `injection.combined`, and the
|
|
141
|
+
dynamic `@injection.language` dialects. The Zed-specific `brackets.scm` and
|
|
142
|
+
`outline.scm` are maintained in that extension repo.
|
|
143
|
+
|
|
144
|
+
### VS Code
|
|
145
|
+
|
|
146
|
+
VS Code does not consume tree-sitter grammars. Marko support there remains
|
|
147
|
+
the tmLanguage-based
|
|
148
|
+
[Marko VS Code extension](https://github.com/marko-js/language-server),
|
|
149
|
+
which this grammar's queries are designed to match visually.
|
|
150
|
+
|
|
151
|
+
## The tree
|
|
152
|
+
|
|
153
|
+
```marko
|
|
154
|
+
div.panel
|
|
155
|
+
<button onClick() { count++ }>${count}</button>
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
```
|
|
159
|
+
(document
|
|
160
|
+
(element
|
|
161
|
+
(tag_name (tag_name_fragment))
|
|
162
|
+
(shorthand_class (tag_name_fragment))
|
|
163
|
+
(concise_open_tag_end)
|
|
164
|
+
(element
|
|
165
|
+
(open_tag_start)
|
|
166
|
+
(tag_name (tag_name_fragment))
|
|
167
|
+
(attr_name)
|
|
168
|
+
(args)
|
|
169
|
+
(method_body (method_body_expr))
|
|
170
|
+
(open_tag_end)
|
|
171
|
+
(placeholder (placeholder_start) (placeholder_expr))
|
|
172
|
+
(close_tag (close_tag_start) (close_tag_name) (close_tag_end))
|
|
173
|
+
(element_end))
|
|
174
|
+
(element_end)))
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
Commonly queried nodes:
|
|
178
|
+
|
|
179
|
+
| node | meaning |
|
|
180
|
+
| ------------------------------------------------------------------------------------------ | ------------------------------------------------------------ |
|
|
181
|
+
| `element` | a tag in either mode, including its body and close |
|
|
182
|
+
| `tag_name`, `tag_name_fragment` | tag names; interpolated names contain `placeholder` children |
|
|
183
|
+
| `shorthand_id`, `shorthand_class` | `#id` / `.class` shorthands |
|
|
184
|
+
| `attr_name`, `attr_value`, `attr_bound_value`, `attr_spread` | attributes (`x=…`, `x:=…`, `...spread`) |
|
|
185
|
+
| `args`, `method_body` | `(…)` arguments and `{ … }` shorthand-method bodies |
|
|
186
|
+
| `tag_var` → `var_pattern`, `var_type` | tag variables: `/pattern` with an optional `: type` |
|
|
187
|
+
| `params` → `param` → `param_pattern`, `param_type`, `param_default` | `\|a: T = 1, b\|` tag parameters |
|
|
188
|
+
| `type_args`, `type_params` → `type_expr` | `<T>` type arguments/parameters |
|
|
189
|
+
| `placeholder`, `placeholder_expr` | `${…}` and `$!{…}` |
|
|
190
|
+
| `scriptlet`, `scriptlet_expr`, `scriptlet_block_expr` | `$ statement` / `$ { block }` |
|
|
191
|
+
| `statement_expr` | the body of statement tags (`import`, `static`, `server`, …) |
|
|
192
|
+
| `text`, `html_comment`, `line_comment`, `block_comment`, `cdata`, `doctype`, `declaration` | content |
|
|
193
|
+
| `html_block` | `--` delimited / single-line content blocks in concise mode |
|
|
194
|
+
|
|
195
|
+
A few structural notes:
|
|
196
|
+
|
|
197
|
+
- `element_end` is a zero-width node marking where an element closes
|
|
198
|
+
implicitly (concise dedent, void tags, self-closing, end of input).
|
|
199
|
+
`concise_open_tag_end` is similarly zero-width (or covers a trailing `;`).
|
|
200
|
+
- Delimiters are visible named tokens (`args_open`/`args_close`,
|
|
201
|
+
`params_open`/`params_close`, `type_open`/`type_close`,
|
|
202
|
+
`attr_group_open`/`attr_group_close`, `method_body_open`/
|
|
203
|
+
`method_body_close`, `scriptlet_block_open`/`scriptlet_block_close`,
|
|
204
|
+
`placeholder_start`/`placeholder_end`), so bracket-matching and
|
|
205
|
+
punctuation queries can target them.
|
|
206
|
+
- Whitespace is significant in Marko, so the grammar has no `extras`; text
|
|
207
|
+
nodes contain their exact whitespace.
|
|
208
|
+
- Invalid input produces an `ERROR` node from the first error onward,
|
|
209
|
+
matching how Marko itself reports a single error per template rather than
|
|
210
|
+
guessing at recovery.
|
|
211
|
+
|
|
212
|
+
## Tag types
|
|
213
|
+
|
|
214
|
+
Tag parsing in Marko depends on the tag. This grammar follows the
|
|
215
|
+
[tags API](https://markojs.com/docs/reference/core-tag)'s `parseOptions` (and
|
|
216
|
+
the vscode tmLanguage grammar it replaces), which sorts tags into three
|
|
217
|
+
groups.
|
|
218
|
+
|
|
219
|
+
Void tags take no children and no closing tag: the html void elements plus
|
|
220
|
+
`const`, `debug`, `id`, `let`, `lifecycle`, `log`, and `return`.
|
|
221
|
+
|
|
222
|
+
Raw-text tags (`script`, `style`, `html-script`, `html-style`,
|
|
223
|
+
`html-comment`) have bodies of text and placeholders only.
|
|
224
|
+
|
|
225
|
+
Statement tags (`import`, `export`, `static`, `server`, `client`, plus
|
|
226
|
+
`class` for class-API compatibility) treat the rest of the line, and any
|
|
227
|
+
indented continuation, as a single embedded statement.
|
|
228
|
+
|
|
229
|
+
## Embedded languages
|
|
230
|
+
|
|
231
|
+
Marko templates embed TypeScript and CSS throughout, and
|
|
232
|
+
`queries/injections.scm` maps it all. TypeScript is injected into every
|
|
233
|
+
expression position: placeholders, attribute values/spreads, arguments,
|
|
234
|
+
shorthand-method bodies, scriptlets, statement-tag bodies, and the
|
|
235
|
+
pattern/default parts of tag variables and parameters.
|
|
236
|
+
|
|
237
|
+
Statement tags split into two groups, mirroring how the compiler consumes
|
|
238
|
+
them. `import`, `export`, and `class` are pass-through TypeScript, where the
|
|
239
|
+
keyword is part of the statement, so the whole element (keyword included) is
|
|
240
|
+
injected and the keyword takes its color from the TS grammar. For `static`,
|
|
241
|
+
`server`, and `client` the keyword is Marko syntax that the compiler strips,
|
|
242
|
+
so only the body is injected and `highlights.scm` captures the keyword as
|
|
243
|
+
`@keyword` (the tmLanguage grammar makes the same split). `<script>`/
|
|
244
|
+
`<html-script>` bodies inject TypeScript and `<style>`/`<html-style>` bodies
|
|
245
|
+
inject CSS, including their concise `script --` / `style --` block forms,
|
|
246
|
+
with `injection.combined` merging chunks split by placeholders. `style`
|
|
247
|
+
dialect shorthands are honored the way the compiler resolves them: the last
|
|
248
|
+
shorthand segment names the injected language, so `<style.scss>` and
|
|
249
|
+
`style.module.scss --` inject `scss` while plain `<style>` falls back to
|
|
250
|
+
`css`. `html-style` is exempt, since its shorthand is a real class attribute,
|
|
251
|
+
not a dialect.
|
|
252
|
+
|
|
253
|
+
Type annotations (tag var/param types, `<T>` type args/params) are captured
|
|
254
|
+
as `@type` instead of injected: a bare type is not a valid TypeScript
|
|
255
|
+
program, so flat coloring is the accurate option (the tmLanguage grammar
|
|
256
|
+
makes the same approximation). One residual case: the parameters of a
|
|
257
|
+
shorthand method (`onClick(event) { … }`) inject as a whole TS program,
|
|
258
|
+
which renders typed parameters slightly off, since that position cannot be
|
|
259
|
+
split without lookahead the scanner doesn't have.
|
|
260
|
+
|
|
261
|
+
## Fidelity and development
|
|
262
|
+
|
|
263
|
+
Marko's parser is [htmljs-parser](https://github.com/marko-js/htmljs-parser);
|
|
264
|
+
this grammar's external scanner reimplements its state machine, and the test
|
|
265
|
+
suite (`npm test`, Node 22) asserts the tree reproduces the parser's event
|
|
266
|
+
stream with byte-precise ranges across the parser's full fixture suite, plus
|
|
267
|
+
thousands of templates from the Marko ecosystem during development.
|
|
268
|
+
|
|
269
|
+
The reference parser and its fixtures are always the same htmljs-parser
|
|
270
|
+
revision: they are fetched together into `.cache/` with degit, so the suite
|
|
271
|
+
compares the grammar against the exact parser the fixtures came from (set
|
|
272
|
+
`HTMLJS_FIXTURES` to point at a local checkout's fixtures instead, and the
|
|
273
|
+
adjacent sources are used as the parser; if this directory is dropped inside
|
|
274
|
+
an htmljs-parser checkout, the local parser sources are used automatically).
|
|
275
|
+
The published `htmljs-parser` package is only a last-resort fallback. The
|
|
276
|
+
grammar tracks the parser, so the suite is only expected to be green against
|
|
277
|
+
the htmljs-parser revision the grammar was synced with.
|
|
278
|
+
|
|
279
|
+
See `__tests__/` and `tools/` to work on the grammar itself; the scanner
|
|
280
|
+
internals are documented at the top of `src/scanner.c`.
|
package/binding.gyp
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"targets": [
|
|
3
|
+
{
|
|
4
|
+
"target_name": "tree_sitter_marko_binding",
|
|
5
|
+
"dependencies": [
|
|
6
|
+
"<!(node -p \"require('node-addon-api').targets\"):node_addon_api_except"
|
|
7
|
+
],
|
|
8
|
+
"include_dirs": ["src"],
|
|
9
|
+
"sources": ["bindings/node/binding.cc", "src/parser.c", "src/scanner.c"],
|
|
10
|
+
"conditions": [
|
|
11
|
+
["OS!='win'", {
|
|
12
|
+
"cflags_c": ["-std=c11"]
|
|
13
|
+
}, {
|
|
14
|
+
"cflags_c": ["/std:c11", "/utf-8"]
|
|
15
|
+
}]
|
|
16
|
+
]
|
|
17
|
+
}
|
|
18
|
+
]
|
|
19
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
#include <napi.h>
|
|
2
|
+
|
|
3
|
+
typedef struct TSLanguage TSLanguage;
|
|
4
|
+
|
|
5
|
+
extern "C" TSLanguage *tree_sitter_marko();
|
|
6
|
+
|
|
7
|
+
namespace {
|
|
8
|
+
|
|
9
|
+
// "tree-sitter", "language" hashed with BLAKE2
|
|
10
|
+
const napi_type_tag LANGUAGE_TYPE_TAG = {0x8AF2E5212AD58ABF, 0xD5006CAD83ABBA16};
|
|
11
|
+
|
|
12
|
+
Napi::Object Init(Napi::Env env, Napi::Object exports) {
|
|
13
|
+
exports["name"] = Napi::String::New(env, "marko");
|
|
14
|
+
auto language = Napi::External<TSLanguage>::New(env, tree_sitter_marko());
|
|
15
|
+
language.TypeTag(&LANGUAGE_TYPE_TAG);
|
|
16
|
+
exports["language"] = language;
|
|
17
|
+
return exports;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
NODE_API_MODULE(tree_sitter_marko_binding, Init)
|
|
21
|
+
|
|
22
|
+
} // namespace
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
type BaseNode = {
|
|
2
|
+
type: string;
|
|
3
|
+
named: boolean;
|
|
4
|
+
};
|
|
5
|
+
|
|
6
|
+
type ChildNode = {
|
|
7
|
+
multiple: boolean;
|
|
8
|
+
required: boolean;
|
|
9
|
+
types: BaseNode[];
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
type NodeInfo =
|
|
13
|
+
| (BaseNode & {
|
|
14
|
+
subtypes: BaseNode[];
|
|
15
|
+
})
|
|
16
|
+
| (BaseNode & {
|
|
17
|
+
fields: { [name: string]: ChildNode };
|
|
18
|
+
children: ChildNode[];
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
type Language = {
|
|
22
|
+
name: string;
|
|
23
|
+
language: unknown;
|
|
24
|
+
nodeTypeInfo: NodeInfo[];
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
declare const language: Language;
|
|
28
|
+
export = language;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
const root = require("path").join(__dirname, "..", "..");
|
|
2
|
+
|
|
3
|
+
module.exports =
|
|
4
|
+
typeof process.versions.bun === "string"
|
|
5
|
+
? // Support `bun build --compile` by being statically analyzable enough to find the .node binding during build-time
|
|
6
|
+
require(
|
|
7
|
+
`../../prebuilds/${process.platform}-${process.arch}/@marko+tree-sitter.node`,
|
|
8
|
+
)
|
|
9
|
+
: require("node-gyp-build")(root);
|
|
10
|
+
|
|
11
|
+
try {
|
|
12
|
+
module.exports.nodeTypeInfo = require("../../src/node-types.json");
|
|
13
|
+
} catch (_) {}
|
package/grammar.js
ADDED
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tree-sitter grammar for Marko templates.
|
|
3
|
+
*
|
|
4
|
+
* This grammar is a faithful port of htmljs-parser (../src). Because nearly
|
|
5
|
+
* all of Marko's syntax is context sensitive (concise mode indentation,
|
|
6
|
+
* expression terminators that depend on the surrounding construct, operator
|
|
7
|
+
* continuation across whitespace, parsed-text tags like <script>, etc.) every
|
|
8
|
+
* terminal is produced by the external scanner in src/scanner.c, which ports
|
|
9
|
+
* the parser's state machine. The rules below only describe how those tokens
|
|
10
|
+
* compose, mirroring the event stream emitted by createParser().
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/// <reference types="tree-sitter-cli/dsl" />
|
|
14
|
+
// @ts-check
|
|
15
|
+
|
|
16
|
+
module.exports = grammar({
|
|
17
|
+
name: "marko",
|
|
18
|
+
|
|
19
|
+
// Whitespace is significant almost everywhere (text content, expression
|
|
20
|
+
// termination, concise indentation), so there are no extras; the external
|
|
21
|
+
// scanner skips insignificant trivia (whitespace/commas/comments inside
|
|
22
|
+
// open tags) itself, exactly where htmljs-parser does.
|
|
23
|
+
extras: () => [],
|
|
24
|
+
|
|
25
|
+
externals: ($) => [
|
|
26
|
+
$.text,
|
|
27
|
+
$.placeholder_start, // "${" (escape=true placeholders)
|
|
28
|
+
$.placeholder_start_raw, // "$!{"
|
|
29
|
+
$._interp_start, // "${" within tag names / shorthands
|
|
30
|
+
$.placeholder_expr,
|
|
31
|
+
$._placeholder_end, // "}"
|
|
32
|
+
$.html_comment, // "<!--" ... "->"
|
|
33
|
+
$.line_comment, // "//" ...
|
|
34
|
+
$.block_comment, // "/*" ... "*/"
|
|
35
|
+
$.cdata, // "<![CDATA[" ... "]]>"
|
|
36
|
+
$.doctype, // "<!" ... ">"
|
|
37
|
+
$.declaration, // "<?" ... ">" | "?>"
|
|
38
|
+
$.scriptlet_start, // "$" at the start of a line in html mode
|
|
39
|
+
$.scriptlet_start_concise, // "$" at the start of a concise line
|
|
40
|
+
$._scriptlet_block_open, // "{"
|
|
41
|
+
$.scriptlet_block_expr,
|
|
42
|
+
$._scriptlet_block_close, // "}" plus an optional trailing ";"
|
|
43
|
+
$.scriptlet_expr, // statement after "$ "
|
|
44
|
+
$._block_open, // "--", "---", ... starting a delimited html block
|
|
45
|
+
$._block_close, // matching delimiter line, or zero-width
|
|
46
|
+
$.open_tag_start, // "<"
|
|
47
|
+
$.tag_name_fragment, // run of tag name characters between placeholders
|
|
48
|
+
$._tag_name_empty, // zero-width: shorthand-only tags like <.foo>
|
|
49
|
+
$._shorthand_id_start, // "#"
|
|
50
|
+
$._shorthand_class_start, // "."
|
|
51
|
+
$.statement_expr, // body of statement tags (import/export/static/class)
|
|
52
|
+
$._tag_var_start, // "/"
|
|
53
|
+
$.var_pattern, // tag variable binding pattern (before any ": type")
|
|
54
|
+
$._var_colon, // ":" between a tag var pattern and its type
|
|
55
|
+
$.var_type, // tag variable type annotation
|
|
56
|
+
$._args_open, // "("
|
|
57
|
+
$.args_expr,
|
|
58
|
+
$._args_close, // ")"
|
|
59
|
+
$._params_open, // "|"
|
|
60
|
+
$.param_pattern, // parameter binding pattern
|
|
61
|
+
$._param_colon, // ":" before a parameter type
|
|
62
|
+
$.param_type, // parameter type annotation
|
|
63
|
+
$._param_eq, // "=" before a parameter default
|
|
64
|
+
$.param_default, // parameter default value expression
|
|
65
|
+
$._param_comma, // "," between parameters
|
|
66
|
+
$._params_close, // "|"
|
|
67
|
+
$._type_args_open, // "<" directly after the tag name
|
|
68
|
+
$._type_params_open, // "<" before tag params or a method shorthand
|
|
69
|
+
$.type_expr,
|
|
70
|
+
$._types_close, // ">"
|
|
71
|
+
$._attr_group_open, // "[" (concise mode)
|
|
72
|
+
$._attr_group_close, // "]"
|
|
73
|
+
$.attr_name,
|
|
74
|
+
$.attr_eq, // "="
|
|
75
|
+
$.attr_bound_eq, // ":="
|
|
76
|
+
$._attr_spread_start, // "..."
|
|
77
|
+
$.attr_value_expr,
|
|
78
|
+
$._method_body_open, // "{"
|
|
79
|
+
$.method_body_expr,
|
|
80
|
+
$._method_body_close, // "}"
|
|
81
|
+
$.open_tag_end, // ">"
|
|
82
|
+
$.open_tag_end_self, // "/>"
|
|
83
|
+
$.concise_open_tag_end, // zero-width (EOL/EOF/"--") or ";"
|
|
84
|
+
$.close_tag_start, // "</"
|
|
85
|
+
$.close_tag_name, // may be zero-width for "</>"
|
|
86
|
+
$.close_tag_end, // ">"
|
|
87
|
+
$.element_end, // zero-width: dedent, void, self-closed, EOF, after </x>
|
|
88
|
+
$._tag_comment, // "//..." or "/*...*/" inside an open tag (no events)
|
|
89
|
+
$.escape, // backslash run before an escaped/double-escaped "${"
|
|
90
|
+
$._error_sentinel, // never emitted; detects error recovery
|
|
91
|
+
],
|
|
92
|
+
|
|
93
|
+
rules: {
|
|
94
|
+
document: ($) => repeat($._child),
|
|
95
|
+
|
|
96
|
+
_child: ($) =>
|
|
97
|
+
choice(
|
|
98
|
+
$.element,
|
|
99
|
+
$.text,
|
|
100
|
+
$.placeholder,
|
|
101
|
+
$.html_comment,
|
|
102
|
+
$.line_comment,
|
|
103
|
+
$.block_comment,
|
|
104
|
+
$.cdata,
|
|
105
|
+
$.doctype,
|
|
106
|
+
$.declaration,
|
|
107
|
+
$.scriptlet,
|
|
108
|
+
$.html_block,
|
|
109
|
+
$.escape,
|
|
110
|
+
),
|
|
111
|
+
|
|
112
|
+
// Covers html mode (<a>...</a>, <br>, <input/>), concise mode (indent
|
|
113
|
+
// based, ended by a zero-width element_end at the dedent), statement tags
|
|
114
|
+
// (import/export/static/class) and parsed-text tags (script/style/
|
|
115
|
+
// textarea/html-comment whose bodies only contain text & placeholders).
|
|
116
|
+
element: ($) =>
|
|
117
|
+
seq(
|
|
118
|
+
optional($.open_tag_start),
|
|
119
|
+
$.tag_name,
|
|
120
|
+
repeat(choice($.shorthand_id, $.shorthand_class)),
|
|
121
|
+
optional($.statement_expr),
|
|
122
|
+
repeat($._tag_part),
|
|
123
|
+
choice($.open_tag_end, $.open_tag_end_self, $.concise_open_tag_end),
|
|
124
|
+
repeat($._child),
|
|
125
|
+
optional($.close_tag),
|
|
126
|
+
$.element_end,
|
|
127
|
+
),
|
|
128
|
+
|
|
129
|
+
tag_name: ($) => choice(repeat1($._tag_name_part), $._tag_name_empty),
|
|
130
|
+
|
|
131
|
+
shorthand_id: ($) => seq($._shorthand_id_start, repeat($._tag_name_part)),
|
|
132
|
+
|
|
133
|
+
shorthand_class: ($) =>
|
|
134
|
+
seq($._shorthand_class_start, repeat($._tag_name_part)),
|
|
135
|
+
|
|
136
|
+
_tag_name_part: ($) =>
|
|
137
|
+
choice($.tag_name_fragment, alias($.tag_name_placeholder, $.placeholder)),
|
|
138
|
+
|
|
139
|
+
tag_name_placeholder: ($) =>
|
|
140
|
+
seq(
|
|
141
|
+
alias($._interp_start, $.placeholder_start),
|
|
142
|
+
$.placeholder_expr,
|
|
143
|
+
alias($._placeholder_end, $.placeholder_end),
|
|
144
|
+
),
|
|
145
|
+
|
|
146
|
+
_tag_part: ($) =>
|
|
147
|
+
choice(
|
|
148
|
+
$.tag_var,
|
|
149
|
+
$.args,
|
|
150
|
+
$.params,
|
|
151
|
+
$.type_args,
|
|
152
|
+
$.type_params,
|
|
153
|
+
$.attr_group,
|
|
154
|
+
$.attr_name,
|
|
155
|
+
$.attr_value,
|
|
156
|
+
$.attr_bound_value,
|
|
157
|
+
$.attr_spread,
|
|
158
|
+
$.method_body,
|
|
159
|
+
$._tag_comment,
|
|
160
|
+
),
|
|
161
|
+
|
|
162
|
+
tag_var: ($) => seq($._tag_var_start, $.tag_var_expr),
|
|
163
|
+
|
|
164
|
+
// The pattern/type split happens exactly where the parser's expression
|
|
165
|
+
// scanner enters type mode (a top-level ":" outside a ternary), so the
|
|
166
|
+
// node's extent always equals the parser's tagVar value range.
|
|
167
|
+
tag_var_expr: ($) =>
|
|
168
|
+
seq($.var_pattern, optional(seq($._var_colon, $.var_type))),
|
|
169
|
+
|
|
170
|
+
// Delimiter tokens are visible named nodes (aliased from the hidden
|
|
171
|
+
// externals) so editors can pair them in bracket-matching queries and
|
|
172
|
+
// capture them as punctuation; they map to no parser events.
|
|
173
|
+
args: ($) =>
|
|
174
|
+
seq(
|
|
175
|
+
alias($._args_open, $.args_open),
|
|
176
|
+
optional($.args_expr),
|
|
177
|
+
alias($._args_close, $.args_close),
|
|
178
|
+
),
|
|
179
|
+
params: ($) =>
|
|
180
|
+
seq(
|
|
181
|
+
alias($._params_open, $.params_open),
|
|
182
|
+
optional($.params_expr),
|
|
183
|
+
alias($._params_close, $.params_close),
|
|
184
|
+
),
|
|
185
|
+
|
|
186
|
+
// Parameters split at depth-0 commas/colons/equals; the scanner keeps
|
|
187
|
+
// the parser's exact "|" termination so the extent is unchanged.
|
|
188
|
+
params_expr: ($) => seq($.param, repeat(seq($._param_comma, $.param))),
|
|
189
|
+
|
|
190
|
+
param: ($) =>
|
|
191
|
+
seq(
|
|
192
|
+
$.param_pattern,
|
|
193
|
+
optional(seq($._param_colon, $.param_type)),
|
|
194
|
+
optional(seq($._param_eq, $.param_default)),
|
|
195
|
+
),
|
|
196
|
+
type_args: ($) =>
|
|
197
|
+
seq(
|
|
198
|
+
alias($._type_args_open, $.type_open),
|
|
199
|
+
optional($.type_expr),
|
|
200
|
+
alias($._types_close, $.type_close),
|
|
201
|
+
),
|
|
202
|
+
type_params: ($) =>
|
|
203
|
+
seq(
|
|
204
|
+
alias($._type_params_open, $.type_open),
|
|
205
|
+
optional($.type_expr),
|
|
206
|
+
alias($._types_close, $.type_close),
|
|
207
|
+
),
|
|
208
|
+
attr_group: ($) =>
|
|
209
|
+
seq(
|
|
210
|
+
alias($._attr_group_open, $.attr_group_open),
|
|
211
|
+
repeat($._tag_part),
|
|
212
|
+
alias($._attr_group_close, $.attr_group_close),
|
|
213
|
+
),
|
|
214
|
+
attr_value: ($) => seq($.attr_eq, $.attr_value_expr),
|
|
215
|
+
attr_bound_value: ($) => seq($.attr_bound_eq, $.attr_value_expr),
|
|
216
|
+
attr_spread: ($) => seq($._attr_spread_start, $.attr_value_expr),
|
|
217
|
+
method_body: ($) =>
|
|
218
|
+
seq(
|
|
219
|
+
alias($._method_body_open, $.method_body_open),
|
|
220
|
+
optional($.method_body_expr),
|
|
221
|
+
alias($._method_body_close, $.method_body_close),
|
|
222
|
+
),
|
|
223
|
+
|
|
224
|
+
placeholder: ($) =>
|
|
225
|
+
seq(
|
|
226
|
+
choice($.placeholder_start, $.placeholder_start_raw),
|
|
227
|
+
$.placeholder_expr,
|
|
228
|
+
alias($._placeholder_end, $.placeholder_end),
|
|
229
|
+
),
|
|
230
|
+
|
|
231
|
+
scriptlet: ($) =>
|
|
232
|
+
seq(
|
|
233
|
+
choice($.scriptlet_start, $.scriptlet_start_concise),
|
|
234
|
+
choice($.scriptlet_block, $.scriptlet_expr),
|
|
235
|
+
),
|
|
236
|
+
|
|
237
|
+
scriptlet_block: ($) =>
|
|
238
|
+
seq(
|
|
239
|
+
alias($._scriptlet_block_open, $.scriptlet_block_open),
|
|
240
|
+
optional($.scriptlet_block_expr),
|
|
241
|
+
alias($._scriptlet_block_close, $.scriptlet_block_close),
|
|
242
|
+
),
|
|
243
|
+
|
|
244
|
+
html_block: ($) => seq($._block_open, repeat($._child), $._block_close),
|
|
245
|
+
|
|
246
|
+
close_tag: ($) => seq($.close_tag_start, $.close_tag_name, $.close_tag_end),
|
|
247
|
+
},
|
|
248
|
+
});
|