@katabatic/compiler 1.0.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 +3 -0
- package/package.json +32 -0
- package/src/analyse/context.js +31 -0
- package/src/analyse/index.js +47 -0
- package/src/analyse/visitors/AssignmentExpression.js +14 -0
- package/src/analyse/visitors/Attribute.js +11 -0
- package/src/analyse/visitors/CallExpression.js +36 -0
- package/src/analyse/visitors/ClassBody.js +20 -0
- package/src/analyse/visitors/ClassDeclaration.js +5 -0
- package/src/analyse/visitors/CustomElement.js +10 -0
- package/src/analyse/visitors/EachBlock.js +10 -0
- package/src/analyse/visitors/Element.js +16 -0
- package/src/analyse/visitors/ExpressionTag.js +9 -0
- package/src/analyse/visitors/IfBlock.js +16 -0
- package/src/analyse/visitors/ImportDeclaration.js +35 -0
- package/src/analyse/visitors/MethodDefinition.js +17 -0
- package/src/analyse/visitors/Program.js +24 -0
- package/src/analyse/visitors/PropertyDefinition.js +12 -0
- package/src/analyse/visitors/Selector.js +23 -0
- package/src/analyse/visitors/Template.js +14 -0
- package/src/builders.js +1663 -0
- package/src/checkers.js +120 -0
- package/src/css-matcher.js +100 -0
- package/src/css-transform.js +21 -0
- package/src/css.js +65 -0
- package/src/exp-matcher.js +65 -0
- package/src/id-matcher.js +17 -0
- package/src/index.js +19 -0
- package/src/module-matcher.js +17 -0
- package/src/parser/attributes.js +66 -0
- package/src/parser/each-block.js +73 -0
- package/src/parser/element.js +115 -0
- package/src/parser/expression.js +44 -0
- package/src/parser/if-block.js +71 -0
- package/src/parser/index.js +10 -0
- package/src/parser/parser.js +259 -0
- package/src/parser/root.js +33 -0
- package/src/parser/script.js +59 -0
- package/src/parser/style.js +57 -0
- package/src/parser/template.js +30 -0
- package/src/parser/tokentype.js +75 -0
- package/src/router/html.js +18 -0
- package/src/router/index.js +130 -0
- package/src/transform/context.js +74 -0
- package/src/transform/index.js +52 -0
- package/src/transform/static/index.js +40 -0
- package/src/transform/static/visitors/Attribute.js +27 -0
- package/src/transform/static/visitors/EachBlock.js +23 -0
- package/src/transform/static/visitors/Element.js +17 -0
- package/src/transform/static/visitors/ExpressionTag.js +6 -0
- package/src/transform/static/visitors/IfBlock.js +28 -0
- package/src/transform/static/visitors/Program.js +10 -0
- package/src/transform/static/visitors/Script.js +9 -0
- package/src/transform/static/visitors/SlotElement.js +8 -0
- package/src/transform/static/visitors/Style.js +9 -0
- package/src/transform/static/visitors/Template.js +12 -0
- package/src/transform/static/visitors/Text.js +5 -0
- package/src/transform/visitors/AssignmentExpression.js +7 -0
- package/src/transform/visitors/Attribute.js +79 -0
- package/src/transform/visitors/CallExpression.js +17 -0
- package/src/transform/visitors/ClassBody.js +36 -0
- package/src/transform/visitors/CustomElement.js +43 -0
- package/src/transform/visitors/EachBlock.js +39 -0
- package/src/transform/visitors/Element.js +49 -0
- package/src/transform/visitors/ExpressionTag.js +6 -0
- package/src/transform/visitors/Fragment.js +42 -0
- package/src/transform/visitors/Identifier.js +10 -0
- package/src/transform/visitors/IfBlock.js +55 -0
- package/src/transform/visitors/ImportDeclaration.js +21 -0
- package/src/transform/visitors/MethodDefinition.js +115 -0
- package/src/transform/visitors/Program.js +65 -0
- package/src/transform/visitors/PropertyDefinition.js +7 -0
- package/src/transform/visitors/Selector.js +41 -0
- package/src/transform/visitors/Style.js +11 -0
- package/src/transform/visitors/Template.js +44 -0
- package/src/transform/visitors/Text.js +5 -0
- package/src/utils/misc.js +9 -0
- package/src/utils/template.js +15 -0
package/src/checkers.js
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
//
|
|
2
|
+
// advanced checkers
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
export function thisMember(node) {
|
|
6
|
+
return node.type === 'MemberExpression' && node.object.type === 'ThisExpression'
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function privateId(node) {
|
|
10
|
+
return node.type === 'PrivateIdentifier'
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function customElements(node) {
|
|
14
|
+
return node.type === 'Identifier' && node.name === 'customElements'
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function define(node) {
|
|
18
|
+
return node.type === 'Identifier' && node.name === 'define'
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function getElementById(node) {
|
|
22
|
+
return (
|
|
23
|
+
node.type === 'CallExpression' &&
|
|
24
|
+
node.callee.type === 'MemberExpression' &&
|
|
25
|
+
node.callee.object.type === 'Identifier' &&
|
|
26
|
+
node.callee.object.name === 'document' &&
|
|
27
|
+
node.callee.property.type === 'Identifier' &&
|
|
28
|
+
node.callee.property.name === 'getElementById'
|
|
29
|
+
)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function querySelector(node) {
|
|
33
|
+
return (
|
|
34
|
+
node.type === 'CallExpression' &&
|
|
35
|
+
node.callee.type === 'MemberExpression' &&
|
|
36
|
+
node.callee.property.type === 'Identifier' &&
|
|
37
|
+
node.callee.property.name === 'querySelector'
|
|
38
|
+
)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function defineCustomElement(node) {
|
|
42
|
+
if (node.type === 'ExpressionStatement') {
|
|
43
|
+
node = node.expression
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return (
|
|
47
|
+
node.type === 'CallExpression' &&
|
|
48
|
+
node.callee.type === 'MemberExpression' &&
|
|
49
|
+
node.callee.object.type === 'Identifier' &&
|
|
50
|
+
node.callee.object.name === 'customElements' &&
|
|
51
|
+
node.callee.property.type === 'Identifier' &&
|
|
52
|
+
node.callee.property.name === 'define'
|
|
53
|
+
)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function constructor(node) {
|
|
57
|
+
return node.type === 'MethodDefinition' && node.kind === 'constructor'
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function connectedCallback(node) {
|
|
61
|
+
return node.type === 'MethodDefinition' && node.key.name === 'connectedCallback'
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function disconnectedCallback(node) {
|
|
65
|
+
return node.type === 'MethodDefinition' && node.key.name === 'disconnectedCallback'
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function getAttribute(node) {
|
|
69
|
+
return node.type === 'MethodDefinition' && node.key.name === 'getAttribute'
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function attributeChangedCallback(node) {
|
|
73
|
+
return node.type === 'MethodDefinition' && node.key.name === 'attributeChangedCallback'
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function observedAttributes(node) {
|
|
77
|
+
return (
|
|
78
|
+
node.type === 'PropertyDefinition' &&
|
|
79
|
+
node.key.name === 'observedAttributes' &&
|
|
80
|
+
node.static === true
|
|
81
|
+
)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function classAttribute(node, withExpressionTag) {
|
|
85
|
+
let result = node.type === 'Attribute' && node.name === 'class'
|
|
86
|
+
if (withExpressionTag === true) {
|
|
87
|
+
result &&= node.value[0]?.type === 'ExpressionTag'
|
|
88
|
+
}
|
|
89
|
+
return result
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function idAttribute(node, withExpressionTag) {
|
|
93
|
+
let result = node.type === 'Attribute' && node.name === 'id'
|
|
94
|
+
if (withExpressionTag === true) {
|
|
95
|
+
result &&= node.value[0]?.type === 'ExpressionTag'
|
|
96
|
+
}
|
|
97
|
+
return result
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function bindAttribute(node) {
|
|
101
|
+
return (
|
|
102
|
+
node.type === 'Attribute' && node.name === 'bind' && node.value[0]?.type === 'ExpressionTag'
|
|
103
|
+
)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export function staticAttribute(node) {
|
|
107
|
+
return (
|
|
108
|
+
node.type === 'Attribute' &&
|
|
109
|
+
node.name === 'static' &&
|
|
110
|
+
(node.value === true || (node.value[0]?.type === 'Text' && node.value[0]?.data === 'true'))
|
|
111
|
+
)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export function shadowRootModeAttribute(node) {
|
|
115
|
+
return node.type === 'Attribute' && node.name === 'shadowRootMode'
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export function ifBlock(node) {
|
|
119
|
+
return node.type === 'IfBlock'
|
|
120
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { walk } from 'zimmerframe'
|
|
2
|
+
import { parse } from 'css-tree'
|
|
3
|
+
|
|
4
|
+
export function matchQuerySelector(query, template) {
|
|
5
|
+
const stylesheet = parse(query + '{}')
|
|
6
|
+
|
|
7
|
+
const selectorList = stylesheet.children.first.prelude
|
|
8
|
+
|
|
9
|
+
let result = false
|
|
10
|
+
for (const selector of selectorList.children) {
|
|
11
|
+
result ||= matchSelector(selector, template)
|
|
12
|
+
}
|
|
13
|
+
return [result, selectorList]
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function matchSelector(selector, template) {
|
|
17
|
+
let result = false
|
|
18
|
+
|
|
19
|
+
let selectors = []
|
|
20
|
+
for (const child of selector.children) {
|
|
21
|
+
switch (child.type) {
|
|
22
|
+
case 'Combinator':
|
|
23
|
+
result ||= matchSelectors(selectors, template)
|
|
24
|
+
selectors = []
|
|
25
|
+
break
|
|
26
|
+
default:
|
|
27
|
+
selectors.push(child)
|
|
28
|
+
break
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
result ||= matchSelectors(selectors, template)
|
|
32
|
+
return result
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function matchSelectors(selectors, template) {
|
|
36
|
+
if (selectors.length === 0) return false
|
|
37
|
+
|
|
38
|
+
let result = false
|
|
39
|
+
walk(template, undefined, {
|
|
40
|
+
Element: (node, ctx) => {
|
|
41
|
+
ctx.next()
|
|
42
|
+
|
|
43
|
+
let scopedIdAttribute
|
|
44
|
+
let scopedClassAttribute
|
|
45
|
+
let scopedElement
|
|
46
|
+
|
|
47
|
+
let classAttribute
|
|
48
|
+
|
|
49
|
+
for (const selector of selectors) {
|
|
50
|
+
switch (selector.type) {
|
|
51
|
+
case 'TypeSelector':
|
|
52
|
+
if (selector.name === node.name) {
|
|
53
|
+
classAttribute ??= node.attributes.find((a) => a.name === 'class')
|
|
54
|
+
scopedElement = node
|
|
55
|
+
scopedClassAttribute = classAttribute
|
|
56
|
+
break
|
|
57
|
+
}
|
|
58
|
+
return
|
|
59
|
+
case 'PseudoClassSelector':
|
|
60
|
+
classAttribute ??= node.attributes.find((a) => a.name === 'class')
|
|
61
|
+
scopedElement = node
|
|
62
|
+
scopedClassAttribute = classAttribute
|
|
63
|
+
break
|
|
64
|
+
case 'IdSelector':
|
|
65
|
+
const idAttribute = node.attributes.find((a) => a.name === 'id')
|
|
66
|
+
if (idAttribute?.value[0].data === selector.name) {
|
|
67
|
+
scopedIdAttribute = idAttribute
|
|
68
|
+
break
|
|
69
|
+
}
|
|
70
|
+
return
|
|
71
|
+
case 'ClassSelector':
|
|
72
|
+
classAttribute ??= node.attributes.find((a) => a.name === 'class')
|
|
73
|
+
const classes = classAttribute?.value[0].data.split(/\s+/)
|
|
74
|
+
|
|
75
|
+
if (classes?.includes(selector.name)) {
|
|
76
|
+
scopedClassAttribute = classAttribute
|
|
77
|
+
break
|
|
78
|
+
}
|
|
79
|
+
return
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (scopedIdAttribute) {
|
|
84
|
+
scopedIdAttribute.metadata ??= {}
|
|
85
|
+
scopedIdAttribute.metadata.isScoped = true
|
|
86
|
+
}
|
|
87
|
+
if (scopedClassAttribute) {
|
|
88
|
+
scopedClassAttribute.metadata ??= {}
|
|
89
|
+
scopedClassAttribute.metadata.isScoped = true
|
|
90
|
+
}
|
|
91
|
+
if (scopedElement) {
|
|
92
|
+
scopedElement.metadata ??= {}
|
|
93
|
+
scopedElement.metadata.isScoped = true
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
result = true
|
|
97
|
+
}
|
|
98
|
+
})
|
|
99
|
+
return result
|
|
100
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { generate, parse } from 'css-tree'
|
|
2
|
+
import { walk } from 'zimmerframe'
|
|
3
|
+
import { Selector, CssTree } from './transform/visitors/Selector.js'
|
|
4
|
+
|
|
5
|
+
export function transformQuerySelector(selectorList, context) {
|
|
6
|
+
if (typeof selectorList === 'string') {
|
|
7
|
+
const stylesheet = parse(selectorList + '{}')
|
|
8
|
+
selectorList = stylesheet.children.first.prelude
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
selectorList = walk(
|
|
12
|
+
selectorList,
|
|
13
|
+
{ context },
|
|
14
|
+
{
|
|
15
|
+
Selector,
|
|
16
|
+
...CssTree
|
|
17
|
+
}
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
return generate(selectorList)
|
|
21
|
+
}
|
package/src/css.js
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
export function match(element, stylesheet) {
|
|
2
|
+
for (const rule of stylesheet.children) {
|
|
3
|
+
if (matchSelectorList(element, rule.prelude)) return true
|
|
4
|
+
}
|
|
5
|
+
return false
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
function matchSelectorList(element, selectorList) {
|
|
9
|
+
if (selectorList) {
|
|
10
|
+
for (const selector of selectorList.children) {
|
|
11
|
+
if (matchSelector(element, selector)) return true
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
return false
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function matchSelector(element, selector) {
|
|
18
|
+
let selectors = []
|
|
19
|
+
for (const child of selector.children) {
|
|
20
|
+
switch (child.type) {
|
|
21
|
+
case 'TypeSelector':
|
|
22
|
+
case 'ClassSelector':
|
|
23
|
+
selectors.push(child)
|
|
24
|
+
break
|
|
25
|
+
case 'PseudoClassSelector':
|
|
26
|
+
if (matchSelectorList(element, child.children?.first)) return true
|
|
27
|
+
selectors.push(child)
|
|
28
|
+
break
|
|
29
|
+
case 'Combinator':
|
|
30
|
+
if (matchSelectors(element, selectors)) return true
|
|
31
|
+
selectors = []
|
|
32
|
+
break
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return matchSelectors(element, selectors)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function matchSelectors(element, selectors) {
|
|
39
|
+
if (selectors.length === 0) return false
|
|
40
|
+
|
|
41
|
+
for (const selector of selectors) {
|
|
42
|
+
switch (selector.type) {
|
|
43
|
+
case 'TypeSelector':
|
|
44
|
+
if (selector.name !== element.name) return false
|
|
45
|
+
break
|
|
46
|
+
case 'ClassSelector':
|
|
47
|
+
const classAtt = element.attributes.find((a) => a.name === 'class')?.value.data
|
|
48
|
+
const classes = classAtt?.split(/\s+/)
|
|
49
|
+
if (!classes?.includes(selector.name)) return false
|
|
50
|
+
break
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return true
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Dead simple clx assuming class2 is always set
|
|
58
|
+
* @param {string} class1
|
|
59
|
+
* @param {string} class2
|
|
60
|
+
* @returns {string}
|
|
61
|
+
*/
|
|
62
|
+
export function clx(class1, class2) {
|
|
63
|
+
if (class1) return class1 + ' ' + class2
|
|
64
|
+
return class2
|
|
65
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { walk } from 'zimmerframe'
|
|
2
|
+
|
|
3
|
+
export function matchExpression(expression, program, blocks) {
|
|
4
|
+
let customElement = program.metadata?.customElement
|
|
5
|
+
|
|
6
|
+
walk(expression, undefined, {
|
|
7
|
+
Identifier(node, ctx) {
|
|
8
|
+
const parentNode = ctx.path.at(-1)
|
|
9
|
+
|
|
10
|
+
switch (parentNode?.type) {
|
|
11
|
+
case 'CallExpression':
|
|
12
|
+
if (parentNode.callee === node) {
|
|
13
|
+
setMethodMetadata(node)
|
|
14
|
+
} else {
|
|
15
|
+
setMetadata(node)
|
|
16
|
+
}
|
|
17
|
+
break
|
|
18
|
+
case 'MemberExpression':
|
|
19
|
+
if (parentNode.object === node) {
|
|
20
|
+
setMetadata(node)
|
|
21
|
+
}
|
|
22
|
+
break
|
|
23
|
+
default:
|
|
24
|
+
setMetadata(node)
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
function setMethodMetadata(node) {
|
|
30
|
+
node.metadata ??= {}
|
|
31
|
+
|
|
32
|
+
if (customElement.methods.includes(node.name)) {
|
|
33
|
+
node.metadata.isPrivate = false
|
|
34
|
+
node.metadata.isMethod = true
|
|
35
|
+
return
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (customElement.private.methods.includes(node.name)) {
|
|
39
|
+
node.metadata.isPrivate = true
|
|
40
|
+
node.metadata.isMethod = true
|
|
41
|
+
return
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function setMetadata(node) {
|
|
46
|
+
node.metadata ??= {}
|
|
47
|
+
|
|
48
|
+
if (blocks.some((b) => b.context?.name === node.name)) {
|
|
49
|
+
node.metadata.isBlockVar = true
|
|
50
|
+
return
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (customElement.properties.includes(node.name)) {
|
|
54
|
+
node.metadata.isPrivate = false
|
|
55
|
+
node.metadata.isProperty = true
|
|
56
|
+
return
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (customElement.private.properties.includes(node.name)) {
|
|
60
|
+
node.metadata.isPrivate = true
|
|
61
|
+
node.metadata.isProperty = true
|
|
62
|
+
return
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { walk } from 'zimmerframe'
|
|
2
|
+
|
|
3
|
+
export function matchElementById(id, template) {
|
|
4
|
+
let result = false
|
|
5
|
+
walk(template, undefined, {
|
|
6
|
+
Attribute: (node, ctx) => {
|
|
7
|
+
if (node.name === 'id' && node.value[0].data === id) {
|
|
8
|
+
node.metadata ??= {}
|
|
9
|
+
node.metadata.isScoped = true
|
|
10
|
+
|
|
11
|
+
result = true
|
|
12
|
+
ctx.stop()
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
})
|
|
16
|
+
return result
|
|
17
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { print } from 'esrap'
|
|
2
|
+
import { parse } from './parser/index.js'
|
|
3
|
+
import { analyse } from './analyse/index.js'
|
|
4
|
+
import { transform } from './transform/index.js'
|
|
5
|
+
import { transform as transformStatic } from './transform/static/index.js'
|
|
6
|
+
|
|
7
|
+
export function compile(source, context) {
|
|
8
|
+
const ast = parse(source)
|
|
9
|
+
const analysis = analyse(ast)
|
|
10
|
+
|
|
11
|
+
const result = analysis.isStatic
|
|
12
|
+
? transformStatic(ast, analysis, context)
|
|
13
|
+
: transform(ast, analysis, context)
|
|
14
|
+
|
|
15
|
+
return {
|
|
16
|
+
...context,
|
|
17
|
+
...print(result)
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { walk } from 'zimmerframe'
|
|
2
|
+
|
|
3
|
+
export function matchModule(name, index, template) {
|
|
4
|
+
let result = false
|
|
5
|
+
walk(template, undefined, {
|
|
6
|
+
CustomElement: (node, ctx) => {
|
|
7
|
+
if (node.name === name) {
|
|
8
|
+
node.metadata ??= {}
|
|
9
|
+
node.metadata.isModule = true
|
|
10
|
+
node.metadata.index = index
|
|
11
|
+
|
|
12
|
+
result = true
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
})
|
|
16
|
+
return result
|
|
17
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { parseAttributeExpressionTag } from './expression.js'
|
|
2
|
+
import { Parser } from './parser.js'
|
|
3
|
+
import { TokenTypes } from './tokentype.js'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
*
|
|
7
|
+
* @param {Parser} p
|
|
8
|
+
* @returns
|
|
9
|
+
*/
|
|
10
|
+
export function parseAttributes(p) {
|
|
11
|
+
const attributes = []
|
|
12
|
+
|
|
13
|
+
p.skipWhitespaces()
|
|
14
|
+
while (!p.peakToken([TokenTypes.gte, TokenTypes.slashGte])) {
|
|
15
|
+
attributes.push(parseAttribute(p))
|
|
16
|
+
p.skipWhitespaces()
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return attributes
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function parseAttribute(p) {
|
|
23
|
+
const start = p.pos
|
|
24
|
+
p.expectToken([TokenTypes.name])
|
|
25
|
+
const name = p.value
|
|
26
|
+
|
|
27
|
+
if (p.readToken([TokenTypes.eq])) {
|
|
28
|
+
const punctToken = p.peakToken([
|
|
29
|
+
TokenTypes.quoteBraceL,
|
|
30
|
+
TokenTypes.doubleQuoteBraceL,
|
|
31
|
+
TokenTypes.quote,
|
|
32
|
+
TokenTypes.doubleQuote
|
|
33
|
+
])
|
|
34
|
+
|
|
35
|
+
let value
|
|
36
|
+
switch (punctToken?.type) {
|
|
37
|
+
case TokenTypes.quoteBraceL:
|
|
38
|
+
case TokenTypes.doubleQuoteBraceL:
|
|
39
|
+
value = [parseAttributeExpressionTag(p)]
|
|
40
|
+
break
|
|
41
|
+
case TokenTypes.quote:
|
|
42
|
+
case TokenTypes.doubleQuote:
|
|
43
|
+
value = [parseText(p)]
|
|
44
|
+
break
|
|
45
|
+
default:
|
|
46
|
+
p.raiseUnexpectedToken()
|
|
47
|
+
break
|
|
48
|
+
}
|
|
49
|
+
return { type: 'Attribute', name, value, start, end: p.pos }
|
|
50
|
+
}
|
|
51
|
+
return { type: 'Attribute', name, value: true, start, end: p.pos }
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
*
|
|
56
|
+
* @param {Parser} p
|
|
57
|
+
* @returns
|
|
58
|
+
*/
|
|
59
|
+
function parseText(p) {
|
|
60
|
+
const start = p.pos
|
|
61
|
+
p.expectToken([TokenTypes.quote, TokenTypes.doubleQuote])
|
|
62
|
+
p.expectToken([TokenTypes.text])
|
|
63
|
+
const data = p.value
|
|
64
|
+
p.expectToken([TokenTypes.quote, TokenTypes.doubleQuote])
|
|
65
|
+
return { type: 'Text', start, end: p.end, data }
|
|
66
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { parseExpressionAt } from 'acorn'
|
|
2
|
+
import { parseFragment } from './element.js'
|
|
3
|
+
import { Parser } from './parser.js'
|
|
4
|
+
import { TokenTypes } from './tokentype.js'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
*
|
|
8
|
+
* @param {Parser} p
|
|
9
|
+
* @returns
|
|
10
|
+
*/
|
|
11
|
+
export function parseEachBlock(p) {
|
|
12
|
+
const start = p.pos
|
|
13
|
+
p.expectToken([TokenTypes.braceLHash, TokenTypes.braceLColumn])
|
|
14
|
+
p.expectToken([TokenTypes.name])
|
|
15
|
+
const name = p.value
|
|
16
|
+
|
|
17
|
+
p.skipWhitespaces()
|
|
18
|
+
const expression = parseExpression(p)
|
|
19
|
+
|
|
20
|
+
p.skipWhitespaces()
|
|
21
|
+
p.expectToken([TokenTypes.name])
|
|
22
|
+
const as = p.value
|
|
23
|
+
if (as !== 'as') throw new Error('expected token as')
|
|
24
|
+
|
|
25
|
+
p.skipWhitespaces()
|
|
26
|
+
const context = parseIdentifier(p)
|
|
27
|
+
|
|
28
|
+
let key
|
|
29
|
+
p.skipWhitespaces()
|
|
30
|
+
if (p.readToken([TokenTypes.parenthesesL])) {
|
|
31
|
+
key = parseExpression(p)
|
|
32
|
+
p.expectToken([TokenTypes.parenthesesR])
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
p.skipWhitespaces()
|
|
36
|
+
p.expectToken([TokenTypes.braceR])
|
|
37
|
+
|
|
38
|
+
const body = parseFragment(p)
|
|
39
|
+
|
|
40
|
+
p.expectToken([TokenTypes.braceLSlash])
|
|
41
|
+
p.expectToken([TokenTypes.name])
|
|
42
|
+
const blockNameClose = p.value
|
|
43
|
+
p.skipWhitespaces()
|
|
44
|
+
p.expectToken([TokenTypes.braceR])
|
|
45
|
+
|
|
46
|
+
if (name !== blockNameClose) throw new Error('wrong closing tag')
|
|
47
|
+
|
|
48
|
+
return { type: 'EachBlock', expression, context, key, body, start, end: p.pos }
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
*
|
|
53
|
+
* @param {Parser} p
|
|
54
|
+
* @returns
|
|
55
|
+
*/
|
|
56
|
+
function parseExpression(p) {
|
|
57
|
+
const node = parseExpressionAt(p.input, p.pos, { ecmaVersion: 'latest' })
|
|
58
|
+
p.pos = node.end
|
|
59
|
+
return node
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
*
|
|
64
|
+
* @param {Parser} p
|
|
65
|
+
* @returns
|
|
66
|
+
*/
|
|
67
|
+
function parseIdentifier(p) {
|
|
68
|
+
const start = p.pos
|
|
69
|
+
p.expectToken([TokenTypes.name])
|
|
70
|
+
const name = p.value
|
|
71
|
+
|
|
72
|
+
return { type: 'Identifier', name, start, end: p.pos }
|
|
73
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { parseAttributes } from './attributes.js'
|
|
2
|
+
import { parseIfBlock } from './if-block.js'
|
|
3
|
+
import { parseEachBlock } from './each-block.js'
|
|
4
|
+
import { parseExpressionTag } from './expression.js'
|
|
5
|
+
import { Parser } from './parser.js'
|
|
6
|
+
import { parseScript } from './script.js'
|
|
7
|
+
import { parseStyle } from './style.js'
|
|
8
|
+
import { TokenTypes } from './tokentype.js'
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
*
|
|
12
|
+
* @param {Parser} p
|
|
13
|
+
* @returns
|
|
14
|
+
*/
|
|
15
|
+
export function parseElement(p) {
|
|
16
|
+
const start = p.pos
|
|
17
|
+
p.expectToken([TokenTypes.lte])
|
|
18
|
+
p.expectToken([TokenTypes.name])
|
|
19
|
+
const name = p.value
|
|
20
|
+
const type = name === 'slot' ? 'SlotElement' : name.includes('-') ? 'CustomElement' : 'Element'
|
|
21
|
+
const attributes = parseAttributes(p)
|
|
22
|
+
p.expectToken([TokenTypes.gte, TokenTypes.slashGte])
|
|
23
|
+
|
|
24
|
+
if (p.type === TokenTypes.gte) {
|
|
25
|
+
const fragment = parseFragment(p)
|
|
26
|
+
|
|
27
|
+
p.expectToken([TokenTypes.lteSlash])
|
|
28
|
+
p.expectToken([TokenTypes.name])
|
|
29
|
+
const tagNameClose = p.value
|
|
30
|
+
p.skipWhitespaces()
|
|
31
|
+
p.expectToken([TokenTypes.gte])
|
|
32
|
+
|
|
33
|
+
if (name !== tagNameClose) throw new Error('wrong closing tag')
|
|
34
|
+
|
|
35
|
+
return { type, name, attributes, fragment, start, end: p.pos }
|
|
36
|
+
}
|
|
37
|
+
return { type, name, attributes, start, end: p.pos }
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
*
|
|
42
|
+
* @param {Parser} p
|
|
43
|
+
* @returns
|
|
44
|
+
*/
|
|
45
|
+
export function parseFragment(p, allowScript = false, allowStyle = false) {
|
|
46
|
+
const nodes = []
|
|
47
|
+
|
|
48
|
+
p.skipWhitespaces()
|
|
49
|
+
while (!p.peakToken([TokenTypes.lteSlash, TokenTypes.braceLSlash, TokenTypes.braceLColumn])) {
|
|
50
|
+
const punctToken = p.peakToken([TokenTypes.lte, TokenTypes.braceLHash, TokenTypes.braceL])
|
|
51
|
+
const nameToken = p.peakToken([TokenTypes.name], punctToken)
|
|
52
|
+
|
|
53
|
+
switch (punctToken?.type) {
|
|
54
|
+
case TokenTypes.lte:
|
|
55
|
+
if (nameToken?.value === 'script') {
|
|
56
|
+
if (!allowScript) throw new Error('invalid script position')
|
|
57
|
+
|
|
58
|
+
nodes.push(parseScript(p))
|
|
59
|
+
break
|
|
60
|
+
}
|
|
61
|
+
if (nameToken?.value === 'style') {
|
|
62
|
+
if (!allowStyle) throw new Error('invalid style position')
|
|
63
|
+
|
|
64
|
+
nodes.push(parseStyle(p))
|
|
65
|
+
break
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
nodes.push(parseElement(p))
|
|
69
|
+
break
|
|
70
|
+
case TokenTypes.braceLHash:
|
|
71
|
+
if (nameToken?.value === 'if') {
|
|
72
|
+
nodes.push(parseIfBlock(p))
|
|
73
|
+
break
|
|
74
|
+
}
|
|
75
|
+
if (nameToken?.value === 'each') {
|
|
76
|
+
nodes.push(parseEachBlock(p))
|
|
77
|
+
break
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
throw new Error(`unknown block ${nameToken?.value}`)
|
|
81
|
+
case TokenTypes.braceL:
|
|
82
|
+
nodes.push(parseExpressionTag(p))
|
|
83
|
+
break
|
|
84
|
+
default:
|
|
85
|
+
nodes.push(parseText(p))
|
|
86
|
+
break
|
|
87
|
+
}
|
|
88
|
+
p.skipWhitespaces()
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return { type: 'Fragment', nodes }
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
*
|
|
96
|
+
* @param {Parser} p
|
|
97
|
+
* @returns
|
|
98
|
+
*/
|
|
99
|
+
function parseText(p) {
|
|
100
|
+
const previousToken = p.token()
|
|
101
|
+
p.expectToken([TokenTypes.text])
|
|
102
|
+
let data = p.value
|
|
103
|
+
|
|
104
|
+
if (previousToken.type === TokenTypes.braceR) {
|
|
105
|
+
// add back skipped whitespaces if the previous sibling is an expressionTag
|
|
106
|
+
data = p.input.substring(previousToken.end, p.start) + data
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (!p.peakToken([TokenTypes.braceL])) {
|
|
110
|
+
// remove trailing spaces if next sibling is not an expressionTag
|
|
111
|
+
data = data.trimEnd()
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return { type: 'Text', start: p.start, end: p.end, data }
|
|
115
|
+
}
|