From ba422ff13574c5cef74eb31a930e6d880e8e6431 Mon Sep 17 00:00:00 2001 From: Benjamin Ye <24237065+enjoydambience@users.noreply.github.com> Date: Wed, 28 Jul 2021 17:43:21 -0700 Subject: [PATCH 1/6] JSX support --- src/transformation/utils/diagnostics.ts | 4 + src/transformation/visitors/index.ts | 2 + src/transformation/visitors/jsx/jsx.ts | 237 +++++++++++++ src/transformation/visitors/jsx/xhtml.ts | 257 ++++++++++++++ .../visitors/language-extensions/multi.ts | 2 +- src/transformation/visitors/literal.ts | 144 ++++---- test/unit/__snapshots__/jsx.spec.ts.snap | 34 ++ test/unit/jsx.spec.ts | 328 ++++++++++++++++++ 8 files changed, 946 insertions(+), 62 deletions(-) create mode 100644 src/transformation/visitors/jsx/jsx.ts create mode 100644 src/transformation/visitors/jsx/xhtml.ts create mode 100644 test/unit/__snapshots__/jsx.spec.ts.snap create mode 100644 test/unit/jsx.spec.ts diff --git a/src/transformation/utils/diagnostics.ts b/src/transformation/utils/diagnostics.ts index 16a0c7395..3e1e21baf 100644 --- a/src/transformation/utils/diagnostics.ts +++ b/src/transformation/utils/diagnostics.ts @@ -143,3 +143,7 @@ export const annotationDeprecated = createWarningDiagnosticFactory( `'@${kind}' is deprecated and will be removed in a future update. Please update your code before upgrading to the next release, otherwise your project will no longer compile. ` + `See https://typescripttolua.github.io/docs/advanced/compiler-annotations#${kind.toLowerCase()} for more information.` ); + +export const unsupportedJsxConfiguration = createErrorDiagnosticFactory( + 'JSX is only supported with "react" jsx option.' +); diff --git a/src/transformation/visitors/index.ts b/src/transformation/visitors/index.ts index b9983ae31..b05527364 100644 --- a/src/transformation/visitors/index.ts +++ b/src/transformation/visitors/index.ts @@ -40,6 +40,7 @@ import { transformTypeOfExpression } from "./typeof"; import { typescriptVisitors } from "./typescript"; import { transformPostfixUnaryExpression, transformPrefixUnaryExpression } from "./unary-expression"; import { transformVariableStatement } from "./variable-declaration"; +import { jsxVisitors } from "./jsx/jsx"; const transformEmptyStatement: FunctionVisitor = () => undefined; const transformParenthesizedExpression: FunctionVisitor = (node, context) => @@ -48,6 +49,7 @@ const transformParenthesizedExpression: FunctionVisitor= charCodes.a && ch <= charCodes.z) || name.includes("-") || name.includes(":"); +} + +function transformTagName(name: ts.JsxTagNameExpression, context: TransformationContext): lua.Expression { + if (ts.isIdentifier(name) && isIntrinsicJsxName(name.escapedText)) { + return lua.createStringLiteral(ts.idText(name), name); + } else { + return context.transformExpression(name); + } +} + +function transformJsxChildren( + children: ts.NodeArray | undefined, + context: TransformationContext +): lua.Expression[] | undefined { + if (!children) return undefined; + + const childrenExpressions = children + .map(child => { + if (ts.isJsxText(child)) { + return processJsxText(child); + } + if (ts.isJsxExpression(child)) { + return child.expression; + } + return child; + }) + .filter(child => child !== undefined) as ts.Expression[]; + + return flattenSpreadExpressions(context, childrenExpressions); +} + +function createJsxFactoryCall( + tagName: lua.Expression, + props: lua.Expression | undefined, + tsChildren: ts.NodeArray | undefined, + tsOriginal: ts.Node, + context: TransformationContext +): lua.Expression { + const transformedChildren = transformJsxChildren(tsChildren, context); + + const jsxFactory = getExpressionFromOption(context.options.jsxFactory, "React.createElement"); + + const args = [tagName]; + if (props) { + args.push(props); + } + if (transformedChildren && transformedChildren.length > 0) { + if (!props) { + args.push(lua.createNilLiteral()); + } + args.push(...transformedChildren); + } + return lua.createCallExpression(jsxFactory, args, tsOriginal); +} + +function transformJsxOpeningLikeElement( + node: ts.JsxOpeningLikeElement, + children: ts.NodeArray | undefined, + context: TransformationContext +): lua.Expression { + const tagName = transformTagName(node.tagName, context); + const props = + node.attributes.properties.length !== 0 ? transformJsxAttributes(node.attributes, context) : undefined; + + return createJsxFactoryCall(tagName, props, children, node, context); +} + +const transformJsxElement: FunctionVisitor = (node, context) => { + checkValidJsxConfig(node, context); + return transformJsxOpeningLikeElement(node.openingElement, node.children, context); +}; +const transformSelfClosingJsxElement: FunctionVisitor = (node, context) => { + checkValidJsxConfig(node, context); + return transformJsxOpeningLikeElement(node, undefined, context); +}; +const transformJsxFragment: FunctionVisitor = (node, context) => { + checkValidJsxConfig(node, context); + const tagName = getExpressionFromOption(context.options.jsxFragmentFactory, "React.Fragment"); + return createJsxFactoryCall(tagName, undefined, node.children, node, context); +}; + +export const jsxVisitors: Visitors = { + [ts.SyntaxKind.JsxElement]: transformJsxElement, + [ts.SyntaxKind.JsxSelfClosingElement]: transformSelfClosingJsxElement, + [ts.SyntaxKind.JsxFragment]: transformJsxFragment, +}; diff --git a/src/transformation/visitors/jsx/xhtml.ts b/src/transformation/visitors/jsx/xhtml.ts new file mode 100644 index 000000000..e231df35c --- /dev/null +++ b/src/transformation/visitors/jsx/xhtml.ts @@ -0,0 +1,257 @@ +/* eslint-disable @typescript-eslint/naming-convention */ +const entities: { [name: string]: string } = { + quot: "\u0022", + amp: "&", + apos: "\u0027", + lt: "<", + gt: ">", + nbsp: "\u00A0", + iexcl: "\u00A1", + cent: "\u00A2", + pound: "\u00A3", + curren: "\u00A4", + yen: "\u00A5", + brvbar: "\u00A6", + sect: "\u00A7", + uml: "\u00A8", + copy: "\u00A9", + ordf: "\u00AA", + laquo: "\u00AB", + not: "\u00AC", + shy: "\u00AD", + reg: "\u00AE", + macr: "\u00AF", + deg: "\u00B0", + plusmn: "\u00B1", + sup2: "\u00B2", + sup3: "\u00B3", + acute: "\u00B4", + micro: "\u00B5", + para: "\u00B6", + middot: "\u00B7", + cedil: "\u00B8", + sup1: "\u00B9", + ordm: "\u00BA", + raquo: "\u00BB", + frac14: "\u00BC", + frac12: "\u00BD", + frac34: "\u00BE", + iquest: "\u00BF", + Agrave: "\u00C0", + Aacute: "\u00C1", + Acirc: "\u00C2", + Atilde: "\u00C3", + Auml: "\u00C4", + Aring: "\u00C5", + AElig: "\u00C6", + Ccedil: "\u00C7", + Egrave: "\u00C8", + Eacute: "\u00C9", + Ecirc: "\u00CA", + Euml: "\u00CB", + Igrave: "\u00CC", + Iacute: "\u00CD", + Icirc: "\u00CE", + Iuml: "\u00CF", + ETH: "\u00D0", + Ntilde: "\u00D1", + Ograve: "\u00D2", + Oacute: "\u00D3", + Ocirc: "\u00D4", + Otilde: "\u00D5", + Ouml: "\u00D6", + times: "\u00D7", + Oslash: "\u00D8", + Ugrave: "\u00D9", + Uacute: "\u00DA", + Ucirc: "\u00DB", + Uuml: "\u00DC", + Yacute: "\u00DD", + THORN: "\u00DE", + szlig: "\u00DF", + agrave: "\u00E0", + aacute: "\u00E1", + acirc: "\u00E2", + atilde: "\u00E3", + auml: "\u00E4", + aring: "\u00E5", + aelig: "\u00E6", + ccedil: "\u00E7", + egrave: "\u00E8", + eacute: "\u00E9", + ecirc: "\u00EA", + euml: "\u00EB", + igrave: "\u00EC", + iacute: "\u00ED", + icirc: "\u00EE", + iuml: "\u00EF", + eth: "\u00F0", + ntilde: "\u00F1", + ograve: "\u00F2", + oacute: "\u00F3", + ocirc: "\u00F4", + otilde: "\u00F5", + ouml: "\u00F6", + divide: "\u00F7", + oslash: "\u00F8", + ugrave: "\u00F9", + uacute: "\u00FA", + ucirc: "\u00FB", + uuml: "\u00FC", + yacute: "\u00FD", + thorn: "\u00FE", + yuml: "\u00FF", + OElig: "\u0152", + oelig: "\u0153", + Scaron: "\u0160", + scaron: "\u0161", + Yuml: "\u0178", + fnof: "\u0192", + circ: "\u02C6", + tilde: "\u02DC", + Alpha: "\u0391", + Beta: "\u0392", + Gamma: "\u0393", + Delta: "\u0394", + Epsilon: "\u0395", + Zeta: "\u0396", + Eta: "\u0397", + Theta: "\u0398", + Iota: "\u0399", + Kappa: "\u039A", + Lambda: "\u039B", + Mu: "\u039C", + Nu: "\u039D", + Xi: "\u039E", + Omicron: "\u039F", + Pi: "\u03A0", + Rho: "\u03A1", + Sigma: "\u03A3", + Tau: "\u03A4", + Upsilon: "\u03A5", + Phi: "\u03A6", + Chi: "\u03A7", + Psi: "\u03A8", + Omega: "\u03A9", + alpha: "\u03B1", + beta: "\u03B2", + gamma: "\u03B3", + delta: "\u03B4", + epsilon: "\u03B5", + zeta: "\u03B6", + eta: "\u03B7", + theta: "\u03B8", + iota: "\u03B9", + kappa: "\u03BA", + lambda: "\u03BB", + mu: "\u03BC", + nu: "\u03BD", + xi: "\u03BE", + omicron: "\u03BF", + pi: "\u03C0", + rho: "\u03C1", + sigmaf: "\u03C2", + sigma: "\u03C3", + tau: "\u03C4", + upsilon: "\u03C5", + phi: "\u03C6", + chi: "\u03C7", + psi: "\u03C8", + omega: "\u03C9", + thetasym: "\u03D1", + upsih: "\u03D2", + piv: "\u03D6", + ensp: "\u2002", + emsp: "\u2003", + thinsp: "\u2009", + zwnj: "\u200C", + zwj: "\u200D", + lrm: "\u200E", + rlm: "\u200F", + ndash: "\u2013", + mdash: "\u2014", + lsquo: "\u2018", + rsquo: "\u2019", + sbquo: "\u201A", + ldquo: "\u201C", + rdquo: "\u201D", + bdquo: "\u201E", + dagger: "\u2020", + Dagger: "\u2021", + bull: "\u2022", + hellip: "\u2026", + permil: "\u2030", + prime: "\u2032", + Prime: "\u2033", + lsaquo: "\u2039", + rsaquo: "\u203A", + oline: "\u203E", + frasl: "\u2044", + euro: "\u20AC", + image: "\u2111", + weierp: "\u2118", + real: "\u211C", + trade: "\u2122", + alefsym: "\u2135", + larr: "\u2190", + uarr: "\u2191", + rarr: "\u2192", + darr: "\u2193", + harr: "\u2194", + crarr: "\u21B5", + lArr: "\u21D0", + uArr: "\u21D1", + rArr: "\u21D2", + dArr: "\u21D3", + hArr: "\u21D4", + forall: "\u2200", + part: "\u2202", + exist: "\u2203", + empty: "\u2205", + nabla: "\u2207", + isin: "\u2208", + notin: "\u2209", + ni: "\u220B", + prod: "\u220F", + sum: "\u2211", + minus: "\u2212", + lowast: "\u2217", + radic: "\u221A", + prop: "\u221D", + infin: "\u221E", + ang: "\u2220", + and: "\u2227", + or: "\u2228", + cap: "\u2229", + cup: "\u222A", + int: "\u222B", + there4: "\u2234", + sim: "\u223C", + cong: "\u2245", + asymp: "\u2248", + ne: "\u2260", + equiv: "\u2261", + le: "\u2264", + ge: "\u2265", + sub: "\u2282", + sup: "\u2283", + nsub: "\u2284", + sube: "\u2286", + supe: "\u2287", + oplus: "\u2295", + otimes: "\u2297", + perp: "\u22A5", + sdot: "\u22C5", + lceil: "\u2308", + rceil: "\u2309", + lfloor: "\u230A", + rfloor: "\u230B", + lang: "\u2329", + rang: "\u232A", + loz: "\u25CA", + spades: "\u2660", + clubs: "\u2663", + hearts: "\u2665", + diams: "\u2666", +}; +export const XHTMLEntities = entities; diff --git a/src/transformation/visitors/language-extensions/multi.ts b/src/transformation/visitors/language-extensions/multi.ts index 85b3c6a29..a433cd3f4 100644 --- a/src/transformation/visitors/language-extensions/multi.ts +++ b/src/transformation/visitors/language-extensions/multi.ts @@ -90,7 +90,7 @@ export function shouldMultiReturnCallBeWrapped(context: TransformationContext, n export function findMultiAssignmentViolations( context: TransformationContext, - node: ts.ObjectLiteralExpression + node: ts.ObjectLiteralExpressionBase ): ts.Node[] { const result: ts.Node[] = []; diff --git a/src/transformation/visitors/literal.ts b/src/transformation/visitors/literal.ts index 8fa85e4e4..75b4a3224 100644 --- a/src/transformation/visitors/literal.ts +++ b/src/transformation/visitors/literal.ts @@ -2,7 +2,7 @@ import * as ts from "typescript"; import * as lua from "../../LuaAST"; import { assertNever } from "../../utils"; import { FunctionVisitor, TransformationContext, Visitors } from "../context"; -import { unsupportedAccessorInObjectLiteral, invalidMultiFunctionUse } from "../utils/diagnostics"; +import { invalidMultiFunctionUse, unsupportedAccessorInObjectLiteral } from "../utils/diagnostics"; import { createExportedIdentifier, getSymbolExportScope } from "../utils/export"; import { LuaLibFeature, transformLuaLibFunction } from "../utils/lualib"; import { createSafeName, hasUnsafeIdentifierName, hasUnsafeSymbolName } from "../utils/safe-names"; @@ -11,6 +11,7 @@ import { isArrayType } from "../utils/typescript"; import { transformFunctionLikeDeclaration } from "./function"; import { flattenSpreadExpressions } from "./call"; import { findMultiAssignmentViolations } from "./language-extensions/multi"; +import { formatJSXStringValueLiteral } from "./jsx/jsx"; // TODO: Move to object-literal.ts? export function transformPropertyName(context: TransformationContext, node: ts.PropertyName): lua.Expression { @@ -62,78 +63,99 @@ const transformNumericLiteralExpression: FunctionVisitor = ex return lua.createNumericLiteral(Number(expression.text), expression); }; -const transformObjectLiteralExpression: FunctionVisitor = (expression, context) => { - const violations = findMultiAssignmentViolations(context, expression); - if (violations.length > 0) { - context.diagnostics.push(...violations.map(e => invalidMultiFunctionUse(e))); - return lua.createNilLiteral(expression); - } - - let properties: lua.TableFieldExpression[] = []; - const tableExpressions: lua.Expression[] = []; +const transformObjectLiteralExpressionOrJsxAttributes: FunctionVisitor = + (expression, context) => { + const violations = findMultiAssignmentViolations(context, expression); + if (violations.length > 0) { + context.diagnostics.push(...violations.map(e => invalidMultiFunctionUse(e))); + return lua.createNilLiteral(expression); + } - for (const element of expression.properties) { - const name = element.name ? transformPropertyName(context, element.name) : undefined; + let properties: lua.TableFieldExpression[] = []; + const tableExpressions: lua.Expression[] = []; + + for (const element of expression.properties) { + const name = element.name ? transformPropertyName(context, element.name) : undefined; + + if (ts.isPropertyAssignment(element)) { + const expression = context.transformExpression(element.initializer); + properties.push(lua.createTableFieldExpression(expression, name, element)); + } else if (ts.isJsxAttribute(element)) { + const initializer = element.initializer; + let expression: lua.Expression; + if (initializer === undefined) { + expression = lua.createBooleanLiteral(true); + } else if (ts.isStringLiteral(initializer)) { + const text = formatJSXStringValueLiteral(initializer.text); + expression = lua.createStringLiteral(text, initializer); + } else if (ts.isJsxExpression(initializer)) { + expression = initializer.expression + ? context.transformExpression(initializer.expression) + : lua.createBooleanLiteral(true); + } else { + assertNever(initializer); + } + properties.push(lua.createTableFieldExpression(expression, name, element)); + } else if (ts.isShorthandPropertyAssignment(element)) { + const valueSymbol = context.checker.getShorthandAssignmentValueSymbol(element); + if (valueSymbol) { + trackSymbolReference(context, valueSymbol, element.name); + } + + const identifier = createShorthandIdentifier(context, valueSymbol, element.name); + properties.push(lua.createTableFieldExpression(identifier, name, element)); + } else if (ts.isMethodDeclaration(element)) { + const expression = transformFunctionLikeDeclaration(element, context); + properties.push(lua.createTableFieldExpression(expression, name, element)); + } else if (ts.isSpreadAssignment(element) || ts.isJsxSpreadAttribute(element)) { + // Create a table for preceding properties to preserve property order + // { x: 0, ...{ y: 2 }, y: 1, z: 2 } --> __TS__ObjectAssign({x = 0}, {y = 2}, {y = 1, z = 2}) + if (properties.length > 0) { + const tableExpression = lua.createTableExpression(properties, expression); + tableExpressions.push(tableExpression); + properties = []; + } + + const type = context.checker.getTypeAtLocation(element.expression); + let tableExpression: lua.Expression; + if (isArrayType(context, type)) { + tableExpression = transformLuaLibFunction( + context, + LuaLibFeature.ArrayToObject, + element.expression, + context.transformExpression(element.expression) + ); + } else { + tableExpression = context.transformExpression(element.expression); + } - if (ts.isPropertyAssignment(element)) { - const expression = context.transformExpression(element.initializer); - properties.push(lua.createTableFieldExpression(expression, name, element)); - } else if (ts.isShorthandPropertyAssignment(element)) { - const valueSymbol = context.checker.getShorthandAssignmentValueSymbol(element); - if (valueSymbol) { - trackSymbolReference(context, valueSymbol, element.name); + tableExpressions.push(tableExpression); + } else if (ts.isAccessor(element)) { + context.diagnostics.push(unsupportedAccessorInObjectLiteral(element)); + } else { + assertNever(element); } + } - const identifier = createShorthandIdentifier(context, valueSymbol, element.name); - properties.push(lua.createTableFieldExpression(identifier, name, element)); - } else if (ts.isMethodDeclaration(element)) { - const expression = transformFunctionLikeDeclaration(element, context); - properties.push(lua.createTableFieldExpression(expression, name, element)); - } else if (ts.isSpreadAssignment(element)) { - // Create a table for preceding properties to preserve property order - // { x: 0, ...{ y: 2 }, y: 1, z: 2 } --> __TS__ObjectAssign({x = 0}, {y = 2}, {y = 1, z = 2}) + if (tableExpressions.length === 0) { + return lua.createTableExpression(properties, expression); + } else { if (properties.length > 0) { const tableExpression = lua.createTableExpression(properties, expression); tableExpressions.push(tableExpression); - properties = []; } - const type = context.checker.getTypeAtLocation(element.expression); - let tableExpression: lua.Expression; - if (isArrayType(context, type)) { - tableExpression = transformLuaLibFunction( - context, - LuaLibFeature.ArrayToObject, - element.expression, - context.transformExpression(element.expression) - ); - } else { - tableExpression = context.transformExpression(element.expression); + if (tableExpressions[0].kind !== lua.SyntaxKind.TableExpression) { + tableExpressions.unshift(lua.createTableExpression(undefined, expression)); } - tableExpressions.push(tableExpression); - } else if (ts.isAccessor(element)) { - context.diagnostics.push(unsupportedAccessorInObjectLiteral(element)); - } else { - assertNever(element); - } - } - - if (tableExpressions.length === 0) { - return lua.createTableExpression(properties, expression); - } else { - if (properties.length > 0) { - const tableExpression = lua.createTableExpression(properties, expression); - tableExpressions.push(tableExpression); + return transformLuaLibFunction(context, LuaLibFeature.ObjectAssign, expression, ...tableExpressions); } - - if (tableExpressions[0].kind !== lua.SyntaxKind.TableExpression) { - tableExpressions.unshift(lua.createTableExpression(undefined, expression)); - } - - return transformLuaLibFunction(context, LuaLibFeature.ObjectAssign, expression, ...tableExpressions); - } -}; + }; +const transformObjectLiteralExpression: FunctionVisitor = + transformObjectLiteralExpressionOrJsxAttributes; +export const transformJsxAttributes: FunctionVisitor = + transformObjectLiteralExpressionOrJsxAttributes; const transformArrayLiteralExpression: FunctionVisitor = (expression, context) => { const filteredElements = expression.elements.map(e => diff --git a/test/unit/__snapshots__/jsx.spec.ts.snap b/test/unit/__snapshots__/jsx.spec.ts.snap new file mode 100644 index 000000000..bca53c972 --- /dev/null +++ b/test/unit/__snapshots__/jsx.spec.ts.snap @@ -0,0 +1,34 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`jsx complex 1`] = ` +"require(\\"lualib_bundle\\"); +local ____exports = {} +local ____react = require(\\"react\\") +local React = ____react.default +function ____exports.__main(self) + local x = 3 + local props = {one = \\"two\\", three = 4} + return React.createElement( + \\"div\\", + {a = \\"b\\"}, + React.createElement(\\"a\\", {c = \\"d\\", r = x}), + React.createElement( + \\"b\\", + __TS__ObjectAssign({[\\"baz-bar\\"] = true}, props), + \\" the \\", + x, + \\"marks the spot \\" + ), + \\"a\\", + \\"and\\", + React.createElement( + React.Fragment, + nil, + \\" \\", + React.createElement(\\"a\\", nil, \\"2\\") + ), + React.createElement(\\"b\\") + ) +end +return ____exports" +`; diff --git a/test/unit/jsx.spec.ts b/test/unit/jsx.spec.ts new file mode 100644 index 000000000..27b6a3381 --- /dev/null +++ b/test/unit/jsx.spec.ts @@ -0,0 +1,328 @@ +import * as util from "../util"; +import { TestBuilder } from "../util"; +import { JsxEmit } from "typescript"; + +// language=TypeScript +const reactLib = ` + export class Component { + static isClass = true + } + + namespace React { + const isLua = typeof Component === "object" + + export function createElement( + this: void, + type: any, + props?: any, + ...children: any[] + ) { + let typeStr: string + if (isLua) { + typeStr = + typeof type === "function" ? "<< function >>" : + typeof type === "object" ? \`<< class \${type.name} >>\` : + type + } else { + typeStr = typeof type === "function" ? (type.isClass ? \`<< class \${type.name} >>\` : "<< function >>") + : type + } + + return { + type: typeStr, + props, + children + }; + } + + export class Fragment extends Component { + } + } + export default React; +`; +// language=TypeScript +const jsxTypings = `declare namespace JSX { + interface IntrinsicElements { + a: any + b: any + foo: any + bar: any + div: any + "with-dash": any + } +} +`; + +function testJsx(...args: [string] | [TemplateStringsArray, ...any[]]): TestBuilder { + return util + .testFunction(...args) + .setOptions({ + jsx: JsxEmit.React, + }) + .setMainFileName("main.tsx") + .addExtraFile("react.ts", reactLib) + .addExtraFile("jsx.d.ts", jsxTypings) + .setTsHeader('import React, { Component } from "./react";'); +} + +describe("jsx", () => { + test("element", () => { + testJsx` + return + `.expectToMatchJsResult(); + }); + test("self closing element", () => { + testJsx` + return + `.expectToMatchJsResult(); + }); + test("element with dash name", () => { + testJsx` + return + `.expectToMatchJsResult(); + }); + test("custom element", () => { + testJsx` + function Foo() {} + return + `.expectToMatchJsResult(); + }); + test("fragment", () => { + testJsx` + return <> + `.expectToMatchJsResult(); + }); + test("fragment with children", () => { + testJsx` + return <> + `.expectToMatchJsResult(); + }); + test("esoteric component names", () => { + testJsx` + class _Foo extends Component {} + return <_Foo /> + `.expectToMatchJsResult(); + + testJsx` + class $ extends Component {} + return <$ /> + `.expectToMatchJsResult(); + + testJsx` + class é extends Component {} + return <é /> + `.expectToMatchJsResult(); + }); + test("nested elements", () => { + testJsx` + return + `.expectToMatchJsResult(); + }); + test("many nested elements", () => { + testJsx` + return + `.expectToMatchJsResult(); + }); + test("interpolated children", () => { + testJsx` + const x = 3 + return {x} + `.expectToMatchJsResult(); + }); + test("string prop", () => { + testJsx` + return + `.expectToMatchJsResult(); + }); + test("value prop", () => { + testJsx` + const x = 5 + return + `.expectToMatchJsResult(); + }); + test("quoted prop", () => { + testJsx` + return + `.expectToMatchJsResult(); + }); + test("shorthand prop", () => { + testJsx` + return + `.expectToMatchJsResult(); + }); + test("spaces in jsxText", () => { + testJsx` + return this + is somemultiline + text thing. + + + `.expectToMatchJsResult(); + }); + test("multiline string jsxText", () => { + testJsx` + return + foo bar + baz + + `.expectToMatchJsResult(); + }); + + test("access tag value", () => { + testJsx` + const a = { b(){} }; + return + `.expectToMatchJsResult(); + testJsx` + const a = { b: { c: { d(){} } } }; + return + `.expectToMatchJsResult(); + }); + + test("spread props", () => { + testJsx` + const x = {c: "d", e: "f"} + return + `.expectToMatchJsResult(); + testJsx` + const x = {c: "d", e: "no"} + return + `.expectToMatchJsResult(); + }); + + test("comment children", () => { + testJsx` + return + {/* comment */} + {/* another comment */} + + `.expectToMatchJsResult(); + testJsx` + return + + {/* comment */} + + + `.expectToMatchJsResult(); + }); + + test("multiline string prop value", () => { + testJsx` + return
+ `.expectToMatchJsResult(); + testJsx` + return
+ `.expectToMatchJsResult(); + }); + + test("prop strings with entities", () => { + testJsx` + return
+ `.expectToMatchJsResult(); + }); + test("jsxText with entities", () => { + testJsx` + return 9+10<21 + `.expectToMatchJsResult(); + }); + + test("Spread children", () => { + // doesn't actually "spread" (typescript's current behavior) + testJsx` + const children = [, ] + return {...children} + `.expectToMatchJsResult(); + }); + + test("complex", () => { + testJsx` + const x = 3 + const props = {one: "two", three: 4} + return + ` + .expectToMatchJsResult() + .expectLuaToMatchSnapshot(); + }); + + test("custom JSX factory", () => { + testJsx` + return c + ` + .setTsHeader('import { MyLib } from "./myJsx";') + .setOptions({ jsxFactory: "MyLib.myCreate" }) + .addExtraFile( + "myJsx.ts", + ` + export namespace MyLib { + export function myCreate( + this: void, + type: any, + props: any, + ...children: any[] + ) { + return { type, props, children, myThing: true }; + } + } + ` + ) + .expectToMatchJsResult(); + }); + test("custom fragment factory", () => { + testJsx` + return <>c + ` + .setTsHeader('import { MyLib } from "./myJsx";') + .setOptions({ jsxFactory: "MyLib.myCreate", jsxFragmentFactory: "MyLib.MyFragment" }) + .addExtraFile( + "myJsx.ts", + ` + export namespace MyLib { + export function myCreate( + this: void, + type: any, + props: any, + ...children: any[] + ) { + return { type: typeof type, props, children, myThing: true }; + } + export function MyFragment() {} + } + ` + ) + .expectToMatchJsResult(); + }); + + test("forward declare components", () => { + testJsx` + const foo = + + function Foo(){} + + return foo + `.expectToMatchJsResult(); + }); + + test("invalid jsx config", () => { + testJsx(` + return + `) + .setOptions({ + jsx: JsxEmit.Preserve, + }) + .expectToHaveDiagnostics(); + }); +}); From 7d8bae58f807d3e5d72a219f305438737481e791 Mon Sep 17 00:00:00 2001 From: Benjamin Ye <24237065+enjoydambience@users.noreply.github.com> Date: Thu, 29 Jul 2021 08:56:55 -0700 Subject: [PATCH 2/6] Add jsx and jsxFrag pragma support --- src/transformation/utils/annotations.ts | 2 + src/transformation/visitors/jsx/jsx.ts | 25 +++++-- test/unit/jsx.spec.ts | 95 +++++++++++++++++-------- 3 files changed, 88 insertions(+), 34 deletions(-) diff --git a/src/transformation/utils/annotations.ts b/src/transformation/utils/annotations.ts index 212f30416..d07df2808 100644 --- a/src/transformation/utils/annotations.ts +++ b/src/transformation/utils/annotations.ts @@ -17,6 +17,8 @@ export enum AnnotationKind { NoSelfInFile = "noSelfInFile", Vararg = "vararg", ForRange = "forRange", + Jsx = "jsx", + JsxFrag = "jsxFrag", } export interface Annotation { diff --git a/src/transformation/visitors/jsx/jsx.ts b/src/transformation/visitors/jsx/jsx.ts index 94925de15..582fde3fc 100644 --- a/src/transformation/visitors/jsx/jsx.ts +++ b/src/transformation/visitors/jsx/jsx.ts @@ -6,6 +6,7 @@ import { transformJsxAttributes } from "../literal"; import { flattenSpreadExpressions } from "../call"; import { unsupportedJsxConfiguration } from "../../utils/diagnostics"; import { XHTMLEntities } from "./xhtml"; +import { AnnotationKind, getFileAnnotations } from "../../utils/annotations"; function checkValidJsxConfig(node: ts.Node, context: TransformationContext) { if (context.options.jsx !== JsxEmit.React) { @@ -14,8 +15,14 @@ function checkValidJsxConfig(node: ts.Node, context: TransformationContext) { } // used for jsxFactory and jsxFragmentFactory options -function getExpressionFromOption(option: string | undefined, defaultValue: string): lua.Expression { - const [first, second] = (option ?? defaultValue).split("."); +function getExpressionFromOption( + node: ts.Node, + fileAnnotation: AnnotationKind, + option: string | undefined, + defaultValue: string +): lua.Expression { + const annotation = getFileAnnotations(node.getSourceFile()).get(fileAnnotation); + const [first, second] = (annotation?.args[0] ?? option ?? defaultValue).split("."); return second === undefined ? lua.createIdentifier(first) : lua.createTableIndexExpression(lua.createIdentifier(first), lua.createStringLiteral(second)); @@ -189,7 +196,12 @@ function createJsxFactoryCall( ): lua.Expression { const transformedChildren = transformJsxChildren(tsChildren, context); - const jsxFactory = getExpressionFromOption(context.options.jsxFactory, "React.createElement"); + const jsxFactory = getExpressionFromOption( + tsOriginal, + AnnotationKind.Jsx, + context.options.jsxFactory, + "React.createElement" + ); const args = [tagName]; if (props) { @@ -226,7 +238,12 @@ const transformSelfClosingJsxElement: FunctionVisitor }; const transformJsxFragment: FunctionVisitor = (node, context) => { checkValidJsxConfig(node, context); - const tagName = getExpressionFromOption(context.options.jsxFragmentFactory, "React.Fragment"); + const tagName = getExpressionFromOption( + node, + AnnotationKind.JsxFrag, + context.options.jsxFragmentFactory, + "React.Fragment" + ); return createJsxFactoryCall(tagName, undefined, node.children, node, context); }; diff --git a/test/unit/jsx.spec.ts b/test/unit/jsx.spec.ts index 27b6a3381..d0b0bf8e7 100644 --- a/test/unit/jsx.spec.ts +++ b/test/unit/jsx.spec.ts @@ -258,27 +258,36 @@ describe("jsx", () => { .expectLuaToMatchSnapshot(); }); + // language=TypeScript + const customJsxLib = `export namespace MyLib { + export function myCreate( + this: void, + type: any, + props: any, + ...children: any[] + ) { + return { type: typeof type, props, children, myThing: true }; + } + + export function MyFragment() { + } + } + `; + test("custom JSX factory", () => { testJsx` return c ` .setTsHeader('import { MyLib } from "./myJsx";') .setOptions({ jsxFactory: "MyLib.myCreate" }) - .addExtraFile( - "myJsx.ts", - ` - export namespace MyLib { - export function myCreate( - this: void, - type: any, - props: any, - ...children: any[] - ) { - return { type, props, children, myThing: true }; - } - } - ` - ) + .addExtraFile("myJsx.ts", customJsxLib) + .expectToMatchJsResult(); + testJsx` + return c + ` + .setTsHeader('import { MyLib } from "./myJsx";const myCreate2 = MyLib.myCreate;') + .setOptions({ jsxFactory: "myCreate2" }) + .addExtraFile("myJsx.ts", customJsxLib) .expectToMatchJsResult(); }); test("custom fragment factory", () => { @@ -287,22 +296,48 @@ describe("jsx", () => { ` .setTsHeader('import { MyLib } from "./myJsx";') .setOptions({ jsxFactory: "MyLib.myCreate", jsxFragmentFactory: "MyLib.MyFragment" }) - .addExtraFile( - "myJsx.ts", - ` - export namespace MyLib { - export function myCreate( - this: void, - type: any, - props: any, - ...children: any[] - ) { - return { type: typeof type, props, children, myThing: true }; - } - export function MyFragment() {} - } - ` + .addExtraFile("myJsx.ts", customJsxLib) + .expectToMatchJsResult(); + testJsx` + return <>c + ` + .setTsHeader('import { MyLib } from "./myJsx";function MyFragment2(){};') + .setOptions({ jsxFactory: "MyLib.myCreate", jsxFragmentFactory: "MyFragment2" }) + .addExtraFile("myJsx.ts", customJsxLib) + .expectToMatchJsResult(); + }); + test("custom JSX pragma", () => { + testJsx` + return c + ` + .setTsHeader('/** @jsx MyLib.myCreate */\nimport { MyLib } from "./myJsx";') + .addExtraFile("myJsx.ts", customJsxLib) + .expectToMatchJsResult(); + testJsx` + return c + ` + .setTsHeader('/** @jsx myCreate2 */import { MyLib } from "./myJsx";const myCreate2 = MyLib.myCreate;') + .addExtraFile("myJsx.ts", customJsxLib) + .expectToMatchJsResult(); + }); + test("custom fragment pragma", () => { + testJsx` + return <>c + ` + .setTsHeader( + '/** @jsx MyLib.myCreate */\n/** @jsxFrag MyLib.MyFragment */\nimport { MyLib } from "./myJsx";' + ) + .addExtraFile("myJsx.ts", customJsxLib) + .expectToMatchJsResult(); + testJsx` + return <>c + ` + .setTsHeader( + "/** @jsx MyLib.myCreate */\n/** @jsxFrag MyFragment2 */\n" + + 'import { MyLib } from "./myJsx";function MyFragment2(){};' ) + .setOptions({ jsxFactory: "MyLib.myCreate", jsxFragmentFactory: "MyFragment" }) + .addExtraFile("myJsx.ts", customJsxLib) .expectToMatchJsResult(); }); From 172b0bab02ecd78ab867b0d8b1c78413ad5e4069 Mon Sep 17 00:00:00 2001 From: Benjamin Ye <24237065+enjoydambience@users.noreply.github.com> Date: Thu, 29 Jul 2021 09:05:32 -0700 Subject: [PATCH 3/6] Remove unneeded flattenSpreadExpressions call. Typescript doesn't actually "spread out" jsx spread children, even though the syntax is supported. --- src/transformation/visitors/jsx/jsx.ts | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/transformation/visitors/jsx/jsx.ts b/src/transformation/visitors/jsx/jsx.ts index 582fde3fc..0d7153ba1 100644 --- a/src/transformation/visitors/jsx/jsx.ts +++ b/src/transformation/visitors/jsx/jsx.ts @@ -3,7 +3,6 @@ import { JsxEmit } from "typescript"; import * as lua from "../../../LuaAST"; import { FunctionVisitor, TransformationContext, Visitors } from "../../context"; import { transformJsxAttributes } from "../literal"; -import { flattenSpreadExpressions } from "../call"; import { unsupportedJsxConfiguration } from "../../utils/diagnostics"; import { XHTMLEntities } from "./xhtml"; import { AnnotationKind, getFileAnnotations } from "../../utils/annotations"; @@ -172,7 +171,7 @@ function transformJsxChildren( ): lua.Expression[] | undefined { if (!children) return undefined; - const childrenExpressions = children + return children .map(child => { if (ts.isJsxText(child)) { return processJsxText(child); @@ -182,9 +181,8 @@ function transformJsxChildren( } return child; }) - .filter(child => child !== undefined) as ts.Expression[]; - - return flattenSpreadExpressions(context, childrenExpressions); + .filter(child => child !== undefined) + .map(child => context.transformExpression(child!)); } function createJsxFactoryCall( From 18df067ebc6db9ff32dcca0c61f0d682f8dd03df Mon Sep 17 00:00:00 2001 From: Benjamin Ye <24237065+enjoydambience@users.noreply.github.com> Date: Thu, 29 Jul 2021 15:52:57 -0700 Subject: [PATCH 4/6] Changes for PR. --- src/CompilerOptions.ts | 5 ++ src/transformation/utils/diagnostics.ts | 4 -- src/transformation/visitors/jsx/jsx.ts | 65 +++++++++--------------- src/transformation/visitors/jsx/xhtml.ts | 3 +- src/transpilation/diagnostics.ts | 2 + 5 files changed, 33 insertions(+), 46 deletions(-) diff --git a/src/CompilerOptions.ts b/src/CompilerOptions.ts index 0610d26b3..9fc232f68 100644 --- a/src/CompilerOptions.ts +++ b/src/CompilerOptions.ts @@ -1,4 +1,5 @@ import * as ts from "typescript"; +import { JsxEmit } from "typescript"; import * as diagnosticFactories from "./transpilation/diagnostics"; type OmitIndexSignature = { @@ -74,5 +75,9 @@ export function validateOptions(options: CompilerOptions): ts.Diagnostic[] { diagnostics.push(diagnosticFactories.cannotBundleLibrary()); } + if (options.jsx && options.jsx !== JsxEmit.React) { + diagnostics.push(diagnosticFactories.unsupportedJsxEmit()); + } + return diagnostics; } diff --git a/src/transformation/utils/diagnostics.ts b/src/transformation/utils/diagnostics.ts index 3e1e21baf..16a0c7395 100644 --- a/src/transformation/utils/diagnostics.ts +++ b/src/transformation/utils/diagnostics.ts @@ -143,7 +143,3 @@ export const annotationDeprecated = createWarningDiagnosticFactory( `'@${kind}' is deprecated and will be removed in a future update. Please update your code before upgrading to the next release, otherwise your project will no longer compile. ` + `See https://typescripttolua.github.io/docs/advanced/compiler-annotations#${kind.toLowerCase()} for more information.` ); - -export const unsupportedJsxConfiguration = createErrorDiagnosticFactory( - 'JSX is only supported with "react" jsx option.' -); diff --git a/src/transformation/visitors/jsx/jsx.ts b/src/transformation/visitors/jsx/jsx.ts index 0d7153ba1..7dc3927c4 100644 --- a/src/transformation/visitors/jsx/jsx.ts +++ b/src/transformation/visitors/jsx/jsx.ts @@ -1,32 +1,34 @@ import * as ts from "typescript"; -import { JsxEmit } from "typescript"; import * as lua from "../../../LuaAST"; import { FunctionVisitor, TransformationContext, Visitors } from "../../context"; import { transformJsxAttributes } from "../literal"; -import { unsupportedJsxConfiguration } from "../../utils/diagnostics"; import { XHTMLEntities } from "./xhtml"; import { AnnotationKind, getFileAnnotations } from "../../utils/annotations"; -function checkValidJsxConfig(node: ts.Node, context: TransformationContext) { - if (context.options.jsx !== JsxEmit.React) { - context.diagnostics.push(unsupportedJsxConfiguration(node)); - } +function findAnnotationByType(node: ts.Node, fileAnnotation: AnnotationKind): string | undefined { + const annotation = getFileAnnotations(node.getSourceFile()).get(fileAnnotation); + return annotation?.args[0]; } -// used for jsxFactory and jsxFragmentFactory options -function getExpressionFromOption( - node: ts.Node, - fileAnnotation: AnnotationKind, - option: string | undefined, - defaultValue: string -): lua.Expression { - const annotation = getFileAnnotations(node.getSourceFile()).get(fileAnnotation); - const [first, second] = (annotation?.args[0] ?? option ?? defaultValue).split("."); +function getExpressionFromOption(option: string): lua.Expression { + const [first, second] = option.split("."); return second === undefined ? lua.createIdentifier(first) : lua.createTableIndexExpression(lua.createIdentifier(first), lua.createStringLiteral(second)); } +function getJsxFactory(node: ts.Node, context: TransformationContext): lua.Expression { + const option = + findAnnotationByType(node, AnnotationKind.Jsx) ?? context.options.jsxFactory ?? "React.createElement"; + return getExpressionFromOption(option); +} + +function getJsxFragmentName(node: ts.Node, context: TransformationContext): lua.Expression { + const option = + findAnnotationByType(node, AnnotationKind.JsxFrag) ?? context.options.jsxFragmentFactory ?? "React.Fragment"; + return getExpressionFromOption(option); +} + /* Implementations of jsx text processing modified from sucrase (https://github.com/alangpierce/sucrase). */ const HEX_NUMBER = /^[\da-fA-F]+$/; @@ -38,8 +40,7 @@ const DECIMAL_NUMBER = /^\d+$/; * before the close-tag. Empty lines are completely removed, and spaces are * added between lines after that. * - * We use JSON.stringify to introduce escape characters as necessary, and trim - * the start and end of each line and remove blank lines. + * We trim the start and end of each line and remove blank lines. */ function formatJSXTextLiteral(text: string): string { let result = ""; @@ -85,7 +86,7 @@ function formatJSXTextLiteral(text: string): string { * Use the same implementation as convertAttribute from * babel-helper-builder-react-jsx. */ -// no multi-line flattening of prop strings in typescript. +// changes from sucrase: no multi-line flattening of prop strings in typescript. export function formatJSXStringValueLiteral(text: string): string { let result = ""; for (let i = 0; i < text.length; i++) { @@ -193,13 +194,7 @@ function createJsxFactoryCall( context: TransformationContext ): lua.Expression { const transformedChildren = transformJsxChildren(tsChildren, context); - - const jsxFactory = getExpressionFromOption( - tsOriginal, - AnnotationKind.Jsx, - context.options.jsxFactory, - "React.createElement" - ); + const jsxFactory = getJsxFactory(tsOriginal, context); const args = [tagName]; if (props) { @@ -226,22 +221,12 @@ function transformJsxOpeningLikeElement( return createJsxFactoryCall(tagName, props, children, node, context); } -const transformJsxElement: FunctionVisitor = (node, context) => { - checkValidJsxConfig(node, context); - return transformJsxOpeningLikeElement(node.openingElement, node.children, context); -}; -const transformSelfClosingJsxElement: FunctionVisitor = (node, context) => { - checkValidJsxConfig(node, context); - return transformJsxOpeningLikeElement(node, undefined, context); -}; +const transformJsxElement: FunctionVisitor = (node, context) => + transformJsxOpeningLikeElement(node.openingElement, node.children, context); +const transformSelfClosingJsxElement: FunctionVisitor = (node, context) => + transformJsxOpeningLikeElement(node, undefined, context); const transformJsxFragment: FunctionVisitor = (node, context) => { - checkValidJsxConfig(node, context); - const tagName = getExpressionFromOption( - node, - AnnotationKind.JsxFrag, - context.options.jsxFragmentFactory, - "React.Fragment" - ); + const tagName = getJsxFragmentName(node, context); return createJsxFactoryCall(tagName, undefined, node.children, node, context); }; diff --git a/src/transformation/visitors/jsx/xhtml.ts b/src/transformation/visitors/jsx/xhtml.ts index e231df35c..814191df0 100644 --- a/src/transformation/visitors/jsx/xhtml.ts +++ b/src/transformation/visitors/jsx/xhtml.ts @@ -1,5 +1,5 @@ /* eslint-disable @typescript-eslint/naming-convention */ -const entities: { [name: string]: string } = { +export const XHTMLEntities: { [name: string]: string } = { quot: "\u0022", amp: "&", apos: "\u0027", @@ -254,4 +254,3 @@ const entities: { [name: string]: string } = { hearts: "\u2665", diams: "\u2666", }; -export const XHTMLEntities = entities; diff --git a/src/transpilation/diagnostics.ts b/src/transpilation/diagnostics.ts index 4b57c329d..098d78740 100644 --- a/src/transpilation/diagnostics.ts +++ b/src/transpilation/diagnostics.ts @@ -53,3 +53,5 @@ export const cannotBundleLibrary = createDiagnosticFactory( () => 'Cannot bundle probjects with"buildmode": "library". Projects including the library can still bundle (which will include external library files).' ); + +export const unsupportedJsxEmit = createDiagnosticFactory(() => 'JSX is only supported with "react" jsx option.'); From ec14feec9c2d9193a6abbc49a94002f866806f2c Mon Sep 17 00:00:00 2001 From: Benjamin Ye <24237065+enjoydambience@users.noreply.github.com> Date: Fri, 30 Jul 2021 08:57:39 -0700 Subject: [PATCH 5/6] Add license for functions copied from sucrase --- src/transformation/visitors/jsx/jsx.ts | 29 +++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/src/transformation/visitors/jsx/jsx.ts b/src/transformation/visitors/jsx/jsx.ts index 7dc3927c4..a6ca42155 100644 --- a/src/transformation/visitors/jsx/jsx.ts +++ b/src/transformation/visitors/jsx/jsx.ts @@ -29,7 +29,32 @@ function getJsxFragmentName(node: ts.Node, context: TransformationContext): lua. return getExpressionFromOption(option); } -/* Implementations of jsx text processing modified from sucrase (https://github.com/alangpierce/sucrase). */ +/* +The following 3 functions for jsx text processing modified from sucrase (https://github.com/alangpierce/sucrase), which +is published with the MIT licence: + +The MIT License (MIT) + +Copyright (c) 2012-2018 various contributors (see AUTHORS) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ const HEX_NUMBER = /^[\da-fA-F]+$/; const DECIMAL_NUMBER = /^\d+$/; @@ -140,6 +165,8 @@ function processEntity(text: string, indexAfterAmpersand: number): { entity: str return { entity, newI: i }; } +// end functions copied from sucrase + function processJsxText(jsxText: ts.JsxText): ts.StringLiteral | undefined { const text = formatJSXTextLiteral(jsxText.text); if (text === "") return undefined; From 3397b54c299e2d7248b8f6fb4ed5f667b5d186b9 Mon Sep 17 00:00:00 2001 From: Benjamin Ye <24237065+enjoydambience@users.noreply.github.com> Date: Fri, 30 Jul 2021 08:59:05 -0700 Subject: [PATCH 6/6] Remove jsx snapshot test --- test/unit/__snapshots__/jsx.spec.ts.snap | 34 ------------------------ test/unit/jsx.spec.ts | 4 +-- 2 files changed, 1 insertion(+), 37 deletions(-) delete mode 100644 test/unit/__snapshots__/jsx.spec.ts.snap diff --git a/test/unit/__snapshots__/jsx.spec.ts.snap b/test/unit/__snapshots__/jsx.spec.ts.snap deleted file mode 100644 index bca53c972..000000000 --- a/test/unit/__snapshots__/jsx.spec.ts.snap +++ /dev/null @@ -1,34 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`jsx complex 1`] = ` -"require(\\"lualib_bundle\\"); -local ____exports = {} -local ____react = require(\\"react\\") -local React = ____react.default -function ____exports.__main(self) - local x = 3 - local props = {one = \\"two\\", three = 4} - return React.createElement( - \\"div\\", - {a = \\"b\\"}, - React.createElement(\\"a\\", {c = \\"d\\", r = x}), - React.createElement( - \\"b\\", - __TS__ObjectAssign({[\\"baz-bar\\"] = true}, props), - \\" the \\", - x, - \\"marks the spot \\" - ), - \\"a\\", - \\"and\\", - React.createElement( - React.Fragment, - nil, - \\" \\", - React.createElement(\\"a\\", nil, \\"2\\") - ), - React.createElement(\\"b\\") - ) -end -return ____exports" -`; diff --git a/test/unit/jsx.spec.ts b/test/unit/jsx.spec.ts index d0b0bf8e7..5734b33ee 100644 --- a/test/unit/jsx.spec.ts +++ b/test/unit/jsx.spec.ts @@ -253,9 +253,7 @@ describe("jsx", () => { <> 2
- ` - .expectToMatchJsResult() - .expectLuaToMatchSnapshot(); + `.expectToMatchJsResult(); }); // language=TypeScript