@@ -162,37 +162,58 @@ namespace ts {
162162 * on the same line as the closing tag. See examples in
163163 * tests/cases/conformance/jsx/tsxReactEmitWhitespace.tsx
164164 * See also https://www.w3.org/TR/html4/struct/text.html#h-9.1 and https://www.w3.org/TR/CSS2/text.html#white-space-model
165+ *
166+ * An equivalent algorithm would be:
167+ * - If there is only one line, return it.
168+ * - If there is only whitespace (but multiple lines), return `undefined`.
169+ * - Split the text into lines.
170+ * - 'trimRight' the first line, 'trimLeft' the last line, 'trim' middle lines.
171+ * - Decode entities on each line (individually).
172+ * - Remove empty lines and join the rest with " ".
165173 */
166174 function fixupWhitespaceAndDecodeEntities ( text : string ) : string | undefined {
167175 let acc : string | undefined ;
176+ // First non-whitespace character on this line.
168177 let firstNonWhitespace = 0 ;
178+ // Last non-whitespace character on this line.
169179 let lastNonWhitespace = - 1 ;
180+ // These initial values are special because the first line is:
181+ // firstNonWhitespace = 0 to indicate that we want leading whitsepace,
182+ // but lastNonWhitespace = -1 as a special flag to indicate that we *don't* include the line if it's all whitespace.
170183
171184 for ( let i = 0 ; i < text . length ; i ++ ) {
172185 const c = text . charCodeAt ( i ) ;
173186 if ( isLineBreak ( c ) ) {
174- if ( firstNonWhitespace !== - 1 && ( lastNonWhitespace - firstNonWhitespace + 1 > 0 ) ) {
175- const part = decodeEntities ( text . substr ( firstNonWhitespace , lastNonWhitespace - firstNonWhitespace + 1 ) ) ;
176- acc = acc === undefined ? part : acc + " " + part ;
187+ // If we've seen any non-whitespace characters on this line, add the 'trim' of the line.
188+ // (lastNonWhitespace === -1 is a special flag to detect whether the first line is all whitespace.)
189+ if ( firstNonWhitespace !== - 1 && lastNonWhitespace !== - 1 ) {
190+ acc = addLineOfJsxText ( acc , text . substr ( firstNonWhitespace , lastNonWhitespace - firstNonWhitespace + 1 ) ) ;
177191 }
178192
193+ // Reset firstNonWhitespace for the next line.
194+ // Don't bother to reset lastNonWhitespace because we ignore it if firstNonWhitespace = -1.
179195 firstNonWhitespace = - 1 ;
180196 }
181- else if ( ! isWhiteSpace ( c ) ) {
197+ else if ( ! isWhiteSpaceSingleLine ( c ) ) {
182198 lastNonWhitespace = i ;
183199 if ( firstNonWhitespace === - 1 ) {
184200 firstNonWhitespace = i ;
185201 }
186202 }
187203 }
188204
189- if ( firstNonWhitespace !== - 1 ) {
190- const lastPart = decodeEntities ( text . substr ( firstNonWhitespace ) ) ;
191- return acc ? acc + lastPart : lastPart ;
192- }
193- else {
194- return acc ;
195- }
205+ return firstNonWhitespace !== - 1
206+ // Last line had a non-whitespace character. Emit the 'trimLeft', meaning keep trailing whitespace.
207+ ? addLineOfJsxText ( acc , text . substr ( firstNonWhitespace ) )
208+ // Last line was all whitespace, so ignore it
209+ : acc ;
210+ }
211+
212+ function addLineOfJsxText ( acc : string | undefined , trimmedLine : string ) : string {
213+ // We do not escape the string here as that is handled by the printer
214+ // when it emits the literal. We do, however, need to decode JSX entities.
215+ const decoded = decodeEntities ( trimmedLine ) ;
216+ return acc === undefined ? decoded : acc + " " + decoded ;
196217 }
197218
198219 /**
0 commit comments