coffeehaml 0.7.0 → 0.7.2
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/README.md +21 -5
- package/dist/ast.js +334 -125
- package/dist/cli.js +363 -291
- package/dist/compiler.js +126 -89
- package/dist/emitter.js +771 -736
- package/dist/errors.js +5 -2
- package/dist/expressions.js +211 -154
- package/dist/index.js +22 -6
- package/dist/lexer.js +536 -340
- package/dist/parser.js +533 -438
- package/dist/prettier/coffeescript-formatter.js +292 -392
- package/dist/prettier/index.js +96 -60
- package/dist/prettier/options.js +179 -147
- package/dist/prettier/printer.js +549 -642
- package/dist/types.js +122 -12
- package/dist/vite-plugin.js +78 -63
- package/package.json +4 -9
- package/dist/ast.d.ts +0 -123
- package/dist/ast.d.ts.map +0 -1
- package/dist/ast.js.map +0 -1
- package/dist/cli.d.ts +0 -3
- package/dist/cli.d.ts.map +0 -1
- package/dist/cli.js.map +0 -1
- package/dist/compiler.d.ts +0 -10
- package/dist/compiler.d.ts.map +0 -1
- package/dist/compiler.js.map +0 -1
- package/dist/emitter.d.ts +0 -4
- package/dist/emitter.d.ts.map +0 -1
- package/dist/emitter.js.map +0 -1
- package/dist/errors.d.ts +0 -3
- package/dist/errors.d.ts.map +0 -1
- package/dist/errors.js.map +0 -1
- package/dist/expressions.d.ts +0 -22
- package/dist/expressions.d.ts.map +0 -1
- package/dist/expressions.js.map +0 -1
- package/dist/index.d.ts +0 -7
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/lexer.d.ts +0 -30
- package/dist/lexer.d.ts.map +0 -1
- package/dist/lexer.js.map +0 -1
- package/dist/parser.d.ts +0 -10
- package/dist/parser.d.ts.map +0 -1
- package/dist/parser.js.map +0 -1
- package/dist/prettier/coffeescript-formatter.d.ts +0 -31
- package/dist/prettier/coffeescript-formatter.d.ts.map +0 -1
- package/dist/prettier/coffeescript-formatter.js.map +0 -1
- package/dist/prettier/index.d.ts +0 -8
- package/dist/prettier/index.d.ts.map +0 -1
- package/dist/prettier/index.js.map +0 -1
- package/dist/prettier/options.d.ts +0 -10
- package/dist/prettier/options.d.ts.map +0 -1
- package/dist/prettier/options.js.map +0 -1
- package/dist/prettier/printer.d.ts +0 -45
- package/dist/prettier/printer.d.ts.map +0 -1
- package/dist/prettier/printer.js.map +0 -1
- package/dist/types.d.ts +0 -76
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js.map +0 -1
- package/dist/vite-plugin.d.ts +0 -7
- package/dist/vite-plugin.d.ts.map +0 -1
- package/dist/vite-plugin.js.map +0 -1
package/dist/emitter.js
CHANGED
|
@@ -1,756 +1,791 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
1
|
+
// Generated by CoffeeScript 2.7.0
|
|
2
|
+
// emitter.coffee — AST → JSX/JS code generation
|
|
3
|
+
var EmitState, VOID_ELEMENTS, attrNameToJs, buildAttributes, compileComponentBody, emitArrowOutput, emitArrowOutputToJs, emitChildToJs, emitComment, emitConditional, emitControlFlow, emitControlFlowToJs, emitDoctype, emitElement, emitElseBranch, emitFilter, emitFilterToJs, emitImplicitDiv, emitLoop, emitNode, emitNodes, emitOutput, emitStatement, emitText, escapeString, mapSourceLocation, splitJsStatements, tagToJs;
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
Document,
|
|
7
|
+
Element,
|
|
8
|
+
ImplicitDiv,
|
|
9
|
+
Text,
|
|
10
|
+
Output,
|
|
11
|
+
ControlFlow,
|
|
12
|
+
Comment,
|
|
13
|
+
Filter,
|
|
14
|
+
Doctype,
|
|
15
|
+
Node,
|
|
16
|
+
Expression
|
|
17
|
+
} from './ast.js';
|
|
18
|
+
|
|
19
|
+
import {
|
|
20
|
+
compileExpression,
|
|
21
|
+
compileStatement
|
|
22
|
+
} from './expressions.js';
|
|
23
|
+
|
|
24
|
+
import {
|
|
25
|
+
CompileWarning
|
|
26
|
+
} from './types.js';
|
|
27
|
+
|
|
28
|
+
import {
|
|
29
|
+
SourceMapGenerator
|
|
30
|
+
} from 'source-map';
|
|
31
|
+
|
|
32
|
+
VOID_ELEMENTS = new Set(['area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'link', 'meta', 'param', 'source', 'track', 'wbr']);
|
|
33
|
+
|
|
34
|
+
EmitState = (function() {
|
|
35
|
+
// ─── Emit State ────────────────────────────────────────────
|
|
36
|
+
class EmitState {
|
|
37
|
+
constructor(options1 = {}) {
|
|
38
|
+
this.options = options1;
|
|
39
|
+
if (this.options.sourceMap && this.options.filename) {
|
|
40
|
+
this.sourceMapGenerator = new SourceMapGenerator({
|
|
41
|
+
file: this.options.filename
|
|
42
|
+
});
|
|
43
|
+
}
|
|
25
44
|
}
|
|
45
|
+
|
|
26
46
|
emit(s) {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
47
|
+
if (s) {
|
|
48
|
+
return this.output += ' '.repeat(this.indent * 2) + s;
|
|
49
|
+
}
|
|
30
50
|
}
|
|
51
|
+
|
|
31
52
|
emitLine(s = '') {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
53
|
+
if (s) {
|
|
54
|
+
this.output += ' '.repeat(this.indent * 2) + s;
|
|
55
|
+
}
|
|
56
|
+
this.output += '\n';
|
|
57
|
+
return this.outputLine++;
|
|
37
58
|
}
|
|
59
|
+
|
|
38
60
|
mapSource(loc) {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
61
|
+
var outputCol;
|
|
62
|
+
if (!(this.sourceMapGenerator && loc)) {
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
outputCol = this.indent * 2;
|
|
66
|
+
return this.sourceMapGenerator.addMapping({
|
|
67
|
+
generated: {
|
|
68
|
+
line: this.outputLine + 1,
|
|
69
|
+
column: outputCol
|
|
70
|
+
},
|
|
71
|
+
original: {
|
|
72
|
+
line: loc.line + 1,
|
|
73
|
+
column: loc.column
|
|
74
|
+
},
|
|
75
|
+
source: this.options.filename || 'source.coffeehaml'
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
48
79
|
indentBlock(fn) {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
}
|
|
53
|
-
|
|
80
|
+
this.indent++;
|
|
81
|
+
fn();
|
|
82
|
+
return this.indent--;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
EmitState.prototype.indent = 0;
|
|
88
|
+
|
|
89
|
+
EmitState.prototype.output = '';
|
|
90
|
+
|
|
91
|
+
EmitState.prototype.needsFragment = false;
|
|
92
|
+
|
|
93
|
+
EmitState.prototype.needsImport = false;
|
|
94
|
+
|
|
95
|
+
EmitState.prototype.options = null;
|
|
96
|
+
|
|
97
|
+
EmitState.prototype.sourceMapGenerator = null;
|
|
98
|
+
|
|
99
|
+
EmitState.prototype.outputLine = 0;
|
|
100
|
+
|
|
101
|
+
EmitState.prototype.warnings = [];
|
|
102
|
+
|
|
103
|
+
EmitState.prototype.hoisted = [];
|
|
104
|
+
|
|
105
|
+
EmitState.prototype.directEmit = false;
|
|
106
|
+
|
|
107
|
+
return EmitState;
|
|
108
|
+
|
|
109
|
+
}).call(this);
|
|
110
|
+
|
|
54
111
|
// ─── Public API ────────────────────────────────────────────
|
|
55
|
-
export
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
}
|
|
86
|
-
|
|
112
|
+
export var emit = function(ast, options = {}) {
|
|
113
|
+
var bodyExpr, hocs, inner, j, len, line, name, ref, result, state, wrap, wrapped;
|
|
114
|
+
state = new EmitState(options);
|
|
115
|
+
state.emitLine(`import { jsx, jsxs, Fragment } from \"${options.jsxRuntime || 'react/jsx-runtime'}\";`);
|
|
116
|
+
// Emit prologue lines
|
|
117
|
+
if (ast.prologue && ast.prologue.length > 0) {
|
|
118
|
+
ref = ast.prologue;
|
|
119
|
+
for (j = 0, len = ref.length; j < len; j++) {
|
|
120
|
+
line = ref[j];
|
|
121
|
+
state.emitLine(line);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
state.emitLine();
|
|
125
|
+
wrap = options.wrap;
|
|
126
|
+
if (wrap && wrap !== 'none') {
|
|
127
|
+
name = options.componentName || 'Component';
|
|
128
|
+
hocs = (function() {
|
|
129
|
+
switch (wrap) {
|
|
130
|
+
case 'component':
|
|
131
|
+
return [];
|
|
132
|
+
case 'observer':
|
|
133
|
+
return ['observer'];
|
|
134
|
+
default:
|
|
135
|
+
return wrap;
|
|
136
|
+
}
|
|
137
|
+
})();
|
|
138
|
+
bodyExpr = compileComponentBody(ast, state);
|
|
139
|
+
inner = `function ${name}(props) { return ${bodyExpr}; }`;
|
|
140
|
+
wrapped = hocs.reduceRight((function(acc, hoc) {
|
|
141
|
+
return `${hoc}(${acc})`;
|
|
142
|
+
}), inner);
|
|
143
|
+
state.emitLine(`export default ${wrapped}`);
|
|
144
|
+
} else {
|
|
145
|
+
state.directEmit = true;
|
|
146
|
+
emitNodes(ast.children, state, true);
|
|
147
|
+
}
|
|
148
|
+
result = {
|
|
149
|
+
code: state.output,
|
|
150
|
+
warnings: state.warnings
|
|
151
|
+
};
|
|
152
|
+
if (state.sourceMapGenerator) {
|
|
153
|
+
result.sourceMap = state.sourceMapGenerator.toString();
|
|
154
|
+
}
|
|
155
|
+
return result;
|
|
156
|
+
};
|
|
157
|
+
|
|
87
158
|
// ─── Component Body Compilation ─────────────────────────────
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
const body = tmpState.output.trim();
|
|
98
|
-
// Hoisted statements (from - blocks) go before the return
|
|
99
|
-
const hoistedJs = tmpState.hoisted.length > 0
|
|
100
|
-
? tmpState.hoisted.join('; ') + '; '
|
|
101
|
-
: '';
|
|
102
|
-
if (!body) {
|
|
103
|
-
if (hoistedJs)
|
|
104
|
-
return `(() => { ${hoistedJs}return null; })()`;
|
|
105
|
-
return 'null';
|
|
106
|
-
}
|
|
107
|
-
// Split into JS statements respecting brackets and strings
|
|
108
|
-
const stmts = splitJsStatements(body);
|
|
109
|
-
const jsx = stmts.length === 0 ? 'null'
|
|
110
|
-
: stmts.length === 1 ? stmts[0]
|
|
111
|
-
: `jsxs(Fragment, { children: [${stmts.join(', ')}] })`;
|
|
159
|
+
compileComponentBody = function(ast, state) {
|
|
160
|
+
var body, hoistedJs, jsx, stmts, tmpState;
|
|
161
|
+
tmpState = new EmitState(state.options);
|
|
162
|
+
tmpState.indent = state.indent;
|
|
163
|
+
emitNodes(ast.children, tmpState, true);
|
|
164
|
+
state.warnings.push(...tmpState.warnings);
|
|
165
|
+
body = tmpState.output.trim();
|
|
166
|
+
hoistedJs = tmpState.hoisted.length > 0 ? tmpState.hoisted.join('; ') + '; ' : '';
|
|
167
|
+
if (!body) {
|
|
112
168
|
if (hoistedJs) {
|
|
113
|
-
|
|
169
|
+
return `(()-> { ${hoistedJs}return null; })()`;
|
|
114
170
|
}
|
|
115
|
-
return
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
171
|
+
return 'null';
|
|
172
|
+
}
|
|
173
|
+
stmts = splitJsStatements(body);
|
|
174
|
+
jsx = stmts.length === 0 ? 'null' : stmts.length === 1 ? stmts[0] : `jsxs(Fragment, { children: [${stmts.join(', ')}] })`;
|
|
175
|
+
if (hoistedJs) {
|
|
176
|
+
return `(()-> { ${hoistedJs}return ${jsx}; })()`;
|
|
177
|
+
}
|
|
178
|
+
return jsx;
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
splitJsStatements = function(body) {
|
|
182
|
+
var ch, depth, i, inString, last, start, stmt, stmts;
|
|
183
|
+
stmts = [];
|
|
184
|
+
depth = 0;
|
|
185
|
+
inString = null;
|
|
186
|
+
start = 0;
|
|
187
|
+
i = 0;
|
|
188
|
+
while (i < body.length) {
|
|
189
|
+
ch = body[i];
|
|
190
|
+
if (inString) {
|
|
191
|
+
if (ch === '\\') {
|
|
192
|
+
i++;
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
195
|
+
if (ch === inString) {
|
|
196
|
+
inString = null;
|
|
197
|
+
}
|
|
198
|
+
i++;
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
201
|
+
if (ch === "'" || ch === '"' || ch === '`') {
|
|
202
|
+
inString = ch;
|
|
203
|
+
i++;
|
|
204
|
+
continue;
|
|
205
|
+
}
|
|
206
|
+
if (ch === '{' || ch === '[' || ch === '(') {
|
|
207
|
+
depth++;
|
|
208
|
+
i++;
|
|
209
|
+
continue;
|
|
210
|
+
}
|
|
211
|
+
if (ch === '}' || ch === ']' || ch === ')') {
|
|
212
|
+
depth--;
|
|
213
|
+
i++;
|
|
214
|
+
continue;
|
|
215
|
+
}
|
|
216
|
+
if (ch === ';' && depth === 0 && body[i + 1] === '\n') {
|
|
217
|
+
stmt = body.slice(start, i).trim();
|
|
218
|
+
if (stmt) {
|
|
219
|
+
stmts.push(stmt);
|
|
220
|
+
}
|
|
221
|
+
start = i + 2;
|
|
222
|
+
i++;
|
|
223
|
+
}
|
|
224
|
+
i++;
|
|
225
|
+
}
|
|
226
|
+
last = body.slice(start).trim();
|
|
227
|
+
if (last) {
|
|
228
|
+
stmts.push(last.endsWith(';') ? last.slice(0, -1).trim() : last);
|
|
229
|
+
}
|
|
230
|
+
return stmts;
|
|
231
|
+
};
|
|
232
|
+
|
|
163
233
|
// ─── Node Emitters ─────────────────────────────────────────
|
|
164
|
-
function
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
}
|
|
234
|
+
emitNodes = function(nodes, state, isRoot = false) {
|
|
235
|
+
var i, node, remaining;
|
|
236
|
+
i = 0;
|
|
237
|
+
while (i < nodes.length) {
|
|
238
|
+
node = nodes[i];
|
|
239
|
+
if (node instanceof ControlFlow) {
|
|
240
|
+
remaining = nodes.slice(i + 1);
|
|
241
|
+
emitControlFlow(node, remaining, state, isRoot);
|
|
242
|
+
return;
|
|
243
|
+
} else {
|
|
244
|
+
emitNode(node, state, isRoot);
|
|
245
|
+
}
|
|
246
|
+
i++;
|
|
247
|
+
}
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
emitNode = function(node, state, isRoot) {
|
|
251
|
+
if (node instanceof Element) {
|
|
252
|
+
return emitElement(node, state);
|
|
253
|
+
} else if (node instanceof ImplicitDiv) {
|
|
254
|
+
return emitImplicitDiv(node, state);
|
|
255
|
+
} else if (node instanceof Text) {
|
|
256
|
+
return emitText(node, state);
|
|
257
|
+
} else if (node instanceof Output) {
|
|
258
|
+
return emitOutput(node, state);
|
|
259
|
+
} else if (node instanceof ControlFlow) {
|
|
260
|
+
return emitControlFlow(node, [], state, isRoot);
|
|
261
|
+
} else if (node instanceof Comment) {
|
|
262
|
+
return emitComment(node, state);
|
|
263
|
+
} else if (node instanceof Filter) {
|
|
264
|
+
return emitFilter(node, state);
|
|
265
|
+
} else if (node instanceof Doctype) {
|
|
266
|
+
return emitDoctype(node, state);
|
|
267
|
+
}
|
|
268
|
+
};
|
|
269
|
+
|
|
194
270
|
// ─── Element ───────────────────────────────────────────────
|
|
195
|
-
function
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
271
|
+
emitElement = function(el, state) {
|
|
272
|
+
var allAttrs, attrResult, attrs, attrsJs, childJs, childParts, childrenAttr, hasChildren, isVoid, keyArg, tagJs;
|
|
273
|
+
mapSourceLocation(el.location, state);
|
|
274
|
+
isVoid = typeof el.tag === 'string' && VOID_ELEMENTS.has(el.tag);
|
|
275
|
+
hasChildren = !isVoid && el.children.length > 0;
|
|
276
|
+
tagJs = tagToJs(el);
|
|
277
|
+
attrResult = buildAttributes(el);
|
|
278
|
+
attrs = attrResult.parts;
|
|
279
|
+
keyArg = attrResult.keyExpr ? `, ${attrResult.keyExpr}` : '';
|
|
280
|
+
if (hasChildren) {
|
|
281
|
+
if (el.children.length === 1) {
|
|
282
|
+
childJs = emitChildToJs(el.children[0], state);
|
|
283
|
+
childrenAttr = `children: ${childJs}`;
|
|
284
|
+
allAttrs = attrs.length > 0 ? `{ ${attrs.join(', ')}, ${childrenAttr} }` : `{ ${childrenAttr} }`;
|
|
285
|
+
return state.emitLine(`jsx(${tagJs}, ${allAttrs}${keyArg});`);
|
|
286
|
+
} else {
|
|
287
|
+
childParts = el.children.map(function(c) {
|
|
288
|
+
return emitChildToJs(c, state);
|
|
289
|
+
});
|
|
290
|
+
childrenAttr = `children: [${childParts.join(', ')}]`;
|
|
291
|
+
allAttrs = attrs.length > 0 ? `{ ${attrs.join(', ')}, ${childrenAttr} }` : `{ ${childrenAttr} }`;
|
|
292
|
+
return state.emitLine(`jsxs(${tagJs}, ${allAttrs}${keyArg});`);
|
|
293
|
+
}
|
|
294
|
+
} else {
|
|
295
|
+
attrsJs = attrs.length > 0 ? `{ ${attrs.join(', ')} }` : 'null';
|
|
296
|
+
return state.emitLine(`jsx(${tagJs}, ${attrsJs}${keyArg});`);
|
|
297
|
+
}
|
|
298
|
+
};
|
|
299
|
+
|
|
300
|
+
tagToJs = function(el) {
|
|
301
|
+
if (el.tag instanceof Expression) {
|
|
302
|
+
return compileExpression(el.tag, el.location);
|
|
303
|
+
}
|
|
304
|
+
if (el.isComponent) {
|
|
305
|
+
return el.tag;
|
|
306
|
+
}
|
|
307
|
+
return `\"${el.tag}\"`;
|
|
308
|
+
};
|
|
309
|
+
|
|
310
|
+
buildAttributes = function(el) {
|
|
311
|
+
var attr, classNames, filteredParts, j, joined, k, keyExpr, len, len1, name, p, parts, ref, unique, valJs;
|
|
312
|
+
parts = [];
|
|
313
|
+
if (el.id) {
|
|
314
|
+
parts.push(`id: \"${el.id}\"`);
|
|
315
|
+
}
|
|
316
|
+
classNames = el.classes.slice();
|
|
317
|
+
ref = el.attributes;
|
|
318
|
+
for (j = 0, len = ref.length; j < len; j++) {
|
|
319
|
+
attr = ref[j];
|
|
320
|
+
if (attr.spread) {
|
|
321
|
+
valJs = compileExpression(attr.expression);
|
|
322
|
+
parts.push(`...${valJs}`);
|
|
323
|
+
continue;
|
|
324
|
+
}
|
|
325
|
+
valJs = compileExpression(attr.value);
|
|
326
|
+
name = attrNameToJs(attr.name);
|
|
327
|
+
if (name === 'className' || name === 'class') {
|
|
328
|
+
if (attr.shorthand) {
|
|
329
|
+
classNames.push(`$\{${valJs}}`);
|
|
330
|
+
} else {
|
|
331
|
+
classNames.push(valJs.replace(/^['"]|['"]$/g, ''));
|
|
332
|
+
}
|
|
333
|
+
} else {
|
|
334
|
+
parts.push(`${name}: ${valJs}`);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
if (classNames.length > 0) {
|
|
338
|
+
unique = [...new Set(classNames)];
|
|
339
|
+
if (unique.length === 1 && !unique[0].includes('${')) {
|
|
340
|
+
parts.push(`className: \"${unique[0]}\"`);
|
|
341
|
+
} else {
|
|
342
|
+
joined = unique.map(function(c) {
|
|
343
|
+
if (c.includes('${')) {
|
|
344
|
+
return c;
|
|
345
|
+
} else {
|
|
346
|
+
return `\"${c}\"`;
|
|
347
|
+
}
|
|
348
|
+
}).join(' + " " + ');
|
|
349
|
+
parts.push(`className: ${joined}`);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
// Extract key
|
|
353
|
+
keyExpr = null;
|
|
354
|
+
filteredParts = [];
|
|
355
|
+
for (k = 0, len1 = parts.length; k < len1; k++) {
|
|
356
|
+
p = parts[k];
|
|
357
|
+
if (p.startsWith('key: ')) {
|
|
358
|
+
keyExpr = p.slice(5).trim();
|
|
359
|
+
} else {
|
|
360
|
+
filteredParts.push(p);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
return {
|
|
364
|
+
parts: filteredParts,
|
|
365
|
+
keyExpr: keyExpr
|
|
366
|
+
};
|
|
367
|
+
};
|
|
368
|
+
|
|
369
|
+
attrNameToJs = function(name) {
|
|
370
|
+
if (name === 'class') {
|
|
371
|
+
return 'className';
|
|
372
|
+
}
|
|
373
|
+
if (name === 'for') {
|
|
374
|
+
return 'htmlFor';
|
|
375
|
+
}
|
|
376
|
+
if (name.includes('-')) {
|
|
377
|
+
return `\"${name}\"`;
|
|
378
|
+
}
|
|
379
|
+
return name;
|
|
380
|
+
};
|
|
381
|
+
|
|
382
|
+
emitImplicitDiv = function(div, state) {
|
|
383
|
+
var el;
|
|
384
|
+
mapSourceLocation(div.location, state);
|
|
385
|
+
el = new Element('div', {
|
|
386
|
+
classes: div.classes,
|
|
387
|
+
id: div.id,
|
|
388
|
+
attributes: div.attributes,
|
|
389
|
+
children: div.children,
|
|
390
|
+
isComponent: false,
|
|
391
|
+
isSelfClosing: false,
|
|
392
|
+
location: div.location
|
|
393
|
+
});
|
|
394
|
+
return emitElement(el, state);
|
|
395
|
+
};
|
|
396
|
+
|
|
397
|
+
// ─── Child Emission ────────────────────────────────────────
|
|
398
|
+
emitChildToJs = function(node, state) {
|
|
399
|
+
var allAttrs, attrResult, attrs, attrsJs, c, childParts, childrenAttr, el, exprSource, fn, hasChildren, keyArg, tagJs, textContinuations;
|
|
400
|
+
if (node instanceof Text) {
|
|
401
|
+
return escapeString(node.value);
|
|
402
|
+
}
|
|
403
|
+
if (node instanceof Output) {
|
|
404
|
+
// Arrow continuation
|
|
405
|
+
if (node.children.length > 0 && /->\s*$/.test(node.expression.source.trim())) {
|
|
406
|
+
return emitArrowOutputToJs(node.expression.source, node.children, state);
|
|
407
|
+
}
|
|
408
|
+
textContinuations = (function() {
|
|
409
|
+
var j, len, ref, results;
|
|
410
|
+
ref = node.children;
|
|
411
|
+
results = [];
|
|
412
|
+
for (j = 0, len = ref.length; j < len; j++) {
|
|
413
|
+
c = ref[j];
|
|
414
|
+
if (c instanceof Text) {
|
|
415
|
+
results.push(c.value);
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
return results;
|
|
419
|
+
})();
|
|
420
|
+
exprSource = textContinuations.length > 0 ? [node.expression.source, ...textContinuations].join(' ') : node.expression.source;
|
|
421
|
+
return compileExpression(new Expression(exprSource), node.location);
|
|
422
|
+
}
|
|
423
|
+
if (node instanceof Element) {
|
|
424
|
+
hasChildren = node.children.length > 0;
|
|
425
|
+
tagJs = tagToJs(node);
|
|
426
|
+
attrResult = buildAttributes(node);
|
|
427
|
+
attrs = attrResult.parts;
|
|
428
|
+
keyArg = attrResult.keyExpr ? `, ${attrResult.keyExpr}` : '';
|
|
206
429
|
if (hasChildren) {
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
}
|
|
228
|
-
if (el.isComponent) {
|
|
229
|
-
return el.tag;
|
|
230
|
-
}
|
|
231
|
-
return `"${el.tag}"`;
|
|
232
|
-
}
|
|
233
|
-
function buildAttributes(el) {
|
|
234
|
-
const parts = [];
|
|
235
|
-
// id
|
|
236
|
-
if (el.id) {
|
|
237
|
-
parts.push(`id: "${el.id}"`);
|
|
238
|
-
}
|
|
239
|
-
// class from .class modifiers
|
|
240
|
-
let classNames = [...el.classes];
|
|
241
|
-
// Named attributes
|
|
242
|
-
for (const attr of el.attributes) {
|
|
243
|
-
// Spread attribute: {...props}
|
|
244
|
-
if (attr.spread) {
|
|
245
|
-
const valJs = compileExpression(attr.expression);
|
|
246
|
-
parts.push(`...${valJs}`);
|
|
247
|
-
continue;
|
|
248
|
-
}
|
|
249
|
-
const valJs = compileExpression(attr.value);
|
|
250
|
-
const name = attrNameToJs(attr.name);
|
|
251
|
-
if (name === 'className' || name === 'class') {
|
|
252
|
-
// Collect class names from className/class attribute
|
|
253
|
-
if (attr.shorthand) {
|
|
254
|
-
classNames.push(`\${${valJs}}`);
|
|
255
|
-
}
|
|
256
|
-
else {
|
|
257
|
-
classNames.push(valJs.replace(/^['"]|['"]$/g, ''));
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
else if (attr.shorthand) {
|
|
261
|
-
parts.push(`${name}: ${valJs}`);
|
|
262
|
-
}
|
|
263
|
-
else {
|
|
264
|
-
parts.push(`${name}: ${valJs}`);
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
// Emit className
|
|
268
|
-
if (classNames.length > 0) {
|
|
269
|
-
const unique = [...new Set(classNames)];
|
|
270
|
-
if (unique.length === 1 && !unique[0].includes('${')) {
|
|
271
|
-
parts.push(`className: "${unique[0]}"`);
|
|
272
|
-
}
|
|
273
|
-
else {
|
|
274
|
-
const joined = unique.map(c => c.includes('${') ? c : `"${c}"`).join(' + " " + ');
|
|
275
|
-
parts.push(`className: ${joined}`);
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
// style as object
|
|
279
|
-
const styleAttr = el.attributes.find(a => !a.spread && attrNameToJs(a.name) === 'style');
|
|
280
|
-
if (styleAttr && !styleAttr.spread && !styleAttr.shorthand) {
|
|
281
|
-
// Already handled above — but ensure it's an object expression
|
|
282
|
-
}
|
|
283
|
-
// Extract key from parts for React 3rd-arg convention
|
|
284
|
-
let keyExpr = null;
|
|
285
|
-
const filteredParts = [];
|
|
286
|
-
for (const p of parts) {
|
|
287
|
-
if (p.startsWith('key: ')) {
|
|
288
|
-
keyExpr = p.slice(5).trim();
|
|
289
|
-
}
|
|
290
|
-
else {
|
|
291
|
-
filteredParts.push(p);
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
return { parts: filteredParts, keyExpr };
|
|
295
|
-
}
|
|
296
|
-
function attrNameToJs(name) {
|
|
297
|
-
// class → className for React
|
|
298
|
-
if (name === 'class')
|
|
299
|
-
return 'className';
|
|
300
|
-
// for → htmlFor for React
|
|
301
|
-
if (name === 'for')
|
|
302
|
-
return 'htmlFor';
|
|
303
|
-
// data-* attributes keep their name
|
|
304
|
-
if (name.includes('-')) {
|
|
305
|
-
// Convert to camelCase quoted: 'data-value' → "data-value"
|
|
306
|
-
return `"${name}"`;
|
|
307
|
-
}
|
|
308
|
-
return name;
|
|
309
|
-
}
|
|
310
|
-
function emitImplicitDiv(div, state) {
|
|
311
|
-
mapSourceLocation(div.location, state);
|
|
312
|
-
const el = new Element('div', {
|
|
313
|
-
classes: div.classes,
|
|
314
|
-
id: div.id,
|
|
315
|
-
attributes: div.attributes,
|
|
316
|
-
children: div.children,
|
|
317
|
-
isComponent: false,
|
|
318
|
-
isSelfClosing: false,
|
|
319
|
-
location: div.location,
|
|
430
|
+
childParts = node.children.map(function(c) {
|
|
431
|
+
return emitChildToJs(c, state);
|
|
432
|
+
});
|
|
433
|
+
childrenAttr = childParts.length === 1 ? `children: ${childParts[0]}` : `children: [${childParts.join(', ')}]`;
|
|
434
|
+
fn = childParts.length === 1 ? 'jsx' : 'jsxs';
|
|
435
|
+
allAttrs = attrs.length > 0 ? `{ ${attrs.join(', ')}, ${childrenAttr} }` : `{ ${childrenAttr} }`;
|
|
436
|
+
return `${fn}(${tagJs}, ${allAttrs}${keyArg})`;
|
|
437
|
+
}
|
|
438
|
+
attrsJs = attrs.length > 0 ? `{ ${attrs.join(', ')} }` : 'null';
|
|
439
|
+
return `jsx(${tagJs}, ${attrsJs}${keyArg})`;
|
|
440
|
+
}
|
|
441
|
+
if (node instanceof ImplicitDiv) {
|
|
442
|
+
el = new Element('div', {
|
|
443
|
+
classes: node.classes,
|
|
444
|
+
id: node.id,
|
|
445
|
+
attributes: node.attributes,
|
|
446
|
+
children: node.children,
|
|
447
|
+
isComponent: false,
|
|
448
|
+
isSelfClosing: false,
|
|
449
|
+
location: node.location
|
|
320
450
|
});
|
|
321
|
-
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
}
|
|
333
|
-
// TEXT continuation: join text children into expression
|
|
334
|
-
const textContinuations = node.children
|
|
335
|
-
.filter(c => c instanceof Text)
|
|
336
|
-
.map(c => c.value);
|
|
337
|
-
const exprSource = textContinuations.length > 0
|
|
338
|
-
? [node.expression.source, ...textContinuations].join(' ')
|
|
339
|
-
: node.expression.source;
|
|
340
|
-
return compileExpression(new Expression(exprSource), node.location);
|
|
341
|
-
}
|
|
342
|
-
if (node instanceof Element) {
|
|
343
|
-
const hasChildren = node.children.length > 0;
|
|
344
|
-
const tagJs = tagToJs(node);
|
|
345
|
-
const attrResult = buildAttributes(node);
|
|
346
|
-
const attrs = attrResult.parts;
|
|
347
|
-
const keyArg = attrResult.keyExpr ? `, ${attrResult.keyExpr}` : '';
|
|
348
|
-
if (hasChildren) {
|
|
349
|
-
const childParts = node.children.map(c => emitChildToJs(c, state));
|
|
350
|
-
const childrenAttr = childParts.length === 1 ? `children: ${childParts[0]}` : `children: [${childParts.join(', ')}]`;
|
|
351
|
-
const fn = childParts.length === 1 ? 'jsx' : 'jsxs';
|
|
352
|
-
const allAttrs = attrs.length > 0 ? `{ ${attrs.join(', ')}, ${childrenAttr} }` : `{ ${childrenAttr} }`;
|
|
353
|
-
return `${fn}(${tagJs}, ${allAttrs}${keyArg})`;
|
|
354
|
-
}
|
|
355
|
-
const attrsJs = attrs.length > 0 ? `{ ${attrs.join(', ')} }` : 'null';
|
|
356
|
-
return `jsx(${tagJs}, ${attrsJs}${keyArg})`;
|
|
357
|
-
}
|
|
358
|
-
if (node instanceof ImplicitDiv) {
|
|
359
|
-
const el = new Element('div', {
|
|
360
|
-
classes: node.classes,
|
|
361
|
-
id: node.id,
|
|
362
|
-
attributes: node.attributes,
|
|
363
|
-
children: node.children,
|
|
364
|
-
isComponent: false,
|
|
365
|
-
isSelfClosing: false,
|
|
366
|
-
location: node.location,
|
|
367
|
-
});
|
|
368
|
-
return emitChildToJs(el, state);
|
|
369
|
-
}
|
|
370
|
-
if (node instanceof ControlFlow) {
|
|
371
|
-
return emitControlFlowToJs(node, state);
|
|
372
|
-
}
|
|
373
|
-
if (node instanceof Filter) {
|
|
374
|
-
return emitFilterToJs(node, state);
|
|
375
|
-
}
|
|
376
|
-
return 'null';
|
|
377
|
-
}
|
|
451
|
+
return emitChildToJs(el, state);
|
|
452
|
+
}
|
|
453
|
+
if (node instanceof ControlFlow) {
|
|
454
|
+
return emitControlFlowToJs(node, state);
|
|
455
|
+
}
|
|
456
|
+
if (node instanceof Filter) {
|
|
457
|
+
return emitFilterToJs(node, state);
|
|
458
|
+
}
|
|
459
|
+
return 'null';
|
|
460
|
+
};
|
|
461
|
+
|
|
378
462
|
// ─── Control Flow ──────────────────────────────────────────
|
|
379
|
-
function
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
if (
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
const iterable = forMatch[2].trim();
|
|
426
|
-
state.emit(`(${compileExpression(new Expression(iterable), cf.location)}).map((${vars}) => `);
|
|
427
|
-
// Wrap children in Fragment if multiple
|
|
428
|
-
if (cf.children.length === 0) {
|
|
429
|
-
state.output += 'null';
|
|
430
|
-
}
|
|
431
|
-
else if (cf.children.length === 1) {
|
|
432
|
-
const childJs = emitChildToJs(cf.children[0], state);
|
|
433
|
-
state.output += childJs;
|
|
434
|
-
}
|
|
435
|
-
else {
|
|
436
|
-
state.output += 'jsxs(Fragment, { children: [';
|
|
437
|
-
if (cf.children.length > 0) {
|
|
438
|
-
state.output += cf.children.map(c => emitChildToJs(c, state)).join(', ');
|
|
439
|
-
}
|
|
440
|
-
state.output += '] })';
|
|
441
|
-
}
|
|
442
|
-
state.output += ')';
|
|
443
|
-
// Handle remaining nodes (they go after the map)
|
|
444
|
-
if (remaining.length > 0) {
|
|
445
|
-
state.output += ',\n';
|
|
446
|
-
emitNodes(remaining, state);
|
|
447
|
-
}
|
|
448
|
-
else {
|
|
449
|
-
state.output += ';\n';
|
|
450
|
-
}
|
|
451
|
-
return;
|
|
452
|
-
}
|
|
453
|
-
}
|
|
454
|
-
if (cf.controlKind === 'while') {
|
|
455
|
-
const cond = compileExpression(cf.expression, cf.location);
|
|
456
|
-
state.emitLine(`(() => {`);
|
|
457
|
-
state.indentBlock(() => {
|
|
458
|
-
state.emitLine(`const __result = [];`);
|
|
459
|
-
state.emitLine(`while (${cond}) {`);
|
|
460
|
-
state.indentBlock(() => {
|
|
461
|
-
if (cf.children.length === 1 && cf.children[0] instanceof Element) {
|
|
462
|
-
state.emit(`__result.push(${emitChildToJs(cf.children[0], state)});\n`);
|
|
463
|
-
}
|
|
464
|
-
else {
|
|
465
|
-
state.emit(`__result.push(jsxs(Fragment, { children: [${cf.children.map(c => emitChildToJs(c, state)).join(', ')}] }));\n`);
|
|
466
|
-
}
|
|
467
|
-
});
|
|
468
|
-
state.emitLine(`}`);
|
|
469
|
-
state.emitLine(`return __result;`);
|
|
470
|
-
});
|
|
471
|
-
state.emit(`})()`);
|
|
472
|
-
if (remaining.length > 0) {
|
|
473
|
-
state.output += ',\n';
|
|
474
|
-
emitNodes(remaining, state);
|
|
475
|
-
}
|
|
476
|
-
else {
|
|
477
|
-
state.output += ';\n';
|
|
478
|
-
}
|
|
479
|
-
return;
|
|
480
|
-
}
|
|
481
|
-
// Fallback
|
|
482
|
-
state.emitLine(`/* TODO: emit loop ${cf.controlKind} */`);
|
|
483
|
-
}
|
|
484
|
-
function emitConditional(cf, remaining, state) {
|
|
485
|
-
// else is always reached via cf.next chain — the " : " was emitted by parent
|
|
486
|
-
if (cf.controlKind === 'else') {
|
|
487
|
-
emitElseBranch(cf, state);
|
|
488
|
-
return;
|
|
489
|
-
}
|
|
490
|
-
// Build ternary chain: cond ? jsx(...) : (elseCond ? jsx(...) : null)
|
|
491
|
-
const condition = compileExpression(cf.expression, cf.location);
|
|
492
|
-
if (cf.controlKind === 'unless') {
|
|
493
|
-
// unless cond → !(cond)
|
|
494
|
-
state.emit(`!(${condition}) ? `);
|
|
495
|
-
}
|
|
496
|
-
else {
|
|
497
|
-
state.emit(`${condition} ? `);
|
|
498
|
-
}
|
|
499
|
-
// Then branch
|
|
500
|
-
if (cf.children.length === 0) {
|
|
501
|
-
state.output += 'null';
|
|
502
|
-
}
|
|
503
|
-
else if (cf.children.length === 1) {
|
|
504
|
-
state.output += emitChildToJs(cf.children[0], state);
|
|
505
|
-
}
|
|
506
|
-
else {
|
|
507
|
-
state.output += `jsxs(Fragment, { children: [${cf.children.map(c => emitChildToJs(c, state)).join(', ')}] })`;
|
|
508
|
-
}
|
|
509
|
-
// Else / remaining
|
|
510
|
-
state.output += ' : ';
|
|
511
|
-
if (cf.next) {
|
|
512
|
-
emitConditional(cf.next, [], state);
|
|
513
|
-
}
|
|
514
|
-
else if (remaining.length > 0) {
|
|
515
|
-
if (remaining.length === 1) {
|
|
516
|
-
state.output += emitChildToJs(remaining[0], state);
|
|
517
|
-
}
|
|
518
|
-
else {
|
|
519
|
-
state.output += `jsxs(Fragment, { children: [${remaining.map(c => emitChildToJs(c, state)).join(', ')}] })`;
|
|
520
|
-
}
|
|
521
|
-
}
|
|
522
|
-
else {
|
|
523
|
-
state.output += 'null';
|
|
524
|
-
}
|
|
525
|
-
state.output += ';\n';
|
|
526
|
-
}
|
|
527
|
-
/** Emit the body of an else branch — no condition prefix. */
|
|
528
|
-
function emitElseBranch(cf, state) {
|
|
529
|
-
if (cf.children.length === 0) {
|
|
463
|
+
emitControlFlow = function(cf, remaining, state, _isRoot) {
|
|
464
|
+
if (cf.controlKind === 'statement') {
|
|
465
|
+
return emitStatement(cf, remaining, state);
|
|
466
|
+
} else if (cf.isLoop()) {
|
|
467
|
+
return emitLoop(cf, remaining, state);
|
|
468
|
+
} else {
|
|
469
|
+
return emitConditional(cf, remaining, state);
|
|
470
|
+
}
|
|
471
|
+
};
|
|
472
|
+
|
|
473
|
+
emitStatement = function(cf, remaining, state) {
|
|
474
|
+
var child, j, js, k, len, len1, ref, s, stmts;
|
|
475
|
+
js = compileStatement(cf.expression.source.trim(), cf.location);
|
|
476
|
+
stmts = [js];
|
|
477
|
+
ref = cf.children;
|
|
478
|
+
for (j = 0, len = ref.length; j < len; j++) {
|
|
479
|
+
child = ref[j];
|
|
480
|
+
if (child instanceof Text) {
|
|
481
|
+
stmts.push(compileStatement(child.value, child.location));
|
|
482
|
+
} else {
|
|
483
|
+
emitNode(child, state, false);
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
if (state.directEmit) {
|
|
487
|
+
for (k = 0, len1 = stmts.length; k < len1; k++) {
|
|
488
|
+
s = stmts[k];
|
|
489
|
+
state.emitLine(s + ';');
|
|
490
|
+
}
|
|
491
|
+
} else {
|
|
492
|
+
state.hoisted.push(...stmts);
|
|
493
|
+
}
|
|
494
|
+
if (remaining.length > 0) {
|
|
495
|
+
return emitNodes(remaining, state);
|
|
496
|
+
}
|
|
497
|
+
};
|
|
498
|
+
|
|
499
|
+
emitLoop = function(cf, remaining, state) {
|
|
500
|
+
var cond, expr, forMatch, iterable, vars;
|
|
501
|
+
if (cf.controlKind === 'for') {
|
|
502
|
+
expr = cf.expression.source.trim();
|
|
503
|
+
forMatch = expr.match(/^(.+?)\s+in\s+(.+)$/);
|
|
504
|
+
if (forMatch) {
|
|
505
|
+
vars = forMatch[1].trim();
|
|
506
|
+
iterable = forMatch[2].trim();
|
|
507
|
+
state.emit(`(${compileExpression(new Expression(iterable), cf.location)}).map((${vars}) => `);
|
|
508
|
+
if (cf.children.length === 0) {
|
|
530
509
|
state.output += 'null';
|
|
531
|
-
|
|
532
|
-
else if (cf.children.length === 1) {
|
|
510
|
+
} else if (cf.children.length === 1) {
|
|
533
511
|
state.output += emitChildToJs(cf.children[0], state);
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
}
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
512
|
+
} else {
|
|
513
|
+
state.output += `jsxs(Fragment, { children: [${cf.children.map(function(c) {
|
|
514
|
+
return emitChildToJs(c, state);
|
|
515
|
+
}).join(', ')}] })`;
|
|
516
|
+
}
|
|
517
|
+
state.output += ')';
|
|
518
|
+
if (remaining.length > 0) {
|
|
519
|
+
state.output += ',\n';
|
|
520
|
+
emitNodes(remaining, state);
|
|
521
|
+
} else {
|
|
522
|
+
state.output += ';\n';
|
|
523
|
+
}
|
|
524
|
+
return;
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
if (cf.controlKind === 'while') {
|
|
528
|
+
cond = compileExpression(cf.expression, cf.location);
|
|
529
|
+
state.emitLine('(() => {');
|
|
530
|
+
state.indentBlock(function() {
|
|
531
|
+
state.emitLine('const __result = [];');
|
|
532
|
+
state.emitLine(`while (${cond}) {`);
|
|
533
|
+
state.indentBlock(function() {
|
|
534
|
+
if (cf.children.length === 1 && cf.children[0] instanceof Element) {
|
|
535
|
+
return state.emit(`__result.push(${emitChildToJs(cf.children[0], state)});\n`);
|
|
536
|
+
} else {
|
|
537
|
+
return state.emit(`__result.push(jsxs(Fragment, { children: [${cf.children.map(function(c) {
|
|
538
|
+
return emitChildToJs(c, state);
|
|
539
|
+
}).join(', ')}] }));\n`);
|
|
540
|
+
}
|
|
541
|
+
});
|
|
542
|
+
state.emitLine('}');
|
|
543
|
+
return state.emitLine('return __result;');
|
|
544
|
+
});
|
|
545
|
+
state.emit('})()');
|
|
546
|
+
if (remaining.length > 0) {
|
|
547
|
+
state.output += ',\n';
|
|
548
|
+
emitNodes(remaining, state);
|
|
549
|
+
} else {
|
|
550
|
+
state.output += ';\n';
|
|
551
|
+
}
|
|
552
|
+
return;
|
|
553
|
+
}
|
|
554
|
+
return state.emitLine(`/* TODO: emit loop ${cf.controlKind} */`);
|
|
555
|
+
};
|
|
556
|
+
|
|
557
|
+
emitConditional = function(cf, remaining, state) {
|
|
558
|
+
var condition;
|
|
559
|
+
if (cf.controlKind === 'else') {
|
|
560
|
+
emitElseBranch(cf, state);
|
|
561
|
+
return;
|
|
562
|
+
}
|
|
563
|
+
condition = compileExpression(cf.expression, cf.location);
|
|
564
|
+
if (cf.controlKind === 'unless') {
|
|
565
|
+
state.emit(`!(${condition}) ? `);
|
|
566
|
+
} else {
|
|
567
|
+
state.emit(`${condition} ? `);
|
|
568
|
+
}
|
|
569
|
+
// Then branch
|
|
570
|
+
if (cf.children.length === 0) {
|
|
571
|
+
state.output += 'null';
|
|
572
|
+
} else if (cf.children.length === 1) {
|
|
573
|
+
state.output += emitChildToJs(cf.children[0], state);
|
|
574
|
+
} else {
|
|
575
|
+
state.output += `jsxs(Fragment, { children: [${cf.children.map(function(c) {
|
|
576
|
+
return emitChildToJs(c, state);
|
|
577
|
+
}).join(', ')}] })`;
|
|
578
|
+
}
|
|
579
|
+
state.output += ' : ';
|
|
580
|
+
if (cf.next) {
|
|
581
|
+
emitConditional(cf.next, [], state);
|
|
582
|
+
} else if (remaining.length > 0) {
|
|
583
|
+
if (remaining.length === 1) {
|
|
584
|
+
state.output += emitChildToJs(remaining[0], state);
|
|
585
|
+
} else {
|
|
586
|
+
state.output += `jsxs(Fragment, { children: [${remaining.map(function(c) {
|
|
587
|
+
return emitChildToJs(c, state);
|
|
588
|
+
}).join(', ')}] })`;
|
|
589
|
+
}
|
|
590
|
+
} else {
|
|
591
|
+
state.output += 'null';
|
|
592
|
+
}
|
|
593
|
+
return state.output += ';\n';
|
|
594
|
+
};
|
|
595
|
+
|
|
596
|
+
emitElseBranch = function(cf, state) {
|
|
597
|
+
if (cf.children.length === 0) {
|
|
598
|
+
return state.output += 'null';
|
|
599
|
+
} else if (cf.children.length === 1) {
|
|
600
|
+
return state.output += emitChildToJs(cf.children[0], state);
|
|
601
|
+
} else {
|
|
602
|
+
return state.output += `jsxs(Fragment, {}, ${cf.children.map(function(c) {
|
|
603
|
+
return emitChildToJs(c, state);
|
|
604
|
+
}).join(', ')})`;
|
|
605
|
+
}
|
|
606
|
+
};
|
|
607
|
+
|
|
608
|
+
emitControlFlowToJs = function(cf, state) {
|
|
609
|
+
var alternate, body, compiledIterable, condExpr, condition, consequent, expr, forMatch, iterable, js, vars;
|
|
610
|
+
if (cf.isLoop() && cf.controlKind === 'for') {
|
|
611
|
+
expr = cf.expression.source.trim();
|
|
612
|
+
forMatch = expr.match(/^(.+?)\s+in\s+(.+)$/);
|
|
613
|
+
if (forMatch) {
|
|
614
|
+
vars = forMatch[1].trim();
|
|
615
|
+
iterable = forMatch[2].trim();
|
|
616
|
+
compiledIterable = compileExpression(new Expression(iterable), cf.location);
|
|
617
|
+
body = cf.children.length === 1 ? emitChildToJs(cf.children[0], state) : `jsxs(Fragment, { children: [${cf.children.map(function(c) {
|
|
618
|
+
return emitChildToJs(c, state);
|
|
619
|
+
}).join(', ')}] })`;
|
|
620
|
+
return `(${compiledIterable}).map((${vars}) => ${body})`;
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
if (cf.controlKind === 'else') {
|
|
624
|
+
if (cf.children.length === 1) {
|
|
625
|
+
return emitChildToJs(cf.children[0], state);
|
|
626
|
+
} else if (cf.children.length > 0) {
|
|
627
|
+
return `jsxs(Fragment, {}, ${cf.children.map(function(c) {
|
|
628
|
+
return emitChildToJs(c, state);
|
|
629
|
+
}).join(', ')})`;
|
|
630
|
+
} else {
|
|
631
|
+
return 'null';
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
if (cf.isConditional()) {
|
|
635
|
+
condition = compileExpression(cf.expression, cf.location);
|
|
636
|
+
condExpr = cf.controlKind === 'unless' ? `!(${condition})` : condition;
|
|
637
|
+
consequent = cf.children.length === 1 ? emitChildToJs(cf.children[0], state) : cf.children.length > 0 ? `jsxs(Fragment, {}, ${cf.children.map(function(c) {
|
|
638
|
+
return emitChildToJs(c, state);
|
|
639
|
+
}).join(', ')})` : 'null';
|
|
640
|
+
alternate = cf.next ? emitControlFlowToJs(cf.next, state) : 'null';
|
|
641
|
+
return `${condExpr} ? ${consequent} : ${alternate}`;
|
|
642
|
+
}
|
|
643
|
+
if (cf.controlKind === 'statement') {
|
|
644
|
+
js = compileExpression(cf.expression, cf.location);
|
|
645
|
+
return `/* - ${js} */ null`;
|
|
646
|
+
}
|
|
647
|
+
return 'null';
|
|
648
|
+
};
|
|
649
|
+
|
|
580
650
|
// ─── Text / Output / Comment / Filter / Doctype ────────────
|
|
581
|
-
function
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
function
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
}
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
state.emitLine(
|
|
658
|
-
}
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
state.warnings.push({
|
|
713
|
-
message: ':javascript filter emits raw JS at module level — an XSS risk with untrusted input. Ensure content is trusted.',
|
|
714
|
-
location: filter.location,
|
|
715
|
-
phase: 'emitter',
|
|
716
|
-
});
|
|
717
|
-
state.emitLine(content);
|
|
718
|
-
break;
|
|
719
|
-
case 'coffee':
|
|
720
|
-
state.emitLine(content);
|
|
721
|
-
break;
|
|
722
|
-
default:
|
|
723
|
-
state.warnings.push({
|
|
724
|
-
message: `:${filterName} filter used but no handler registered. Add one to compilerOptions.filters.`,
|
|
725
|
-
location: filter.location,
|
|
726
|
-
phase: 'emitter',
|
|
727
|
-
});
|
|
728
|
-
state.emitLine(`"${escapeString(content)}";`);
|
|
729
|
-
}
|
|
730
|
-
}
|
|
731
|
-
/** Emit a filter as a JS expression (for use as a child within parent elements). */
|
|
732
|
-
function emitFilterToJs(filter, state) {
|
|
733
|
-
const { filterName, content } = filter;
|
|
734
|
-
const handler = state.options.filters?.[filterName];
|
|
735
|
-
if (handler) {
|
|
736
|
-
const html = handler(content, filterName);
|
|
737
|
-
return `jsx(Fragment, { dangerouslySetInnerHTML: { __html: ${JSON.stringify(html)} } })`;
|
|
738
|
-
}
|
|
739
|
-
// No handler — emit raw text as a string child
|
|
740
|
-
return escapeString(content);
|
|
741
|
-
}
|
|
742
|
-
function emitDoctype(doctype, state) {
|
|
743
|
-
// Doctype is typically handled by the HTML renderer, but for React
|
|
744
|
-
// we emit it as a comment or skip it
|
|
745
|
-
state.emitLine(`// DOCTYPE ${doctype.value || 'html'}`);
|
|
746
|
-
}
|
|
651
|
+
emitText = function(text, state) {
|
|
652
|
+
return state.emitLine(`\"${escapeString(text.value)}\";`);
|
|
653
|
+
};
|
|
654
|
+
|
|
655
|
+
emitOutput = function(out, state) {
|
|
656
|
+
var child, elementChildren, exprSource, j, js, k, len, len1, ref, result, textLines;
|
|
657
|
+
textLines = [];
|
|
658
|
+
elementChildren = [];
|
|
659
|
+
ref = out.children;
|
|
660
|
+
for (j = 0, len = ref.length; j < len; j++) {
|
|
661
|
+
child = ref[j];
|
|
662
|
+
if (child instanceof Text) {
|
|
663
|
+
textLines.push(child.value);
|
|
664
|
+
} else {
|
|
665
|
+
elementChildren.push(child);
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
exprSource = out.expression.source;
|
|
669
|
+
if (textLines.length > 0) {
|
|
670
|
+
exprSource = [exprSource, ...textLines].join(' ');
|
|
671
|
+
}
|
|
672
|
+
// Arrow continuation
|
|
673
|
+
if (elementChildren.length > 0 && /->\s*$/.test(exprSource.trim())) {
|
|
674
|
+
emitArrowOutput(exprSource, elementChildren, out, state);
|
|
675
|
+
return;
|
|
676
|
+
}
|
|
677
|
+
js = compileExpression(new Expression(exprSource), out.location);
|
|
678
|
+
result = out.outputKind === 'escaped' ? `jsx(Fragment, { children: ${js} })` : `jsx(Fragment, {}, ${js})`;
|
|
679
|
+
for (k = 0, len1 = elementChildren.length; k < len1; k++) {
|
|
680
|
+
child = elementChildren[k];
|
|
681
|
+
emitNode(child, state, false);
|
|
682
|
+
}
|
|
683
|
+
return state.emitLine(result + ';');
|
|
684
|
+
};
|
|
685
|
+
|
|
686
|
+
emitArrowOutput = function(exprSource, elementChildren, out, state) {
|
|
687
|
+
var arrowArgs, arrowFn, arrowMatch, body, callTarget, compiledTarget, result, stripped;
|
|
688
|
+
stripped = exprSource.replace(/\s*->\s*$/, '').trim();
|
|
689
|
+
arrowMatch = stripped.match(/^(.+?)\s*\(([^)]*)\)\s*$/);
|
|
690
|
+
if (arrowMatch) {
|
|
691
|
+
callTarget = arrowMatch[1].trim();
|
|
692
|
+
arrowArgs = arrowMatch[2].trim();
|
|
693
|
+
} else {
|
|
694
|
+
callTarget = stripped;
|
|
695
|
+
arrowArgs = '';
|
|
696
|
+
}
|
|
697
|
+
compiledTarget = compileExpression(new Expression(callTarget), out.location);
|
|
698
|
+
body = elementChildren.length === 1 ? emitChildToJs(elementChildren[0], state) : `jsxs(Fragment, { children: [${elementChildren.map(function(c) {
|
|
699
|
+
return emitChildToJs(c, state);
|
|
700
|
+
}).join(', ')}] })`;
|
|
701
|
+
arrowFn = arrowArgs ? `(${arrowArgs}) => ${body}` : `() => ${body}`;
|
|
702
|
+
result = out.outputKind === 'escaped' ? `jsx(Fragment, { children: ${compiledTarget}(${arrowFn}) })` : `jsx(Fragment, {}, ${compiledTarget}(${arrowFn}))`;
|
|
703
|
+
return state.emitLine(result + ';');
|
|
704
|
+
};
|
|
705
|
+
|
|
706
|
+
emitArrowOutputToJs = function(exprSource, elementChildren, state) {
|
|
707
|
+
var arrowArgs, arrowFn, arrowMatch, body, callTarget, compiledTarget, stripped;
|
|
708
|
+
stripped = exprSource.replace(/\s*->\s*$/, '').trim();
|
|
709
|
+
arrowMatch = stripped.match(/^(.+?)\s*\(([^)]*)\)\s*$/);
|
|
710
|
+
if (arrowMatch) {
|
|
711
|
+
callTarget = arrowMatch[1].trim();
|
|
712
|
+
arrowArgs = arrowMatch[2].trim();
|
|
713
|
+
} else {
|
|
714
|
+
callTarget = stripped;
|
|
715
|
+
arrowArgs = '';
|
|
716
|
+
}
|
|
717
|
+
compiledTarget = compileExpression(new Expression(callTarget));
|
|
718
|
+
body = elementChildren.length === 1 ? emitChildToJs(elementChildren[0], state) : `jsxs(Fragment, { children: [${elementChildren.map(function(c) {
|
|
719
|
+
return emitChildToJs(c, state);
|
|
720
|
+
}).join(', ')}] })`;
|
|
721
|
+
arrowFn = arrowArgs ? `(${arrowArgs}) => ${body}` : `() => ${body}`;
|
|
722
|
+
return `${compiledTarget}(${arrowFn})`;
|
|
723
|
+
};
|
|
724
|
+
|
|
725
|
+
emitComment = function(comment, state) {
|
|
726
|
+
if (comment.commentKind === 'html') {
|
|
727
|
+
return state.emitLine(`/* <!-- ${comment.text} --> */`);
|
|
728
|
+
}
|
|
729
|
+
};
|
|
730
|
+
|
|
731
|
+
emitFilter = function(filter, state) {
|
|
732
|
+
var content, filterName, handler, html, ref;
|
|
733
|
+
({filterName, content} = filter);
|
|
734
|
+
handler = (ref = state.options.filters) != null ? ref[filterName] : void 0;
|
|
735
|
+
if (handler) {
|
|
736
|
+
html = handler(content, filterName);
|
|
737
|
+
state.emitLine(`jsx(Fragment, { dangerouslySetInnerHTML: { __html: ${JSON.stringify(html)} } })`);
|
|
738
|
+
return;
|
|
739
|
+
}
|
|
740
|
+
switch (filterName) {
|
|
741
|
+
case 'css':
|
|
742
|
+
state.warnings.push({
|
|
743
|
+
message: ':css filter emits raw CSS string — not usable as a <style> tag in React. Use a plugin handler to inject CSS properly.',
|
|
744
|
+
location: filter.location,
|
|
745
|
+
phase: 'emitter'
|
|
746
|
+
});
|
|
747
|
+
return state.emitLine(`\"${escapeString(content)}\";`);
|
|
748
|
+
case 'javascript':
|
|
749
|
+
state.warnings.push({
|
|
750
|
+
message: ':javascript filter emits raw JS at module level — an XSS risk with untrusted input. Ensure content is trusted.',
|
|
751
|
+
location: filter.location,
|
|
752
|
+
phase: 'emitter'
|
|
753
|
+
});
|
|
754
|
+
return state.emitLine(content);
|
|
755
|
+
case 'coffee':
|
|
756
|
+
return state.emitLine(content);
|
|
757
|
+
default:
|
|
758
|
+
state.warnings.push({
|
|
759
|
+
message: `:${filterName} filter used but no handler registered. Add one to compilerOptions.filters.`,
|
|
760
|
+
location: filter.location,
|
|
761
|
+
phase: 'emitter'
|
|
762
|
+
});
|
|
763
|
+
return state.emitLine(`\"${escapeString(content)}\";`);
|
|
764
|
+
}
|
|
765
|
+
};
|
|
766
|
+
|
|
767
|
+
emitFilterToJs = function(filter, state) {
|
|
768
|
+
var content, filterName, handler, html, ref;
|
|
769
|
+
({filterName, content} = filter);
|
|
770
|
+
handler = (ref = state.options.filters) != null ? ref[filterName] : void 0;
|
|
771
|
+
if (handler) {
|
|
772
|
+
html = handler(content, filterName);
|
|
773
|
+
return `jsx(Fragment, { dangerouslySetInnerHTML: { __html: ${JSON.stringify(html)} } })`;
|
|
774
|
+
}
|
|
775
|
+
return escapeString(content);
|
|
776
|
+
};
|
|
777
|
+
|
|
778
|
+
emitDoctype = function(doctype, state) {
|
|
779
|
+
return state.emitLine(`// DOCTYPE ${doctype.value || 'html'}`);
|
|
780
|
+
};
|
|
781
|
+
|
|
747
782
|
// ─── Helpers ───────────────────────────────────────────────
|
|
748
|
-
function
|
|
749
|
-
|
|
750
|
-
}
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
}
|
|
756
|
-
|
|
783
|
+
escapeString = function(s) {
|
|
784
|
+
return JSON.stringify(s);
|
|
785
|
+
};
|
|
786
|
+
|
|
787
|
+
mapSourceLocation = function(loc, state) {
|
|
788
|
+
if (loc) {
|
|
789
|
+
return state.mapSource(loc.start);
|
|
790
|
+
}
|
|
791
|
+
};
|