@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 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 attrs = attributes.length > 0 ? `{ ${attributes.join(', ')} }` : null;
28
- return attrs ? `h(${tagName}, ${attrs})` : `h(${tagName})`;
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 attrs = attributes.length > 0 ? `{ ${attributes.join(', ')} }` : null;
64
+ const attrsString = formatAttributes(attributes);
37
65
  const children = content ? content : null;
38
- if (attrs && children) {
39
- return `h(${tagName}, ${attrs}, ${children})`;
40
- } else if (attrs) {
41
- return `h(${tagName}, ${attrs})`;
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
- return `${eventName}: ${handlerName}`;
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
- return eventName;
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 `${attributeName}: ${attributeValue}`;
127
+ return `${formattedName}: ${attributeValue}`;
77
128
  } else if (attributeValue.trim().match(/^[a-zA-Z_]\w*$/)) {
78
- return `${attributeName}: ${attributeValue}`;
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 `${attributeName}: computed(() => ${computedValue})`;
140
+ return `${formattedName}: computed(() => ${computedValue})`;
90
141
  }
91
- return `${attributeName}: ${computedValue}`;
142
+ return `${formattedName}: ${computedValue}`;
92
143
  }
93
144
  }
94
145
  / attributeName:attributeName _ {
95
- return attributeName;
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
- return `${attributeName}: ${attributeValue}`;
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]* { return text(); }
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@canvasengine/compiler",
3
- "version": "2.0.0-beta.11",
3
+ "version": "2.0.0-beta.13",
4
4
  "description": "",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -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);