@react-pug/react-pug-core 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 (35) hide show
  1. package/README.md +60 -0
  2. package/dist/index.d.ts +8 -0
  3. package/dist/index.d.ts.map +1 -0
  4. package/dist/index.js +24 -0
  5. package/dist/index.js.map +1 -0
  6. package/dist/language/diagnosticMapping.d.ts +25 -0
  7. package/dist/language/diagnosticMapping.d.ts.map +1 -0
  8. package/dist/language/diagnosticMapping.js +89 -0
  9. package/dist/language/diagnosticMapping.js.map +1 -0
  10. package/dist/language/extractRegions.d.ts +39 -0
  11. package/dist/language/extractRegions.d.ts.map +1 -0
  12. package/dist/language/extractRegions.js +419 -0
  13. package/dist/language/extractRegions.js.map +1 -0
  14. package/dist/language/mapping.d.ts +181 -0
  15. package/dist/language/mapping.d.ts.map +1 -0
  16. package/dist/language/mapping.js +32 -0
  17. package/dist/language/mapping.js.map +1 -0
  18. package/dist/language/positionMapping.d.ts +6 -0
  19. package/dist/language/positionMapping.d.ts.map +1 -0
  20. package/dist/language/positionMapping.js +138 -0
  21. package/dist/language/positionMapping.js.map +1 -0
  22. package/dist/language/pugToTsx.d.ts +39 -0
  23. package/dist/language/pugToTsx.d.ts.map +1 -0
  24. package/dist/language/pugToTsx.js +1484 -0
  25. package/dist/language/pugToTsx.js.map +1 -0
  26. package/dist/language/shadowDocument.d.ts +15 -0
  27. package/dist/language/shadowDocument.d.ts.map +1 -0
  28. package/dist/language/shadowDocument.js +480 -0
  29. package/dist/language/shadowDocument.js.map +1 -0
  30. package/dist/language/sourceTransform.d.ts +88 -0
  31. package/dist/language/sourceTransform.d.ts.map +1 -0
  32. package/dist/language/sourceTransform.js +80 -0
  33. package/dist/language/sourceTransform.js.map +1 -0
  34. package/package.json +22 -0
  35. package/tsconfig.json +12 -0
