diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 5d57e9d202874..14a6906217f14 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -435,7 +435,7 @@ namespace ts { * Get symbols that represent parameter-property-declaration as parameter and as property declaration * @param parameter a parameterDeclaration node * @param parameterName a name of the parameter to get the symbols for. - * @return a tuple of two symbols + * @return a tuple of two symbols */ function getSymbolsOfParameterPropertyDeclaration(parameter: ParameterDeclaration, parameterName: string): [Symbol, Symbol] { const constructoDeclaration = parameter.parent; @@ -8363,13 +8363,27 @@ namespace ts { checkGrammarJsxElement(node); checkJsxPreconditions(node); - // If we're compiling under --jsx react, the symbol 'React' should - // be marked as 'used' so we don't incorrectly elide its import. And if there - // is no 'React' symbol in scope, we should issue an error. + /** + * Mark symbol as 'used' so we don't incorreclty elide its import. + * If the symbol is not found in scope, we should issue an error. + */ + function markReferenced(symbolName: string) { + const symbol = resolveName(node.tagName, symbolName, SymbolFlags.Value, Diagnostics.Cannot_find_name_0, symbolName); + if (symbol) { + getSymbolLinks(symbol).referenced = true; + } + } + if (compilerOptions.jsx === JsxEmit.React) { - const reactSym = resolveName(node.tagName, "React", SymbolFlags.Value, Diagnostics.Cannot_find_name_0, "React"); - if (reactSym) { - getSymbolLinks(reactSym).referenced = true; + markReferenced("React"); + } + else if (compilerOptions.jsx === JsxEmit.Transform) { + if (compilerOptions.jsxNamespace !== "") { + markReferenced(compilerOptions.jsxNamespace || "JSX"); + } + else { + markReferenced(compilerOptions.jsxComposeFunction || "compose"); + markReferenced(compilerOptions.jsxSpreadFunction || "spread"); } } diff --git a/src/compiler/commandLineParser.ts b/src/compiler/commandLineParser.ts index 034b7022e8232..1708c06fab1d5 100644 --- a/src/compiler/commandLineParser.ts +++ b/src/compiler/commandLineParser.ts @@ -48,11 +48,32 @@ namespace ts { name: "jsx", type: { "preserve": JsxEmit.Preserve, - "react": JsxEmit.React + "react": JsxEmit.React, + "transform": JsxEmit.Transform }, paramType: Diagnostics.KIND, - description: Diagnostics.Specify_JSX_code_generation_Colon_preserve_or_react, - error: Diagnostics.Argument_for_jsx_must_be_preserve_or_react + description: Diagnostics.Specify_JSX_code_generation_Colon_preserve_transform_or_react, + error: Diagnostics.Argument_for_jsx_must_be_preserve_transform_or_react + }, + { + name: "jsxNamespace", + type: "string", + description: Diagnostics.JSX_transform_Colon_Namespace_Default_to_JSX + }, + { + name: "jsxComposeFunction", + type: "string", + description: Diagnostics.JSX_transform_Colon_Compose_function_Default_to_compose + }, + { + name: "jsxSpreadFunction", + type: "string", + description: Diagnostics.JSX_transform_Colon_Spread_function_Default_to_spread + }, + { + name: "jsxChildrenAsArray", + type: "boolean", + description: Diagnostics.JSX_transform_Colon_Pass_children_as_array_Default_to_false }, { name: "listFiles", @@ -514,7 +535,7 @@ namespace ts { for (const extension of supportedExtensions) { const filesInDirWithExtension = host.readDirectory(basePath, extension, exclude); for (const fileName of filesInDirWithExtension) { - // .ts extension would read the .d.ts extension files too but since .d.ts is lower priority extension, + // .ts extension would read the .d.ts extension files too but since .d.ts is lower priority extension, // lets pick them when its turn comes up if (extension === ".ts" && fileExtensionIs(fileName, ".d.ts")) { continue; diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index a43f3534df6b5..e23d533b18179 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -798,7 +798,7 @@ "A decorator can only decorate a method implementation, not an overload.": { "category": "Error", "code": 1249 - }, + }, "'with' statements are not allowed in an async function block.": { "category": "Error", "code": 1300 @@ -2377,12 +2377,12 @@ "category": "Message", "code": 6078 }, - "Specify JSX code generation: 'preserve' or 'react'": { + "Specify JSX code generation: 'preserve', 'transform', or 'react'": { "category": "Message", "code": 6080 }, - "Argument for '--jsx' must be 'preserve' or 'react'.": { - "category": "Message", + "Argument for '--jsx' must be 'preserve', 'transform', or 'react'.": { + "category": "Error", "code": 6081 }, "Only 'amd' and 'system' modules are supported alongside --{0}.": { @@ -2393,7 +2393,22 @@ "category": "Message", "code": 6083 }, - + "JSX transform: Namespace. Default to 'JSX'": { + "category": "Message", + "code": 6084 + }, + "JSX transform: Compose function. Default to 'compose'": { + "category": "Message", + "code": 6085 + }, + "JSX transform: Spread function. Default to 'spread'": { + "category": "Message", + "code": 6086 + }, + "JSX transform: Pass children as array. Default to false.": { + "category": "Message", + "code": 6087 + }, "Variable '{0}' implicitly has an '{1}' type.": { "category": "Error", "code": 7005 diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index c266a0fa77e30..99efef11bfb6c 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -1144,9 +1144,10 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi emit(span.literal); } - function jsxEmitReact(node: JsxElement | JsxSelfClosingElement) { + function jsxEmitTransform(node: JsxElement | JsxSelfClosingElement, jsxOptions: any) { /// Emit a tag name, which is either '"div"' for lower-cased names, or /// 'Div' for upper-cased or dotted names + /// May add an option to disable this as generic transform may not follow this convension function emitTagName(name: Identifier | QualifiedName) { if (name.kind === SyntaxKind.Identifier && isIntrinsicJsxName((name).text)) { write("\""); @@ -1186,28 +1187,29 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi function emitJsxElement(openingNode: JsxOpeningLikeElement, children?: JsxChild[]) { const syntheticReactRef = createSynthesizedNode(SyntaxKind.Identifier); - syntheticReactRef.text = "React"; + syntheticReactRef.text = jsxOptions.namespace; syntheticReactRef.parent = openingNode; - // Call React.createElement(tag, ... + // Call React.createElement(tag, ... or JSX.compose(tag,... emitLeadingComments(openingNode); emitExpressionIdentifier(syntheticReactRef); - write(".createElement("); + write(`.${jsxOptions.composeFunction}(`); emitTagName(openingNode.tagName); write(", "); // Attribute list if (openingNode.attributes.length === 0) { // When there are no attributes, React wants "null" + // May need to add an jsxOption to override this behavior. write("null"); } else { // Either emit one big object literal (no spread attribs), or - // a call to React.__spread + // a call to React.__spread or JSX.spread const attrs = openingNode.attributes; if (forEach(attrs, attr => attr.kind === SyntaxKind.JsxSpreadAttribute)) { emitExpressionIdentifier(syntheticReactRef); - write(".__spread("); + write(`.${jsxOptions.spreadFunction}(`); let haveOpenedObjectLiteral = false; for (let i = 0; i < attrs.length; i++) { @@ -1243,7 +1245,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi } if (haveOpenedObjectLiteral) write("}"); - write(")"); // closing paren to React.__spread( + write(")"); // closing paren to React.__spread( or JSX.spread( } else { // One object literal with all the attributes in them @@ -1260,6 +1262,12 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi // Children if (children) { + let skipFirstComma = false; + if (jsxOptions.childrenAsArray) { + write(", ["); + skipFirstComma = true; + } + for (var i = 0; i < children.length; i++) { // Don't emit empty expressions if (children[i].kind === SyntaxKind.JsxExpression && !((children[i]).expression)) { @@ -1270,21 +1278,36 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi if (children[i].kind === SyntaxKind.JsxText) { const text = getTextToEmit(children[i]); if (text !== undefined) { - write(", \""); + if (skipFirstComma) { + skipFirstComma = false; + } + else { + write(", "); + } + + write("\""); write(text); write("\""); } } else { - write(", "); + if (skipFirstComma) { + skipFirstComma = false; + } + else { + write(", "); + } emit(children[i]); } } + if (jsxOptions.childrenAsArray) { + write("]"); + } } // Closing paren - write(")"); // closes "React.createElement(" + write(")"); // closes "React.createElement(" or "JSX.compose(" emitTrailingComments(openingNode); } @@ -7176,7 +7199,20 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi function emitJsxElement(node: JsxElement | JsxSelfClosingElement) { switch (compilerOptions.jsx) { case JsxEmit.React: - jsxEmitReact(node); + jsxEmitTransform(node, { + namespace: "React", + composeFunction: "createElement", + spreadFunction: "__spread", + childrenAsArray: false + }); + break; + case JsxEmit.Transform: + jsxEmitTransform(node, { + namespace: compilerOptions.jsxNamespace || "JSX", + composeFunction: compilerOptions.jsxComposeFunction || "compose", + spreadFunction: compilerOptions.jsxSpreadFunction || "spread", + childrenAsArray: compilerOptions.jsxChildrenAsArray || false + }); break; case JsxEmit.Preserve: // Fall back to preserve if None was specified (we'll error earlier) @@ -7237,6 +7273,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi function getTextToEmit(node: JsxText) { switch (compilerOptions.jsx) { case JsxEmit.React: + case JsxEmit.Transform: let text = trimReactWhitespaceAndApplyEntities(node); if (text === undefined || text.length === 0) { return undefined; @@ -7253,6 +7290,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi function emitJsxText(node: JsxText) { switch (compilerOptions.jsx) { case JsxEmit.React: + case JsxEmit.Transform: write("\""); write(trimReactWhitespaceAndApplyEntities(node)); write("\""); @@ -7275,6 +7313,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi write("}"); break; case JsxEmit.React: + case JsxEmit.Transform: emit(node.expression); break; } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 24f70373a8ace..48088a0f45872 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -2382,6 +2382,10 @@ namespace ts { inlineSourceMap?: boolean; inlineSources?: boolean; jsx?: JsxEmit; + jsxNamespace?: string; + jsxComposeFunction?: string; + jsxSpreadFunction?: string; + jsxChildrenAsArray?: boolean; listFiles?: boolean; locale?: string; mapRoot?: string; @@ -2441,7 +2445,8 @@ namespace ts { export const enum JsxEmit { None = 0, Preserve = 1, - React = 2 + React = 2, + Transform = 3 } export const enum NewLineKind {