@@ -114,25 +114,7 @@ function format(options) {
114114 }
115115
116116 if ( is ( 'comment' , node ) ) {
117- /**
118- * indent last line of comment
119- * e.g
120- * <!--
121- * foo
122- * -->
123- * to
124- * <!--
125- * foo
126- * -->
127- */
128-
129- let commentLines = node . value . split ( single )
130- if ( commentLines . length > 1 ) {
131- commentLines [ commentLines . length - 1 ] =
132- repeat ( indent , level - 1 ) +
133- commentLines [ commentLines . length - 1 ] . trim ( )
134- node . value = commentLines . join ( single )
135- }
117+ indentComment ( node , indent , level )
136118 }
137119
138120 /**
@@ -144,7 +126,7 @@ function format(options) {
144126
145127 // clear empty script, textarea, pre, style tags
146128 if ( length ) {
147- const empty = containsOnlyEmptyTextNodes ( node )
129+ const empty = hasOnlyEmptyTextChildren ( node )
148130 const isEmbeddedContent =
149131 isElement ( node , 'style' ) || isElement ( node , 'script' )
150132 if ( empty ) {
@@ -158,6 +140,9 @@ function format(options) {
158140 return visit . SKIP
159141 }
160142
143+ let newline = false
144+ const willBreak = collapsed ( node , children )
145+
161146 /**
162147 * Indent children
163148 */
@@ -166,12 +151,16 @@ function format(options) {
166151 let child = children [ index ]
167152
168153 // only indent text in nodes
169- // root text nodes should't influence other root nodes
154+ // root text nodes should't influence other root nodes^^
170155 if ( node . type === 'root' ) {
171156 break
172157 }
173158
174159 if ( is ( 'text' , child ) ) {
160+ if ( containsNewline ( child ) || willBreak ) {
161+ newline = true
162+ }
163+
175164 child . value = child . value
176165 // reduce newlines to one newline
177166 // $& contains the lastMatch
@@ -182,12 +171,9 @@ function format(options) {
182171 // reset
183172 result = [ ]
184173 index = - 1
185-
186174 node . children = result
187175
188- let prevChild
189- let hasLeadingNewline = false
190-
176+ let prevChild = null
191177 if ( length ) {
192178 // walk through children
193179 // hint: a child has no children informations we already walking through
@@ -215,25 +201,14 @@ function format(options) {
215201 value : double + repeat ( indent , indentLevel )
216202 } )
217203 } else if (
218- ! endsWithNewline ( prevChild ) &&
219- beforeChildNodeAddedHook ( node , children , child , index , prevChild )
204+ beforeChildNodeAddedHook ( node , children , child , index , prevChild ) ||
205+ ( newline && index === 0 )
220206 ) {
221- // all template expression are indented on a ewline thats why need to check
222- // so that we don't add another one
223- if ( index === 0 && checkForTemplateExpression ( child . value ) ) {
224- hasLeadingNewline = true
225- }
226207 // only necessary because we are trying to indent tags on newlines
227208 // even when in inline context when possible
228209 if ( is ( 'text' , prevChild ) ) {
229210 // remove trailing whitespaces and tabs because a newline is inserted before
230211 prevChild . value = prevChild . value . replace ( / [ \t ] + $ / , '' )
231-
232- // adds a leading newline because the sibling node is indented on a newline
233- if ( index === 1 && hasLeadingNewline === false ) {
234- prevChild . value =
235- single + repeat ( indent , indentLevel ) + prevChild . value
236- }
237212 }
238213 // remove leading whitespaces and tabs because a newline is inserted before
239214 if ( is ( 'text' , child ) ) {
@@ -245,23 +220,14 @@ function format(options) {
245220 value : single + repeat ( indent , indentLevel )
246221 } )
247222 }
248- // adds a leading newline when he sibling element was inteded on a newline and when no newlines was added
249- else if (
250- is ( 'text' , child ) &&
251- hasLeadingNewline === false &&
252- endsWithNewline ( child ) &&
253- ! startsWithNewline ( child )
254- ) {
255- child . value = single + repeat ( indent , indentLevel ) + child . value
256- }
257223
258224 prevChild = child
259225
260226 result . push ( child )
261227 }
262228 }
263229
264- if ( afterChildNodesAddedHook ( node , prevChild ) ) {
230+ if ( afterChildNodesAddedHook ( node , prevChild ) || newline ) {
265231 result . push ( {
266232 type : 'text' ,
267233 value : single + repeat ( indent , level - 1 )
@@ -279,12 +245,36 @@ function startsWithNewline(node) {
279245 return is ( 'text' , node ) && node . value && / ^ \s * \n / . test ( node . value )
280246}
281247
248+ function containsNewline ( node ) {
249+ return node . value . indexOf ( single ) !== - 1
250+ }
251+
252+ /**
253+ * indent last line of comment
254+ * e.g
255+ * <!--
256+ * foo
257+ * -->
258+ * to
259+ * <!--
260+ * foo
261+ * -->
262+ */
263+ function indentComment ( node , indent , level ) {
264+ let commentLines = node . value . split ( single )
265+ if ( commentLines . length > 1 ) {
266+ commentLines [ commentLines . length - 1 ] =
267+ repeat ( indent , level - 1 ) + commentLines [ commentLines . length - 1 ] . trim ( )
268+ node . value = commentLines . join ( single )
269+ }
270+ }
271+
282272function handleTemplateExpression ( child , children ) {
283273 const brackets = checkForTemplateExpression ( child . value )
284274 if ( brackets ) {
285275 // dont touch nodes with single text element
286276 if (
287- containsOnlyTextNodes ( {
277+ hasOnlyTextChildren ( {
288278 children
289279 } )
290280 ) {
@@ -300,7 +290,30 @@ function handleTemplateExpression(child, children) {
300290 }
301291}
302292
293+ /**
294+ * Check if any children will be wrapped on a newline
295+ * @param {* } node
296+ * @param {* } children
297+ */
298+ function collapsed ( node , children ) {
299+ let index = - 1
300+ let prevChild = false
301+ while ( ++ index < children . length ) {
302+ let child = children [ index ]
303+ if ( beforeChildNodeAddedHook ( node , children , child , index , prevChild ) ) {
304+ return true
305+ }
306+ prevChild = child
307+ }
308+ }
309+
303310function beforeChildNodeAddedHook ( node , children , child , index , prev ) {
311+ // don't add newline when prev child already has one
312+ if ( endsWithNewline ( prev ) ) {
313+ return false
314+ }
315+
316+ // every template expression is indented on a newline
304317 if ( is ( 'text' , child ) && handleTemplateExpression ( child , children ) ) {
305318 return true
306319 }
@@ -310,11 +323,12 @@ function beforeChildNodeAddedHook(node, children, child, index, prev) {
310323 return true
311324 }
312325
326+ // embedded content is indented on newlines
313327 if ( isElement ( child , [ 'script' , 'style' ] ) && index !== 0 ) {
314328 return true
315329 }
316330
317- // don't add newline on the first element
331+ // don't add newline on the first element of the page
318332 const isRootElement = node . type === 'root' && index === 0
319333 if ( isRootElement ) {
320334 return false
@@ -336,15 +350,15 @@ function afterChildNodesAddedHook(node, prev) {
336350 /**
337351 * e.g <label><input/>foo</label>
338352 */
339- if ( hasChilds && ! containsOnlyTextNodes ( node ) && ! isVoid ( node ) ) {
353+ if ( hasChilds && ! hasOnlyTextChildren ( node ) && ! isVoid ( node ) ) {
340354 return true
341355 }
342356
343357 /**
344358 * e.g <label>foo</label>
345359 */
346- const isPrevRawText = is ( 'text' , prev )
347- return hasChilds && ! isVoid ( node ) && ! isPrevRawText
360+ const isPrevTextNode = is ( 'text' , prev )
361+ return hasChilds && ! isVoid ( node ) && ! isPrevTextNode
348362}
349363
350364function checkForTemplateExpression ( value ) {
@@ -363,7 +377,7 @@ function checkForTemplateExpression(value) {
363377 return null
364378}
365379
366- function containsOnlyTextNodes ( node ) {
380+ function hasOnlyTextChildren ( node ) {
367381 const children = node . children || [ ]
368382
369383 if ( children . length === 0 ) {
@@ -373,7 +387,7 @@ function containsOnlyTextNodes(node) {
373387 return children . every ( n => is ( 'text' , n ) )
374388}
375389
376- function containsOnlyEmptyTextNodes ( node ) {
390+ function hasOnlyEmptyTextChildren ( node ) {
377391 const children = node . children || [ ]
378392
379393 if ( children . length === 0 ) {
0 commit comments