@@ -0,0 +1,1484 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TsxEmitter = void 0;
4
+ exports.compilePugToTsx = compilePugToTsx;
5
+ const mapping_1 = require("./mapping");
6
+ const extractRegions_1 = require("./extractRegions");
7
+ // ── TsxEmitter ──────────────────────────────────────────────────
8
+ class TsxEmitter {
9
+ constructor() {
10
+ this.tsx = '';
11
+ this.mappings = [];
12
+ this.offset = 0;
13
+ }
14
+ /** Emit text that maps 1:1 to pug source */
15
+ emitMapped(text, pugOffset, info) {
16
+ this.mappings.push({
17
+ sourceOffsets: [pugOffset],
18
+ generatedOffsets: [this.offset],
19
+ lengths: [text.length],
20
+ data: info,
21
+ });
22
+ this.tsx += text;
23
+ this.offset += text.length;
24
+ }
25
+ /** Emit text that maps with different source/generated lengths */
26
+ emitDerived(text, pugOffset, pugLength, info) {
27
+ this.mappings.push({
28
+ sourceOffsets: [pugOffset],
29
+ generatedOffsets: [this.offset],
30
+ lengths: [pugLength],
31
+ generatedLengths: [text.length],
32
+ data: info,
33
+ });
34
+ this.tsx += text;
35
+ this.offset += text.length;
36
+ }
37
+ /** Emit structural TSX with no pug source (unmapped) */
38
+ emitSynthetic(text) {
39
+ this.tsx += text;
40
+ this.offset += text.length;
41
+ }
42
+ getResult() {
43
+ return { tsx: this.tsx, mappings: this.mappings };
44
+ }
45
+ }
46
+ exports.TsxEmitter = TsxEmitter;
47
+ // ── Helpers ─────────────────────────────────────────────────────
48
+ /** Create a parse-recovery variant of pug text for typing-in-progress scenarios. */
49
+ function buildTypingRecoveryText(text) {
50
+ const lines = text.split('\n');
51
+ let changed = false;
52
+ const recovered = lines.map((line) => {
53
+ let next = line;
54
+ // Keep unbuffered code lines parseable while user is still typing.
55
+ if (/^\s*-\s*$/.test(next)) {
56
+ next += ' undefined';
57
+ changed = true;
58
+ }
59
+ // Keep `tag=` lines parseable while value is still empty.
60
+ if (/^\s*[A-Za-z][\w:-]*(?:[.#][A-Za-z_][\w-]*)*\s*=\s*$/.test(next)) {
61
+ next += ' undefined';
62
+ changed = true;
63
+ }
64
+ let inSingle = false;
65
+ let inDouble = false;
66
+ let escaped = false;
67
+ let openParen = 0;
68
+ let closeParen = 0;
69
+ let openInterp = 0;
70
+ let closeBrace = 0;
71
+ for (let i = 0; i < next.length; i++) {
72
+ const ch = next[i];
73
+ if (escaped) {
74
+ escaped = false;
75
+ continue;
76
+ }
77
+ if (ch === '\\') {
78
+ escaped = true;
79
+ continue;
80
+ }
81
+ if (!inDouble && ch === '\'') {
82
+ inSingle = !inSingle;
83
+ continue;
84
+ }
85
+ if (!inSingle && ch === '"') {
86
+ inDouble = !inDouble;
87
+ continue;
88
+ }
89
+ if (inSingle || inDouble)
90
+ continue;
91
+ if (ch === '(')
92
+ openParen++;
93
+ else if (ch === ')')
94
+ closeParen++;
95
+ if ((ch === '#' || ch === '!') && next[i + 1] === '{') {
96
+ openInterp++;
97
+ }
98
+ else if (ch === '}') {
99
+ closeBrace++;
100
+ }
101
+ }
102
+ const missingParens = Math.max(0, openParen - closeParen);
103
+ if (missingParens > 0) {
104
+ next += ')'.repeat(missingParens);
105
+ changed = true;
106
+ }
107
+ const missingInterpBraces = Math.max(0, openInterp - closeBrace);
108
+ if (missingInterpBraces > 0) {
109
+ next += '}'.repeat(missingInterpBraces);
110
+ changed = true;
111
+ }
112
+ return next;
113
+ });
114
+ return changed ? recovered.join('\n') : text;
115
+ }
116
+ /** Convert pug line/column (1-based) to offset in the pug text */
117
+ function lineColToOffset(text, line, column) {
118
+ const lines = text.split('\n');
119
+ let offset = 0;
120
+ for (let i = 0; i < line - 1 && i < lines.length; i++) {
121
+ offset += lines[i].length + 1; // +1 for \n
122
+ }
123
+ return offset + (column - 1);
124
+ }
125
+ /** Find a whole-word occurrence in `lineText` starting at `fromIndex` (0-based). */
126
+ function findWordIndex(lineText, word, fromIndex) {
127
+ if (!word)
128
+ return -1;
129
+ let searchFrom = Math.max(0, fromIndex);
130
+ while (searchFrom < lineText.length) {
131
+ const idx = lineText.indexOf(word, searchFrom);
132
+ if (idx < 0)
133
+ return -1;
134
+ const before = idx > 0 ? lineText[idx - 1] : '';
135
+ const after = idx + word.length < lineText.length ? lineText[idx + word.length] : '';
136
+ const isWordChar = (ch) => /[A-Za-z0-9_$]/.test(ch);
137
+ if (!isWordChar(before) && !isWordChar(after))
138
+ return idx;
139
+ searchFrom = idx + 1;
140
+ }
141
+ return -1;
142
+ }
143
+ /** Resolve an expression offset by searching for the expression value on its source line. */
144
+ function findValueOffsetOnLine(pugText, line, column, value, fallbackOffset) {
145
+ if (!value)
146
+ return fallbackOffset;
147
+ const lineText = pugText.split('\n')[line - 1] ?? '';
148
+ const lineStart = lineColToOffset(pugText, line, 1);
149
+ const fromIndex = Math.max(0, column - 1);
150
+ const idx = lineText.indexOf(value, fromIndex);
151
+ if (idx >= 0)
152
+ return lineStart + idx;
153
+ return fallbackOffset;
154
+ }
155
+ const interpolationContextStack = [];
156
+ const compileContextStack = [];
157
+ function currentInterpolationContext() {
158
+ return interpolationContextStack.length > 0
159
+ ? interpolationContextStack[interpolationContextStack.length - 1]
160
+ : null;
161
+ }
162
+ function currentCompileMode() {
163
+ return compileContextStack.length > 0
164
+ ? compileContextStack[compileContextStack.length - 1].mode
165
+ : 'languageService';
166
+ }
167
+ function currentClassAttribute() {
168
+ return compileContextStack.length > 0
169
+ ? compileContextStack[compileContextStack.length - 1].classAttribute
170
+ : 'className';
171
+ }
172
+ function currentClassMerge() {
173
+ return compileContextStack.length > 0
174
+ ? compileContextStack[compileContextStack.length - 1].classMerge
175
+ : 'concatenate';
176
+ }
177
+ function currentComponentPathFromUppercaseClassShorthand() {
178
+ return compileContextStack.length > 0
179
+ ? compileContextStack[compileContextStack.length - 1].componentPathFromUppercaseClassShorthand
180
+ : true;
181
+ }
182
+ function createInterpolationMarker(index, length) {
183
+ const seed = `_${index.toString(36)}_`;
184
+ if (seed.length === length)
185
+ return seed;
186
+ if (seed.length < length)
187
+ return seed + '_'.repeat(length - seed.length);
188
+ const middleLength = Math.max(1, length - 2);
189
+ const middle = index.toString(36).slice(0, middleLength).padEnd(middleLength, '_');
190
+ return (`_${middle}_`).slice(0, length);
191
+ }
192
+ function findInterpolationEnd(text, exprStart) {
193
+ let i = exprStart;
194
+ let depth = 1;
195
+ let inSingle = false;
196
+ let inDouble = false;
197
+ let inTemplate = false;
198
+ let inLineComment = false;
199
+ let inBlockComment = false;
200
+ let escaped = false;
201
+ while (i < text.length) {
202
+ const ch = text[i];
203
+ const next = text[i + 1] ?? '';
204
+ if (inLineComment) {
205
+ if (ch === '\n')
206
+ inLineComment = false;
207
+ i += 1;
208
+ continue;
209
+ }
210
+ if (inBlockComment) {
211
+ if (ch === '*' && next === '/') {
212
+ inBlockComment = false;
213
+ i += 2;
214
+ continue;
215
+ }
216
+ i += 1;
217
+ continue;
218
+ }
219
+ if (inSingle) {
220
+ if (escaped) {
221
+ escaped = false;
222
+ }
223
+ else if (ch === '\\') {
224
+ escaped = true;
225
+ }
226
+ else if (ch === '\'') {
227
+ inSingle = false;
228
+ }
229
+ i += 1;
230
+ continue;
231
+ }
232
+ if (inDouble) {
233
+ if (escaped) {
234
+ escaped = false;
235
+ }
236
+ else if (ch === '\\') {
237
+ escaped = true;
238
+ }
239
+ else if (ch === '"') {
240
+ inDouble = false;
241
+ }
242
+ i += 1;
243
+ continue;
244
+ }
245
+ if (inTemplate) {
246
+ if (escaped) {
247
+ escaped = false;
248
+ }
249
+ else if (ch === '\\') {
250
+ escaped = true;
251
+ }
252
+ else if (ch === '`') {
253
+ inTemplate = false;
254
+ }
255
+ else if (ch === '$' && next === '{') {
256
+ depth += 1;
257
+ i += 2;
258
+ continue;
259
+ }
260
+ i += 1;
261
+ continue;
262
+ }
263
+ if (ch === '/' && next === '/') {
264
+ inLineComment = true;
265
+ i += 2;
266
+ continue;
267
+ }
268
+ if (ch === '/' && next === '*') {
269
+ inBlockComment = true;
270
+ i += 2;
271
+ continue;
272
+ }
273
+ if (ch === '\'') {
274
+ inSingle = true;
275
+ i += 1;
276
+ continue;
277
+ }
278
+ if (ch === '"') {
279
+ inDouble = true;
280
+ i += 1;
281
+ continue;
282
+ }
283
+ if (ch === '`') {
284
+ inTemplate = true;
285
+ i += 1;
286
+ continue;
287
+ }
288
+ if (ch === '{') {
289
+ depth += 1;
290
+ i += 1;
291
+ continue;
292
+ }
293
+ if (ch === '}') {
294
+ depth -= 1;
295
+ if (depth === 0)
296
+ return i;
297
+ i += 1;
298
+ continue;
299
+ }
300
+ i += 1;
301
+ }
302
+ return null;
303
+ }
304
+ function prepareTemplateInterpolations(text) {
305
+ const interpolations = [];
306
+ let out = '';
307
+ let cursor = 0;
308
+ let markerIndex = 0;
309
+ for (let i = 0; i < text.length; i += 1) {
310
+ if (text[i] !== '$' || text[i + 1] !== '{')
311
+ continue;
312
+ const start = i;
313
+ const end = findInterpolationEnd(text, i + 2);
314
+ if (end == null)
315
+ continue;
316
+ const exprStart = start + 2;
317
+ const exprEnd = end;
318
+ const totalLength = end - start + 1;
319
+ const marker = createInterpolationMarker(markerIndex, totalLength);
320
+ markerIndex += 1;
321
+ interpolations.push({
322
+ marker,
323
+ start,
324
+ end,
325
+ exprStart,
326
+ exprEnd,
327
+ expression: text.slice(exprStart, exprEnd),
328
+ });
329
+ out += text.slice(cursor, start);
330
+ out += marker;
331
+ cursor = end + 1;
332
+ i = end;
333
+ }
334
+ out += text.slice(cursor);
335
+ return {
336
+ sanitizedText: out,
337
+ context: { interpolations },
338
+ };
339
+ }
340
+ function findNextInterpolationOccurrence(text, from, context) {
341
+ if (!context || context.interpolations.length === 0)
342
+ return null;
343
+ let bestIdx = -1;
344
+ let bestInterpolation = null;
345
+ for (const interpolation of context.interpolations) {
346
+ const idx = text.indexOf(interpolation.marker, from);
347
+ if (idx < 0)
348
+ continue;
349
+ if (bestIdx < 0 || idx < bestIdx) {
350
+ bestIdx = idx;
351
+ bestInterpolation = interpolation;
352
+ }
353
+ }
354
+ if (bestIdx < 0 || bestInterpolation == null)
355
+ return null;
356
+ return { index: bestIdx, interpolation: bestInterpolation };
357
+ }
358
+ function strippedToRawOffset(rawText, strippedOffset, commonIndent) {
359
+ if (commonIndent === 0)
360
+ return strippedOffset;
361
+ let stripped = 0;
362
+ let raw = 0;
363
+ const lines = rawText.split('\n');
364
+ for (let i = 0; i < lines.length; i++) {
365
+ const line = lines[i];
366
+ const indentToRemove = line.trim().length === 0 ? line.length : commonIndent;
367
+ const strippedLineLen = Math.max(0, line.length - indentToRemove);
368
+ if (strippedOffset <= stripped + strippedLineLen) {
369
+ const colInStripped = strippedOffset - stripped;
370
+ return raw + indentToRemove + colInStripped;
371
+ }
372
+ stripped += strippedLineLen + 1;
373
+ raw += line.length + 1;
374
+ }
375
+ return raw;
376
+ }
377
+ function countIndent(line) {
378
+ return line.match(/^(\s*)/)?.[1].length ?? 0;
379
+ }
380
+ function isBlankLine(line) {
381
+ return line.trim().length === 0;
382
+ }
383
+ function stripCommonIndent(text) {
384
+ const lines = text.split('\n');
385
+ let minIndent = Infinity;
386
+ for (const line of lines) {
387
+ if (line.trim().length === 0)
388
+ continue;
389
+ const indent = countIndent(line);
390
+ if (indent < minIndent)
391
+ minIndent = indent;
392
+ }
393
+ if (minIndent === Infinity || minIndent === 0) {
394
+ return { stripped: text, indent: 0 };
395
+ }
396
+ return {
397
+ stripped: lines
398
+ .map((line) => (line.trim().length === 0 ? '' : line.slice(minIndent)))
399
+ .join('\n'),
400
+ indent: minIndent,
401
+ };
402
+ }
403
+ function matchStyleTagLine(line) {
404
+ const match = line.match(/^style(?:\((.*)\))?\s*$/);
405
+ if (!match)
406
+ return null;
407
+ return { attrText: match[1] ?? null };
408
+ }
409
+ function parseStyleLang(attrText, line, column, offset) {
410
+ if (attrText == null || attrText.trim().length === 0) {
411
+ return { lang: 'css', error: null };
412
+ }
413
+ const attrMatch = attrText.match(/^\s*lang\s*=\s*(['"])([^'"]+)\1\s*$/);
414
+ if (!attrMatch) {
415
+ return {
416
+ lang: null,
417
+ error: {
418
+ code: 'invalid-style-attrs',
419
+ message: 'style tag only supports a single lang attribute',
420
+ line,
421
+ column,
422
+ offset,
423
+ },
424
+ };
425
+ }
426
+ const lang = attrMatch[2];
427
+ if (lang === 'css' || lang === 'styl' || lang === 'sass' || lang === 'scss') {
428
+ return { lang, error: null };
429
+ }
430
+ return {
431
+ lang: null,
432
+ error: {
433
+ code: 'unsupported-style-lang',
434
+ message: `Unsupported style lang "${lang}". Expected css, styl, sass, or scss`,
435
+ line,
436
+ column,
437
+ offset,
438
+ },
439
+ };
440
+ }
441
+ function extractTerminalStyleBlock(pugText) {
442
+ if (!pugText.includes('style')) {
443
+ return {
444
+ pugTextWithoutStyle: pugText,
445
+ styleBlock: null,
446
+ transformError: null,
447
+ };
448
+ }
449
+ const lines = pugText.split('\n');
450
+ const lineStarts = [];
451
+ let runningOffset = 0;
452
+ for (const line of lines) {
453
+ lineStarts.push(runningOffset);
454
+ runningOffset += line.length + 1;
455
+ }
456
+ const topLevelIndices = [];
457
+ let styleIndex = -1;
458
+ let styleAttrs = null;
459
+ for (let i = 0; i < lines.length; i += 1) {
460
+ const line = lines[i];
461
+ if (isBlankLine(line))
462
+ continue;
463
+ const indent = countIndent(line);
464
+ const matched = matchStyleTagLine(line.trim());
465
+ if (matched) {
466
+ if (indent !== 0) {
467
+ return {
468
+ pugTextWithoutStyle: pugText,
469
+ styleBlock: null,
470
+ transformError: {
471
+ code: 'style-tag-must-be-last',
472
+ message: 'style tag must be at the highest level and the last top-level node in a pug template',
473
+ line: i + 1,
474
+ column: indent + 1,
475
+ offset: lineStarts[i] + indent,
476
+ },
477
+ };
478
+ }
479
+ if (styleIndex >= 0) {
480
+ return {
481
+ pugTextWithoutStyle: pugText,
482
+ styleBlock: null,
483
+ transformError: {
484
+ code: 'style-tag-must-be-last',
485
+ message: 'style tag must be at the highest level and the last top-level node in a pug template',
486
+ line: i + 1,
487
+ column: 1,
488
+ offset: lineStarts[i],
489
+ },
490
+ };
491
+ }
492
+ styleIndex = i;
493
+ styleAttrs = matched.attrText;
494
+ topLevelIndices.push(i);
495
+ continue;
496
+ }
497
+ if (indent === 0) {
498
+ topLevelIndices.push(i);
499
+ }
500
+ }
501
+ if (styleIndex < 0) {
502
+ return {
503
+ pugTextWithoutStyle: pugText,
504
+ styleBlock: null,
505
+ transformError: null,
506
+ };
507
+ }
508
+ const lastTopLevelIndex = topLevelIndices[topLevelIndices.length - 1] ?? -1;
509
+ if (styleIndex !== lastTopLevelIndex) {
510
+ return {
511
+ pugTextWithoutStyle: pugText,
512
+ styleBlock: null,
513
+ transformError: {
514
+ code: 'style-tag-must-be-last',
515
+ message: 'style tag must be at the highest level and the last top-level node in a pug template',
516
+ line: styleIndex + 1,
517
+ column: 1,
518
+ offset: lineStarts[styleIndex],
519
+ },
520
+ };
521
+ }
522
+ const line = lines[styleIndex];
523
+ const styleColumn = countIndent(line) + 1;
524
+ const styleOffset = lineStarts[styleIndex] + countIndent(line);
525
+ const parsedLang = parseStyleLang(styleAttrs, styleIndex + 1, styleColumn, styleOffset);
526
+ if (parsedLang.error) {
527
+ return {
528
+ pugTextWithoutStyle: pugText,
529
+ styleBlock: null,
530
+ transformError: parsedLang.error,
531
+ };
532
+ }
533
+ const bodyLines = lines.slice(styleIndex + 1);
534
+ const bodyText = bodyLines.join('\n');
535
+ const strippedBody = stripCommonIndent(bodyText);
536
+ const bodyOffset = styleIndex + 1 < lineStarts.length ? lineStarts[styleIndex + 1] : lineStarts[styleIndex] + line.length;
537
+ const bodyEndOffset = pugText.length;
538
+ const prefixLines = lines.slice(0, styleIndex);
539
+ let pugTextWithoutStyle = prefixLines.join('\n');
540
+ if (pugTextWithoutStyle.length > 0 && pugText.endsWith('\n')) {
541
+ pugTextWithoutStyle += '\n';
542
+ }
543
+ return {
544
+ pugTextWithoutStyle,
545
+ styleBlock: {
546
+ lang: parsedLang.lang,
547
+ content: strippedBody.stripped,
548
+ tagOffset: styleOffset,
549
+ contentStart: bodyOffset,
550
+ contentEnd: bodyEndOffset,
551
+ commonIndent: strippedBody.indent,
552
+ line: styleIndex + 1,
553
+ column: styleColumn,
554
+ },
555
+ transformError: null,
556
+ };
557
+ }
558
+ function emitCompiledPugRegionInExpression(expression, expressionOffset, region, compiled, emitter) {
559
+ if (compiled.mappings.length === 0) {
560
+ emitter.emitSynthetic(compiled.tsx);
561
+ return;
562
+ }
563
+ const rawText = expression.slice(region.pugTextStart, region.pugTextEnd);
564
+ const segments = [];
565
+ for (const mapping of compiled.mappings) {
566
+ const sourceOffsets = mapping.sourceOffsets ?? [];
567
+ const generatedOffsets = mapping.generatedOffsets ?? [];
568
+ const lengths = mapping.lengths ?? [];
569
+ const generatedLengths = mapping.generatedLengths ?? [];
570
+ const segmentCount = Math.min(sourceOffsets.length, generatedOffsets.length, lengths.length);
571
+ for (let i = 0; i < segmentCount; i += 1) {
572
+ segments.push({
573
+ generatedStart: generatedOffsets[i],
574
+ generatedLength: generatedLengths[i] ?? lengths[i],
575
+ sourceStart: sourceOffsets[i],
576
+ sourceLength: lengths[i],
577
+ info: mapping.data,
578
+ });
579
+ }
580
+ }
581
+ segments.sort((a, b) => a.generatedStart - b.generatedStart);
582
+ let cursor = 0;
583
+ for (const segment of segments) {
584
+ if (segment.generatedStart > cursor) {
585
+ emitter.emitSynthetic(compiled.tsx.slice(cursor, segment.generatedStart));
586
+ }
587
+ const chunk = compiled.tsx.slice(segment.generatedStart, segment.generatedStart + segment.generatedLength);
588
+ const rawOffset = strippedToRawOffset(rawText, segment.sourceStart, region.commonIndent);
589
+ const sourceOffset = expressionOffset + region.pugTextStart + rawOffset;
590
+ if (segment.generatedLength === segment.sourceLength) {
591
+ emitter.emitMapped(chunk, sourceOffset, segment.info);
592
+ }
593
+ else {
594
+ emitter.emitDerived(chunk, sourceOffset, segment.sourceLength, segment.info);
595
+ }
596
+ cursor = segment.generatedStart + segment.generatedLength;
597
+ }
598
+ if (cursor < compiled.tsx.length) {
599
+ emitter.emitSynthetic(compiled.tsx.slice(cursor));
600
+ }
601
+ }
602
+ function emitJsExpressionWithNestedPug(expression, expressionOffset, emitter, info = mapping_1.FULL_FEATURES) {
603
+ if (!expression.includes('pug`') && !expression.includes('pug `')) {
604
+ emitter.emitMapped(expression, expressionOffset, info);
605
+ return;
606
+ }
607
+ const regions = (0, extractRegions_1.extractPugRegions)(expression, 'inline-expression.tsx', 'pug');
608
+ if (regions.length === 0) {
609
+ emitter.emitMapped(expression, expressionOffset, info);
610
+ return;
611
+ }
612
+ let cursor = 0;
613
+ for (const region of regions) {
614
+ if (region.originalStart > cursor) {
615
+ const plain = expression.slice(cursor, region.originalStart);
616
+ emitJsExpressionWithNestedPug(plain, expressionOffset + cursor, emitter, info);
617
+ }
618
+ const compiled = compilePugToTsx(region.pugText, {
619
+ mode: currentCompileMode(),
620
+ classAttribute: currentClassAttribute(),
621
+ classMerge: currentClassMerge(),
622
+ });
623
+ emitCompiledPugRegionInExpression(expression, expressionOffset, region, compiled, emitter);
624
+ cursor = region.originalEnd;
625
+ }
626
+ if (cursor < expression.length) {
627
+ const tail = expression.slice(cursor);
628
+ emitJsExpressionWithNestedPug(tail, expressionOffset + cursor, emitter, info);
629
+ }
630
+ }
631
+ function emitExpressionWithTemplateInterpolations(expression, expressionOffset, emitter, info = mapping_1.FULL_FEATURES) {
632
+ const context = currentInterpolationContext();
633
+ let cursor = 0;
634
+ while (cursor < expression.length) {
635
+ const hit = findNextInterpolationOccurrence(expression, cursor, context);
636
+ if (!hit) {
637
+ emitJsExpressionWithNestedPug(expression.slice(cursor), expressionOffset + cursor, emitter, info);
638
+ break;
639
+ }
640
+ if (hit.index > cursor) {
641
+ emitJsExpressionWithNestedPug(expression.slice(cursor, hit.index), expressionOffset + cursor, emitter, info);
642
+ }
643
+ const interpolation = hit.interpolation;
644
+ if (interpolation.expression.trim().length === 0) {
645
+ emitter.emitSynthetic('undefined');
646
+ }
647
+ else {
648
+ emitJsExpressionWithNestedPug(interpolation.expression, interpolation.exprStart, emitter, info);
649
+ }
650
+ cursor = hit.index + interpolation.marker.length;
651
+ }
652
+ }
653
+ /** HTML void elements that should self-close */
654
+ const VOID_ELEMENTS = new Set([
655
+ 'area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input',
656
+ 'link', 'meta', 'param', 'source', 'track', 'wbr',
657
+ ]);
658
+ // ── AST Walking ─────────────────────────────────────────────────
659
+ function emitNodes(nodes, emitter, pugText) {
660
+ // If there are unbuffered code blocks mixed with JSX, use IIFE
661
+ const hasUnbufferedCode = nodes.some(isUnbufferedCode);
662
+ if (hasUnbufferedCode) {
663
+ emitBlockWithCodeSupport(nodes, emitter, pugText);
664
+ return;
665
+ }
666
+ // Multiple sibling nodes that produce JSX need fragment wrapping
667
+ const jsxNodes = nodes.filter(n => n.type === 'Tag' || (n.type === 'Code' && n.buffer) || n.type === 'Conditional'
668
+ || n.type === 'Each' || n.type === 'While' || n.type === 'Case');
669
+ const needsFragment = jsxNodes.length > 1;
670
+ if (needsFragment)
671
+ emitter.emitSynthetic('<>');
672
+ for (const node of nodes) {
673
+ emitNode(node, emitter, pugText);
674
+ }
675
+ if (needsFragment)
676
+ emitter.emitSynthetic('</>');
677
+ }
678
+ function emitNode(node, emitter, pugText) {
679
+ switch (node.type) {
680
+ case 'Tag':
681
+ emitTag(node, emitter, pugText);
682
+ break;
683
+ case 'Text':
684
+ emitText(node, emitter, pugText);
685
+ break;
686
+ case 'Code':
687
+ emitCode(node, emitter, pugText);
688
+ break;
689
+ case 'Conditional':
690
+ emitConditional(node, emitter, pugText);
691
+ break;
692
+ case 'Each':
693
+ emitEach(node, emitter, pugText);
694
+ break;
695
+ case 'While':
696
+ emitWhile(node, emitter, pugText);
697
+ break;
698
+ case 'Case':
699
+ emitCase(node, emitter, pugText);
700
+ break;
701
+ case 'When':
702
+ // When nodes are handled inside emitCase
703
+ break;
704
+ case 'Comment':
705
+ case 'BlockComment':
706
+ // Skip comments in TSX output
707
+ break;
708
+ }
709
+ }
710
+ function emitAttributeValueAsExpression(attr, emitter, pugText) {
711
+ if (attr.val === true) {
712
+ emitter.emitSynthetic('true');
713
+ return;
714
+ }
715
+ if (typeof attr.val !== 'string') {
716
+ emitter.emitSynthetic('undefined');
717
+ return;
718
+ }
719
+ const attrOffset = lineColToOffset(pugText, attr.line, attr.column);
720
+ const valOffset = attrOffset + attr.name.length + 1;
721
+ const val = attr.val;
722
+ if ((val.startsWith('"') && val.endsWith('"')) || (val.startsWith('\'') && val.endsWith('\''))) {
723
+ emitter.emitMapped(val, valOffset, mapping_1.FULL_FEATURES);
724
+ return;
725
+ }
726
+ emitExpressionWithTemplateInterpolations(val, valOffset, emitter, mapping_1.FULL_FEATURES);
727
+ }
728
+ function emitStaticClassLiteral(classNames, emitter) {
729
+ const combinedClass = classNames.map((c) => c.name).join(' ');
730
+ if (combinedClass.length === 0)
731
+ return;
732
+ const first = classNames[0];
733
+ emitter.emitDerived(combinedClass, first.offset, Math.max(1, first.sourceLength), mapping_1.CSS_CLASS);
734
+ }
735
+ function emitMergedClassShorthandAttribute(targetAttr, mergeMode, classNames, existingAttr, emitter, pugText) {
736
+ const emitTargetAttrName = () => {
737
+ emitter.emitSynthetic(' ');
738
+ if (existingAttr) {
739
+ emitter.emitMapped(existingAttr.name, lineColToOffset(pugText, existingAttr.line, existingAttr.column), mapping_1.FULL_FEATURES);
740
+ }
741
+ else {
742
+ emitter.emitSynthetic(targetAttr);
743
+ }
744
+ };
745
+ if (mergeMode === 'classnames') {
746
+ emitTargetAttrName();
747
+ emitter.emitSynthetic('={[');
748
+ for (let i = 0; i < classNames.length; i += 1) {
749
+ if (i > 0)
750
+ emitter.emitSynthetic(', ');
751
+ emitter.emitSynthetic('"');
752
+ emitter.emitDerived(classNames[i].name, classNames[i].offset, Math.max(1, classNames[i].sourceLength), mapping_1.CSS_CLASS);
753
+ emitter.emitSynthetic('"');
754
+ }
755
+ if (existingAttr) {
756
+ if (classNames.length > 0)
757
+ emitter.emitSynthetic(', ');
758
+ emitAttributeValueAsExpression(existingAttr, emitter, pugText);
759
+ }
760
+ emitter.emitSynthetic(']}');
761
+ return;
762
+ }
763
+ if (existingAttr) {
764
+ emitTargetAttrName();
765
+ emitter.emitSynthetic('={');
766
+ emitter.emitSynthetic('"');
767
+ emitStaticClassLiteral(classNames, emitter);
768
+ emitter.emitSynthetic('" + " " + (');
769
+ emitAttributeValueAsExpression(existingAttr, emitter, pugText);
770
+ emitter.emitSynthetic(')}');
771
+ return;
772
+ }
773
+ emitTargetAttrName();
774
+ emitter.emitSynthetic('="');
775
+ emitStaticClassLiteral(classNames, emitter);
776
+ emitter.emitSynthetic('"');
777
+ }
778
+ function emitTag(node, emitter, pugText) {
779
+ const tagOffset = lineColToOffset(pugText, node.line, node.column);
780
+ // Collect class names and id from shorthand attrs
781
+ const classNames = [];
782
+ let idValue = null;
783
+ const regularAttrs = [];
784
+ for (const attr of node.attrs) {
785
+ if (attr.name === 'class' && typeof attr.val === 'string') {
786
+ // Shorthand class: val is like "'card'" (with quotes)
787
+ const raw = attr.val;
788
+ if (raw.startsWith("'") && raw.endsWith("'")) {
789
+ const classOffset = lineColToOffset(pugText, attr.line, attr.column);
790
+ classNames.push({
791
+ name: raw.slice(1, -1),
792
+ offset: classOffset,
793
+ sourceLength: Math.max(1, raw.length),
794
+ nameOffset: classOffset + 1,
795
+ });
796
+ }
797
+ else if (raw.startsWith('"') && raw.endsWith('"')) {
798
+ const classOffset = lineColToOffset(pugText, attr.line, attr.column);
799
+ classNames.push({
800
+ name: raw.slice(1, -1),
801
+ offset: classOffset,
802
+ sourceLength: Math.max(1, raw.length),
803
+ nameOffset: classOffset + 1,
804
+ });
805
+ }
806
+ else {
807
+ // Dynamic class expression
808
+ regularAttrs.push(attr);
809
+ }
810
+ }
811
+ else if (attr.name === 'id' && typeof attr.val === 'string') {
812
+ const raw = attr.val;
813
+ if (raw.startsWith("'") && raw.endsWith("'")) {
814
+ idValue = raw.slice(1, -1);
815
+ }
816
+ else if (raw.startsWith('"') && raw.endsWith('"')) {
817
+ idValue = raw.slice(1, -1);
818
+ }
819
+ else {
820
+ regularAttrs.push(attr);
821
+ }
822
+ }
823
+ else {
824
+ regularAttrs.push(attr);
825
+ }
826
+ }
827
+ const componentPathFromUppercaseClassShorthand = currentComponentPathFromUppercaseClassShorthand();
828
+ const componentPathSegments = [];
829
+ if (componentPathFromUppercaseClassShorthand && /^[A-Z]/.test(node.name)) {
830
+ for (const classShorthand of classNames) {
831
+ if (!/^[A-Z]/.test(classShorthand.name))
832
+ break;
833
+ componentPathSegments.push(classShorthand);
834
+ }
835
+ if (componentPathSegments.length > 0) {
836
+ classNames.splice(0, componentPathSegments.length);
837
+ }
838
+ }
839
+ const resolvedTagName = componentPathSegments.length > 0
840
+ ? `${node.name}.${componentPathSegments.map((segment) => segment.name).join('.')}`
841
+ : node.name;
842
+ // Check if tag name is synthetic (implicit div from shorthand)
843
+ const isSyntheticDiv = node.name === 'div' && (classNames.length > 0 || idValue !== null)
844
+ && node.column === (pugText.split('\n')[node.line - 1]?.indexOf('.') ?? -1) + 1;
845
+ // Emit opening tag
846
+ emitter.emitSynthetic('<');
847
+ if (isSyntheticDiv) {
848
+ emitter.emitSynthetic(node.name);
849
+ }
850
+ else {
851
+ emitter.emitMapped(node.name, tagOffset, mapping_1.FULL_FEATURES);
852
+ for (const segment of componentPathSegments) {
853
+ emitter.emitSynthetic('.');
854
+ emitter.emitDerived(segment.name, segment.nameOffset, Math.max(1, segment.name.length), mapping_1.FULL_FEATURES);
855
+ }
856
+ }
857
+ // Emit shorthand classes according to current class strategy.
858
+ if (classNames.length > 0) {
859
+ const targetAttr = currentClassAttribute();
860
+ const mergeMode = currentClassMerge();
861
+ const existingIndex = regularAttrs.findIndex((a) => a.name === targetAttr);
862
+ const existingAttr = existingIndex >= 0 ? regularAttrs.splice(existingIndex, 1)[0] : null;
863
+ emitMergedClassShorthandAttribute(targetAttr, mergeMode, classNames, existingAttr, emitter, pugText);
864
+ }
865
+ // Emit id from shorthand
866
+ if (idValue !== null) {
867
+ emitter.emitSynthetic(' id="');
868
+ emitter.emitSynthetic(idValue);
869
+ emitter.emitSynthetic('"');
870
+ }
871
+ // Emit regular attributes
872
+ for (const attr of regularAttrs) {
873
+ emitAttribute(attr, emitter, pugText);
874
+ }
875
+ // Determine if tag has children
876
+ const children = node.block?.nodes ?? [];
877
+ const hasChildren = children.length > 0;
878
+ const isComponentTag = /^[A-Z]/.test(resolvedTagName);
879
+ const isVoid = !isComponentTag && VOID_ELEMENTS.has(node.name.toLowerCase());
880
+ if (!hasChildren || isVoid) {
881
+ emitter.emitSynthetic(' />');
882
+ }
883
+ else {
884
+ emitter.emitSynthetic('>');
885
+ emitChildren(children, emitter, pugText);
886
+ emitter.emitSynthetic('</');
887
+ if (isSyntheticDiv) {
888
+ emitter.emitSynthetic(node.name);
889
+ }
890
+ else {
891
+ emitter.emitSynthetic(resolvedTagName);
892
+ }
893
+ emitter.emitSynthetic('>');
894
+ }
895
+ }
896
+ function emitAttribute(attr, emitter, pugText) {
897
+ const attrOffset = lineColToOffset(pugText, attr.line, attr.column);
898
+ // Spread attribute: ...props
899
+ if (attr.name.startsWith('...')) {
900
+ const varName = attr.name.slice(3);
901
+ emitter.emitSynthetic(' {');
902
+ emitter.emitSynthetic('...');
903
+ emitter.emitMapped(varName, attrOffset + 3, mapping_1.FULL_FEATURES);
904
+ emitter.emitSynthetic('}');
905
+ return;
906
+ }
907
+ emitter.emitSynthetic(' ');
908
+ emitter.emitMapped(attr.name, attrOffset, mapping_1.FULL_FEATURES);
909
+ // Boolean attribute: disabled -> disabled
910
+ if (attr.val === true) {
911
+ return;
912
+ }
913
+ // Value attribute: onClick=handler -> onClick={handler}
914
+ if (typeof attr.val === 'string') {
915
+ const val = attr.val;
916
+ // Check if value is a quoted string: "hello" or 'hello'
917
+ if ((val.startsWith('"') && val.endsWith('"')) ||
918
+ (val.startsWith("'") && val.endsWith("'"))) {
919
+ emitter.emitSynthetic('={');
920
+ // Find the value offset: after "name=" in the source
921
+ const valOffset = attrOffset + attr.name.length + 1; // +1 for '='
922
+ emitter.emitMapped(val, valOffset, mapping_1.FULL_FEATURES);
923
+ emitter.emitSynthetic('}');
924
+ }
925
+ else {
926
+ // Expression value
927
+ emitter.emitSynthetic('={');
928
+ const valOffset = attrOffset + attr.name.length + 1;
929
+ emitExpressionWithTemplateInterpolations(val, valOffset, emitter, mapping_1.FULL_FEATURES);
930
+ emitter.emitSynthetic('}');
931
+ }
932
+ }
933
+ }
934
+ function emitText(node, emitter, pugText) {
935
+ const lineText = pugText.split('\n')[node.line - 1] ?? '';
936
+ const lineStart = lineColToOffset(pugText, node.line, 1);
937
+ const markerIndex = Math.max(0, node.column - 1);
938
+ const valueIndex = lineText.indexOf(node.val, markerIndex);
939
+ const offset = valueIndex >= 0
940
+ ? lineStart + valueIndex
941
+ : lineColToOffset(pugText, node.line, node.column);
942
+ const context = currentInterpolationContext();
943
+ let cursor = 0;
944
+ while (cursor < node.val.length) {
945
+ const hit = findNextInterpolationOccurrence(node.val, cursor, context);
946
+ if (!hit) {
947
+ if (cursor < node.val.length) {
948
+ emitter.emitMapped(node.val.slice(cursor), offset + cursor, mapping_1.SYNTHETIC);
949
+ }
950
+ break;
951
+ }
952
+ if (hit.index > cursor) {
953
+ emitter.emitMapped(node.val.slice(cursor, hit.index), offset + cursor, mapping_1.SYNTHETIC);
954
+ }
955
+ const interpolation = hit.interpolation;
956
+ emitter.emitSynthetic('{');
957
+ if (interpolation.expression.trim().length === 0) {
958
+ emitter.emitSynthetic('undefined');
959
+ }
960
+ else {
961
+ emitJsExpressionWithNestedPug(interpolation.expression, interpolation.exprStart, emitter);
962
+ }
963
+ emitter.emitSynthetic('}');
964
+ cursor = hit.index + interpolation.marker.length;
965
+ }
966
+ }
967
+ function emitCode(node, emitter, pugText, wrapInJsxBraces = true) {
968
+ const markerOffset = lineColToOffset(pugText, node.line, node.column);
969
+ const lineText = pugText.split('\n')[node.line - 1] ?? '';
970
+ const markerIndex = Math.max(0, node.column - 1);
971
+ const valueIndex = lineText.indexOf(node.val, markerIndex);
972
+ const fallbackShift = node.buffer ? 2 : 1;
973
+ const valueOffset = valueIndex >= 0
974
+ ? lineColToOffset(pugText, node.line, 1) + valueIndex
975
+ : markerOffset + fallbackShift;
976
+ if (node.buffer && node.isInline) {
977
+ // Inline interpolation: #{expr} -> {expr} in JSX child context, bare expr in JS expression context
978
+ if (wrapInJsxBraces)
979
+ emitter.emitSynthetic('{');
980
+ emitExpressionWithTemplateInterpolations(node.val, valueOffset, emitter, mapping_1.FULL_FEATURES);
981
+ if (wrapInJsxBraces)
982
+ emitter.emitSynthetic('}');
983
+ }
984
+ else if (node.buffer) {
985
+ // Buffered code: = expr -> {expr} in JSX child context, bare expr in JS expression context
986
+ if (wrapInJsxBraces)
987
+ emitter.emitSynthetic('{');
988
+ emitExpressionWithTemplateInterpolations(node.val, valueOffset, emitter, mapping_1.FULL_FEATURES);
989
+ if (wrapInJsxBraces)
990
+ emitter.emitSynthetic('}');
991
+ }
992
+ else {
993
+ // Unbuffered code block: - const x = 10
994
+ // Emitted as a statement; IIFE wrapping is handled by emitNodesWithCodeBlocks
995
+ emitExpressionWithTemplateInterpolations(node.val, valueOffset, emitter, mapping_1.FULL_FEATURES);
996
+ emitter.emitSynthetic(';');
997
+ }
998
+ }
999
+ function emitChildren(nodes, emitter, pugText) {
1000
+ const hasUnbufferedCode = nodes.some(isUnbufferedCode);
1001
+ if (hasUnbufferedCode) {
1002
+ emitter.emitSynthetic('{');
1003
+ emitBlockWithCodeSupport(nodes, emitter, pugText);
1004
+ emitter.emitSynthetic('}');
1005
+ }
1006
+ else {
1007
+ for (const node of nodes) {
1008
+ emitNode(node, emitter, pugText);
1009
+ }
1010
+ }
1011
+ }
1012
+ /** Check if a node is an unbuffered code block */
1013
+ function isUnbufferedCode(node) {
1014
+ return node.type === 'Code' && !node.buffer;
1015
+ }
1016
+ /** Check if a node produces JSX output */
1017
+ function isJsxProducing(node) {
1018
+ return node.type === 'Tag' || node.type === 'Conditional'
1019
+ || node.type === 'Each' || node.type === 'While' || node.type === 'Case'
1020
+ || (node.type === 'Code' && node.buffer);
1021
+ }
1022
+ /**
1023
+ * Emit a block of nodes that may contain unbuffered code mixed with JSX.
1024
+ * When code blocks are mixed with JSX-producing nodes, wraps in an IIFE:
1025
+ * (() => { code; return (<jsx/>); })()
1026
+ */
1027
+ function emitBlockWithCodeSupport(nodes, emitter, pugText) {
1028
+ const hasUnbufferedCode = nodes.some(isUnbufferedCode);
1029
+ const hasJsx = nodes.some(isJsxProducing);
1030
+ if (hasUnbufferedCode && hasJsx) {
1031
+ // IIFE wrapping: (() => { code; return (<jsx/>); })()
1032
+ emitter.emitSynthetic('(() => {');
1033
+ // Emit all unbuffered code as statements
1034
+ for (const node of nodes) {
1035
+ if (isUnbufferedCode(node)) {
1036
+ emitNode(node, emitter, pugText);
1037
+ }
1038
+ }
1039
+ // Emit JSX-producing nodes as the return value
1040
+ const jsxNodes = nodes.filter(n => !isUnbufferedCode(n));
1041
+ emitter.emitSynthetic('return (');
1042
+ if (jsxNodes.length === 0) {
1043
+ emitter.emitSynthetic('null');
1044
+ }
1045
+ else if (jsxNodes.length === 1) {
1046
+ emitNode(jsxNodes[0], emitter, pugText);
1047
+ }
1048
+ else {
1049
+ emitter.emitSynthetic('<>');
1050
+ for (const node of jsxNodes) {
1051
+ emitNode(node, emitter, pugText);
1052
+ }
1053
+ emitter.emitSynthetic('</>');
1054
+ }
1055
+ emitter.emitSynthetic(');})()');
1056
+ }
1057
+ else if (hasUnbufferedCode) {
1058
+ // Only code, no JSX -- emit as IIFE returning null
1059
+ emitter.emitSynthetic('(() => {');
1060
+ for (const node of nodes) {
1061
+ emitNode(node, emitter, pugText);
1062
+ }
1063
+ emitter.emitSynthetic('return null;})()');
1064
+ }
1065
+ else {
1066
+ // No unbuffered code -- emit normally
1067
+ emitNodes(nodes, emitter, pugText);
1068
+ }
1069
+ }
1070
+ /** Emit a single node as a JS expression (no JSX-child wrapping braces). */
1071
+ function emitNodeAsExpression(node, emitter, pugText) {
1072
+ switch (node.type) {
1073
+ case 'Tag':
1074
+ emitTag(node, emitter, pugText);
1075
+ break;
1076
+ case 'Text':
1077
+ emitter.emitSynthetic(JSON.stringify(node.val));
1078
+ break;
1079
+ case 'Code':
1080
+ emitCode(node, emitter, pugText, false);
1081
+ break;
1082
+ case 'Conditional':
1083
+ emitConditional(node, emitter, pugText, false);
1084
+ break;
1085
+ case 'Each':
1086
+ emitEach(node, emitter, pugText, false);
1087
+ break;
1088
+ case 'While':
1089
+ emitWhile(node, emitter, pugText, false);
1090
+ break;
1091
+ case 'Case':
1092
+ emitCase(node, emitter, pugText, false);
1093
+ break;
1094
+ case 'When':
1095
+ case 'Comment':
1096
+ case 'BlockComment':
1097
+ emitter.emitSynthetic('null');
1098
+ break;
1099
+ }
1100
+ }
1101
+ /** Emit a block where the caller expects a JS expression result. */
1102
+ function emitBlockAsExpression(nodes, emitter, pugText) {
1103
+ const hasUnbufferedCode = nodes.some(isUnbufferedCode);
1104
+ if (hasUnbufferedCode) {
1105
+ emitter.emitSynthetic('(() => {');
1106
+ for (const node of nodes) {
1107
+ if (isUnbufferedCode(node))
1108
+ emitNode(node, emitter, pugText);
1109
+ }
1110
+ const exprNodes = nodes.filter((n) => !isUnbufferedCode(n));
1111
+ emitter.emitSynthetic('return ');
1112
+ if (exprNodes.length === 0) {
1113
+ emitter.emitSynthetic('null');
1114
+ }
1115
+ else if (exprNodes.length === 1) {
1116
+ emitNodeAsExpression(exprNodes[0], emitter, pugText);
1117
+ }
1118
+ else {
1119
+ emitter.emitSynthetic('(<>');
1120
+ for (const node of exprNodes) {
1121
+ emitNode(node, emitter, pugText);
1122
+ }
1123
+ emitter.emitSynthetic('</>)');
1124
+ }
1125
+ emitter.emitSynthetic(';})()');
1126
+ return;
1127
+ }
1128
+ if (nodes.length === 0) {
1129
+ emitter.emitSynthetic('null');
1130
+ }
1131
+ else if (nodes.length === 1) {
1132
+ emitNodeAsExpression(nodes[0], emitter, pugText);
1133
+ }
1134
+ else {
1135
+ emitter.emitSynthetic('<>');
1136
+ for (const node of nodes)
1137
+ emitNode(node, emitter, pugText);
1138
+ emitter.emitSynthetic('</>');
1139
+ }
1140
+ }
1141
+ // ── Control flow emitters ──────────────────────────────────────
1142
+ /** if show -> show ? <consequent> : <alternate> */
1143
+ function emitConditional(node, emitter, pugText, wrapInJsxBraces = true) {
1144
+ const testOffset = lineColToOffset(pugText, node.line, node.column);
1145
+ const exprOffset = findValueOffsetOnLine(pugText, node.line, node.column, node.test, testOffset + 3);
1146
+ if (wrapInJsxBraces)
1147
+ emitter.emitSynthetic('{');
1148
+ emitExpressionWithTemplateInterpolations(node.test, exprOffset, emitter, mapping_1.FULL_FEATURES);
1149
+ emitter.emitSynthetic(' ? ');
1150
+ // Consequent block
1151
+ const consequentNodes = node.consequent?.nodes ?? [];
1152
+ emitBlockAsExpression(consequentNodes, emitter, pugText);
1153
+ emitter.emitSynthetic(' : ');
1154
+ // Alternate: can be another Conditional (else if) or a Block (else) or null
1155
+ if (node.alternate == null) {
1156
+ emitter.emitSynthetic('null');
1157
+ }
1158
+ else if (node.alternate.type === 'Conditional') {
1159
+ // Chained: else if -> nested ternary (without wrapping braces)
1160
+ emitConditionalInner(node.alternate, emitter, pugText);
1161
+ }
1162
+ else {
1163
+ // else block
1164
+ const altNodes = node.alternate.nodes ?? [];
1165
+ emitBlockAsExpression(altNodes, emitter, pugText);
1166
+ }
1167
+ if (wrapInJsxBraces)
1168
+ emitter.emitSynthetic('}');
1169
+ }
1170
+ /** Inner conditional for chained else-if (no wrapping {} braces) */
1171
+ function emitConditionalInner(node, emitter, pugText) {
1172
+ const testOffset = lineColToOffset(pugText, node.line, node.column);
1173
+ const exprOffset = findValueOffsetOnLine(pugText, node.line, node.column, node.test, testOffset + 8);
1174
+ emitExpressionWithTemplateInterpolations(node.test, exprOffset, emitter, mapping_1.FULL_FEATURES);
1175
+ emitter.emitSynthetic(' ? ');
1176
+ const consequentNodes = node.consequent?.nodes ?? [];
1177
+ emitBlockAsExpression(consequentNodes, emitter, pugText);
1178
+ emitter.emitSynthetic(' : ');
1179
+ if (node.alternate == null) {
1180
+ emitter.emitSynthetic('null');
1181
+ }
1182
+ else if (node.alternate.type === 'Conditional') {
1183
+ emitConditionalInner(node.alternate, emitter, pugText);
1184
+ }
1185
+ else {
1186
+ const altNodes = node.alternate.nodes ?? [];
1187
+ emitBlockAsExpression(altNodes, emitter, pugText);
1188
+ }
1189
+ }
1190
+ function createNonConflictingName(base, disallowed) {
1191
+ if (!disallowed.has(base))
1192
+ return base;
1193
+ let suffix = 1;
1194
+ while (disallowed.has(`${base}${suffix}`))
1195
+ suffix++;
1196
+ return `${base}${suffix}`;
1197
+ }
1198
+ /** each item, i in items -> {(() => { const __r = []; ... for (const item of items) ... })()} */
1199
+ function emitEach(node, emitter, pugText, wrapInJsxBraces = true) {
1200
+ const pugLine = pugText.split('\n')[node.line - 1] ?? '';
1201
+ const lineStart = lineColToOffset(pugText, node.line, 1);
1202
+ const nodeStart = Math.max(0, node.column - 1);
1203
+ const valIndex = pugLine.indexOf(node.val, nodeStart);
1204
+ const valOffset = valIndex >= 0
1205
+ ? lineStart + valIndex
1206
+ : lineColToOffset(pugText, node.line, node.column) + 5;
1207
+ const keyIndex = node.key != null && valIndex >= 0
1208
+ ? pugLine.indexOf(node.key, valIndex + node.val.length)
1209
+ : -1;
1210
+ const keyOffset = keyIndex >= 0
1211
+ ? lineStart + keyIndex
1212
+ : (node.key != null
1213
+ ? findValueOffsetOnLine(pugText, node.line, node.column, node.key, lineStart + Math.max(0, pugLine.indexOf(node.key)))
1214
+ : -1);
1215
+ const inSearchStart = keyIndex >= 0
1216
+ ? keyIndex + (node.key?.length ?? 0)
1217
+ : (valIndex >= 0 ? valIndex + node.val.length : nodeStart);
1218
+ const inIndex = findWordIndex(pugLine, 'in', inSearchStart);
1219
+ const objIndex = inIndex >= 0
1220
+ ? pugLine.indexOf(node.obj, inIndex + 2)
1221
+ : -1;
1222
+ const objOffset = objIndex >= 0
1223
+ ? lineStart + objIndex
1224
+ : findValueOffsetOnLine(pugText, node.line, node.column, node.obj, lineStart + Math.max(0, pugLine.indexOf(node.obj)));
1225
+ const bodyNodes = node.block?.nodes ?? [];
1226
+ const elseNodes = node.alternate?.nodes ?? null;
1227
+ const disallowedNames = new Set([node.val]);
1228
+ if (node.key != null)
1229
+ disallowedNames.add(node.key);
1230
+ const resultVar = createNonConflictingName('__pugEachResult', disallowedNames);
1231
+ disallowedNames.add(resultVar);
1232
+ const indexVar = node.key != null
1233
+ ? createNonConflictingName('__pugEachIndex', disallowedNames)
1234
+ : null;
1235
+ if (wrapInJsxBraces)
1236
+ emitter.emitSynthetic('{');
1237
+ emitter.emitSynthetic('(() => {');
1238
+ if (currentCompileMode() === 'runtime') {
1239
+ emitter.emitSynthetic(`const ${resultVar} = [];`);
1240
+ }
1241
+ else {
1242
+ emitter.emitSynthetic(`const ${resultVar}: JSX.Element[] = [];`);
1243
+ }
1244
+ if (indexVar != null)
1245
+ emitter.emitSynthetic(`let ${indexVar} = 0;`);
1246
+ emitter.emitSynthetic('for (const ');
1247
+ emitExpressionWithTemplateInterpolations(node.val, valOffset, emitter, mapping_1.FULL_FEATURES);
1248
+ emitter.emitSynthetic(' of ');
1249
+ emitExpressionWithTemplateInterpolations(node.obj, objOffset, emitter, mapping_1.FULL_FEATURES);
1250
+ emitter.emitSynthetic(') {');
1251
+ if (node.key != null && indexVar != null) {
1252
+ emitter.emitSynthetic('const ');
1253
+ emitExpressionWithTemplateInterpolations(node.key, keyOffset, emitter, mapping_1.FULL_FEATURES);
1254
+ emitter.emitSynthetic(` = ${indexVar};`);
1255
+ }
1256
+ emitter.emitSynthetic(`${resultVar}.push(`);
1257
+ emitBlockAsExpression(bodyNodes, emitter, pugText);
1258
+ emitter.emitSynthetic(');');
1259
+ if (indexVar != null)
1260
+ emitter.emitSynthetic(`${indexVar}++;`);
1261
+ emitter.emitSynthetic('}');
1262
+ if (elseNodes != null) {
1263
+ emitter.emitSynthetic(`return ${resultVar}.length ? ${resultVar} : `);
1264
+ emitBlockAsExpression(elseNodes, emitter, pugText);
1265
+ emitter.emitSynthetic(';');
1266
+ }
1267
+ else {
1268
+ emitter.emitSynthetic(`return ${resultVar};`);
1269
+ }
1270
+ emitter.emitSynthetic('})()');
1271
+ if (wrapInJsxBraces)
1272
+ emitter.emitSynthetic('}');
1273
+ }
1274
+ /** while test -> {(() => { const __r: JSX.Element[] = []; while (test) { __r.push(<body/>); } return __r; })()} */
1275
+ function emitWhile(node, emitter, pugText, wrapInJsxBraces = true) {
1276
+ const testOffset = lineColToOffset(pugText, node.line, node.column);
1277
+ const exprOffset = findValueOffsetOnLine(pugText, node.line, node.column, node.test, testOffset + 6);
1278
+ if (wrapInJsxBraces)
1279
+ emitter.emitSynthetic('{');
1280
+ if (currentCompileMode() === 'runtime') {
1281
+ emitter.emitSynthetic('(() => {const __r = [];while (');
1282
+ }
1283
+ else {
1284
+ emitter.emitSynthetic('(() => {const __r: JSX.Element[] = [];while (');
1285
+ }
1286
+ emitExpressionWithTemplateInterpolations(node.test, exprOffset, emitter, mapping_1.FULL_FEATURES);
1287
+ emitter.emitSynthetic(') {__r.push(');
1288
+ const bodyNodes = node.block?.nodes ?? [];
1289
+ emitBlockAsExpression(bodyNodes, emitter, pugText);
1290
+ emitter.emitSynthetic(');}return __r;})()');
1291
+ if (wrapInJsxBraces)
1292
+ emitter.emitSynthetic('}');
1293
+ }
1294
+ /** case expr / when val1 / default -> {expr === val1 ? <c1> : <default>} */
1295
+ function emitCase(node, emitter, pugText, wrapInJsxBraces = true) {
1296
+ const caseOffset = lineColToOffset(pugText, node.line, node.column);
1297
+ const exprOffset = findValueOffsetOnLine(pugText, node.line, node.column, node.expr, caseOffset + 5);
1298
+ const whenNodes = (node.block?.nodes ?? []).filter((n) => n.type === 'When');
1299
+ if (whenNodes.length === 0) {
1300
+ if (wrapInJsxBraces)
1301
+ emitter.emitSynthetic('{');
1302
+ emitter.emitSynthetic('null');
1303
+ if (wrapInJsxBraces)
1304
+ emitter.emitSynthetic('}');
1305
+ return;
1306
+ }
1307
+ if (wrapInJsxBraces)
1308
+ emitter.emitSynthetic('{');
1309
+ emitWhenChain(whenNodes, 0, node.expr, exprOffset, emitter, pugText);
1310
+ if (wrapInJsxBraces)
1311
+ emitter.emitSynthetic('}');
1312
+ }
1313
+ /** Recursively emit chained ternaries for when nodes */
1314
+ function emitWhenChain(whens, index, caseExpr, caseExprOffset, emitter, pugText) {
1315
+ if (index >= whens.length) {
1316
+ emitter.emitSynthetic('null');
1317
+ return;
1318
+ }
1319
+ const when = whens[index];
1320
+ const whenOffset = lineColToOffset(pugText, when.line, when.column);
1321
+ if (when.expr === 'default') {
1322
+ // Default case: just emit the body
1323
+ const bodyNodes = when.block?.nodes ?? [];
1324
+ emitBlockAsExpression(bodyNodes, emitter, pugText);
1325
+ return;
1326
+ }
1327
+ const whenExprOffset = findValueOffsetOnLine(pugText, when.line, when.column, when.expr, whenOffset + 5);
1328
+ // Emit: caseExpr === whenExpr ? <body> : <next>
1329
+ emitExpressionWithTemplateInterpolations(caseExpr, caseExprOffset, emitter, mapping_1.VERIFY_ONLY);
1330
+ emitter.emitSynthetic(' === ');
1331
+ emitExpressionWithTemplateInterpolations(when.expr, whenExprOffset, emitter, mapping_1.FULL_FEATURES);
1332
+ emitter.emitSynthetic(' ? ');
1333
+ const bodyNodes = when.block?.nodes ?? [];
1334
+ emitBlockAsExpression(bodyNodes, emitter, pugText);
1335
+ emitter.emitSynthetic(' : ');
1336
+ emitWhenChain(whens, index + 1, caseExpr, caseExprOffset, emitter, pugText);
1337
+ }
1338
+ function fallbackNullExpression(mode) {
1339
+ return mode === 'runtime' ? 'null' : '(null as any as JSX.Element)';
1340
+ }
1341
+ /**
1342
+ * Compile pug text to TSX with source mappings.
1343
+ * Pipeline: pug-lexer -> pug-strip-comments -> pug-parser -> TsxEmitter
1344
+ */
1345
+ function compilePugToTsx(pugText, options = {}) {
1346
+ const mode = options.mode ?? 'languageService';
1347
+ const classAttribute = options.classAttribute ?? 'className';
1348
+ const classMerge = options.classMerge ?? 'concatenate';
1349
+ const componentPathFromUppercaseClassShorthand = options.componentPathFromUppercaseClassShorthand ?? true;
1350
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
1351
+ const lex = require('@startupjs/pug-lexer');
1352
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
1353
+ const parse = require('pug-parser');
1354
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
1355
+ const stripComments = require('pug-strip-comments');
1356
+ const extractedStyle = extractTerminalStyleBlock(pugText);
1357
+ const prepared = prepareTemplateInterpolations(extractedStyle.pugTextWithoutStyle);
1358
+ const pugTextForParse = prepared.sanitizedText;
1359
+ interpolationContextStack.push(prepared.context);
1360
+ compileContextStack.push({
1361
+ mode,
1362
+ classAttribute,
1363
+ classMerge,
1364
+ componentPathFromUppercaseClassShorthand,
1365
+ });
1366
+ try {
1367
+ if (extractedStyle.transformError) {
1368
+ return {
1369
+ tsx: fallbackNullExpression(mode),
1370
+ mappings: [],
1371
+ lexerTokens: [],
1372
+ parseError: null,
1373
+ styleBlock: null,
1374
+ transformError: extractedStyle.transformError,
1375
+ };
1376
+ }
1377
+ let tokens;
1378
+ let parseError = null;
1379
+ try {
1380
+ tokens = lex(pugTextForParse, { filename: 'template.pug' });
1381
+ }
1382
+ catch (err) {
1383
+ parseError = {
1384
+ message: err.message ?? 'Pug lexer error',
1385
+ line: err.line ?? 1,
1386
+ column: err.column ?? 1,
1387
+ offset: 0,
1388
+ };
1389
+ const recoveredText = buildTypingRecoveryText(pugTextForParse);
1390
+ if (recoveredText !== pugTextForParse) {
1391
+ try {
1392
+ tokens = lex(recoveredText, { filename: 'template.pug' });
1393
+ }
1394
+ catch {
1395
+ return {
1396
+ tsx: fallbackNullExpression(mode),
1397
+ mappings: [],
1398
+ lexerTokens: [],
1399
+ parseError,
1400
+ styleBlock: extractedStyle.styleBlock,
1401
+ transformError: null,
1402
+ };
1403
+ }
1404
+ }
1405
+ else {
1406
+ return {
1407
+ tsx: fallbackNullExpression(mode),
1408
+ mappings: [],
1409
+ lexerTokens: [],
1410
+ parseError,
1411
+ styleBlock: extractedStyle.styleBlock,
1412
+ transformError: null,
1413
+ };
1414
+ }
1415
+ }
1416
+ const lexerTokens = tokens
1417
+ .filter((t) => t.loc)
1418
+ .map((t) => ({
1419
+ type: t.type,
1420
+ loc: t.loc,
1421
+ val: t.val != null ? String(t.val) : undefined,
1422
+ }));
1423
+ let ast;
1424
+ try {
1425
+ const stripped = stripComments(tokens, { filename: 'template.pug' });
1426
+ ast = parse(stripped, { filename: 'template.pug' });
1427
+ }
1428
+ catch (err) {
1429
+ if (parseError == null) {
1430
+ parseError = {
1431
+ message: err.message ?? 'Pug parser error',
1432
+ line: err.line ?? 1,
1433
+ column: err.column ?? 1,
1434
+ offset: 0,
1435
+ };
1436
+ }
1437
+ // Recovery parse: keep IntelliSense usable while template is temporarily incomplete.
1438
+ const recoveredText = buildTypingRecoveryText(pugTextForParse);
1439
+ if (recoveredText !== pugTextForParse) {
1440
+ try {
1441
+ const recoveredTokens = lex(recoveredText, { filename: 'template.pug' });
1442
+ const recoveredStripped = stripComments(recoveredTokens, { filename: 'template.pug' });
1443
+ ast = parse(recoveredStripped, { filename: 'template.pug' });
1444
+ }
1445
+ catch {
1446
+ // Fall through to placeholder.
1447
+ }
1448
+ }
1449
+ if (!ast) {
1450
+ return {
1451
+ tsx: fallbackNullExpression(mode),
1452
+ mappings: [],
1453
+ lexerTokens,
1454
+ parseError,
1455
+ styleBlock: extractedStyle.styleBlock,
1456
+ transformError: null,
1457
+ };
1458
+ }
1459
+ }
1460
+ const emitter = new TsxEmitter();
1461
+ if (ast.nodes.length === 0) {
1462
+ emitter.emitSynthetic(fallbackNullExpression(mode));
1463
+ }
1464
+ else {
1465
+ emitter.emitSynthetic('(');
1466
+ emitBlockAsExpression(ast.nodes, emitter, pugTextForParse);
1467
+ emitter.emitSynthetic(')');
1468
+ }
1469
+ const result = emitter.getResult();
1470
+ return {
1471
+ tsx: result.tsx,
1472
+ mappings: result.mappings,
1473
+ lexerTokens,
1474
+ parseError,
1475
+ styleBlock: extractedStyle.styleBlock,
1476
+ transformError: null,
1477
+ };
1478
+ }
1479
+ finally {
1480
+ compileContextStack.pop();
1481
+ interpolationContextStack.pop();
1482
+ }
1483
+ }
1484
+ //# sourceMappingURL=pugToTsx.js.map