@canvasengine/compiler 2.0.0-beta.11 → 2.0.0-beta.13
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/grammar.pegjs +72 -16
- package/package.json +1 -1
- package/tests/compiler.spec.ts +36 -0
package/grammar.pegjs
CHANGED
|
@@ -5,6 +5,34 @@
|
|
|
5
5
|
`at line ${start.line}, column ${start.column} to line ${end.line}, column ${end.column}`;
|
|
6
6
|
throw new Error(errorMessage);
|
|
7
7
|
}
|
|
8
|
+
|
|
9
|
+
function formatAttributes(attributes) {
|
|
10
|
+
if (attributes.length === 0) {
|
|
11
|
+
return null;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// Check if there's exactly one attribute and it's a spread attribute
|
|
15
|
+
if (attributes.length === 1 && attributes[0].startsWith('...')) {
|
|
16
|
+
// Return the identifier directly, removing the '...'
|
|
17
|
+
return attributes[0].substring(3);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Otherwise, format as an object literal
|
|
21
|
+
const formattedAttrs = attributes.map(attr => {
|
|
22
|
+
// If it's a spread attribute, keep it as is
|
|
23
|
+
if (attr.startsWith('...')) {
|
|
24
|
+
return attr;
|
|
25
|
+
}
|
|
26
|
+
// If it's a standalone attribute (doesn't contain ':'), format as shorthand property 'name'
|
|
27
|
+
if (!attr.includes(':')) {
|
|
28
|
+
return attr; // JS object literal shorthand
|
|
29
|
+
}
|
|
30
|
+
// Otherwise (key: value), keep it as is
|
|
31
|
+
return attr;
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
return `{ ${formattedAttrs.join(', ')} }`;
|
|
35
|
+
}
|
|
8
36
|
}
|
|
9
37
|
|
|
10
38
|
start
|
|
@@ -24,8 +52,8 @@ element
|
|
|
24
52
|
|
|
25
53
|
selfClosingElement
|
|
26
54
|
= _ "<" _ tagName:tagName _ attributes:attributes _ "/>" _ {
|
|
27
|
-
const
|
|
28
|
-
return
|
|
55
|
+
const attrsString = formatAttributes(attributes);
|
|
56
|
+
return attrsString ? `h(${tagName}, ${attrsString})` : `h(${tagName})`;
|
|
29
57
|
}
|
|
30
58
|
|
|
31
59
|
openCloseElement
|
|
@@ -33,12 +61,12 @@ openCloseElement
|
|
|
33
61
|
if (tagName !== closingTagName) {
|
|
34
62
|
error("Mismatched opening and closing tags");
|
|
35
63
|
}
|
|
36
|
-
const
|
|
64
|
+
const attrsString = formatAttributes(attributes);
|
|
37
65
|
const children = content ? content : null;
|
|
38
|
-
if (
|
|
39
|
-
return `h(${tagName}, ${
|
|
40
|
-
} else if (
|
|
41
|
-
return `h(${tagName}, ${
|
|
66
|
+
if (attrsString && children) {
|
|
67
|
+
return `h(${tagName}, ${attrsString}, ${children})`;
|
|
68
|
+
} else if (attrsString) {
|
|
69
|
+
return `h(${tagName}, ${attrsString})`;
|
|
42
70
|
} else if (children) {
|
|
43
71
|
return `h(${tagName}, null, ${children})`;
|
|
44
72
|
} else {
|
|
@@ -61,21 +89,44 @@ attribute
|
|
|
61
89
|
= staticAttribute
|
|
62
90
|
/ dynamicAttribute
|
|
63
91
|
/ eventHandler
|
|
92
|
+
/ spreadAttribute
|
|
93
|
+
|
|
94
|
+
spreadAttribute
|
|
95
|
+
= "..." expr:(functionCallExpr / dotNotation) {
|
|
96
|
+
return "..." + expr;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
functionCallExpr
|
|
100
|
+
= name:dotNotation "(" args:functionArgs? ")" {
|
|
101
|
+
return `${name}(${args || ''})`;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
dotNotation
|
|
105
|
+
= first:identifier rest:("." identifier)* {
|
|
106
|
+
return text();
|
|
107
|
+
}
|
|
64
108
|
|
|
65
109
|
eventHandler
|
|
66
110
|
= "@" eventName:identifier _ "=" _ "{" _ handlerName:attributeValue _ "}" {
|
|
67
|
-
|
|
111
|
+
const needsQuotes = /[^a-zA-Z0-9_$]/.test(eventName);
|
|
112
|
+
const formattedName = needsQuotes ? `'${eventName}'` : eventName;
|
|
113
|
+
return `${formattedName}: ${handlerName}`;
|
|
68
114
|
}
|
|
69
115
|
/ "@" eventName:attributeName _ {
|
|
70
|
-
|
|
116
|
+
const needsQuotes = /[^a-zA-Z0-9_$]/.test(eventName);
|
|
117
|
+
return needsQuotes ? `'${eventName}'` : eventName;
|
|
71
118
|
}
|
|
72
119
|
|
|
73
120
|
dynamicAttribute
|
|
74
121
|
= attributeName:attributeName _ "=" _ "{" _ attributeValue:attributeValue _ "}" {
|
|
122
|
+
// Check if attributeName needs to be quoted (contains dash or other invalid JS identifier chars)
|
|
123
|
+
const needsQuotes = /[^a-zA-Z0-9_$]/.test(attributeName);
|
|
124
|
+
const formattedName = needsQuotes ? `'${attributeName}'` : attributeName;
|
|
125
|
+
|
|
75
126
|
if (attributeValue.startsWith('h(') || attributeValue.includes('=>')) {
|
|
76
|
-
return `${
|
|
127
|
+
return `${formattedName}: ${attributeValue}`;
|
|
77
128
|
} else if (attributeValue.trim().match(/^[a-zA-Z_]\w*$/)) {
|
|
78
|
-
return `${
|
|
129
|
+
return `${formattedName}: ${attributeValue}`;
|
|
79
130
|
} else {
|
|
80
131
|
let foundSignal = false;
|
|
81
132
|
const computedValue = attributeValue.replace(/@?[a-zA-Z_][a-zA-Z0-9_]*(?!:)/g, (match) => {
|
|
@@ -86,13 +137,14 @@ dynamicAttribute
|
|
|
86
137
|
return `${match}()`;
|
|
87
138
|
});
|
|
88
139
|
if (foundSignal) {
|
|
89
|
-
return `${
|
|
140
|
+
return `${formattedName}: computed(() => ${computedValue})`;
|
|
90
141
|
}
|
|
91
|
-
return `${
|
|
142
|
+
return `${formattedName}: ${computedValue}`;
|
|
92
143
|
}
|
|
93
144
|
}
|
|
94
145
|
/ attributeName:attributeName _ {
|
|
95
|
-
|
|
146
|
+
const needsQuotes = /[^a-zA-Z0-9_$]/.test(attributeName);
|
|
147
|
+
return needsQuotes ? `'${attributeName}'` : attributeName;
|
|
96
148
|
}
|
|
97
149
|
|
|
98
150
|
attributeValue
|
|
@@ -127,7 +179,9 @@ simpleParams
|
|
|
127
179
|
|
|
128
180
|
staticAttribute
|
|
129
181
|
= attributeName:attributeName _ "=" _ "\"" attributeValue:staticValue "\"" {
|
|
130
|
-
|
|
182
|
+
const needsQuotes = /[^a-zA-Z0-9_$]/.test(attributeName);
|
|
183
|
+
const formattedName = needsQuotes ? `'${attributeName}'` : attributeName;
|
|
184
|
+
return `${formattedName}: ${attributeValue}`;
|
|
131
185
|
}
|
|
132
186
|
|
|
133
187
|
eventAttribute
|
|
@@ -177,7 +231,9 @@ ifCondition
|
|
|
177
231
|
}
|
|
178
232
|
|
|
179
233
|
tagName
|
|
180
|
-
= [a-zA-Z][a-zA-Z0-9]*
|
|
234
|
+
= segments:([a-zA-Z][a-zA-Z0-9]* ("." [a-zA-Z][a-zA-Z0-9]*)*) {
|
|
235
|
+
return text();
|
|
236
|
+
}
|
|
181
237
|
|
|
182
238
|
attributeName
|
|
183
239
|
= [a-zA-Z][a-zA-Z0-9-]* { return text(); }
|
package/package.json
CHANGED
package/tests/compiler.spec.ts
CHANGED
|
@@ -51,12 +51,42 @@ describe("Compiler", () => {
|
|
|
51
51
|
expect(output).toBe(`h(Canvas)`);
|
|
52
52
|
});
|
|
53
53
|
|
|
54
|
+
test("should compile component with dot notation", () => {
|
|
55
|
+
const input = `<MyComp.test />`;
|
|
56
|
+
const output = parser.parse(input);
|
|
57
|
+
expect(output).toBe(`h(MyComp.test)`);
|
|
58
|
+
});
|
|
59
|
+
|
|
54
60
|
test("should compile component with dynamic attribute", () => {
|
|
55
61
|
const input = `<Canvas width={x} />`;
|
|
56
62
|
const output = parser.parse(input);
|
|
57
63
|
expect(output).toBe(`h(Canvas, { width: x })`);
|
|
58
64
|
});
|
|
59
65
|
|
|
66
|
+
test("should compile component with spread operator", () => {
|
|
67
|
+
const input = `<Canvas ...obj />`;
|
|
68
|
+
const output = parser.parse(input);
|
|
69
|
+
expect(output).toBe(`h(Canvas, obj)`);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
test("should compile component with spread operator object", () => {
|
|
73
|
+
const input = `<Canvas ...obj.prop />`;
|
|
74
|
+
const output = parser.parse(input);
|
|
75
|
+
expect(output).toBe(`h(Canvas, obj.prop)`);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
test("should compile component with spread operator function", () => {
|
|
79
|
+
const input = `<Canvas ...fn() />`;
|
|
80
|
+
const output = parser.parse(input);
|
|
81
|
+
expect(output).toBe(`h(Canvas, fn())`);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
test("should compile component with spread operator function and params", () => {
|
|
85
|
+
const input = `<Canvas ...fn(x, y) />`;
|
|
86
|
+
const output = parser.parse(input);
|
|
87
|
+
expect(output).toBe(`h(Canvas, fn(x, y))`);
|
|
88
|
+
});
|
|
89
|
+
|
|
60
90
|
test("should compile component with dynamic attribute but is not a signal", () => {
|
|
61
91
|
const input = `<Canvas width={20} />`;
|
|
62
92
|
const output = parser.parse(input);
|
|
@@ -129,6 +159,12 @@ describe("Compiler", () => {
|
|
|
129
159
|
expect(output).toBe(`h(Canvas, { width: 'val' })`);
|
|
130
160
|
});
|
|
131
161
|
|
|
162
|
+
test("should compile component with static string attribute with dash", () => {
|
|
163
|
+
const input = `<Canvas max-width="val" />`;
|
|
164
|
+
const output = parser.parse(input);
|
|
165
|
+
expect(output).toBe(`h(Canvas, { 'max-width': 'val' })`);
|
|
166
|
+
});
|
|
167
|
+
|
|
132
168
|
test("should compile component with static attribute (with number)", () => {
|
|
133
169
|
const input = `<Canvas width="10" />`;
|
|
134
170
|
const output = parser.parse(input);
|