@katabatic/compiler 1.1.4 → 1.1.6
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/package.json +5 -1
- package/src/css-matcher.js +3 -2
- package/src/parser/each-block.js +7 -3
- package/src/parser/element.js +29 -2
- package/src/parser/if-block.js +5 -1
- package/src/parser/parser.js +12 -18
- package/src/parser/root.js +1 -1
- package/src/parser/tokentype.js +21 -5
- package/src/transform/visitors/Attribute.js +3 -5
- package/src/transform/visitors/Program.js +4 -5
- package/test/parser/index.test.js +17 -0
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@katabatic/compiler",
|
|
3
3
|
"license": "MIT",
|
|
4
|
-
"version": "1.1.
|
|
4
|
+
"version": "1.1.6",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
@@ -16,6 +16,10 @@
|
|
|
16
16
|
"import": "./src/router/index.js"
|
|
17
17
|
}
|
|
18
18
|
},
|
|
19
|
+
"scripts": {
|
|
20
|
+
"test": "vitest"
|
|
21
|
+
|
|
22
|
+
},
|
|
19
23
|
"devDependencies": {
|
|
20
24
|
"@types/css-tree": "^2.3.10",
|
|
21
25
|
"@types/node": "^22.15.21",
|
package/src/css-matcher.js
CHANGED
|
@@ -71,9 +71,10 @@ function matchSelectors(selectors, template) {
|
|
|
71
71
|
return
|
|
72
72
|
case 'ClassSelector':
|
|
73
73
|
classAttribute ??= node.attributes.find((a) => a.name === 'class')
|
|
74
|
-
const
|
|
74
|
+
const expression = classAttribute?.value[0].expression
|
|
75
|
+
const classes = classAttribute?.value[0].data?.split(/\s+/)
|
|
75
76
|
|
|
76
|
-
if (classes?.includes(selector.name)) {
|
|
77
|
+
if (expression || classes?.includes(selector.name)) {
|
|
77
78
|
scopedClassAttribute = classAttribute
|
|
78
79
|
break
|
|
79
80
|
}
|
package/src/parser/each-block.js
CHANGED
|
@@ -13,10 +13,10 @@ export function parseEachBlock(p) {
|
|
|
13
13
|
p.expectToken([TokenTypes.braceLHash, TokenTypes.braceLColumn])
|
|
14
14
|
p.expectToken([TokenTypes.name])
|
|
15
15
|
const name = p.value
|
|
16
|
-
|
|
16
|
+
|
|
17
17
|
p.skipWhitespaces()
|
|
18
18
|
const expression = parseExpression(p)
|
|
19
|
-
|
|
19
|
+
|
|
20
20
|
p.skipWhitespaces()
|
|
21
21
|
p.expectToken([TokenTypes.name])
|
|
22
22
|
const as = p.value
|
|
@@ -43,7 +43,11 @@ export function parseEachBlock(p) {
|
|
|
43
43
|
p.skipWhitespaces()
|
|
44
44
|
p.expectToken([TokenTypes.braceR])
|
|
45
45
|
|
|
46
|
-
if (
|
|
46
|
+
if (blockNameClose !== name) {
|
|
47
|
+
throw new Error(
|
|
48
|
+
`wrong closing block ${blockNameClose} at position ${p.pos}, expected ${name}`
|
|
49
|
+
)
|
|
50
|
+
}
|
|
47
51
|
|
|
48
52
|
return { type: 'EachBlock', expression, context, key, body, start, end: p.pos }
|
|
49
53
|
}
|
package/src/parser/element.js
CHANGED
|
@@ -30,13 +30,32 @@ export function parseElement(p) {
|
|
|
30
30
|
p.skipWhitespaces()
|
|
31
31
|
p.expectToken([TokenTypes.gte])
|
|
32
32
|
|
|
33
|
-
if (
|
|
33
|
+
if (tagNameClose !== name) {
|
|
34
|
+
throw new Error(
|
|
35
|
+
`wrong closing tag ${tagNameClose} at position ${p.pos}, expected ${name}`
|
|
36
|
+
)
|
|
37
|
+
}
|
|
34
38
|
|
|
35
39
|
return { type, name, attributes, fragment, start, end: p.pos }
|
|
36
40
|
}
|
|
37
41
|
return { type, name, attributes, fragment: { type: 'Fragment', nodes: [] }, start, end: p.pos }
|
|
38
42
|
}
|
|
39
43
|
|
|
44
|
+
/**
|
|
45
|
+
*
|
|
46
|
+
* @param {Parser} p
|
|
47
|
+
* @returns
|
|
48
|
+
*/
|
|
49
|
+
export function parseComment(p) {
|
|
50
|
+
const start = p.pos
|
|
51
|
+
p.expectToken([TokenTypes.lteExclHyph2])
|
|
52
|
+
p.expectToken([TokenTypes.commentText])
|
|
53
|
+
const data = p.value
|
|
54
|
+
p.expectToken([TokenTypes.hyph2Gte])
|
|
55
|
+
|
|
56
|
+
return { type: 'Comment', data, start, end: p.pos }
|
|
57
|
+
}
|
|
58
|
+
|
|
40
59
|
/**
|
|
41
60
|
*
|
|
42
61
|
* @param {Parser} p
|
|
@@ -47,7 +66,12 @@ export function parseFragment(p, allowScript = false, allowStyle = false) {
|
|
|
47
66
|
|
|
48
67
|
p.skipWhitespaces()
|
|
49
68
|
while (!p.peakToken([TokenTypes.lteSlash, TokenTypes.braceLSlash, TokenTypes.braceLColumn])) {
|
|
50
|
-
const punctToken = p.peakToken([
|
|
69
|
+
const punctToken = p.peakToken([
|
|
70
|
+
TokenTypes.lteExclHyph2,
|
|
71
|
+
TokenTypes.lte,
|
|
72
|
+
TokenTypes.braceLHash,
|
|
73
|
+
TokenTypes.braceL
|
|
74
|
+
])
|
|
51
75
|
const nameToken = p.peakToken([TokenTypes.name], punctToken)
|
|
52
76
|
|
|
53
77
|
switch (punctToken?.type) {
|
|
@@ -67,6 +91,9 @@ export function parseFragment(p, allowScript = false, allowStyle = false) {
|
|
|
67
91
|
|
|
68
92
|
nodes.push(parseElement(p))
|
|
69
93
|
break
|
|
94
|
+
case TokenTypes.lteExclHyph2:
|
|
95
|
+
nodes.push(parseComment(p))
|
|
96
|
+
break
|
|
70
97
|
case TokenTypes.braceLHash:
|
|
71
98
|
if (nameToken?.value === 'if') {
|
|
72
99
|
nodes.push(parseIfBlock(p))
|
package/src/parser/if-block.js
CHANGED
|
@@ -49,7 +49,11 @@ export function parseIfBlock(p, elseif = false) {
|
|
|
49
49
|
p.skipWhitespaces()
|
|
50
50
|
p.expectToken([TokenTypes.braceR])
|
|
51
51
|
|
|
52
|
-
if (
|
|
52
|
+
if (blockNameClose !== name) {
|
|
53
|
+
throw new Error(
|
|
54
|
+
`wrong closing block ${blockNameClose} at position ${p.pos}, expected ${name}`
|
|
55
|
+
)
|
|
56
|
+
}
|
|
53
57
|
}
|
|
54
58
|
|
|
55
59
|
return { type: 'IfBlock', elseif, test, consequent, alternate, start, end: p.pos }
|
package/src/parser/parser.js
CHANGED
|
@@ -61,7 +61,11 @@ export class Parser {
|
|
|
61
61
|
if (this.readStringToken(type)) return
|
|
62
62
|
}
|
|
63
63
|
}
|
|
64
|
-
throw new Error(
|
|
64
|
+
throw new Error(
|
|
65
|
+
`wrong token ${this.input[this.pos]} at position ${this.pos}. expected ${types
|
|
66
|
+
.map((t) => t.label)
|
|
67
|
+
.join(', ')}`
|
|
68
|
+
)
|
|
65
69
|
}
|
|
66
70
|
|
|
67
71
|
/**
|
|
@@ -88,8 +92,10 @@ export class Parser {
|
|
|
88
92
|
readStringToken(type) {
|
|
89
93
|
const start = this.pos
|
|
90
94
|
while (this.pos < this.input.length) {
|
|
91
|
-
|
|
95
|
+
const code = this.input.charCodeAt(this.pos)
|
|
96
|
+
if (!type.test(code, this.input, this.pos)) break
|
|
92
97
|
this.pos++
|
|
98
|
+
this.line = code
|
|
93
99
|
}
|
|
94
100
|
|
|
95
101
|
if (this.pos !== start) {
|
|
@@ -114,7 +120,8 @@ export class Parser {
|
|
|
114
120
|
const start = after?.end ?? this.pos
|
|
115
121
|
let pos = start
|
|
116
122
|
while (pos < this.input.length) {
|
|
117
|
-
|
|
123
|
+
const code = this.input.charCodeAt(pos)
|
|
124
|
+
if (!type.test(code, this.input, pos)) break
|
|
118
125
|
pos++
|
|
119
126
|
}
|
|
120
127
|
|
|
@@ -178,7 +185,8 @@ export class Parser {
|
|
|
178
185
|
return (
|
|
179
186
|
this.input.charCodeAt(pos) === type.charCodes[0] &&
|
|
180
187
|
(type.charCodes[1] ? this.input.charCodeAt(pos + 1) === type.charCodes[1] : true) &&
|
|
181
|
-
(type.charCodes[2] ? this.input.charCodeAt(pos + 2) === type.charCodes[2] : true)
|
|
188
|
+
(type.charCodes[2] ? this.input.charCodeAt(pos + 2) === type.charCodes[2] : true) &&
|
|
189
|
+
(type.charCodes[3] ? this.input.charCodeAt(pos + 3) === type.charCodes[3] : true)
|
|
182
190
|
)
|
|
183
191
|
}
|
|
184
192
|
|
|
@@ -236,20 +244,6 @@ export class Parser {
|
|
|
236
244
|
}
|
|
237
245
|
}
|
|
238
246
|
|
|
239
|
-
/**
|
|
240
|
-
*
|
|
241
|
-
* @param {number} code
|
|
242
|
-
* @returns {boolean}
|
|
243
|
-
*/
|
|
244
|
-
function isIdentifierChar(code) {
|
|
245
|
-
if (code < 48) return false
|
|
246
|
-
if (code < 58) return true
|
|
247
|
-
if (code < 65) return false
|
|
248
|
-
if (code < 91) return true
|
|
249
|
-
if (code < 123) return true
|
|
250
|
-
return false
|
|
251
|
-
}
|
|
252
|
-
|
|
253
247
|
/**
|
|
254
248
|
*
|
|
255
249
|
* @param {boolean} test
|
package/src/parser/root.js
CHANGED
|
@@ -24,7 +24,7 @@ export function parseRoot(p) {
|
|
|
24
24
|
template = parseTemplate(p)
|
|
25
25
|
break
|
|
26
26
|
default:
|
|
27
|
-
throw new Error(
|
|
27
|
+
throw new Error(`unexpected html tag ${nameToken.value} at position ${p.pos}`)
|
|
28
28
|
}
|
|
29
29
|
p.skipWhitespaces()
|
|
30
30
|
}
|
package/src/parser/tokentype.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* @typedef {Object} TokenType
|
|
3
3
|
* @property {string} label
|
|
4
4
|
* @property {Array<number>} [charCodes]
|
|
5
|
-
* @property {(
|
|
5
|
+
* @property {(code: number, input: string, pos: number) => boolean} [test]
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
/**
|
|
@@ -18,7 +18,7 @@ function charTT(label, charCodes) {
|
|
|
18
18
|
/**
|
|
19
19
|
*
|
|
20
20
|
* @param {string} label
|
|
21
|
-
* @param {(
|
|
21
|
+
* @param {(code: number, input: string, pos: number) => boolean} test
|
|
22
22
|
* @returns {TokenType}
|
|
23
23
|
*/
|
|
24
24
|
function stringTT(label, test) {
|
|
@@ -29,13 +29,16 @@ export const TokenTypes = {
|
|
|
29
29
|
name: stringTT('name', name),
|
|
30
30
|
quotedText: stringTT('text', quotedText),
|
|
31
31
|
doubleQuotedText: stringTT('text', doubleQuotedText),
|
|
32
|
+
commentText: stringTT('text', commentText),
|
|
32
33
|
text: stringTT('text', text),
|
|
33
34
|
|
|
34
35
|
// html punctuation
|
|
35
36
|
lte: charTT('<', [60]),
|
|
36
37
|
gte: charTT('>', [62]),
|
|
37
|
-
slashGte: charTT('/>', [47, 62]),
|
|
38
38
|
lteSlash: charTT('</', [60, 47]),
|
|
39
|
+
slashGte: charTT('/>', [47, 62]),
|
|
40
|
+
lteExclHyph2: charTT('<!--', [60, 33, 45, 45]),
|
|
41
|
+
hyph2Gte: charTT('-->', [45, 45, 62]),
|
|
39
42
|
eq: charTT('=', [61]),
|
|
40
43
|
quote: charTT("'", [39]),
|
|
41
44
|
doubleQuote: charTT('"', [34]),
|
|
@@ -85,10 +88,23 @@ function doubleQuotedText(code) {
|
|
|
85
88
|
return code > 0 && code !== 34 // Blocks NULL (0) and Double Quote (34)
|
|
86
89
|
}
|
|
87
90
|
|
|
91
|
+
function commentText(code, input, pos) {
|
|
92
|
+
if (
|
|
93
|
+
input.charCodeAt(pos) === 45 &&
|
|
94
|
+
input.charCodeAt(pos + 1) === 45 &&
|
|
95
|
+
input.charCodeAt(pos + 2) === 62
|
|
96
|
+
) {
|
|
97
|
+
// Blocks the sequence "-->"
|
|
98
|
+
return false
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return code >= 32 || code === 9 || code === 10 || code === 13 // Allows printable characters and standard whitespace (Tab, LF, CR)
|
|
102
|
+
}
|
|
103
|
+
|
|
88
104
|
function text(code) {
|
|
89
105
|
// Quickly allow all extended ASCII and UTF-16 surrogate bytes
|
|
90
106
|
if (code > 127) return true
|
|
91
107
|
|
|
92
|
-
// Blocks NULL (0), Less-Than (<), Left-Brace ({)
|
|
93
|
-
return code !== 0 && code !== 60 && code !== 123
|
|
108
|
+
// Blocks NULL (0), Less-Than (<), Left-Brace ({)
|
|
109
|
+
return code !== 0 && code !== 60 && code !== 123
|
|
94
110
|
}
|
|
@@ -47,13 +47,11 @@ export function Attribute(node, ctx) {
|
|
|
47
47
|
)
|
|
48
48
|
ctx.state.handlers.push(stmt)
|
|
49
49
|
} else if (moduleId) {
|
|
50
|
-
const stmt = b.$effect([
|
|
51
|
-
b.$set(moduleId, elementId, node.name, template.expressions[0])
|
|
52
|
-
])
|
|
50
|
+
const stmt = b.$effect([b.$set(moduleId, elementId, node.name, b.template(template))])
|
|
53
51
|
ctx.state.effects.push(stmt)
|
|
54
52
|
} else if (bindingId) {
|
|
55
53
|
const stmt1 = b.$effect([
|
|
56
|
-
b.assignment(b.member(bindingId, node.name), template
|
|
54
|
+
b.assignment(b.member(bindingId, node.name), b.template(template))
|
|
57
55
|
])
|
|
58
56
|
const stmt2 = b.$effect([
|
|
59
57
|
b.assignment(template.expressions[0], b.member(bindingId, node.name))
|
|
@@ -62,7 +60,7 @@ export function Attribute(node, ctx) {
|
|
|
62
60
|
ctx.state.effects.push(stmt1, stmt2)
|
|
63
61
|
} else {
|
|
64
62
|
const stmt = b.$effect([
|
|
65
|
-
b.setAttribute(elementId, b.literal(node.name), template
|
|
63
|
+
b.setAttribute(elementId, b.literal(node.name), b.template(template))
|
|
66
64
|
])
|
|
67
65
|
ctx.state.effects.push(stmt)
|
|
68
66
|
}
|
|
@@ -82,11 +82,10 @@ export function Program(node, ctx) {
|
|
|
82
82
|
stmts2.push(stmt)
|
|
83
83
|
|
|
84
84
|
// $shadowRootMode
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
}
|
|
85
|
+
stmt = b.exp(
|
|
86
|
+
b.declaration('$shadowRootMode', b.literal(ctx.state.template?.metadata?.shadowRootMode))
|
|
87
|
+
)
|
|
88
|
+
stmts2.push(stmt)
|
|
90
89
|
|
|
91
90
|
return {
|
|
92
91
|
...node,
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
import { parse } from '../../src/parser'
|
|
3
|
+
|
|
4
|
+
describe('parse()', () => {
|
|
5
|
+
describe('with template containing comments', () => {
|
|
6
|
+
it('should return an AST succesfully', () => {
|
|
7
|
+
const ast = parse(`<template><div><!-- comment --></div></template>`)
|
|
8
|
+
expect(ast).not.toBeNull()
|
|
9
|
+
})
|
|
10
|
+
})
|
|
11
|
+
describe('with template containing &', () => {
|
|
12
|
+
it('should return an AST succesfully', () => {
|
|
13
|
+
const ast = parse(`<template><div>&</div></template>`)
|
|
14
|
+
expect(ast).not.toBeNull()
|
|
15
|
+
})
|
|
16
|
+
})
|
|
17
|
+
})
|