tree-sitter-astro
tree-sitter-astro copied to clipboard
Tree-sitter grammar for the Astro web framework
tree-sitter-astro
Tree-sitter grammar for the Astro web framework.
Troubleshooting
Parser dependencies
If the syntax highlighting doesn't work for embedded CSS/JSX, make sure you have all of the following tree-sitter parsers installed:
-
css
(for CSS instyle
elements) -
typescript
(for the frontmatter)
lazy.nvim
If you're using the lazy.nvim
plugin manager, do not use the opts
key for the nvim-treesitter plugin (it won't work, as nvim-treesitter doesn't have a .config()
function). Use require('nvim-treesitter.configs').setup(opts)
instead. For more information, take a look at #16.
Specification
There are two parts to an Astro document, as shown below:
---
{frontmatter}
---
{astro html}
These have to be handled differently.
The frontmatter
This is just injected TypeScript.
Astro's special HTML format
Astro's special HTML format is not JSX/TSX, but rather an extension of HTML. This grammar is an extension of the grammar from tree-sitter-html.
Astro's HTML is parsed as regular HTML with four exceptions.
Fragment tags
Astro allows tags with no name.
<>
<p> hi </p>
</>
These are used as tags that aren't actually present in the final HTML.
These are implemented by special-casing unnamed elements in the external lexer.
Attribute interpolations
These are special attributes that evaluate to the result of a TypeScript expression. Astro's docs call these "expressions". They come in two forms:
<div style={styleMap}></div>
<div {...attrMap}></div>
These are just handled as injected TypeScript.
Attribute backtick strings
These are also special attributes that evaluate to the result of a TypeScript expression, but instead of being wrapped in braces, they're JS backtick strings. They appear to be undocumented.
<a href=`/pages/${nextPage}`>Link</a>
These are also handled as injected TypeScript.
HTML interpolations
These are TypeScript expressions that get evaluated to dynamically make a HTML node. Astro's docs also calls these "expressions", but for the purposes of the parser they are very different.
<div>
{variable}
</div>
In the Astro compiler, HTML interpolations are actually handled as special HTML nodes. We do the same here.
Balanced curly braces inside HTML interpolations will turn into a nested stack of HTML interpolations.
Note that since this is not JSX, there are things valid in Astro that aren't in JSX, and there are things that are valid in JSX that aren't in Astro. For instance,
<ValidAstro>
{(() => {
let name =
<!-- You can just leave HTML comments anywhere you want. -->
<!-- They are completely disregarded by the compiler. -->
"value";
// Multiple elements can be put next to each other,
// which isn't possible in JSX without a containing element.
let ret = (
<p> paragraph 1 </p>
<p> paragraph 2 </p>
);
})()}
</ValidAstro>
<ValidJSX>
{(() => {
let p = 2;
let invalidInAstro = 1<p>"</p>";
// In JavaScript, the above parses as (1 < p) > "</p>",
// which evaluates to false.
// However, since the Astro compiler has no knowledge of JS,
// it sees the above as the text "1", then
// a p-tag with a quote character as innerText, then a single quote character again.
// This compiles to something like
// let invalidInAstro = 1 $$render(`<p>"</p>`)";
// which is a syntax error.
})()}
</ValidJSX>