svast
v0.2.1
Published
Types for svast, a Svelte AST specification
Downloads
314
Readme
svast
Svelte Abstract Syntax Tree
This AST implements the Unist spec. I think. All node types that implement a unique interface are camelCased and prefixed with svelte
. I have inlined the unist nodes that svast
extends for reference but the canonical documentation in the unist repo should be prefered.
This AST seeks to be language agnostic and has no opinion on the contents of any expression. Some Svelte syntax is impossible to parse in a language agnostic way, this specification does not conern itself with this problem right now.
Base Unist Nodes
I have prefixed these with Unist
for clarity. See the actual spec
UnistNode
interface UnistNode {
type: string
data: UnistData?
position: UnistPosition?
}
This is the base node that pretty much everything extends.
UnistPosition
interface UnistPosition {
start: UnistPoint
end: UnistPoint
indent: [number >= 1]?
}
The UnistPosition
node represents the location of a node in the source file.
The start
field represents the first character of the parsed source for that node.
The end
position represents the last character of the parsed source for that node.
I do not understand what indent
is right now.
UnistPoint
interface UnistPoint {
line: number >= 1
column: number >= 1
offset: number >= 0?
}
The UnistPoint
node represents one place in a source file.
The line
field (1-indexed integer) represents a line in a source file.
The column
field (1-indexed integer) represents a column in a source file.
The offset
field (0-indexed integer) represents a character in a source file.
UnistData
interface UnistData { }
The UnistData
node represents information associated by the ecosystem with the node.
This space is guaranteed to never be specified by unist or specifications implementing unist.
UnistParent
interface UnistParent <: UnistNode {
children: [UnistNode]
}
Nodes containing other nodes (said to be children) extend the abstract interface UnistParent
(Node).
The children
field is a list representing the children of a node.
SVAST Nodes
Parent
interface Parent <: UnistParent {
children: [
| SvelteElement
| SvelteComponent
| Comment
| Text
| SvelteExpression
| VoidBlock
| BranchingBlock
| IfBlock
| EachBlock
| AwaitBlock
| SvelteTag
]
}
A Parent
is a node with children which is a list of nodes.
Literal
interface Literal<T> <: UnistNode {
type: T
value: string
}
A node containing a value. It is that simple. This is generic is used by other nodes, such a comment
.
Root
interface Root <: Parent {
type: "root"
}
The root node of a tree.
BaseTag
interface BaseTag <: Parent {
tagName: string
properties: [Property | Directive]
selfClosing: boolean
}
The BaseTag
node is the node that all element and component types extend.
The tagName
field contains the element's local name.
The properties
field is a list of the element's attributes and directives. This field is a list of nodes that implement the Property
or Directive
interfaces.
The selfClosing
field describes whether or not the source element was self closing or not. This isn't strictly abstract but is helpful in certain cases.
Meta
interface SvelteTag <: BaseTag {
type: "svelteMeta"
}
The SvelteTag
represent special svelte
namespaced tag names such as <svelte:self />
.
The following input:
<svelte:self this={Component} />
Yields:
{
type: 'svelteTag',
tagName: 'self',
properties: [{
type: 'svelteProperty',
name: 'this',
modifiers: [],
value: [{
type: 'svelteExpression',
value: 'Component'
}]
}],
selfClosing: true,
children: []
}
Element
interface Element <: BaseTag {
type: "svelteElement"
}
The Element
node represents a DOM-element in Svelte.
The following input:
<input on:click|preventDefault={handleClick} />
Yields:
{
type: 'svelteElement',
tagName: 'input',
properties: [{
type: 'svelteDirective',
name: 'on',
specifier: 'click',
modifiers: [{
type: 'modifier', value: 'preventDefault'
}],
value: [{
type: 'svelteExpression',
value: 'handleClick'
}]
}],
selfClosing: true,
children: []
}
Component
interface Component <: BaseTag {
type: "svelteComponent"
}
The Component
interface represents Svelte components, PascalCased tags.
This input:
<MyComponent on:click|preventDefault={handleClick} />
Yields:
{
type: 'svelteElement',
tagName: 'MyComponent',
properties: [{
type: 'svelteDirective',
name: 'on',
specifier: 'click',
modifiers: [{
type: 'svelteModifier', value: 'preventDefault'
}],
value: [{
type: 'svelteExpression',
value: 'handleClick'
}]
}],
selfClosing: true,
children: []
}
Script
interface Component <: BaseTag {
type: "svelteScript"
}
The Script
interface represents Svelte script tags. The always have a single child text node containing the script contents.
This input:
<script>
console.log('boo');
</script>
Yields:
{
type: 'svelteScript',
tagName: 'script',
properties: [],
selfClosing: false,
children: [
{
type: 'text',
value: '\n console.log('boo');\n'
}
]
}
Style
interface Component <: BaseTag {
type: "svelteScript"
}
The Style
interface represents Svelte style tags. The always have a single child text node containing the style contents.
This input:
<style>
h1 {
color: pink;
}
</style>
Yields:
{
type: 'svelteStyle',
tagName: 'style',
properties: [],
selfClosing: false,
children: [
{
type: 'text',
value: '\n h1 {\n color: pink;\n }\n'
}
]
}
BaseProperty
interface Property <: UnistNode {
name: string
shorthand: 'none' | 'boolean' | 'expression'
value: [Text | Expression]
modifiers: [Literal]
}
Property
interface Property <: BaseProperty {
type: 'svelteProperty'
}
The Property
node represents an element's properties and reflect HTML, SVG, ARIA, XML, XMLNS, or XLink attributes.
The name
field contains the exact name of the attribute or property as it is in the source. kebal-case names or not modified.
The shorthand
field signifies whether or not shorthand property syntax was used. There are two type of shorthand, short hand expressions ({prop}
) and shorthand booleans (prop
).
The value
field is always a list of nodes that implement either the Text
or Expression
interfaces. In the case of shorthand property expressions, the value
field will be a list with one node (an Expression
) whose value is the same as the attribute name.
The modifiers
field represents any modifiers applied to a property name. In Svelte this takes the form of on:click|once|capture={...}
. This value should be a list of Literal nodes, describing the modifier name.
This input:
<a name="hello {friend}!" />
Yields:
{
type: 'svelteElement',
tagName: 'a',
properties: [{
type: 'svelteProperty',
name: 'name',
value: [{
type: 'text',
value: 'hello'
}, {
type: 'svelteExpression',
value: 'friend'
}, {
type: 'text',
value: '!'
}],
shorthand: 'none',
modifiers: [],
}],
selfClosing: true,
children: []
}
Directive
interface Directive <: BaseProperty {
type: 'svelteDirective'
specifier: string
}
The Directive
node represents a Svelte directive x:y={z}
.
The name
field reprsents the directive 'type', the part of the attrubute before the :
.
The specificer
field describes the local implementation of that directive type. It is the part of the attribute name after the :
but before the =
or a whitespace character.
In the case of shorthand
being true
, value
will be a list of one Expression
node with a value equal to the specifier
value.
The following input:
<a class:myclass={x ? y : z} on:click|preventDefault={(e) => fn(e)} />
Yields:
{
type: 'svelteElement',
tagName: 'a',
properties: [{
type: 'svelteDirective',
name: 'class',
specifier: 'myclass',
value: [{
type: 'svelteExpression',
value: 'x ? y : z'
}],
shorthand: 'none',
modifiers: [],
}, {
type: 'svelteDirective',
name: 'on',
specifier: 'click',
value: [{
type: 'svelteExpression',
value: '(e) => fn(e)'
}],
shorthand: 'none',
modifiers: [{
type: 'svelteModifier',
value: 'preventDefault'
}],
}],
selfClosing: true,
children: []
}
Comment
interface Comment <: Literal {
type: "comment"
}
Represents an HTML comment.
The value
field should contain the contents of the comment.
This comment:
<!--Some thing here-->
Yields:
{type: 'comment', value: 'Some thing here'}
Text
interface Text <: Literal {
type: "text"
}
Represents bare text.
The value
field should contain the text.
The following input:
<div>Hello there</div>
Yields:
{
type: 'svelteElement',
tagName: 'div',
properties: [],
selfClosing: false,
children: [{
type: 'text',
value: 'Hello there'
}]
}
VoidBlock
interface VoidBlock <: Node {
type: 'svelteVoidBlock'
name: string
expression: Expression
}
The VoidBlock
node represents a void block. Void blocks do not allow branches.
The name
field is be the name of the block.
The expression
field is an Expression
node containing the expression value for that block.
For the following input:
{@html `<p>something</p>`}
Yields:
{
type: 'svelteVoidBlock',
name: 'html',
expression: {
type: 'svelteExpression',
value: '<p>something</p>'
}
}
BranchingBlock
interface BranchingBlock <: Parent {
type: 'svelteBranchingBlock'
name: string
branches: [Branch]
}
The BranchingBlock
node represents a Svelte Block that allows an arbitrary number of named branches, the first branch is alway defined by the opening block statement {#name expresion}
.
The name
field represents the name of the block.
The branches
field contains any branches that block has. A block must have at least one branch, even if it is empty.
This node is used for all non-void blocks other than each
blocks.
The following input:
{#custom someExpression}
Hello
{/custom}
Yields:
{
type: 'svelteBranchingBlock',
name: 'custom',
branches: [{
type: 'svelteBranch',
name: 'custom',
expression: {
type: 'svelteExpression',
value: 'someExpression'
},
children: [{
type: 'text',
value: 'Hello'
}]
}]
}
EachBlock
export interface EachBlock <: SvelteParent {
type: 'svelteEachBlock'
expression: Expression
itemName: Expression
itemIndex: Expression?
itemKey: Expression?
}
The EachBlock
node represents a Svelte #each
block.
The expression
field is the collection that is being iterated. The value is an Expression
node.
The itemName
field is the identifier referring to a single element of the collection during the loop. The value is an Expression
node.
The itemIndex
field is the identifier used to refer to the index
of the iterated item during the loop, if one exists.
The itemKey
field is optional and is the value that should be used as a key for eachitem, if one exists. The presence of this field signifies that the each block is keyed.
The follwing input:
{#each array.filter(v => v.prop) as { some, thing }, index (thing)}
<p>{some}</p>
{/each}
Yields:
{
type: 'svelteEachBlock',
itemName: {
type: 'svelteExpression',
value: '{ some, thing }'
},
itemIndex: {
type: 'svelteExpression',
value: 'index'
},
itemKey: {
type: 'svelteExpression',
value: 'thing'
},
children: [{
type: 'svelteElement',
tagName: 'p',
properties: [],
selfClosing: false,
children: [{
type: 'svelteExpression',
value: 'some'
}]
}]
}
Branch
interface Branch <: Parent {
type: 'svelteBranch'
name: string
expression: Expression
}
The Branch
node describes a branch of a Svelte block.
The expression
fields contains the expression associated with that branch.
The end.