@@ -195,39 +195,77 @@ function splitInlineTags(content: string): string {
195195 // Split on tags, but keep mixed text/tag content together
196196 let result = content
197197
198+ // Protect whitespace-preserving tags (pre, code, textarea) from splitting
199+ const protectedBlocks : string [ ] = [ ]
200+ const placeholder = '__PROTECTED_BLOCK_'
201+ result = result . replace ( / ( < (?: p r e | c o d e | t e x t a r e a ) [ ^ > ] * > ) ( [ \s \S ] * ?) ( < \/ (?: p r e | c o d e | t e x t a r e a ) > ) / gi, ( match ) => {
202+ const index = protectedBlocks . length
203+ protectedBlocks . push ( match )
204+ return `${ placeholder } ${ index } __`
205+ } )
206+
198207 // Split directives onto their own lines FIRST (before other processing)
199- // Split opening block directives
200- result = result . replace ( / ( @ (?: i f | f o r e a c h | f o r | w h i l e | u n l e s s | s e c t i o n | c o m p o n e n t | s l o t | p u s h | p r e p e n d ) (?: \( [ ^ ) ] * \) ) ? ) / g, '\n$1\n' )
208+ // Split opening block directives - need to handle nested parentheses and quotes
209+ result = result . replace ( / ( @ (?: i f | f o r e a c h | f o r | w h i l e | u n l e s s | s e c t i o n | c o m p o n e n t | s l o t | p u s h | p r e p e n d | e r r o r | a u t h | g u e s t | c a n ) (?: \( (?: [ ^ ( ) ' " ] | ' [ ^ ' ] * ' | " [ ^ " ] * " | \( (?: [ ^ ( ) ' " ] | ' [ ^ ' ] * ' | " [ ^ " ] * " ) * \) ) * \) ) ? ) / g, '\n$1\n' )
201210 // Split closing directives
202211 result = result . replace ( / ( @ e n d \w + ) / g, '\n$1\n' )
203- // Split middle directives
204- result = result . replace ( / ( @ (?: e l s e | e l s e i f ) (?: \( [ ^ ) ] * \) ) ? ) / g, '\n$1\n' )
212+ // Split middle directives - same pattern for nested parens/quotes
213+ result = result . replace ( / ( @ (?: e l s e | e l s e i f | e m p t y ) (?: \( (?: [ ^ ( ) ' " ] | ' [ ^ ' ] * ' | " [ ^ " ] * " | \( (?: [ ^ ( ) ' " ] | ' [ ^ ' ] * ' | " [ ^ " ] * " ) * \) ) * \) ) ? ) / g, '\n$1\n' )
214+ // Split standalone directives (csrf, method, etc.)
215+ result = result . replace ( / ( @ (?: c s r f | m e t h o d | i n c l u d e | e x t e n d s | y i e l d | s t a c k | v i t e | a s s e t ) (?: \( [ ^ ) ] * \) ) ? ) / g, '\n$1\n' )
205216
206217 // Split opening tags onto new lines (unless there's text before them)
207- result = result . replace ( / (?< = > ) ( < (? ! \/ ) [ ^ > ] + > ) / g, '\n$1' )
208-
209- // Split closing tags onto new lines (unless there's text after them on same line)
210- result = result . replace ( / ( < \/ [ ^ > ] + > ) (? = < ) / g, '$1\n' )
218+ // But not for empty self-closing elements
219+ result = result . replace ( / (?< = > ) ( < (? ! \/ ) [ ^ > ] + > ) / g, ( match , _p1 , offset , string ) => {
220+ // Don't split if this is immediately followed by its closing tag (empty element)
221+ const after = string . substring ( offset + match . length , Math . min ( string . length , offset + match . length + 20 ) )
222+ const tagName = match . match ( / < ( \w + ) / ) ?. [ 1 ]
223+ if ( tagName && after . match ( new RegExp ( `^<\/${ tagName } >` ) ) ) {
224+ return match // Keep empty elements together
225+ }
226+ return `\n${ match } `
227+ } )
211228
212229 // Also split before closing tags if there's an opening tag before
213230 // BUT don't split when it's an empty element (opening immediately followed by closing)
214- result = result . replace ( / ( > ) ( < \/ ) (? ! [ \w \s ] * > $ ) / g, ( match , gt , closeTag , offset , string ) => {
231+ result = result . replace ( / ( > ) ( < \/ ) / g, ( match , gt , closeTag , offset , string ) => {
215232 // Check if this is part of an empty element pattern like <tag></tag>
216233 const before = string . substring ( Math . max ( 0 , offset - 50 ) , offset )
217234 const after = string . substring ( offset + match . length , Math . min ( string . length , offset + match . length + 20 ) )
218235
219- // If the closing tag immediately follows opening (empty element), don't split
220- if ( before . match ( / < \w [ ^ > ] * > $ / ) && after . match ( / ^ \w [ ^ > ] * > / ) ) {
221- // eslint-disable-next-line regexp/optimal-quantifier-concatenation
222- const openTagName = before . match ( / < ( \w + ) [ ^ > ] * > $ / ) ?. [ 1 ]
223- const closeTagName = after . match ( / ^ ( \w + ) > / ) ?. [ 1 ]
224- if ( openTagName === closeTagName ) {
225- return match // Keep empty elements together
226- }
236+ // Extract tag name from the part after >< /
237+ // after looks like "script>" or "div>" etc (without the </)
238+ const closeTagName = after . match ( / ^ ( \w + ) > / ) ?. [ 1 ]
239+ // Extract tag name from the opening tag immediately before > (before doesn't include the >)
240+ // eslint-disable-next-line regexp/optimal-quantifier-concatenation
241+ const openTagName = before . match ( / < ( \w + ) [ ^ > ] * $ / ) ?. [ 1 ]
242+
243+ // If the closing tag immediately follows opening tag with same name (empty element), don't split
244+ if ( openTagName && closeTagName && openTagName === closeTagName ) {
245+ return match // Keep empty elements together
227246 }
228247 return `${ gt } \n${ closeTag } `
229248 } )
230249
250+ // Split closing tags onto new lines (when followed by another tag or at end of content)
251+ // But skip empty elements (opening tag immediately followed by closing with no content)
252+ result = result . replace ( / ( < \/ [ ^ > ] + > ) (? = < | $ ) / g, ( match , _p1 , offset , string ) => {
253+ // Check if this closing tag is part of an empty element (e.g., <script></script>)
254+ const before = string . substring ( Math . max ( 0 , offset - 20 ) , offset )
255+ const tagName = match . match ( / < \/ ( \w + ) > / ) ?. [ 1 ]
256+ // Only skip newline if opening tag is immediately before closing tag (empty element)
257+ const emptyPattern = new RegExp ( `<${ tagName } [^>]*>$` )
258+ if ( tagName && before . match ( emptyPattern ) ) {
259+ return match // Don't add newline for empty elements
260+ }
261+ return `${ match } \n`
262+ } )
263+
264+ // Restore protected blocks
265+ result = result . replace ( new RegExp ( `${ placeholder } (\\d+)__` , 'g' ) , ( _match , index ) => {
266+ return protectedBlocks [ Number . parseInt ( index , 10 ) ]
267+ } )
268+
231269 // Clean up multiple consecutive newlines
232270 result = result . replace ( / \n { 2 , } / g, '\n' )
233271
0 commit comments