@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.
Files changed (78) hide show
  1. package/README.md +3 -0
  2. package/package.json +32 -0
  3. package/src/analyse/context.js +31 -0
  4. package/src/analyse/index.js +47 -0
  5. package/src/analyse/visitors/AssignmentExpression.js +14 -0
  6. package/src/analyse/visitors/Attribute.js +11 -0
  7. package/src/analyse/visitors/CallExpression.js +36 -0
  8. package/src/analyse/visitors/ClassBody.js +20 -0
  9. package/src/analyse/visitors/ClassDeclaration.js +5 -0
  10. package/src/analyse/visitors/CustomElement.js +10 -0
  11. package/src/analyse/visitors/EachBlock.js +10 -0
  12. package/src/analyse/visitors/Element.js +16 -0
  13. package/src/analyse/visitors/ExpressionTag.js +9 -0
  14. package/src/analyse/visitors/IfBlock.js +16 -0
  15. package/src/analyse/visitors/ImportDeclaration.js +35 -0
  16. package/src/analyse/visitors/MethodDefinition.js +17 -0
  17. package/src/analyse/visitors/Program.js +24 -0
  18. package/src/analyse/visitors/PropertyDefinition.js +12 -0
  19. package/src/analyse/visitors/Selector.js +23 -0
  20. package/src/analyse/visitors/Template.js +14 -0
  21. package/src/builders.js +1663 -0
  22. package/src/checkers.js +120 -0
  23. package/src/css-matcher.js +100 -0
  24. package/src/css-transform.js +21 -0
  25. package/src/css.js +65 -0
  26. package/src/exp-matcher.js +65 -0
  27. package/src/id-matcher.js +17 -0
  28. package/src/index.js +19 -0
  29. package/src/module-matcher.js +17 -0
  30. package/src/parser/attributes.js +66 -0
  31. package/src/parser/each-block.js +73 -0
  32. package/src/parser/element.js +115 -0
  33. package/src/parser/expression.js +44 -0
  34. package/src/parser/if-block.js +71 -0
  35. package/src/parser/index.js +10 -0
  36. package/src/parser/parser.js +259 -0
  37. package/src/parser/root.js +33 -0
  38. package/src/parser/script.js +59 -0
  39. package/src/parser/style.js +57 -0
  40. package/src/parser/template.js +30 -0
  41. package/src/parser/tokentype.js +75 -0
  42. package/src/router/html.js +18 -0
  43. package/src/router/index.js +130 -0
  44. package/src/transform/context.js +74 -0
  45. package/src/transform/index.js +52 -0
  46. package/src/transform/static/index.js +40 -0
  47. package/src/transform/static/visitors/Attribute.js +27 -0
  48. package/src/transform/static/visitors/EachBlock.js +23 -0
  49. package/src/transform/static/visitors/Element.js +17 -0
  50. package/src/transform/static/visitors/ExpressionTag.js +6 -0
  51. package/src/transform/static/visitors/IfBlock.js +28 -0
  52. package/src/transform/static/visitors/Program.js +10 -0
  53. package/src/transform/static/visitors/Script.js +9 -0
  54. package/src/transform/static/visitors/SlotElement.js +8 -0
  55. package/src/transform/static/visitors/Style.js +9 -0
  56. package/src/transform/static/visitors/Template.js +12 -0
  57. package/src/transform/static/visitors/Text.js +5 -0
  58. package/src/transform/visitors/AssignmentExpression.js +7 -0
  59. package/src/transform/visitors/Attribute.js +79 -0
  60. package/src/transform/visitors/CallExpression.js +17 -0
  61. package/src/transform/visitors/ClassBody.js +36 -0
  62. package/src/transform/visitors/CustomElement.js +43 -0
  63. package/src/transform/visitors/EachBlock.js +39 -0
  64. package/src/transform/visitors/Element.js +49 -0
  65. package/src/transform/visitors/ExpressionTag.js +6 -0
  66. package/src/transform/visitors/Fragment.js +42 -0
  67. package/src/transform/visitors/Identifier.js +10 -0
  68. package/src/transform/visitors/IfBlock.js +55 -0
  69. package/src/transform/visitors/ImportDeclaration.js +21 -0
  70. package/src/transform/visitors/MethodDefinition.js +115 -0
  71. package/src/transform/visitors/Program.js +65 -0
  72. package/src/transform/visitors/PropertyDefinition.js +7 -0
  73. package/src/transform/visitors/Selector.js +41 -0
  74. package/src/transform/visitors/Style.js +11 -0
  75. package/src/transform/visitors/Template.js +44 -0
  76. package/src/transform/visitors/Text.js +5 -0
  77. package/src/utils/misc.js +9 -0
  78. package/src/utils/template.js +15 -0
@@ -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
+ }