mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-05-23 16:48:41 -05:00
Emitter for JSX
This commit is contained in:
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user