@fluffjs/cli 0.0.8 → 0.1.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 (142) hide show
  1. package/BabelHelpers.d.ts +26 -0
  2. package/BabelHelpers.js +65 -0
  3. package/Cli.d.ts +5 -10
  4. package/Cli.js +123 -52
  5. package/CodeGenerator.d.ts +53 -39
  6. package/CodeGenerator.js +330 -725
  7. package/ComponentCompiler.d.ts +14 -16
  8. package/ComponentCompiler.js +187 -256
  9. package/DomPreProcessor.d.ts +36 -0
  10. package/DomPreProcessor.js +645 -0
  11. package/ErrorHelpers.d.ts +5 -0
  12. package/ErrorHelpers.js +8 -0
  13. package/ExpressionTransformer.d.ts +38 -28
  14. package/ExpressionTransformer.js +558 -230
  15. package/Generator.d.ts +1 -5
  16. package/Generator.js +128 -67
  17. package/GetterDependencyExtractor.d.ts +4 -0
  18. package/GetterDependencyExtractor.js +73 -0
  19. package/IndexHtmlTransformer.d.ts +6 -7
  20. package/IndexHtmlTransformer.js +82 -88
  21. package/Parse5Helpers.d.ts +16 -0
  22. package/Parse5Helpers.js +81 -0
  23. package/TemplateParser.d.ts +39 -21
  24. package/TemplateParser.js +462 -268
  25. package/Typeguards.d.ts +24 -0
  26. package/Typeguards.js +30 -0
  27. package/babel-plugin-class-transform.d.ts +3 -18
  28. package/babel-plugin-class-transform.js +3 -11
  29. package/babel-plugin-component.d.ts +4 -13
  30. package/babel-plugin-component.js +7 -0
  31. package/babel-plugin-imports.d.ts +3 -11
  32. package/babel-plugin-imports.js +5 -31
  33. package/babel-plugin-reactive.d.ts +2 -19
  34. package/babel-plugin-reactive.js +21 -76
  35. package/bin.js +2 -2
  36. package/fluff-esbuild-plugin.d.ts +2 -5
  37. package/fluff-esbuild-plugin.js +4 -1
  38. package/index.d.ts +6 -2
  39. package/index.js +1 -1
  40. package/interfaces/BabelPluginClassTransformState.d.ts +5 -0
  41. package/interfaces/BabelPluginComponentState.d.ts +4 -0
  42. package/interfaces/BabelPluginComponentState.js +1 -0
  43. package/interfaces/BabelPluginImportsState.d.ts +5 -0
  44. package/interfaces/BabelPluginImportsState.js +1 -0
  45. package/interfaces/BabelPluginReactiveState.d.ts +13 -0
  46. package/interfaces/BabelPluginReactiveState.js +1 -0
  47. package/interfaces/BabelPluginReactiveWatchCallInfo.d.ts +7 -0
  48. package/interfaces/BabelPluginReactiveWatchCallInfo.js +1 -0
  49. package/interfaces/BabelPluginReactiveWatchInfo.d.ts +5 -0
  50. package/interfaces/BabelPluginReactiveWatchInfo.js +1 -0
  51. package/interfaces/BabelToken.d.ts +8 -0
  52. package/interfaces/BabelToken.js +1 -0
  53. package/interfaces/BindingInfo.d.ts +12 -0
  54. package/interfaces/BindingInfo.js +1 -0
  55. package/interfaces/BreakMarkerConfig.d.ts +4 -0
  56. package/interfaces/BreakMarkerConfig.js +1 -0
  57. package/interfaces/BreakNode.d.ts +4 -0
  58. package/interfaces/BreakNode.js +1 -0
  59. package/interfaces/BundleOptions.d.ts +8 -0
  60. package/interfaces/BundleOptions.js +1 -0
  61. package/interfaces/ClassTransformOptions.d.ts +10 -0
  62. package/interfaces/ClassTransformOptions.js +1 -0
  63. package/interfaces/CliOptions.d.ts +6 -0
  64. package/interfaces/CliOptions.js +1 -0
  65. package/interfaces/CommentNode.d.ts +5 -0
  66. package/interfaces/CommentNode.js +1 -0
  67. package/interfaces/CompileResult.d.ts +6 -0
  68. package/interfaces/CompileResult.js +1 -0
  69. package/interfaces/CompilerOptions.d.ts +6 -0
  70. package/interfaces/CompilerOptions.js +1 -0
  71. package/interfaces/ComponentInfo.d.ts +8 -0
  72. package/interfaces/ComponentInfo.js +1 -0
  73. package/interfaces/ComponentMetadata.d.ts +9 -0
  74. package/interfaces/ComponentMetadata.js +1 -0
  75. package/interfaces/ControlFlow.d.ts +19 -0
  76. package/interfaces/ControlFlow.js +1 -0
  77. package/interfaces/ControlFlowNode.d.ts +6 -0
  78. package/interfaces/ControlFlowNode.js +1 -0
  79. package/interfaces/ControlFlowParseResult.d.ts +10 -0
  80. package/interfaces/ControlFlowParseResult.js +1 -0
  81. package/interfaces/ElementNode.d.ts +11 -0
  82. package/interfaces/ElementNode.js +1 -0
  83. package/interfaces/FluffConfigInterface.d.ts +7 -0
  84. package/interfaces/FluffConfigInterface.js +1 -0
  85. package/interfaces/FluffPluginOptions.d.ts +9 -0
  86. package/interfaces/FluffPluginOptions.js +1 -0
  87. package/interfaces/FluffTarget.d.ts +15 -0
  88. package/interfaces/FluffTarget.js +1 -0
  89. package/interfaces/ForMarkerConfig.d.ts +9 -0
  90. package/interfaces/ForMarkerConfig.js +1 -0
  91. package/interfaces/ForNode.d.ts +13 -0
  92. package/interfaces/ForNode.js +1 -0
  93. package/interfaces/GeneratorOptions.d.ts +5 -0
  94. package/interfaces/GeneratorOptions.js +1 -0
  95. package/interfaces/HtmlTransformOptions.d.ts +9 -0
  96. package/interfaces/HtmlTransformOptions.js +1 -0
  97. package/interfaces/IfBranch.d.ts +8 -0
  98. package/interfaces/IfBranch.js +1 -0
  99. package/interfaces/IfMarkerConfig.d.ts +8 -0
  100. package/interfaces/IfMarkerConfig.js +1 -0
  101. package/interfaces/IfNode.d.ts +7 -0
  102. package/interfaces/IfNode.js +1 -0
  103. package/interfaces/ImportTransformOptions.d.ts +7 -0
  104. package/interfaces/ImportTransformOptions.js +1 -0
  105. package/interfaces/InterpolationNode.d.ts +12 -0
  106. package/interfaces/InterpolationNode.js +1 -0
  107. package/interfaces/ParsedTemplate.d.ts +6 -0
  108. package/interfaces/ParsedTemplate.js +1 -0
  109. package/interfaces/ParsedTemplateOld.d.ts +9 -0
  110. package/interfaces/ParsedTemplateOld.js +1 -0
  111. package/interfaces/PropertyChain.d.ts +2 -0
  112. package/interfaces/PropertyChain.js +1 -0
  113. package/interfaces/Scope.d.ts +5 -0
  114. package/interfaces/Scope.js +1 -0
  115. package/interfaces/ServeOptions.d.ts +5 -0
  116. package/interfaces/ServeOptions.js +1 -0
  117. package/interfaces/SwitchCase.d.ts +8 -0
  118. package/interfaces/SwitchCase.js +1 -0
  119. package/interfaces/SwitchMarkerConfig.d.ts +11 -0
  120. package/interfaces/SwitchMarkerConfig.js +1 -0
  121. package/interfaces/SwitchNode.d.ts +10 -0
  122. package/interfaces/SwitchNode.js +1 -0
  123. package/interfaces/TemplateBinding.d.ts +10 -0
  124. package/interfaces/TemplateBinding.js +1 -0
  125. package/interfaces/TemplateNode.d.ts +7 -0
  126. package/interfaces/TemplateNode.js +1 -0
  127. package/interfaces/TextMarkerConfig.d.ts +10 -0
  128. package/interfaces/TextMarkerConfig.js +1 -0
  129. package/interfaces/TextNode.d.ts +5 -0
  130. package/interfaces/TextNode.js +1 -0
  131. package/interfaces/TokenizeResult.d.ts +6 -0
  132. package/interfaces/TokenizeResult.js +1 -0
  133. package/interfaces/TransformOptions.d.ts +11 -0
  134. package/interfaces/TransformOptions.js +1 -0
  135. package/interfaces/index.d.ts +34 -0
  136. package/interfaces/index.js +1 -0
  137. package/package.json +9 -1
  138. package/types/FluffConfig.d.ts +5 -27
  139. package/ControlFlowParser.d.ts +0 -55
  140. package/ControlFlowParser.js +0 -279
  141. package/types.d.ts +0 -46
  142. /package/{types.js → interfaces/BabelPluginClassTransformState.js} +0 -0
