Fixed visitJsxText, plus PR Feedback

This commit is contained in:
Ron Buckton 2016-03-01 15:16:41 -08:00
parent a7f9cda7bf
commit 7d05ba28bf
3 changed files with 217 additions and 130 deletions

View File

@ -172,25 +172,101 @@ namespace ts {
}
/**
* Maps an array. If the mapped value is an array, it is spread into the result.
* Flattens an array containing a mix of array or non-array elements.
*
* @param array The array to flatten.
*/
export function flatMap<T, U>(array: T[], f: (x: T, i: number) => U | U[]): U[] {
export function flatten<T>(array: (T | T[])[]): T[] {
let result: T[];
if (array) {
result = [];
for (const v of array) {
if (v) {
if (isArray(v)) {
addRange(result, v);
}
else {
result.push(v);
}
}
}
}
return result;
}
/**
* Maps an array. If the mapped value is an array, it is spread into the result.
*
* @param array The array to map.
* @param mapfn The callback used to map the result into one or more values.
*/
export function flatMap<T, U>(array: T[], mapfn: (x: T, i: number) => U | U[]): U[] {
let result: U[];
if (array) {
result = [];
for (let i = 0; i < array.length; i++) {
const v = array[i];
const ar = f(v, i);
if (ar) {
// We cast to <U> here to leverage the behavior of Array#concat
// which will append a single value here.
result = result.concat(<U[]>ar);
const v = mapfn(array[i], i);
if (v) {
if (isArray(v)) {
addRange(result, v);
}
else {
result.push(v);
}
}
}
}
return result;
}
/**
* Maps contiguous spans of values with the same key.
*
* @param array The array to map.
* @param keyfn A callback used to select the key for an element.
* @param mapfn A callback used to map a contiguous chunk of values to a single value.
*/
export function spanMap<T, K, U>(array: T[], keyfn: (x: T, i: number) => K, mapfn: (chunk: T[], key: K) => U): U[] {
let result: U[];
if (array) {
result = [];
const len = array.length;
let previousKey: K;
let key: K;
let start = 0;
let pos = 0;
while (start < len) {
while (pos < len) {
const value = array[pos];
key = keyfn(value, pos);
if (pos === 0) {
previousKey = key;
}
else if (key !== previousKey) {
break;
}
pos++;
}
if (start < pos) {
const v = mapfn(array.slice(start, pos), previousKey);
if (v) {
result.push(v);
}
start = pos;
}
previousKey = key;
pos++;
}
}
return result;
}
export function concatenate<T>(array1: T[], array2: T[]): T[] {
if (!array2 || !array2.length) return array1;
if (!array1 || !array1.length) return array2;

View File

@ -9,6 +9,11 @@ namespace ts {
const compilerOptions = context.getCompilerOptions();
return transformSourceFile;
/**
* Transform JSX-specific syntax in a SourceFile.
*
* @param node A SourceFile node.
*/
function transformSourceFile(node: SourceFile) {
return visitEachChild(node, visitor, context);
}
@ -64,7 +69,6 @@ namespace ts {
}
function visitJsxOpeningLikeElement(node: JsxOpeningLikeElement, children: JsxChild[]) {
// We must the node onto the node stack if it is not already at the top.
const tagName = getTagName(node);
let objectProperties: Expression;
if (node.attributes.length === 0) {
@ -75,41 +79,35 @@ namespace ts {
// Either emit one big object literal (no spread attribs), or
// a call to React.__spread
const attrs = node.attributes;
if (forEach(attrs, isJsxSpreadAttribute)) {
const segments: Expression[] = [];
let properties: ObjectLiteralElement[] = [];
for (const attr of attrs) {
if (isJsxSpreadAttribute(attr)) {
if (properties) {
segments.push(createObjectLiteral(properties));
properties = undefined;
}
addNode(segments, transformJsxSpreadAttributeToExpression(attr));
}
else {
if (!properties) {
properties = [];
}
addNode(properties, transformJsxAttributeToObjectLiteralElement(attr));
}
}
if (properties) {
segments.push(createObjectLiteral(properties));
}
objectProperties = createJsxSpread(compilerOptions.reactNamespace, segments);
if (!forEach(attrs, isJsxSpreadAttribute)) {
objectProperties = createObjectLiteral(map(node.attributes, transformJsxAttributeToObjectLiteralElement));
}
else {
const properties = map(node.attributes, transformJsxAttributeToObjectLiteralElement);
objectProperties = createObjectLiteral(properties);
objectProperties = createJsxSpread(compilerOptions.reactNamespace,
concatenate(
// We must always emit at least one object literal before a spread
// argument.
isJsxSpreadAttribute(attrs[0]) ? [createObjectLiteral()] : undefined,
// Map spans of JsxAttribute nodes into object literals and spans
// of JsxSpreadAttribute nodes into expressions.
flatten(
spanMap(attrs, isJsxSpreadAttribute, (attrs, isSpread) => isSpread
? map(attrs, transformJsxSpreadAttributeToExpression)
: createObjectLiteral(map(attrs, transformJsxAttributeToObjectLiteralElement))
)
)
)
);
}
}
const childExpressions = filter(map(children, transformJsxChildToExpression), isDefined);
return createJsxCreateElement(compilerOptions.reactNamespace, tagName, objectProperties, childExpressions);
return createJsxCreateElement(
compilerOptions.reactNamespace,
tagName,
objectProperties,
filter(map(children, transformJsxChildToExpression), isDefined)
);
}
function transformJsxSpreadAttributeToExpression(node: JsxSpreadAttribute) {
@ -125,39 +123,29 @@ namespace ts {
}
function visitJsxText(node: JsxText) {
const text = getTextToEmit(node);
if (text !== undefined) {
return createLiteral(text);
}
return undefined;
}
function getTextToEmit(node: JsxText) {
const text = trimReactWhitespaceAndApplyEntities(node);
if (text === undefined || text.length === 0) {
return undefined;
}
else {
return text;
}
}
function trimReactWhitespaceAndApplyEntities(node: JsxText): string {
const text = getTextOfNode(node, /*includeTrivia*/ true);
let result: string = undefined;
let parts: Expression[];
let firstNonWhitespace = 0;
let lastNonWhitespace = -1;
// 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
// 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);
result = (result ? result + "\" + ' ' + \"" : "") + part;
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)));
}
firstNonWhitespace = -1;
}
else if (!isWhiteSpace(c)) {
@ -170,22 +158,41 @@ namespace ts {
if (firstNonWhitespace !== -1) {
const part = text.substr(firstNonWhitespace);
result = (result ? result + "\" + ' ' + \"" : "") + part;
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 (result) {
// Replace entities like &nbsp;
result = result.replace(/&(\w+);/g, function(s: any, m: string) {
if (entities[m] !== undefined) {
return String.fromCharCode(entities[m]);
}
else {
return s;
}
});
if (parts) {
return reduceLeft(parts, aggregateJsxTextParts);
}
return result;
return undefined;
}
/**
* Aggregates two expressions by interpolating them with a whitespace literal.
*/
function aggregateJsxTextParts(left: Expression, right: Expression) {
return createAdd(createAdd(left, createLiteral(" ")), right);
}
/**
* Decodes JSX entities.
*/
function decodeEntities(text: string) {
return text.replace(/&(\w+);/g, function(s: any, m: string) {
if (entities[m] !== undefined) {
return String.fromCharCode(entities[m]);
}
else {
return s;
}
});
}
function getTagName(node: JsxElement | JsxOpeningLikeElement): Expression {
@ -210,7 +217,7 @@ namespace ts {
*/
function getAttributeName(node: JsxAttribute): StringLiteral | Identifier {
const name = node.name;
if (/[A-Za-z_]+[\w*]/.test(name.text)) {
if (/^[A-Za-z_]\w*$/.test(name.text)) {
return createLiteral(name.text);
}
else {
@ -422,62 +429,62 @@ namespace ts {
"uarr": 0x2191,
"rarr": 0x2192,
"darr": 0x2193,
"harr": 0x2194,
"crarr": 0x21B5,
"lArr": 0x21D0,
"uArr": 0x21D1,
"rArr": 0x21D2,
"dArr": 0x21D3,
"hArr": 0x21D4,
"forall": 0x2200,
"part": 0x2202,
"exist": 0x2203,
"empty": 0x2205,
"nabla": 0x2207,
"isin": 0x2208,
"notin": 0x2209,
"ni": 0x220B,
"prod": 0x220F,
"sum": 0x2211,
"minus": 0x2212,
"lowast": 0x2217,
"radic": 0x221A,
"prop": 0x221D,
"infin": 0x221E,
"ang": 0x2220,
"and": 0x2227,
"or": 0x2228,
"cap": 0x2229,
"cup": 0x222A,
"int": 0x222B,
"there4": 0x2234,
"sim": 0x223C,
"cong": 0x2245,
"asymp": 0x2248,
"ne": 0x2260,
"equiv": 0x2261,
"le": 0x2264,
"ge": 0x2265,
"sub": 0x2282,
"sup": 0x2283,
"nsub": 0x2284,
"sube": 0x2286,
"supe": 0x2287,
"oplus": 0x2295,
"otimes": 0x2297,
"perp": 0x22A5,
"sdot": 0x22C5,
"lceil": 0x2308,
"rceil": 0x2309,
"lfloor": 0x230A,
"rfloor": 0x230B,
"lang": 0x2329,
"rang": 0x232A,
"loz": 0x25CA,
"spades": 0x2660,
"clubs": 0x2663,
"hearts": 0x2665,
"diams": 0x2666
};
"harr": 0x2194,
"crarr": 0x21B5,
"lArr": 0x21D0,
"uArr": 0x21D1,
"rArr": 0x21D2,
"dArr": 0x21D3,
"hArr": 0x21D4,
"forall": 0x2200,
"part": 0x2202,
"exist": 0x2203,
"empty": 0x2205,
"nabla": 0x2207,
"isin": 0x2208,
"notin": 0x2209,
"ni": 0x220B,
"prod": 0x220F,
"sum": 0x2211,
"minus": 0x2212,
"lowast": 0x2217,
"radic": 0x221A,
"prop": 0x221D,
"infin": 0x221E,
"ang": 0x2220,
"and": 0x2227,
"or": 0x2228,
"cap": 0x2229,
"cup": 0x222A,
"int": 0x222B,
"there4": 0x2234,
"sim": 0x223C,
"cong": 0x2245,
"asymp": 0x2248,
"ne": 0x2260,
"equiv": 0x2261,
"le": 0x2264,
"ge": 0x2265,
"sub": 0x2282,
"sup": 0x2283,
"nsub": 0x2284,
"sube": 0x2286,
"supe": 0x2287,
"oplus": 0x2295,
"otimes": 0x2297,
"perp": 0x22A5,
"sdot": 0x22C5,
"lceil": 0x2308,
"rceil": 0x2309,
"lfloor": 0x230A,
"rfloor": 0x230B,
"lang": 0x2329,
"rang": 0x232A,
"loz": 0x25CA,
"spades": 0x2660,
"clubs": 0x2663,
"hearts": 0x2665,
"diams": 0x2666
};
}
}

View File

@ -3268,6 +3268,10 @@ namespace ts {
return node.kind === SyntaxKind.JsxSpreadAttribute;
}
export function isJsxAttribute(node: Node): node is JsxAttribute {
return node.kind === SyntaxKind.JsxAttribute;
}
// Clauses
export function isCaseOrDefaultClause(node: Node): node is CaseOrDefaultClause {