mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-02-24 04:30:53 -06:00
Merge pull request #10782 from Microsoft/react_whitespace
For JSX text, construct a single literal node `"foo bar"` instead of `"foo" + " " + "bar"`
This commit is contained in:
commit
91af4ae6b3
@ -160,33 +160,50 @@ namespace ts {
|
||||
}
|
||||
}
|
||||
|
||||
function visitJsxText(node: JsxText) {
|
||||
const text = getTextOfNode(node, /*includeTrivia*/ true);
|
||||
let parts: Expression[];
|
||||
let firstNonWhitespace = 0;
|
||||
let lastNonWhitespace = -1;
|
||||
function visitJsxText(node: JsxText): StringLiteral | undefined {
|
||||
const fixed = fixupWhitespaceAndDecodeEntities(getTextOfNode(node, /*includeTrivia*/ true));
|
||||
return fixed === undefined ? undefined : createLiteral(fixed);
|
||||
}
|
||||
|
||||
/**
|
||||
* JSX trims whitespace at the end and beginning of lines, except that the
|
||||
* start/end of a tag is considered a start/end of a line only if that line is
|
||||
* on the same line as the closing tag. See examples in
|
||||
* tests/cases/conformance/jsx/tsxReactEmitWhitespace.tsx
|
||||
* 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
|
||||
*
|
||||
* An equivalent algorithm would be:
|
||||
* - If there is only one line, return it.
|
||||
* - If there is only whitespace (but multiple lines), return `undefined`.
|
||||
* - Split the text into lines.
|
||||
* - 'trimRight' the first line, 'trimLeft' the last line, 'trim' middle lines.
|
||||
* - Decode entities on each line (individually).
|
||||
* - Remove empty lines and join the rest with " ".
|
||||
*/
|
||||
function fixupWhitespaceAndDecodeEntities(text: string): string | undefined {
|
||||
let acc: string | undefined;
|
||||
// First non-whitespace character on this line.
|
||||
let firstNonWhitespace = 0;
|
||||
// Last non-whitespace character on this line.
|
||||
let lastNonWhitespace = -1;
|
||||
// These initial values are special because the first line is:
|
||||
// firstNonWhitespace = 0 to indicate that we want leading whitsepace,
|
||||
// but lastNonWhitespace = -1 as a special flag to indicate that we *don't* include the line if it's all whitespace.
|
||||
|
||||
// JSX trims whitespace at the end and beginning of lines, except that the
|
||||
// start/end of a tag is considered a start/end of a line only if that line is
|
||||
// on the same line as the closing tag. See examples in
|
||||
// tests/cases/conformance/jsx/tsxReactEmitWhitespace.tsx
|
||||
for (let i = 0; i < text.length; i++) {
|
||||
const c = text.charCodeAt(i);
|
||||
if (isLineBreak(c)) {
|
||||
if (firstNonWhitespace !== -1 && (lastNonWhitespace - firstNonWhitespace + 1 > 0)) {
|
||||
const part = text.substr(firstNonWhitespace, lastNonWhitespace - firstNonWhitespace + 1);
|
||||
if (!parts) {
|
||||
parts = [];
|
||||
}
|
||||
|
||||
// We do not escape the string here as that is handled by the printer
|
||||
// when it emits the literal. We do, however, need to decode JSX entities.
|
||||
parts.push(createLiteral(decodeEntities(part)));
|
||||
// If we've seen any non-whitespace characters on this line, add the 'trim' of the line.
|
||||
// (lastNonWhitespace === -1 is a special flag to detect whether the first line is all whitespace.)
|
||||
if (firstNonWhitespace !== -1 && lastNonWhitespace !== -1) {
|
||||
acc = addLineOfJsxText(acc, text.substr(firstNonWhitespace, lastNonWhitespace - firstNonWhitespace + 1));
|
||||
}
|
||||
|
||||
// Reset firstNonWhitespace for the next line.
|
||||
// Don't bother to reset lastNonWhitespace because we ignore it if firstNonWhitespace = -1.
|
||||
firstNonWhitespace = -1;
|
||||
}
|
||||
else if (!isWhiteSpace(c)) {
|
||||
else if (!isWhiteSpaceSingleLine(c)) {
|
||||
lastNonWhitespace = i;
|
||||
if (firstNonWhitespace === -1) {
|
||||
firstNonWhitespace = i;
|
||||
@ -194,29 +211,18 @@ namespace ts {
|
||||
}
|
||||
}
|
||||
|
||||
if (firstNonWhitespace !== -1) {
|
||||
const part = text.substr(firstNonWhitespace);
|
||||
if (!parts) {
|
||||
parts = [];
|
||||
}
|
||||
|
||||
// We do not escape the string here as that is handled by the printer
|
||||
// when it emits the literal. We do, however, need to decode JSX entities.
|
||||
parts.push(createLiteral(decodeEntities(part)));
|
||||
}
|
||||
|
||||
if (parts) {
|
||||
return reduceLeft(parts, aggregateJsxTextParts);
|
||||
}
|
||||
|
||||
return undefined;
|
||||
return firstNonWhitespace !== -1
|
||||
// Last line had a non-whitespace character. Emit the 'trimLeft', meaning keep trailing whitespace.
|
||||
? addLineOfJsxText(acc, text.substr(firstNonWhitespace))
|
||||
// Last line was all whitespace, so ignore it
|
||||
: acc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Aggregates two expressions by interpolating them with a whitespace literal.
|
||||
*/
|
||||
function aggregateJsxTextParts(left: Expression, right: Expression) {
|
||||
return createAdd(createAdd(left, createLiteral(" ")), right);
|
||||
function addLineOfJsxText(acc: string | undefined, trimmedLine: string): string {
|
||||
// We do not escape the string here as that is handled by the printer
|
||||
// when it emits the literal. We do, however, need to decode JSX entities.
|
||||
const decoded = decodeEntities(trimmedLine);
|
||||
return acc === undefined ? decoded : acc + " " + decoded;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -41,7 +41,7 @@ var p = 0;
|
||||
<div>
|
||||
</div>;
|
||||
|
||||
// Emit "foo" + ' ' + "bar"
|
||||
// Emit "foo bar"
|
||||
<div>
|
||||
|
||||
foo
|
||||
@ -50,6 +50,18 @@ var p = 0;
|
||||
|
||||
</div>;
|
||||
|
||||
// Emit "hello\\ world"
|
||||
<div>
|
||||
|
||||
hello\
|
||||
|
||||
world
|
||||
</div>;
|
||||
|
||||
// Emit " a b c d "
|
||||
<div> a
|
||||
b c
|
||||
d </div>;
|
||||
|
||||
|
||||
//// [file.js]
|
||||
@ -75,5 +87,9 @@ React.createElement("div", null, " 3 ");
|
||||
React.createElement("div", null, "3");
|
||||
// Emit no args
|
||||
React.createElement("div", null);
|
||||
// Emit "foo" + ' ' + "bar"
|
||||
React.createElement("div", null, "foo" + " " + "bar");
|
||||
// Emit "foo bar"
|
||||
React.createElement("div", null, "foo bar");
|
||||
// Emit "hello\\ world"
|
||||
React.createElement("div", null, "hello\\ world");
|
||||
// Emit " a b c d "
|
||||
React.createElement("div", null, " a b c d ");
|
||||
|
||||
@ -79,7 +79,7 @@ var p = 0;
|
||||
</div>;
|
||||
>div : Symbol(JSX.IntrinsicElements, Decl(file.tsx, 1, 22))
|
||||
|
||||
// Emit "foo" + ' ' + "bar"
|
||||
// Emit "foo bar"
|
||||
<div>
|
||||
>div : Symbol(JSX.IntrinsicElements, Decl(file.tsx, 1, 22))
|
||||
|
||||
@ -90,4 +90,21 @@ var p = 0;
|
||||
</div>;
|
||||
>div : Symbol(JSX.IntrinsicElements, Decl(file.tsx, 1, 22))
|
||||
|
||||
// Emit "hello\\ world"
|
||||
<div>
|
||||
>div : Symbol(JSX.IntrinsicElements, Decl(file.tsx, 1, 22))
|
||||
|
||||
hello\
|
||||
|
||||
world
|
||||
</div>;
|
||||
>div : Symbol(JSX.IntrinsicElements, Decl(file.tsx, 1, 22))
|
||||
|
||||
// Emit " a b c d "
|
||||
<div> a
|
||||
>div : Symbol(JSX.IntrinsicElements, Decl(file.tsx, 1, 22))
|
||||
|
||||
b c
|
||||
d </div>;
|
||||
>div : Symbol(JSX.IntrinsicElements, Decl(file.tsx, 1, 22))
|
||||
|
||||
|
||||
@ -88,7 +88,7 @@ var p = 0;
|
||||
</div>;
|
||||
>div : any
|
||||
|
||||
// Emit "foo" + ' ' + "bar"
|
||||
// Emit "foo bar"
|
||||
<div>
|
||||
><div> foo bar </div> : JSX.Element
|
||||
>div : any
|
||||
@ -100,4 +100,23 @@ var p = 0;
|
||||
</div>;
|
||||
>div : any
|
||||
|
||||
// Emit "hello\\ world"
|
||||
<div>
|
||||
><div> hello\world</div> : JSX.Element
|
||||
>div : any
|
||||
|
||||
hello\
|
||||
|
||||
world
|
||||
</div>;
|
||||
>div : any
|
||||
|
||||
// Emit " a b c d "
|
||||
<div> a
|
||||
><div> a b c d </div> : JSX.Element
|
||||
>div : any
|
||||
|
||||
b c
|
||||
d </div>;
|
||||
>div : any
|
||||
|
||||
|
||||
@ -42,7 +42,7 @@ var p = 0;
|
||||
<div>
|
||||
</div>;
|
||||
|
||||
// Emit "foo" + ' ' + "bar"
|
||||
// Emit "foo bar"
|
||||
<div>
|
||||
|
||||
foo
|
||||
@ -51,3 +51,15 @@ var p = 0;
|
||||
|
||||
</div>;
|
||||
|
||||
// Emit "hello\\ world"
|
||||
<div>
|
||||
|
||||
hello\
|
||||
|
||||
world
|
||||
</div>;
|
||||
|
||||
// Emit " a b c d "
|
||||
<div> a
|
||||
b c
|
||||
d </div>;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user