@@ -0,0 +1,645 @@
1
+ import { parse } from '@babel/parser';
2
+ import * as t from '@babel/types';
3
+ import * as parse5 from 'parse5';
4
+ import { SAXParser } from 'parse5-sax-parser';
5
+ import { Readable } from 'stream';
6
+ import { generate } from './BabelHelpers.js';
7
+ import { ErrorHelpers } from './ErrorHelpers.js';
8
+ import { ExpressionTransformer } from './ExpressionTransformer.js';
9
+ import { Parse5Helpers } from './Parse5Helpers.js';
10
+ import { Typeguards } from './Typeguards.js';
11
+ const RESTRICTED_ELEMENTS = ['select', 'table', 'tbody', 'thead', 'tfoot', 'tr', 'td', 'th', 'colgroup'];
12
+ export class DomPreProcessor {
13
+ stack = [];
14
+ source = '';
15
+ rootFragment = null;
16
+ elementStack = [];
17
+ async process(html) {
18
+ this.stack.length = 0;
19
+ this.source = html;
20
+ this.rootFragment = parse5.parseFragment('');
21
+ this.elementStack.length = 0;
22
+ return new Promise((resolve, reject) => {
23
+ const parser = new SAXParser({ sourceCodeLocationInfo: true });
24
+ parser.on('startTag', (token) => {
25
+ try {
26
+ this.handleStartTag(token);
27
+ }
28
+ catch (e) {
29
+ reject(ErrorHelpers.toError(e));
30
+ }
31
+ });
32
+ parser.on('endTag', (token) => {
33
+ this.handleEndTag(token);
34
+ });
35
+ parser.on('text', (token) => {
36
+ this.handleText(token);
37
+ });
38
+ parser.on('comment', (token) => {
39
+ this.handleComment(token);
40
+ });
41
+ parser.on('doctype', (token) => {
42
+ this.appendDoctype(token.name ?? 'html');
43
+ });
44
+ parser.on('end', () => {
45
+ if (!this.rootFragment) {
46
+ reject(new Error('Internal error: parse5 root fragment not initialized'));
47
+ return;
48
+ }
49
+ resolve(parse5.serialize(this.rootFragment));
50
+ });
51
+ parser.on('error', (err) => {
52
+ reject(ErrorHelpers.toError(err));
53
+ });
54
+ const stream = Readable.from([html]);
55
+ stream.pipe(parser);
56
+ });
57
+ }
58
+ handleStartTag(token) {
59
+ let { tagName } = token;
60
+ if (tagName.startsWith('x-fluff')) {
61
+ throw new Error(`Security: <${tagName}> tags are reserved and cannot be used in templates`);
62
+ }
63
+ if (RESTRICTED_ELEMENTS.includes(tagName)) {
64
+ tagName = `x-fluff-el-${tagName}`;
65
+ }
66
+ const subscribeMap = new Map();
67
+ const subscribeAttr = token.attrs.find(a => a.name === 'x-fluff-subscribe');
68
+ for (const attr of token.attrs) {
69
+ if (attr.name === 'x-fluff-subscribe')
70
+ continue;
71
+ if (attr.name.startsWith('x-fluff')) {
72
+ throw new Error('Security: x-fluff-* attributes are reserved and cannot be used in templates');
73
+ }
74
+ }
75
+ if (subscribeAttr) {
76
+ for (const part of subscribeAttr.value.split(',')) {
77
+ const [bindingName, propName] = part.trim()
78
+ .split(':');
79
+ if (bindingName && propName) {
80
+ subscribeMap.set(bindingName.trim(), propName.trim());
81
+ }
82
+ }
83
+ }
84
+ const attrs = [];
85
+ for (const attr of token.attrs) {
86
+ if (attr.name === 'x-fluff-subscribe')
87
+ continue;
88
+ const originalName = this.getOriginalAttributeName(token, attr.name);
89
+ const transformed = this.transformAttribute(originalName, attr.value, subscribeMap);
90
+ if (transformed) {
91
+ attrs.push({ name: transformed.name, value: transformed.value });
92
+ }
93
+ else {
94
+ attrs.push({ name: originalName, value: attr.value });
95
+ }
96
+ }
97
+ const el = Parse5Helpers.createElement(tagName, attrs);
98
+ this.appendNode(el);
99
+ if (!token.selfClosing) {
100
+ this.elementStack.push(el);
101
+ }
102
+ }
103
+ transformAttribute(name, value, subscribeMap) {
104
+ if (name.startsWith('[(') && name.endsWith(')]')) {
105
+ const propName = name.slice(2, -2);
106
+ const bindingObj = {
107
+ name: propName, binding: 'two-way', expression: value
108
+ };
109
+ const subscribeTo = subscribeMap.get(propName);
110
+ if (subscribeTo)
111
+ bindingObj.subscribe = subscribeTo;
112
+ const json = JSON.stringify(bindingObj);
113
+ return {
114
+ name: `x-fluff-attrib-${propName.toLowerCase()}`, value: json
115
+ };
116
+ }
117
+ if (name.startsWith('[') && name.endsWith(']')) {
118
+ const propName = name.slice(1, -1);
119
+ if (propName.startsWith('class.')) {
120
+ const className = propName.slice(6);
121
+ const bindingObj = {
122
+ name: className, binding: 'class', expression: value
123
+ };
124
+ const subscribeTo = subscribeMap.get(propName);
125
+ if (subscribeTo)
126
+ bindingObj.subscribe = subscribeTo;
127
+ const json = JSON.stringify(bindingObj);
128
+ return {
129
+ name: `x-fluff-attrib-class-${className.toLowerCase()}`, value: json
130
+ };
131
+ }
132
+ if (propName.startsWith('style.')) {
133
+ const styleName = propName.slice(6);
134
+ const bindingObj = {
135
+ name: styleName, binding: 'style', expression: value
136
+ };
137
+ const subscribeTo = subscribeMap.get(propName);
138
+ if (subscribeTo)
139
+ bindingObj.subscribe = subscribeTo;
140
+ const json = JSON.stringify(bindingObj);
141
+ return {
142
+ name: `x-fluff-attrib-style-${styleName.toLowerCase()}`, value: json
143
+ };
144
+ }
145
+ const bindingObj = {
146
+ name: propName, binding: 'property', expression: value
147
+ };
148
+ const subscribeTo = subscribeMap.get(propName);
149
+ if (subscribeTo)
150
+ bindingObj.subscribe = subscribeTo;
151
+ const json = JSON.stringify(bindingObj);
152
+ return {
153
+ name: `x-fluff-attrib-${propName.toLowerCase()}`, value: json
154
+ };
155
+ }
156
+ if (name.startsWith('(') && name.endsWith(')')) {
157
+ const eventName = name.slice(1, -1);
158
+ const json = JSON.stringify({
159
+ name: eventName, binding: 'event', expression: value
160
+ });
161
+ return {
162
+ name: `x-fluff-attrib-${eventName.toLowerCase()}`, value: json
163
+ };
164
+ }
165
+ if (name.startsWith('#')) {
166
+ const refName = name.slice(1);
167
+ const json = JSON.stringify({
168
+ name: refName, binding: 'ref', expression: value || refName
169
+ });
170
+ return {
171
+ name: `x-fluff-attrib-ref-${refName.toLowerCase()}`, value: json
172
+ };
173
+ }
174
+ if (value.includes('{{') && value.includes('}}')) {
175
+ const expression = this.convertInterpolationToExpression(value);
176
+ const json = JSON.stringify({
177
+ name, binding: 'property', expression
178
+ });
179
+ return {
180
+ name: `x-fluff-attrib-${name.toLowerCase()}`, value: json
181
+ };
182
+ }
183
+ return null;
184
+ }
185
+ convertInterpolationToExpression(value) {
186
+ const parts = [];
187
+ let pos = 0;
188
+ while (pos < value.length) {
189
+ const startIdx = value.indexOf('{{', pos);
190
+ if (startIdx === -1) {
191
+ const remaining = value.slice(pos);
192
+ if (remaining.length > 0) {
193
+ parts.push(t.stringLiteral(remaining));
194
+ }
195
+ break;
196
+ }
197
+ if (startIdx > pos) {
198
+ const staticPart = value.slice(pos, startIdx);
199
+ parts.push(t.stringLiteral(staticPart));
200
+ }
201
+ const endIdx = value.indexOf('}}', startIdx);
202
+ if (endIdx === -1) {
203
+ parts.push(t.stringLiteral(value.slice(startIdx)));
204
+ break;
205
+ }
206
+ const expr = value.slice(startIdx + 2, endIdx)
207
+ .trim();
208
+ const exprAst = parse(`(${expr})`, { sourceType: 'module' });
209
+ const [exprStmt] = exprAst.program.body;
210
+ if (t.isExpressionStatement(exprStmt)) {
211
+ parts.push(exprStmt.expression);
212
+ }
213
+ pos = endIdx + 2;
214
+ }
215
+ if (parts.length === 0) {
216
+ return '\'\'';
217
+ }
218
+ if (parts.length === 1) {
219
+ const program = t.program([t.expressionStatement(parts[0])]);
220
+ return generate(program, { compact: true })
221
+ .code
222
+ .replace(/;$/, '');
223
+ }
224
+ let result = parts[0];
225
+ for (let i = 1; i < parts.length; i++) {
226
+ result = t.binaryExpression('+', result, parts[i]);
227
+ }
228
+ const program = t.program([t.expressionStatement(result)]);
229
+ return generate(program, { compact: true })
230
+ .code
231
+ .replace(/;$/, '');
232
+ }
233
+ handleEndTag(token) {
234
+ let { tagName } = token;
235
+ if (RESTRICTED_ELEMENTS.includes(tagName)) {
236
+ tagName = `x-fluff-el-${tagName}`;
237
+ }
238
+ const top = this.elementStack[this.elementStack.length - 1];
239
+ if (top?.tagName === tagName) {
240
+ this.elementStack.pop();
241
+ return;
242
+ }
243
+ for (let i = this.elementStack.length - 1; i >= 0; i--) {
244
+ if (this.elementStack[i].tagName === tagName) {
245
+ this.elementStack.splice(i);
246
+ return;
247
+ }
248
+ }
249
+ }
250
+ handleText(token) {
251
+ const { text } = token;
252
+ let pos = 0;
253
+ while (pos < text.length) {
254
+ const atIndex = text.indexOf('@', pos);
255
+ const interpolationIndex = text.indexOf('{{', pos);
256
+ const blockEndIndex = text.indexOf('}', pos);
257
+ const nextSpecial = this.findNextSpecial(atIndex, interpolationIndex, blockEndIndex);
258
+ if (nextSpecial === -1) {
259
+ this.appendText(text.slice(pos));
260
+ break;
261
+ }
262
+ if (nextSpecial > pos) {
263
+ this.appendText(text.slice(pos, nextSpecial));
264
+ }
265
+ if (nextSpecial === interpolationIndex) {
266
+ const result = this.parseInterpolation(text, nextSpecial);
267
+ if (result) {
268
+ const attrs = [
269
+ { name: 'x-fluff-expr', value: result.expression }
270
+ ];
271
+ if (result.pipes.length > 0) {
272
+ attrs.push({ name: 'x-fluff-pipes', value: JSON.stringify(result.pipes) });
273
+ }
274
+ const el = Parse5Helpers.createElement('x-fluff-text', attrs);
275
+ this.appendNode(el);
276
+ pos = result.endPos;
277
+ }
278
+ else {
279
+ this.appendText('{{');
280
+ pos = nextSpecial + 2;
281
+ }
282
+ }
283
+ else if (nextSpecial === blockEndIndex) {
284
+ if (this.stack.length > 0) {
285
+ const frame = this.stack.pop();
286
+ if (frame) {
287
+ const tagName = `x-fluff-${frame.type}`;
288
+ const top = this.elementStack[this.elementStack.length - 1];
289
+ if (top?.tagName === tagName) {
290
+ this.elementStack.pop();
291
+ }
292
+ }
293
+ pos = nextSpecial + 1;
294
+ }
295
+ else {
296
+ this.appendText('}');
297
+ pos = nextSpecial + 1;
298
+ }
299
+ }
300
+ else if (nextSpecial === atIndex) {
301
+ const result = this.parseControlFlow(text, nextSpecial);
302
+ if (result) {
303
+ const el = Parse5Helpers.createElement(result.tagName, result.attrs);
304
+ this.appendNode(el);
305
+ if (result.opensBlock) {
306
+ this.elementStack.push(el);
307
+ }
308
+ pos = result.endPos;
309
+ }
310
+ else {
311
+ this.appendText('@');
312
+ pos = nextSpecial + 1;
313
+ }
314
+ }
315
+ else {
316
+ this.appendText(text[pos]);
317
+ pos++;
318
+ }
319
+ }
320
+ }
321
+ appendNode(node) {
322
+ if (!this.rootFragment) {
323
+ throw new Error('Internal error: parse5 root fragment not initialized');
324
+ }
325
+ const parent = this.elementStack.length > 0
326
+ ? this.elementStack[this.elementStack.length - 1]
327
+ : this.rootFragment;
328
+ node.parentNode = parent;
329
+ parent.childNodes.push(node);
330
+ }
331
+ appendText(value) {
332
+ if (value.length === 0)
333
+ return;
334
+ const textNode = {
335
+ nodeName: '#text',
336
+ value,
337
+ parentNode: null
338
+ };
339
+ this.appendNode(textNode);
340
+ }
341
+ appendDoctype(name) {
342
+ if (!this.rootFragment) {
343
+ throw new Error('Internal error: parse5 root fragment not initialized');
344
+ }
345
+ const doctypeNode = {
346
+ nodeName: '#documentType',
347
+ name,
348
+ publicId: '',
349
+ systemId: '',
350
+ parentNode: null
351
+ };
352
+ doctypeNode.parentNode = this.rootFragment;
353
+ this.rootFragment.childNodes.push(doctypeNode);
354
+ }
355
+ findNextSpecial(atIndex, interpolationIndex, blockEndIndex) {
356
+ let min = -1;
357
+ if (atIndex !== -1 && (min === -1 || atIndex < min)) {
358
+ min = atIndex;
359
+ }
360
+ if (interpolationIndex !== -1 && (min === -1 || interpolationIndex < min)) {
361
+ min = interpolationIndex;
362
+ }
363
+ if (blockEndIndex !== -1 && (min === -1 || blockEndIndex < min)) {
364
+ min = blockEndIndex;
365
+ }
366
+ return min;
367
+ }
368
+ parseInterpolation(text, startPos) {
369
+ const closeIndex = text.indexOf('}}', startPos + 2);
370
+ if (closeIndex === -1) {
371
+ return null;
372
+ }
373
+ const exprText = text.slice(startPos + 2, closeIndex)
374
+ .trim();
375
+ if (exprText.length === 0) {
376
+ return null;
377
+ }
378
+ const result = ExpressionTransformer.parsePrimaryExpression(exprText);
379
+ return {
380
+ expression: result.expression, endPos: closeIndex + 2, pipes: result.pipes
381
+ };
382
+ }
383
+ parseControlFlow(text, startPos) {
384
+ const remaining = text.slice(startPos + 1);
385
+ if (remaining.startsWith('if')) {
386
+ return this.parseIfStatement(text, startPos);
387
+ }
388
+ else if (remaining.startsWith('else if')) {
389
+ return this.parseElseIfStatement(text, startPos);
390
+ }
391
+ else if (remaining.startsWith('else')) {
392
+ return this.parseElseStatement(text, startPos);
393
+ }
394
+ else if (remaining.startsWith('for')) {
395
+ return this.parseForStatement(text, startPos);
396
+ }
397
+ else if (remaining.startsWith('switch')) {
398
+ return this.parseSwitchStatement(text, startPos);
399
+ }
400
+ else if (remaining.startsWith('case')) {
401
+ return this.parseCaseStatement(text, startPos);
402
+ }
403
+ else if (remaining.startsWith('default')) {
404
+ return this.parseDefaultStatement(text, startPos);
405
+ }
406
+ else if (remaining.startsWith('empty')) {
407
+ return this.parseEmptyStatement(text, startPos);
408
+ }
409
+ else if (remaining.startsWith('fallthrough')) {
410
+ return this.parseFallthroughStatement(text, startPos);
411
+ }
412
+ else if (remaining.startsWith('break')) {
413
+ return this.parseBreakStatement(text, startPos);
414
+ }
415
+ return null;
416
+ }
417
+ parseExpressionBlockCore(text, startPos, keywordLength) {
418
+ const afterKeyword = startPos + 1 + keywordLength;
419
+ const parenStart = this.skipWhitespace(text, afterKeyword);
420
+ if (text[parenStart] !== '(') {
421
+ return null;
422
+ }
423
+ const exprStart = parenStart + 1;
424
+ const result = ExpressionTransformer.parsePrimaryExpression(text, exprStart);
425
+ if (!result) {
426
+ return null;
427
+ }
428
+ const afterExpr = this.skipWhitespace(text, result.endPos);
429
+ if (text[afterExpr] !== ')') {
430
+ return null;
431
+ }
432
+ const bracePos = this.skipWhitespace(text, afterExpr + 1);
433
+ if (text[bracePos] !== '{') {
434
+ return null;
435
+ }
436
+ return { expression: result.expression, bracePos };
437
+ }
438
+ parseSimpleBlockCore(text, startPos, keywordLength) {
439
+ const afterKeyword = startPos + 1 + keywordLength;
440
+ const bracePos = this.skipWhitespace(text, afterKeyword);
441
+ if (text[bracePos] !== '{') {
442
+ return null;
443
+ }
444
+ return bracePos;
445
+ }
446
+ parseIfStatement(text, startPos) {
447
+ const result = this.parseExpressionBlockCore(text, startPos, 2);
448
+ if (!result)
449
+ return null;
450
+ this.stack.push({ type: 'if', condition: result.expression });
451
+ return {
452
+ tagName: 'x-fluff-if',
453
+ attrs: [{ name: 'x-fluff-condition', value: result.expression }],
454
+ endPos: result.bracePos + 1,
455
+ opensBlock: true
456
+ };
457
+ }
458
+ parseElseIfStatement(text, startPos) {
459
+ const result = this.parseExpressionBlockCore(text, startPos, 7);
460
+ if (!result)
461
+ return null;
462
+ this.stack.push({ type: 'else-if', condition: result.expression });
463
+ return {
464
+ tagName: 'x-fluff-else-if',
465
+ attrs: [{ name: 'x-fluff-condition', value: result.expression }],
466
+ endPos: result.bracePos + 1,
467
+ opensBlock: true
468
+ };
469
+ }
470
+ parseElseStatement(text, startPos) {
471
+ const bracePos = this.parseSimpleBlockCore(text, startPos, 4);
472
+ if (bracePos === null)
473
+ return null;
474
+ this.stack.push({ type: 'else' });
475
+ return {
476
+ tagName: 'x-fluff-else',
477
+ attrs: [],
478
+ endPos: bracePos + 1,
479
+ opensBlock: true
480
+ };
481
+ }
482
+ parseForStatement(text, startPos) {
483
+ const afterKeyword = startPos + 1 + 3;
484
+ const parenStart = this.skipWhitespace(text, afterKeyword);
485
+ if (text[parenStart] !== '(') {
486
+ return null;
487
+ }
488
+ const result = this.parseForContent(text, parenStart + 1);
489
+ if (!result) {
490
+ return null;
491
+ }
492
+ const afterExpr = this.skipWhitespace(text, result.endPos);
493
+ if (text[afterExpr] !== ')') {
494
+ return null;
495
+ }
496
+ const bracePos = this.skipWhitespace(text, afterExpr + 1);
497
+ if (text[bracePos] !== '{') {
498
+ return null;
499
+ }
500
+ this.stack.push({ type: 'for', iterator: result.iterator, iterable: result.iterable, trackBy: result.trackBy });
501
+ const attrs = [
502
+ { name: 'x-fluff-iterator', value: result.iterator },
503
+ { name: 'x-fluff-iterable', value: result.iterable }
504
+ ];
505
+ if (result.trackBy) {
506
+ attrs.push({ name: 'x-fluff-track', value: result.trackBy });
507
+ }
508
+ return {
509
+ tagName: 'x-fluff-for',
510
+ attrs,
511
+ endPos: bracePos + 1,
512
+ opensBlock: true
513
+ };
514
+ }
515
+ parseSwitchStatement(text, startPos) {
516
+ const result = this.parseExpressionBlockCore(text, startPos, 6);
517
+ if (!result)
518
+ return null;
519
+ this.stack.push({ type: 'switch', expression: result.expression });
520
+ return {
521
+ tagName: 'x-fluff-switch',
522
+ attrs: [{ name: 'x-fluff-expr', value: result.expression }],
523
+ endPos: result.bracePos + 1,
524
+ opensBlock: true
525
+ };
526
+ }
527
+ parseCaseStatement(text, startPos) {
528
+ const result = this.parseExpressionBlockCore(text, startPos, 4);
529
+ if (!result)
530
+ return null;
531
+ this.stack.push({ type: 'case', value: result.expression });
532
+ return {
533
+ tagName: 'x-fluff-case',
534
+ attrs: [{ name: 'x-fluff-value', value: result.expression }],
535
+ endPos: result.bracePos + 1,
536
+ opensBlock: true
537
+ };
538
+ }
539
+ parseDefaultStatement(text, startPos) {
540
+ const bracePos = this.parseSimpleBlockCore(text, startPos, 7);
541
+ if (bracePos === null)
542
+ return null;
543
+ this.stack.push({ type: 'default' });
544
+ return {
545
+ tagName: 'x-fluff-default',
546
+ attrs: [],
547
+ endPos: bracePos + 1,
548
+ opensBlock: true
549
+ };
550
+ }
551
+ parseEmptyStatement(text, startPos) {
552
+ const bracePos = this.parseSimpleBlockCore(text, startPos, 5);
553
+ if (bracePos === null)
554
+ return null;
555
+ this.stack.push({ type: 'empty' });
556
+ return {
557
+ tagName: 'x-fluff-empty',
558
+ attrs: [],
559
+ endPos: bracePos + 1,
560
+ opensBlock: true
561
+ };
562
+ }
563
+ parseSimpleStatement(startPos, keywordLength, tagName) {
564
+ return {
565
+ tagName,
566
+ attrs: [],
567
+ endPos: startPos + 1 + keywordLength,
568
+ opensBlock: false
569
+ };
570
+ }
571
+ parseFallthroughStatement(text, startPos) {
572
+ return this.parseSimpleStatement(startPos, 11, 'x-fluff-fallthrough');
573
+ }
574
+ parseBreakStatement(text, startPos) {
575
+ return this.parseSimpleStatement(startPos, 5, 'x-fluff-break');
576
+ }
577
+ parseForContent(text, startPos) {
578
+ let pos = this.skipWhitespace(text, startPos);
579
+ const iteratorMatch = /^([a-zA-Z_$][a-zA-Z0-9_$]*)/.exec(text.slice(pos));
580
+ if (!iteratorMatch) {
581
+ return null;
582
+ }
583
+ const [, iterator] = iteratorMatch;
584
+ pos += iterator.length;
585
+ pos = this.skipWhitespace(text, pos);
586
+ if (!text.slice(pos)
587
+ .startsWith('of ')) {
588
+ return null;
589
+ }
590
+ pos += 3;
591
+ const iterableResult = ExpressionTransformer.parsePrimaryExpression(text, pos);
592
+ if (!iterableResult) {
593
+ return null;
594
+ }
595
+ const iterable = iterableResult.expression.trim();
596
+ pos = iterableResult.endPos;
597
+ let trackBy = undefined;
598
+ const afterIterable = this.skipWhitespace(text, pos);
599
+ if (text.slice(afterIterable)
600
+ .startsWith('; track ')) // TODO: SPACING SENSITIVE - SHITTY CODE - FIX
601
+ {
602
+ pos = afterIterable + 8;
603
+ const trackResult = ExpressionTransformer.parsePrimaryExpression(text, pos);
604
+ if (trackResult) {
605
+ trackBy = trackResult.expression.trim();
606
+ pos = trackResult.endPos;
607
+ }
608
+ }
609
+ else {
610
+ pos = afterIterable;
611
+ }
612
+ return { iterator, iterable, trackBy, endPos: pos };
613
+ }
614
+ skipWhitespace(text, pos) {
615
+ while (pos < text.length && /\s/.test(text[pos])) {
616
+ pos++;
617
+ }
618
+ return pos;
619
+ }
620
+ handleComment(token) {
621
+ const commentNode = {
622
+ nodeName: '#comment',
623
+ data: token.text,
624
+ parentNode: null
625
+ };
626
+ this.appendNode(commentNode);
627
+ }
628
+ getOriginalAttributeName(token, lowercaseName) {
629
+ const loc = token.sourceCodeLocation;
630
+ if (!loc || !('attrs' in loc) || !Typeguards.isAttrsRecord(loc.attrs)) {
631
+ return lowercaseName;
632
+ }
633
+ const attrLoc = loc.attrs[lowercaseName];
634
+ if (!attrLoc) {
635
+ return lowercaseName;
636
+ }
637
+ const attrSource = this.source.slice(attrLoc.startOffset, attrLoc.endOffset);
638
+ const eqIndex = attrSource.indexOf('=');
639
+ if (eqIndex === -1) {
640
+ return attrSource.trim();
641
+ }
642
+ return attrSource.slice(0, eqIndex)
643
+ .trim();
644
+ }
645
+ }
@@ -0,0 +1,5 @@
1
+ export declare class ErrorHelpers {
2
+ static getErrorMessage(e: unknown): string;
3
+ static toError(e: unknown): Error;
4
+ }
5
+ //# sourceMappingURL=ErrorHelpers.d.ts.map
@@ -0,0 +1,8 @@
1
+ export class ErrorHelpers {
2
+ static getErrorMessage(e) {
3
+ return e instanceof Error ? e.message : String(e);
4
+ }
5
+ static toError(e) {
6
+ return e instanceof Error ? e : new Error(String(e));
7
+ }
8
+ }