Additional fixes for React emit.

This commit is contained in:
Ron Buckton 2016-04-05 16:50:19 -07:00
parent 221cbcfe29
commit 0b2264cc54
17 changed files with 148 additions and 93 deletions

View File

@ -66,7 +66,7 @@ namespace ts {
// We don't use "clone" from core.ts here, as we need to preserve the prototype chain of
// the original node. We also need to exclude specific properties and only include own-
// properties (to skip members already defined on the shared prototype).
const clone = <T>createSynthesizedNode(node.kind);
const clone = <T>createNode(node.kind, /*location*/ undefined);
clone.flags = node.flags;
clone.original = node;
@ -784,17 +784,25 @@ namespace ts {
);
}
export function createJsxSpread(reactNamespace: string, segments: Expression[]) {
function createReactNamespace(reactNamespace: string, parent: JsxOpeningLikeElement) {
// Create an identifier and give it a parent. This allows us to resolve the react
// namespace during emit.
const react = createIdentifier(reactNamespace || "React");
react.parent = parent;
return react;
}
export function createReactSpread(reactNamespace: string, segments: Expression[], parentElement: JsxOpeningLikeElement) {
return createCall(
createPropertyAccess(
createIdentifier(reactNamespace || "React"),
createReactNamespace(reactNamespace, parentElement),
"__spread"
),
segments
);
}
export function createJsxCreateElement(reactNamespace: string, tagName: Expression, props: Expression, children: Expression[]): LeftHandSideExpression {
export function createReactCreateElement(reactNamespace: string, tagName: Expression, props: Expression, children: Expression[], parentElement: JsxOpeningLikeElement, location: TextRange): LeftHandSideExpression {
const argumentsList = [tagName];
if (props) {
argumentsList.push(props);
@ -805,15 +813,16 @@ namespace ts {
argumentsList.push(createNull());
}
addRange(argumentsList, children);
addNodes(argumentsList, children, /*startOnNewLine*/ children.length > 1);
}
return createCall(
createPropertyAccess(
createIdentifier(reactNamespace || "React"),
createReactNamespace(reactNamespace, parentElement),
"createElement"
),
argumentsList
argumentsList,
location
);
}

View File

@ -1017,9 +1017,7 @@ const _super = (function (geti, seti) {
function emitNewExpression(node: NewExpression) {
write("new ");
emitExpression(node.expression);
if (node.arguments) {
emitExpressionList(node, node.arguments, ListFormat.NewExpressionArguments);
}
emitExpressionList(node, node.arguments, ListFormat.NewExpressionArguments);
}
function emitTaggedTemplateExpression(node: TaggedTemplateExpression) {
@ -1536,7 +1534,7 @@ const _super = (function (geti, seti) {
write("interface ");
emit(node.name);
emitTypeParameters(node, node.typeParameters);
emitList(node, node.heritageClauses, ListFormat.SingleLine);
emitList(node, node.heritageClauses, ListFormat.HeritageClauses);
write(" {");
emitList(node, node.members, ListFormat.InterfaceMembers);
write("}");
@ -1722,7 +1720,7 @@ const _super = (function (geti, seti) {
function emitJsxSelfClosingElement(node: JsxSelfClosingElement) {
write("<");
emit(node.tagName);
emitJsxTagName(node.tagName);
write(" ");
emitList(node, node.attributes, ListFormat.JsxElementAttributes);
write("/>");
@ -1730,7 +1728,7 @@ const _super = (function (geti, seti) {
function emitJsxOpeningElement(node: JsxOpeningElement) {
write("<");
emit(node.tagName);
emitJsxTagName(node.tagName);
writeIfAny(node.attributes, " ");
emitList(node, node.attributes, ListFormat.JsxElementAttributes);
write(">");
@ -1742,7 +1740,7 @@ const _super = (function (geti, seti) {
function emitJsxClosingElement(node: JsxClosingElement) {
write("</");
emit(node.tagName);
emitJsxTagName(node.tagName);
write(">");
}
@ -1758,9 +1756,20 @@ const _super = (function (geti, seti) {
}
function emitJsxExpression(node: JsxExpression) {
write("{");
emitExpression(node.expression);
write("}");
if (node.expression) {
write("{");
emitExpression(node.expression);
write("}");
}
}
function emitJsxTagName(node: EntityName) {
if (node.kind === SyntaxKind.Identifier) {
emitExpression(<Identifier>node);
}
else {
emit(node);
}
}
//
@ -2168,6 +2177,7 @@ const _super = (function (geti, seti) {
// Emit each child.
let previousSibling: Node;
let shouldDecreaseIndentAfterEmit: boolean;
const delimiter = getDelimiter(format);
for (let i = 0; i < count; i++) {
const child = children[start + i];
@ -2178,6 +2188,13 @@ const _super = (function (geti, seti) {
// Write either a line terminator or whitespace to separate the elements.
if (shouldWriteSeparatingLineTerminator(previousSibling, child, format)) {
// If a synthesized node in a single-line list starts on a new
// line, we should increase the indent.
if ((format & (ListFormat.LinesMask | ListFormat.Indented)) === ListFormat.SingleLine) {
increaseIndent();
shouldDecreaseIndentAfterEmit = true;
}
writeLine();
shouldEmitInterveningComments = false;
}
@ -2196,6 +2213,11 @@ const _super = (function (geti, seti) {
// Emit this child.
emit(child);
if (shouldDecreaseIndentAfterEmit) {
decreaseIndent();
shouldDecreaseIndentAfterEmit = false;
}
previousSibling = child;
}
@ -2322,7 +2344,7 @@ const _super = (function (geti, seti) {
}
}
else {
return false;
return nextNode.startsOnNewLine;
}
}
@ -2619,40 +2641,43 @@ const _super = (function (geti, seti) {
None = 0,
// Line separators
SingleLine = 1 << 0, // Prints the list on a single line (default).
MultiLine = 1 << 1, // Prints the list on multiple lines.
PreserveLines = 1 << 2, // Prints the list using line preservation if possible.
SingleLine = 0, // Prints the list on a single line (default).
MultiLine = 1 << 0, // Prints the list on multiple lines.
PreserveLines = 1 << 1, // Prints the list using line preservation if possible.
LinesMask = SingleLine | MultiLine | PreserveLines,
// Delimiters
NotDelimited = 0, // There is no delimiter between list items (default).
BarDelimited = 1 << 3, // Each list item is space-and-bar (" |") delimited.
AmpersandDelimited = 1 << 4, // Each list item is space-and-ampersand (" &") delimited.
CommaDelimited = 1 << 5, // Each list item is comma (",") delimited.
AllowTrailingComma = 1 << 6, // Write a trailing comma (",") if present.
BarDelimited = 1 << 2, // Each list item is space-and-bar (" |") delimited.
AmpersandDelimited = 1 << 3, // Each list item is space-and-ampersand (" &") delimited.
CommaDelimited = 1 << 4, // Each list item is comma (",") delimited.
DelimitersMask = BarDelimited | AmpersandDelimited | CommaDelimited,
AllowTrailingComma = 1 << 5, // Write a trailing comma (",") if present.
// Whitespace
Indented = 1 << 7, // The list should be indented.
SpaceBetweenBraces = 1 << 8, // Inserts a space after the opening brace and before the closing brace.
SpaceBetweenSiblings = 1 << 9, // Inserts a space between each sibling node.
Indented = 1 << 6, // The list should be indented.
SpaceBetweenBraces = 1 << 7, // Inserts a space after the opening brace and before the closing brace.
SpaceBetweenSiblings = 1 << 8, // Inserts a space between each sibling node.
// Brackets/Braces
Braces = 1 << 10, // The list is surrounded by "{" and "}".
Parenthesis = 1 << 11, // The list is surrounded by "(" and ")".
AngleBrackets = 1 << 12, // The list is surrounded by "<" and ">".
SquareBrackets = 1 << 13, // The list is surrounded by "[" and "]".
OptionalIfUndefined = 1 << 14, // Do not emit brackets if the list is undefined.
OptionalIfEmpty = 1 << 15, // Do not emit brackets if the list is empty.
Optional = OptionalIfUndefined | OptionalIfEmpty,
Braces = 1 << 9, // The list is surrounded by "{" and "}".
Parenthesis = 1 << 10, // The list is surrounded by "(" and ")".
AngleBrackets = 1 << 11, // The list is surrounded by "<" and ">".
SquareBrackets = 1 << 12, // The list is surrounded by "[" and "]".
BracketsMask = Braces | Parenthesis | AngleBrackets | SquareBrackets,
OptionalIfUndefined = 1 << 13, // Do not emit brackets if the list is undefined.
OptionalIfEmpty = 1 << 14, // Do not emit brackets if the list is empty.
Optional = OptionalIfUndefined | OptionalIfEmpty,
// Other
PreferNewLine = 1 << 16, // Prefer adding a LineTerminator between synthesized nodes.
NoTrailingNewLine = 1 << 17, // Do not emit a trailing NewLine for a MultiLine list.
PreferNewLine = 1 << 15, // Prefer adding a LineTerminator between synthesized nodes.
NoTrailingNewLine = 1 << 16, // Do not emit a trailing NewLine for a MultiLine list.
// Precomputed Formats
Modifiers = SingleLine | SpaceBetweenSiblings,
HeritageClauses = SingleLine | SpaceBetweenSiblings,
TypeLiteralMembers = MultiLine | Indented,
TupleTypeElements = CommaDelimited | SpaceBetweenSiblings | SingleLine | Indented,
UnionTypeConstituents = BarDelimited | SpaceBetweenSiblings | SingleLine,

View File

@ -33,10 +33,10 @@ namespace ts {
function visitorWorker(node: Node): VisitResult<Node> {
switch (node.kind) {
case SyntaxKind.JsxElement:
return visitJsxElement(<JsxElement>node);
return visitJsxElement(<JsxElement>node, /*isChild*/ false);
case SyntaxKind.JsxSelfClosingElement:
return visitJsxSelfClosingElement(<JsxSelfClosingElement>node);
return visitJsxSelfClosingElement(<JsxSelfClosingElement>node, /*isChild*/ false);
case SyntaxKind.JsxExpression:
return visitJsxExpression(<JsxExpression>node);
@ -56,10 +56,10 @@ namespace ts {
return visitJsxExpression(<JsxExpression>node);
case SyntaxKind.JsxElement:
return visitJsxElement(<JsxElement>node);
return visitJsxElement(<JsxElement>node, /*isChild*/ true);
case SyntaxKind.JsxSelfClosingElement:
return visitJsxSelfClosingElement(<JsxSelfClosingElement>node);
return visitJsxSelfClosingElement(<JsxSelfClosingElement>node, /*isChild*/ true);
default:
Debug.failBadSyntaxKind(node);
@ -67,15 +67,15 @@ namespace ts {
}
}
function visitJsxElement(node: JsxElement) {
return visitJsxOpeningLikeElement(node.openingElement, node.children);
function visitJsxElement(node: JsxElement, isChild: boolean) {
return visitJsxOpeningLikeElement(node.openingElement, node.children, isChild, /*location*/ node);
}
function visitJsxSelfClosingElement(node: JsxSelfClosingElement) {
return visitJsxOpeningLikeElement(node, /*children*/ undefined);
function visitJsxSelfClosingElement(node: JsxSelfClosingElement, isChild: boolean) {
return visitJsxOpeningLikeElement(node, /*children*/ undefined, isChild, /*location*/ node);
}
function visitJsxOpeningLikeElement(node: JsxOpeningLikeElement, children: JsxChild[]) {
function visitJsxOpeningLikeElement(node: JsxOpeningLikeElement, children: JsxChild[], isChild: boolean, location: TextRange) {
const tagName = getTagName(node);
let objectProperties: Expression;
const attrs = node.attributes;
@ -102,15 +102,23 @@ namespace ts {
// Either emit one big object literal (no spread attribs), or
// a call to React.__spread
objectProperties = singleOrUndefined(segments)
|| createJsxSpread(compilerOptions.reactNamespace, segments);
|| createReactSpread(compilerOptions.reactNamespace, segments, node);
}
return createJsxCreateElement(
const element = createReactCreateElement(
compilerOptions.reactNamespace,
tagName,
objectProperties,
filter(map(children, transformJsxChildToExpression), isDefined)
filter(map(children, transformJsxChildToExpression), isDefined),
node,
location
);
if (isChild) {
startOnNewLine(element);
}
return element;
}
function transformJsxSpreadAttributeToExpression(node: JsxSpreadAttribute) {

View File

@ -148,7 +148,10 @@ var x = <div attr1={"foo" + "bar"} attr2={"foo" + "bar" +
</div>);
(<div attr1="foo">
(<div
/* a multi-line
comment */
attr1="foo">
<span // a double-slash comment
attr2="bar"/>
</div>);

View File

@ -8,7 +8,7 @@ declare var React: any;
//// [keywordInJsxIdentifier.js]
React.createElement("foo", {"class-id": true});
React.createElement("foo", {class: true});
React.createElement("foo", {"class-id": "1"});
React.createElement("foo", {class: "1"});
React.createElement("foo", { "class-id": true });
React.createElement("foo", { class: true });
React.createElement("foo", { "class-id": "1" });
React.createElement("foo", { class: "1" });

View File

@ -4,4 +4,4 @@
//// [reactNamespaceInvalidInput.js]
my-React-Lib.createElement("foo", {data: true});
my-React-Lib.createElement("foo", { data: true });

View File

@ -13,8 +13,8 @@ declare var x: any;
//// [reactNamespaceJSXEmit.js]
myReactLib.createElement("foo", {data: true});
myReactLib.createElement(Bar, {x: x});
myReactLib.createElement("foo", { data: true });
myReactLib.createElement(Bar, { x: x });
myReactLib.createElement("x-component", null);
myReactLib.createElement(Bar, myReactLib.__spread({}, x));
myReactLib.createElement(Bar, myReactLib.__spread({}, x, {y: 2}));
myReactLib.createElement(Bar, myReactLib.__spread({}, x, { y: 2 }));

View File

@ -5,4 +5,4 @@
//// [reactNamespaceMissingDeclaration.js]
// Error myReactLib not declared
myReactLib.createElement("foo", {data: true});
myReactLib.createElement("foo", { data: true });

View File

@ -12,6 +12,8 @@ var x = <div></div><div></div>
//// [file1.js]
React.createElement("div", null), React.createElement("div", null);
React.createElement("div", null)
,
React.createElement("div", null);
//// [file2.js]
var x = (React.createElement("div", null), React.createElement("div", null));

View File

@ -70,5 +70,8 @@ var SomeClass = (function () {
return SomeClass;
}());
var whitespace1 = React.createElement("div", null, " ");
var whitespace2 = React.createElement("div", null, " ", p, " ");
var whitespace2 = React.createElement("div", null,
" ",
p,
" ");
var whitespace3 = React.createElement("div", null, p);

View File

@ -8,4 +8,11 @@ declare var Foo, Bar, baz;
<Foo> <Bar> q </Bar> <Bar/> s <Bar/><Bar/></Foo>;
//// [test.js]
React.createElement(Foo, null, " ", React.createElement(Bar, null, " q "), " ", React.createElement(Bar, null), " s ", React.createElement(Bar, null), React.createElement(Bar, null));
React.createElement(Foo, null,
" ",
React.createElement(Bar, null, " q "),
" ",
React.createElement(Bar, null),
" s ",
React.createElement(Bar, null),
React.createElement(Bar, null));

View File

@ -27,4 +27,4 @@ var test_1 = require("./test");
// Should emit test_1.React.createElement
// and React.__spread
var foo;
var spread1 = test_1.React.createElement("div", test_1.React.__spread({x: ''}, foo, {y: ''}));
var spread1 = test_1.React.createElement("div", test_1.React.__spread({ x: '' }, foo, { y: '' }));

View File

@ -36,7 +36,7 @@ var M;
// Should emit M.React.createElement
// and M.React.__spread
var foo;
var spread1 = M.React.createElement("div", M.React.__spread({x: ''}, foo, {y: ''}));
var spread1 = M.React.createElement("div", M.React.__spread({ x: '' }, foo, { y: '' }));
// Quotes
var x = M.React.createElement("div", null, "This \"quote\" thing");
})(M || (M = {}));

View File

@ -12,5 +12,5 @@ declare var React: any;
//// [file.js]
React.createElement("div", null, "Dot goes here: · &notAnEntity; ");
React.createElement("div", null, "Dot goes here: \u00B7 &notAnEntity; ");
React.createElement("div", null, "Be careful of \"-ed strings!");

View File

@ -38,23 +38,21 @@ let render = (ctrl, model) =>
//// [file.js]
// A simple render function with nesting and control statements
var render = function (ctrl, model) {
return vdom.createElement("section", {class: "todoapp"},
vdom.createElement("header", {class: "header"},
vdom.createElement("h1", null, "todos <x>"),
vdom.createElement("input", {class: "new-todo", autofocus: true, autocomplete: "off", placeholder: "What needs to be done?", value: model.newTodo, onKeyup: ctrl.addTodo.bind(ctrl, model)})),
vdom.createElement("section", {class: "main", style: { display: (model.todos && model.todos.length) ? "block" : "none" }},
vdom.createElement("input", {class: "toggle-all", type: "checkbox", onChange: ctrl.toggleAll.bind(ctrl)}),
vdom.createElement("ul", {class: "todo-list"}, model.filteredTodos.map(function (todo) {
return vdom.createElement("li", {class: { todo: true, completed: todo.completed, editing: todo == model.editedTodo }},
vdom.createElement("div", {class: "view"},
return vdom.createElement("section", { class: "todoapp" },
vdom.createElement("header", { class: "header" },
vdom.createElement("h1", null, "todos <x>"),
vdom.createElement("input", { class: "new-todo", autofocus: true, autocomplete: "off", placeholder: "What needs to be done?", value: model.newTodo, onKeyup: ctrl.addTodo.bind(ctrl, model) })),
vdom.createElement("section", { class: "main", style: { display: (model.todos && model.todos.length) ? "block" : "none" } },
vdom.createElement("input", { class: "toggle-all", type: "checkbox", onChange: ctrl.toggleAll.bind(ctrl) }),
vdom.createElement("ul", { class: "todo-list" }, model.filteredTodos.map(function (todo) {
return vdom.createElement("li", { class: { todo: true, completed: todo.completed, editing: todo == model.editedTodo } },
vdom.createElement("div", { class: "view" },
(!todo.editable) ?
vdom.createElement("input", {class: "toggle", type: "checkbox"})
: null,
vdom.createElement("label", {onDoubleClick: function () { ctrl.editTodo(todo); }}, todo.title),
vdom.createElement("button", {class: "destroy", onClick: ctrl.removeTodo.bind(ctrl, todo)}),
vdom.createElement("div", {class: "iconBorder"},
vdom.createElement("div", {class: "icon"})
))
);
vdom.createElement("input", { class: "toggle", type: "checkbox" })
: null,
vdom.createElement("label", { onDoubleClick: function () { ctrl.editTodo(todo); } }, todo.title),
vdom.createElement("button", { class: "destroy", onClick: ctrl.removeTodo.bind(ctrl, todo) }),
vdom.createElement("div", { class: "iconBorder" },
vdom.createElement("div", { class: "icon" }))));
}))));
};

View File

@ -59,9 +59,9 @@ var p = 0;
// Emit " "
React.createElement("div", null, " ");
// Emit " ", p, " "
React.createElement("div", null,
" ",
p,
React.createElement("div", null,
" ",
p,
" ");
// Emit only p
React.createElement("div", null, p);
@ -76,4 +76,4 @@ React.createElement("div", null, "3");
// Emit no args
React.createElement("div", null);
// Emit "foo" + ' ' + "bar"
React.createElement("div", null, "foo" + ' ' + "bar");
React.createElement("div", null, "foo" + " " + "bar");

View File

@ -18,15 +18,15 @@ declare var React: any;
//// [file.js]
// Emit ' word' in the last string
React.createElement("div", null,
"word ",
React.createElement("code", null, "code"),
React.createElement("div", null,
"word ",
React.createElement("code", null, "code"),
" word");
// Same here
React.createElement("div", null,
React.createElement("code", null, "code"),
React.createElement("div", null,
React.createElement("code", null, "code"),
" word");
// And here
React.createElement("div", null,
React.createElement("code", null),
React.createElement("div", null,
React.createElement("code", null),
" word");