|
| 1 | +import { findMdxExpressionEnd } from './markup' |
| 2 | + |
| 3 | +export interface RegorExpressionPlaceholder { |
| 4 | + placeholder: string |
| 5 | + source: string |
| 6 | +} |
| 7 | + |
| 8 | +/** |
| 9 | + * Protects Regor {{ ... }} expressions while the surrounding markup is |
| 10 | + * formatted as HTML. The expression source is restored byte-for-byte. |
| 11 | + */ |
| 12 | +export function maskRegorExpressions(source: string) { |
| 13 | + const expressions: RegorExpressionPlaceholder[] = [] |
| 14 | + let placeholderContent = '' |
| 15 | + let cursor = 0 |
| 16 | + |
| 17 | + while (cursor < source.length) { |
| 18 | + const start = source.indexOf('{{', cursor) |
| 19 | + if (start === -1) { |
| 20 | + placeholderContent += source.slice(cursor) |
| 21 | + break |
| 22 | + } |
| 23 | + |
| 24 | + placeholderContent += source.slice(cursor, start) |
| 25 | + |
| 26 | + const end = findRegorExpressionEnd(source, start) |
| 27 | + if (end === -1) { |
| 28 | + // Keep incomplete input untouched rather than guessing its boundary. |
| 29 | + placeholderContent += source.slice(start) |
| 30 | + break |
| 31 | + } |
| 32 | + |
| 33 | + const placeholder = createPlaceholder(source, expressions.length) |
| 34 | + expressions.push({ placeholder, source: source.slice(start, end) }) |
| 35 | + placeholderContent += placeholder |
| 36 | + cursor = end |
| 37 | + } |
| 38 | + |
| 39 | + return { expressions, placeholderContent } |
| 40 | +} |
| 41 | + |
| 42 | +export function restoreRegorExpressions( |
| 43 | + formatted: string, |
| 44 | + expressions: RegorExpressionPlaceholder[], |
| 45 | +) { |
| 46 | + let restored = formatted |
| 47 | + |
| 48 | + for (const expression of expressions) { |
| 49 | + restored = restored.split(expression.placeholder).join(expression.source) |
| 50 | + } |
| 51 | + |
| 52 | + return restored |
| 53 | +} |
| 54 | + |
| 55 | +function findRegorExpressionEnd(source: string, start: number) { |
| 56 | + // Reuse the expression scanner already used by MDX. Starting at the first |
| 57 | + // brace makes {{ ... }} two outer brace levels, so nested object literals, |
| 58 | + // quoted braces and template-literal expressions remain balanced correctly. |
| 59 | + const end = findMdxExpressionEnd(source, start) |
| 60 | + if (end === -1) return -1 |
| 61 | + |
| 62 | + return source.slice(end - 2, end) === '}}' ? end : -1 |
| 63 | +} |
| 64 | + |
| 65 | +function createPlaceholder(source: string, index: number) { |
| 66 | + let placeholder = `__PURESTACK_REGOR_EXPR_${index}__` |
| 67 | + |
| 68 | + while (source.includes(placeholder)) { |
| 69 | + placeholder = `_${placeholder}_` |
| 70 | + } |
| 71 | + |
| 72 | + return placeholder |
| 73 | +} |
0 commit comments