Emitter for JSX

This commit is contained in:
Ryan Cavanaugh
2015-06-18 14:01:49 -07:00
parent 195db034f8
commit f5336db82a

View File

@@ -47,16 +47,18 @@ var __param = (this && this.__param) || function (paramIndex, decorator) {
return function (target, key) { decorator(target, key, paramIndex); }
};`;
let compilerOptions = host.getCompilerOptions();
let languageVersion = compilerOptions.target || ScriptTarget.ES3;
let sourceMapDataList: SourceMapData[] = compilerOptions.sourceMap || compilerOptions.inlineSourceMap ? [] : undefined;
let diagnostics: Diagnostic[] = [];
let newLine = host.getNewLine();
let jsxDesugaring = host.getCompilerOptions().jsx === JsxEmit.React;
if (targetSourceFile === undefined) {
forEach(host.getSourceFiles(), sourceFile => {
if (shouldEmitToOwnFile(sourceFile, compilerOptions)) {
let jsFilePath = getOwnEmitOutputFilePath(sourceFile, host, ".js");
let jsFilePath = getOwnEmitOutputFilePath(sourceFile, host, (!sourceFile.isTSXFile || jsxDesugaring) ? ".js" : ".jsx");
emitFile(jsFilePath, sourceFile);
}
});
@@ -68,7 +70,7 @@ var __param = (this && this.__param) || function (paramIndex, decorator) {
else {
// targetSourceFile is specified (e.g calling emitter from language service or calling getSemanticDiagnostic from language service)
if (shouldEmitToOwnFile(targetSourceFile, compilerOptions)) {
let jsFilePath = getOwnEmitOutputFilePath(targetSourceFile, host, ".js");
let jsFilePath = getOwnEmitOutputFilePath(targetSourceFile, host, (host.getSourceFiles().every(f => !f.isTSXFile) || jsxDesugaring) ? ".js" : ".jsx");
emitFile(jsFilePath, targetSourceFile);
}
else if (!isDeclarationFile(targetSourceFile) && compilerOptions.out) {
@@ -1106,6 +1108,203 @@ var __param = (this && this.__param) || function (paramIndex, decorator) {
emit(span.literal);
}
function jsxEmitReact(node: JsxElement|JsxSelfClosingElement) {
/// Emit a tag name, which is either '"div"' for lower-cased names, or
/// 'Div' for upper-cased or dotted names
function emitTagName(name: Identifier|QualifiedName) {
if (name.kind === SyntaxKind.Identifier) {
var ch = (<Identifier>name).text.charAt(0);
if (ch.toUpperCase() === ch) {
emit(name);
}
else {
write('"');
emit(name);
write('"');
}
return ch.toUpperCase() !== ch;
}
else {
Debug.assert(name.kind === SyntaxKind.QualifiedName);
emit(name);
}
}
/// Emit an attribute name, which is quoted if it needs to be quoted. Because
/// these emit into an object literal property name, we don't need to be worried
/// about keywords, just non-identifier characters
function emitAttributeName(name: Identifier) {
if (/[A-Za-z_]+[\w*]/.test(name.text)) {
write('"');
emit(name);
write('"');
}
else {
emit(name);
}
}
/// Emit an name/value pair for an attribute (e.g. "x: 3")
function emitJsxAttribute(node: JsxAttribute) {
emitAttributeName(node.name);
write(': ');
emit(node.initializer);
}
function emitJsxElement(openingNode: JsxOpeningElement|JsxSelfClosingElement, children?: JsxChild[]) {
// Call React.createElement(tag, ...
emitLeadingComments(openingNode);
write('React.createElement(');
emitTagName(openingNode.tagName);
write(', ');
// Attribute list
if (openingNode.attributes.length === 0) {
// When there are no attributes, React wants 'null'
write('null');
}
else {
// Either emit one big object literal (no spread attribs), or
// a call to React.__spread
let attrs = openingNode.attributes;
if (attrs.some(attr => attr.kind === SyntaxKind.JsxSpreadAttribute)) {
write('React.__spread(');
let haveOpenedObjectLiteral = false;
for (var i = 0; i < attrs.length; i++) {
if (attrs[i].kind === SyntaxKind.JsxSpreadAttribute) {
if (haveOpenedObjectLiteral) {
write('}');
haveOpenedObjectLiteral = false;
}
if (i > 0) write(', ');
emit((<JsxSpreadAttribute>attrs[i]).expression);
}
else {
Debug.assert(attrs[i].kind === SyntaxKind.JsxAttribute);
if (haveOpenedObjectLiteral) {
write(', ');
}
else {
haveOpenedObjectLiteral = true;
if (i > 0) write(', ');
write('{');
}
emitJsxAttribute(<JsxAttribute>attrs[i]);
}
}
if (haveOpenedObjectLiteral) write('}');
write(')'); // closing paren to React.__spread(
}
else {
// One object literal with all the attributes in them
write('{');
for (var i = 0; i < attrs.length; i++) {
if (i > 0) write(', ');
emitJsxAttribute(<JsxAttribute>attrs[i]);
}
write('}');
}
}
// Children
if (children) {
for (var i = 0; i < children.length; i++) {
// Don't emit empty expressions
if (children[i].kind === SyntaxKind.JsxExpression && !((<JsxExpression>children[i]).expression)) continue;
// Don't emit empty strings
if (children[i].kind === SyntaxKind.JsxText && !shouldEmitJsxText(<JsxText>children[i])) continue;
write(', ');
emit(children[i]);
}
}
// Closing paren
write(')'); // closes 'React.createElement('
emitTrailingComments(openingNode);
}
if (node.kind === SyntaxKind.JsxElement) {
emitJsxElement((<JsxElement>node).openingElement, (<JsxElement>node).children);
}
else {
Debug.assert(node.kind === SyntaxKind.JsxSelfClosingElement);
emitJsxElement(<JsxSelfClosingElement>node);
}
}
function jsxEmitPreserve(node: JsxElement|JsxSelfClosingElement) {
function emitJsxAttribute(node: JsxAttribute) {
emit(node.name);
write('=');
emit(node.initializer);
}
function emitJsxSpreadAttribute(node: JsxSpreadAttribute) {
write('{...');
emit(node.expression);
write('}');
}
function emitAttributes(attribs: NodeArray<JsxAttribute|JsxSpreadAttribute>) {
for (var i = 0, n = attribs.length; i < n; i++) {
if (i > 0) write(' ');
if (attribs[i].kind === SyntaxKind.JsxSpreadAttribute) {
emitJsxSpreadAttribute(<JsxSpreadAttribute>attribs[i]);
}
else {
Debug.assert(attribs[i].kind === SyntaxKind.JsxAttribute);
emitJsxAttribute(<JsxAttribute>attribs[i]);
}
}
}
function emitJsxOpeningOrSelfClosingElement(node: JsxOpeningElement|JsxSelfClosingElement) {
write('<');
emit(node.tagName);
if (node.attributes.length > 0 || (node.kind === SyntaxKind.JsxSelfClosingElement)) {
write(' ');
}
emitAttributes(node.attributes);
if (node.kind === SyntaxKind.JsxSelfClosingElement) {
write('/>');
}
else {
write('>');
}
}
function emitJsxClosingElement(node: JsxClosingElement) {
write('</');
emit(node.tagName);
write('>');
}
function emitJsxElement(node: JsxElement) {
emitJsxOpeningOrSelfClosingElement(node.openingElement);
for (var i = 0, n = node.children.length; i < n; i++) {
emit(node.children[i]);
}
emitJsxClosingElement(node.closingElement);
}
if (node.kind === SyntaxKind.JsxElement) {
emitJsxElement(<JsxElement>node);
}
else {
Debug.assert(node.kind === SyntaxKind.JsxSelfClosingElement);
emitJsxOpeningOrSelfClosingElement(<JsxSelfClosingElement>node);
}
}
// This function specifically handles numeric/string literals for enum and accessor 'identifiers'.
// In a sense, it does not actually emit identifiers as much as it declares a name for a specific property.
// For example, this is utilized when feeding in a result to Object.defineProperty.
@@ -1182,6 +1381,8 @@ var __param = (this && this.__param) || function (paramIndex, decorator) {
case SyntaxKind.ForInStatement:
case SyntaxKind.ForOfStatement:
case SyntaxKind.IfStatement:
case SyntaxKind.JsxSelfClosingElement:
case SyntaxKind.JsxOpeningElement:
case SyntaxKind.NewExpression:
case SyntaxKind.ParenthesizedExpression:
case SyntaxKind.PostfixUnaryExpression:
@@ -1658,8 +1859,8 @@ var __param = (this && this.__param) || function (paramIndex, decorator) {
function parenthesizeForAccess(expr: Expression): LeftHandSideExpression {
// When diagnosing whether the expression needs parentheses, the decision should be based
// on the innermost expression in a chain of nested type assertions.
while (expr.kind === SyntaxKind.TypeAssertionExpression) {
expr = (<TypeAssertion>expr).expression;
while (expr.kind === SyntaxKind.TypeAssertionExpression || expr.kind === SyntaxKind.AsExpression) {
expr = (<AssertionExpression>expr).expression;
}
// isLeftHandSideExpression is almost the correct criterion for when it is not necessary
@@ -1805,8 +2006,8 @@ var __param = (this && this.__param) || function (paramIndex, decorator) {
}
function skipParentheses(node: Expression): Expression {
while (node.kind === SyntaxKind.ParenthesizedExpression || node.kind === SyntaxKind.TypeAssertionExpression) {
node = (<ParenthesizedExpression | TypeAssertion>node).expression;
while (node.kind === SyntaxKind.ParenthesizedExpression || node.kind === SyntaxKind.TypeAssertionExpression || node.kind === SyntaxKind.AsExpression) {
node = (<ParenthesizedExpression | AssertionExpression>node).expression;
}
return node;
}
@@ -1956,12 +2157,12 @@ var __param = (this && this.__param) || function (paramIndex, decorator) {
// not the user. If we didn't want them, the emitter would not have put them
// there.
if (!nodeIsSynthesized(node) && node.parent.kind !== SyntaxKind.ArrowFunction) {
if (node.expression.kind === SyntaxKind.TypeAssertionExpression) {
if (node.expression.kind === SyntaxKind.TypeAssertionExpression || node.expression.kind === SyntaxKind.AsExpression) {
let operand = (<TypeAssertion>node.expression).expression;
// Make sure we consider all nested cast expressions, e.g.:
// (<any><number><any>-A).x;
while (operand.kind === SyntaxKind.TypeAssertionExpression) {
while (operand.kind === SyntaxKind.TypeAssertionExpression || operand.kind === SyntaxKind.AsExpression) {
operand = (<TypeAssertion>operand).expression;
}
@@ -3807,7 +4008,7 @@ var __param = (this && this.__param) || function (paramIndex, decorator) {
emitClassLikeDeclarationForES6AndHigher(node);
}
}
function emitClassLikeDeclarationForES6AndHigher(node: ClassLikeDeclaration) {
let thisNodeIsDecorated = nodeIsDecorated(node);
if (node.kind === SyntaxKind.ClassDeclaration) {
@@ -4066,7 +4267,7 @@ var __param = (this && this.__param) || function (paramIndex, decorator) {
write(".prototype");
}
}
function emitDecoratorsOfClass(node: ClassLikeDeclaration) {
emitDecoratorsOfMembers(node, /*staticFlag*/ 0);
emitDecoratorsOfMembers(node, NodeFlags.Static);
@@ -4978,7 +5179,7 @@ var __param = (this && this.__param) || function (paramIndex, decorator) {
let started = false;
for (let importNode of externalImports) {
// do not create variable declaration for exports and imports that lack import clause
let skipNode =
let skipNode =
importNode.kind === SyntaxKind.ExportDeclaration ||
(importNode.kind === SyntaxKind.ImportDeclaration && !(<ImportDeclaration>importNode).importClause)
@@ -5075,7 +5276,7 @@ var __param = (this && this.__param) || function (paramIndex, decorator) {
write("};");
return emitExportStarFunction(exportedNamesStorageRef);
function emitExportStarFunction(localNames: string): string {
const exportStarFunction = makeUniqueName("exportStar");
@@ -5102,7 +5303,7 @@ var __param = (this && this.__param) || function (paramIndex, decorator) {
return exportStarFunction;
}
function writeExportedName(node: Identifier | Declaration): void {
// do not record default exports
// they are local to module and never overwritten (explicitly skipped) by star export
@@ -5398,7 +5599,7 @@ var __param = (this && this.__param) || function (paramIndex, decorator) {
if (importNode.kind === SyntaxKind.ImportDeclaration &&
(<ImportDeclaration>importNode).importClause.namedBindings) {
let namedBindings = (<ImportDeclaration>importNode).importClause.namedBindings;
if (namedBindings.kind === SyntaxKind.NamespaceImport) {
// emit re-export for namespace
@@ -5657,6 +5858,91 @@ var __param = (this && this.__param) || function (paramIndex, decorator) {
}
}
function emitJsxElement(node: JsxElement|JsxSelfClosingElement) {
switch (compilerOptions.jsx) {
case JsxEmit.React:
jsxEmitReact(node);
break;
// Fall back to preserve if None was specified (we'll error earlier)
default:
jsxEmitPreserve(node);
break;
}
}
function trimReactWhitespace(node: JsxText): string {
// Could be empty string, do not use !node.formattedReactText
if (node.formattedReactText !== undefined) {
return node.formattedReactText;
}
let lines: string[] = [];
let text = getTextOfNode(node);
let firstNonWhitespace = 0;
let lastNonWhitespace = -1;
for (var i = 0; i < text.length; i++) {
var c = text.charCodeAt(i);
if (c === CharacterCodes.lineFeed || c === CharacterCodes.carriageReturn) {
if (firstNonWhitespace !== -1 && (lastNonWhitespace - firstNonWhitespace + 1 > 0)) {
lines.push(text.substr(firstNonWhitespace, lastNonWhitespace - firstNonWhitespace + 1));
}
firstNonWhitespace = -1;
}
else if (!isWhiteSpace(c)) {
lastNonWhitespace = i;
if (firstNonWhitespace === -1) {
firstNonWhitespace = i;
}
}
}
if (firstNonWhitespace !== -1) {
lines.push(text.substr(firstNonWhitespace));
}
return node.formattedReactText = lines.join('" + \' \' + "');
}
function shouldEmitJsxText(node: JsxText) {
if (compilerOptions.jsx === JsxEmit.React) {
return trimReactWhitespace(node).length > 0;
}
else {
return true;
}
}
function emitJsxText(node: JsxText) {
switch (compilerOptions.jsx) {
case JsxEmit.React:
write('"');
write(trimReactWhitespace(node));
write('"');
break;
case JsxEmit.Preserve:
default: // Emit JSX-preserve as default when no --jsx flag is specified
write(getTextOfNode(node, true));
break;
}
}
function emitJsxExpression(node: JsxExpression) {
if (node.expression && node.expression.kind !== SyntaxKind.OmittedExpression) {
switch (compilerOptions.jsx) {
case JsxEmit.Preserve:
default:
write('{');
emit(node.expression);
write('}');
break;
case JsxEmit.React:
emit(node.expression);
break;
}
}
}
function emitDirectivePrologues(statements: Node[], startWithNewLine: boolean): number {
for (let i = 0; i < statements.length; ++i) {
if (isPrologueDirective(statements[i])) {
@@ -5799,7 +6085,7 @@ var __param = (this && this.__param) || function (paramIndex, decorator) {
if (node.kind !== SyntaxKind.Block &&
node.parent &&
node.parent.kind === SyntaxKind.ArrowFunction &&
(<ArrowFunction>node.parent).body === node &&
(<ArrowFunction>node.parent).body === node &&
compilerOptions.target <= ScriptTarget.ES5) {
return false;
@@ -5844,6 +6130,13 @@ var __param = (this && this.__param) || function (paramIndex, decorator) {
return emitTemplateExpression(<TemplateExpression>node);
case SyntaxKind.TemplateSpan:
return emitTemplateSpan(<TemplateSpan>node);
case SyntaxKind.JsxElement:
case SyntaxKind.JsxSelfClosingElement:
return emitJsxElement(<JsxElement|JsxSelfClosingElement>node);
case SyntaxKind.JsxText:
return emitJsxText(<JsxText>node);
case SyntaxKind.JsxExpression:
return emitJsxExpression(<JsxExpression>node);
case SyntaxKind.QualifiedName:
return emitQualifiedName(<QualifiedName>node);
case SyntaxKind.ObjectBindingPattern:
@@ -5874,6 +6167,8 @@ var __param = (this && this.__param) || function (paramIndex, decorator) {
return emitTaggedTemplateExpression(<TaggedTemplateExpression>node);
case SyntaxKind.TypeAssertionExpression:
return emit((<TypeAssertion>node).expression);
case SyntaxKind.AsExpression:
return emit((<AsExpression>node).expression);
case SyntaxKind.ParenthesizedExpression:
return emitParenExpression(<ParenthesizedExpression>node);
case SyntaxKind.FunctionDeclaration: