Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix/create element new jsx transform #41151

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 3 additions & 3 deletions src/compiler/factory/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ namespace ts {
}
}

function createJsxFactoryExpression(factory: NodeFactory, jsxFactoryEntity: EntityName | undefined, reactNamespace: string, parent: JsxOpeningLikeElement | JsxOpeningFragment): Expression {
export function createJsxFactoryExpression(factory: NodeFactory, jsxFactoryEntity: EntityName | undefined, reactNamespace: string, parent: JsxOpeningLikeElement | JsxOpeningFragment): Expression {
return jsxFactoryEntity ?
createJsxFactoryExpressionFromEntityName(factory, jsxFactoryEntity, parent) :
factory.createPropertyAccessExpression(
Expand All @@ -64,7 +64,7 @@ namespace ts {
);
}

export function createExpressionForJsxElement(factory: NodeFactory, jsxFactoryEntity: EntityName | undefined, reactNamespace: string, tagName: Expression, props: Expression | undefined, children: readonly Expression[] | undefined, parentElement: JsxOpeningLikeElement, location: TextRange): LeftHandSideExpression {
export function createExpressionForJsxElement(factory: NodeFactory, callee: Expression, tagName: Expression, props: Expression | undefined, children: readonly Expression[] | undefined, location: TextRange): LeftHandSideExpression {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the signature differs now from the accompanying createExpressionForJsxFragment but this is just a util file so I don't think it's particularly important but let me know if you'd like this to be refactored somehow.

const argumentsList = [tagName];
if (props) {
argumentsList.push(props);
Expand All @@ -88,7 +88,7 @@ namespace ts {

return setTextRange(
factory.createCallExpression(
createJsxFactoryExpression(factory, jsxFactoryEntity, reactNamespace, parentElement),
callee,
/*typeArguments*/ undefined,
argumentsList
),
Expand Down
74 changes: 45 additions & 29 deletions src/compiler/transformers/jsx.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ namespace ts {
interface PerFileState {
importSpecifier?: string;
filenameDeclaration?: VariableDeclaration & { name: Identifier; };
utilizedImplicitRuntimeImports?: Map<ImportSpecifier>;
utilizedImplicitRuntimeImports?: Map<Map<ImportSpecifier>>;
}

const {
Expand Down Expand Up @@ -40,17 +40,25 @@ namespace ts {
}

function getImplicitImportForName(name: string) {
const existing = currentFileState.utilizedImplicitRuntimeImports?.get(name);
const importSource = name === "createElement"
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is a special case for the createElement within this "generic" util, but in the name of KISS I think it's perfectly OK here. The transform is tied to the semantics defined by React anyhow, this won't change often and there is near-zero chance that createElement will get reintroduced in the future in some other entrypoint in JSX runtimes

? currentFileState.importSpecifier!
: `${currentFileState.importSpecifier}/${compilerOptions.jsx === JsxEmit.ReactJSXDev ? "jsx-dev-runtime" : "jsx-runtime"}`;
const existing = currentFileState.utilizedImplicitRuntimeImports?.get(importSource)?.get(name);
if (existing) {
return existing.name;
}
if (!currentFileState.utilizedImplicitRuntimeImports) {
currentFileState.utilizedImplicitRuntimeImports = createMap();
}
let specifierSourceImports = currentFileState.utilizedImplicitRuntimeImports.get(importSource);
if (!specifierSourceImports) {
specifierSourceImports = createMap();
currentFileState.utilizedImplicitRuntimeImports.set(importSource, specifierSourceImports);
}
const generatedName = factory.createUniqueName(`_${name}`, GeneratedIdentifierFlags.Optimistic | GeneratedIdentifierFlags.FileLevel | GeneratedIdentifierFlags.AllowNameSubstitution);
const specifier = factory.createImportSpecifier(factory.createIdentifier(name), generatedName);
generatedName.generatedImportReference = specifier;
currentFileState.utilizedImplicitRuntimeImports.set(name, specifier);
specifierSourceImports.set(name, specifier);
return generatedName;
}

Expand All @@ -73,29 +81,30 @@ namespace ts {
if (currentFileState.filenameDeclaration) {
statements = insertStatementAfterCustomPrologue(statements.slice(), factory.createVariableStatement(/*modifiers*/ undefined, factory.createVariableDeclarationList([currentFileState.filenameDeclaration], NodeFlags.Const)));
}
if (currentFileState.utilizedImplicitRuntimeImports && currentFileState.utilizedImplicitRuntimeImports.size && currentFileState.importSpecifier !== undefined) {
const specifier = `${currentFileState.importSpecifier}/${compilerOptions.jsx === JsxEmit.ReactJSXDev ? "jsx-dev-runtime" : "jsx-runtime"}`;
if (isExternalModule(node)) {
// Add `import` statement
const importStatement = factory.createImportDeclaration(/*decorators*/ undefined, /*modifiers*/ undefined, factory.createImportClause(/*typeOnly*/ false, /*name*/ undefined, factory.createNamedImports(arrayFrom(currentFileState.utilizedImplicitRuntimeImports.values()))), factory.createStringLiteral(specifier));
setParentRecursive(importStatement, /*incremental*/ false);
statements = insertStatementAfterCustomPrologue(statements.slice(), importStatement);
}
else if (isExternalOrCommonJsModule(node)) {
// Add `require` statement
const requireStatement = factory.createVariableStatement(/*modifiers*/ undefined, factory.createVariableDeclarationList([
factory.createVariableDeclaration(
factory.createObjectBindingPattern(map(arrayFrom(currentFileState.utilizedImplicitRuntimeImports.values()), s => factory.createBindingElement(/*dotdotdot*/ undefined, s.propertyName, s.name))),
/*exclaimationToken*/ undefined,
/*type*/ undefined,
factory.createCallExpression(factory.createIdentifier("require"), /*typeArguments*/ undefined, [factory.createStringLiteral(specifier)])
)
], NodeFlags.Const));
setParentRecursive(requireStatement, /*incremental*/ false);
statements = insertStatementAfterCustomPrologue(statements.slice(), requireStatement);
}
else {
// Do nothing (script file) - consider an error in the checker?
if (currentFileState.utilizedImplicitRuntimeImports) {
for (const [importSource, importSpecifiersMap] of arrayFrom(currentFileState.utilizedImplicitRuntimeImports.entries())) {
if (isExternalModule(node)) {
// Add `import` statement
const importStatement = factory.createImportDeclaration(/*decorators*/ undefined, /*modifiers*/ undefined, factory.createImportClause(/*typeOnly*/ false, /*name*/ undefined, factory.createNamedImports(arrayFrom(importSpecifiersMap.values()))), factory.createStringLiteral(importSource));
setParentRecursive(importStatement, /*incremental*/ false);
statements = insertStatementAfterCustomPrologue(statements.slice(), importStatement);
}
else if (isExternalOrCommonJsModule(node)) {
// Add `require` statement
const requireStatement = factory.createVariableStatement(/*modifiers*/ undefined, factory.createVariableDeclarationList([
factory.createVariableDeclaration(
factory.createObjectBindingPattern(map(arrayFrom(importSpecifiersMap.values()), s => factory.createBindingElement(/*dotdotdot*/ undefined, s.propertyName, s.name))),
/*exclaimationToken*/ undefined,
/*type*/ undefined,
factory.createCallExpression(factory.createIdentifier("require"), /*typeArguments*/ undefined, [factory.createStringLiteral(importSource)])
)
], NodeFlags.Const));
setParentRecursive(requireStatement, /*incremental*/ false);
statements = insertStatementAfterCustomPrologue(statements.slice(), requireStatement);
}
else {
// Do nothing (script file) - consider an error in the checker?
}
}
}
if (statements !== visited.statements) {
Expand Down Expand Up @@ -306,14 +315,21 @@ namespace ts {
}
}

const callee = currentFileState.importSpecifier === undefined
? createJsxFactoryExpression(
factory,
context.getEmitResolver().getJsxFactoryEntity(currentSourceFile),
compilerOptions.reactNamespace!, // TODO: GH#18217
node
)
: getImplicitImportForName("createElement");

const element = createExpressionForJsxElement(
factory,
context.getEmitResolver().getJsxFactoryEntity(currentSourceFile),
compilerOptions.reactNamespace!, // TODO: GH#18217
callee,
tagName,
objectProperties,
mapDefined(children, transformJsxChildToExpression),
node,
location
);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
//// [jsxJsxsCjsTransformKeyProp.tsx]
/// <reference path="/.lib/react16.d.ts" />
const props = { answer: 42 }
const a = <div key="foo" {...props}>text</div>;
const b = <div {...props} key="bar">text</div>;

export {};


//// [jsxJsxsCjsTransformKeyProp.js]
"use strict";
var __assign = (this && this.__assign) || function () {
__assign = Object.assign || function(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
exports.__esModule = true;
var react_1 = require("react");
var jsx_runtime_1 = require("react/jsx-runtime");
/// <reference path="react16.d.ts" />
var props = { answer: 42 };
var a = jsx_runtime_1.jsx("div", __assign({}, props, { children: "text" }), "foo");
var b = react_1.createElement("div", __assign({}, props, { key: "bar" }), "text");
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
=== tests/cases/conformance/jsx/jsxs/jsxJsxsCjsTransformKeyProp.tsx ===
/// <reference path="react16.d.ts" />
const props = { answer: 42 }
>props : Symbol(props, Decl(jsxJsxsCjsTransformKeyProp.tsx, 1, 5))
>answer : Symbol(answer, Decl(jsxJsxsCjsTransformKeyProp.tsx, 1, 15))

const a = <div key="foo" {...props}>text</div>;
>a : Symbol(a, Decl(jsxJsxsCjsTransformKeyProp.tsx, 2, 5))
>div : Symbol(JSX.IntrinsicElements.div, Decl(react16.d.ts, 2420, 114))
>key : Symbol(key, Decl(jsxJsxsCjsTransformKeyProp.tsx, 2, 14))
>props : Symbol(props, Decl(jsxJsxsCjsTransformKeyProp.tsx, 1, 5))
>div : Symbol(JSX.IntrinsicElements.div, Decl(react16.d.ts, 2420, 114))

const b = <div {...props} key="bar">text</div>;
>b : Symbol(b, Decl(jsxJsxsCjsTransformKeyProp.tsx, 3, 5))
>div : Symbol(JSX.IntrinsicElements.div, Decl(react16.d.ts, 2420, 114))
>props : Symbol(props, Decl(jsxJsxsCjsTransformKeyProp.tsx, 1, 5))
>key : Symbol(key, Decl(jsxJsxsCjsTransformKeyProp.tsx, 3, 25))
>div : Symbol(JSX.IntrinsicElements.div, Decl(react16.d.ts, 2420, 114))

export {};

Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
=== tests/cases/conformance/jsx/jsxs/jsxJsxsCjsTransformKeyProp.tsx ===
/// <reference path="react16.d.ts" />
const props = { answer: 42 }
>props : { answer: number; }
>{ answer: 42 } : { answer: number; }
>answer : number
>42 : 42

const a = <div key="foo" {...props}>text</div>;
>a : JSX.Element
><div key="foo" {...props}>text</div> : JSX.Element
>div : any
>key : string
>props : { answer: number; }
>div : any

const b = <div {...props} key="bar">text</div>;
>b : JSX.Element
><div {...props} key="bar">text</div> : JSX.Element
>div : any
>props : { answer: number; }
>key : string
>div : any

export {};

Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
//// [jsxJsxsCjsTransformKeyProp.tsx]
/// <reference path="/.lib/react16.d.ts" />
const props = { answer: 42 }
const a = <div key="foo" {...props}>text</div>;
const b = <div {...props} key="bar">text</div>;

export {};


//// [jsxJsxsCjsTransformKeyProp.js]
"use strict";
var __assign = (this && this.__assign) || function () {
__assign = Object.assign || function(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
exports.__esModule = true;
var react_1 = require("react");
var jsx_dev_runtime_1 = require("react/jsx-dev-runtime");
var _jsxFileName = "tests/cases/conformance/jsx/jsxs/jsxJsxsCjsTransformKeyProp.tsx";
/// <reference path="react16.d.ts" />
var props = { answer: 42 };
var a = jsx_dev_runtime_1.jsxDEV("div", __assign({}, props, { children: "text" }), "foo", false, { fileName: _jsxFileName, lineNumber: 3, columnNumber: 10 }, this);
var b = react_1.createElement("div", __assign({}, props, { key: "bar" }), "text");
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
=== tests/cases/conformance/jsx/jsxs/jsxJsxsCjsTransformKeyProp.tsx ===
/// <reference path="react16.d.ts" />
const props = { answer: 42 }
>props : Symbol(props, Decl(jsxJsxsCjsTransformKeyProp.tsx, 1, 5))
>answer : Symbol(answer, Decl(jsxJsxsCjsTransformKeyProp.tsx, 1, 15))

const a = <div key="foo" {...props}>text</div>;
>a : Symbol(a, Decl(jsxJsxsCjsTransformKeyProp.tsx, 2, 5))
>div : Symbol(JSX.IntrinsicElements.div, Decl(react16.d.ts, 2420, 114))
>key : Symbol(key, Decl(jsxJsxsCjsTransformKeyProp.tsx, 2, 14))
>props : Symbol(props, Decl(jsxJsxsCjsTransformKeyProp.tsx, 1, 5))
>div : Symbol(JSX.IntrinsicElements.div, Decl(react16.d.ts, 2420, 114))

const b = <div {...props} key="bar">text</div>;
>b : Symbol(b, Decl(jsxJsxsCjsTransformKeyProp.tsx, 3, 5))
>div : Symbol(JSX.IntrinsicElements.div, Decl(react16.d.ts, 2420, 114))
>props : Symbol(props, Decl(jsxJsxsCjsTransformKeyProp.tsx, 1, 5))
>key : Symbol(key, Decl(jsxJsxsCjsTransformKeyProp.tsx, 3, 25))
>div : Symbol(JSX.IntrinsicElements.div, Decl(react16.d.ts, 2420, 114))

export {};

Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
=== tests/cases/conformance/jsx/jsxs/jsxJsxsCjsTransformKeyProp.tsx ===
/// <reference path="react16.d.ts" />
const props = { answer: 42 }
>props : { answer: number; }
>{ answer: 42 } : { answer: number; }
>answer : number
>42 : 42

const a = <div key="foo" {...props}>text</div>;
>a : JSX.Element
><div key="foo" {...props}>text</div> : JSX.Element
>div : any
>key : string
>props : { answer: number; }
>div : any

const b = <div {...props} key="bar">text</div>;
>b : JSX.Element
><div {...props} key="bar">text</div> : JSX.Element
>div : any
>props : { answer: number; }
>key : string
>div : any

export {};

Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
//// [jsxJsxsCjsTransformKeyPropCustomImport.tsx]
/// <reference path="/.lib/react16.d.ts" />
const props = { answer: 42 }
const a = <div key="foo" {...props}>text</div>;
const b = <div {...props} key="bar">text</div>;

export {};


//// [jsxJsxsCjsTransformKeyPropCustomImport.js]
"use strict";
var __assign = (this && this.__assign) || function () {
__assign = Object.assign || function(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
exports.__esModule = true;
var preact_1 = require("preact");
var jsx_runtime_1 = require("preact/jsx-runtime");
/// <reference path="react16.d.ts" />
var props = { answer: 42 };
var a = jsx_runtime_1.jsx("div", __assign({}, props, { children: "text" }), "foo");
var b = preact_1.createElement("div", __assign({}, props, { key: "bar" }), "text");
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
=== tests/cases/conformance/jsx/jsxs/jsxJsxsCjsTransformKeyPropCustomImport.tsx ===
/// <reference path="react16.d.ts" />
const props = { answer: 42 }
>props : Symbol(props, Decl(jsxJsxsCjsTransformKeyPropCustomImport.tsx, 1, 5))
>answer : Symbol(answer, Decl(jsxJsxsCjsTransformKeyPropCustomImport.tsx, 1, 15))

const a = <div key="foo" {...props}>text</div>;
>a : Symbol(a, Decl(jsxJsxsCjsTransformKeyPropCustomImport.tsx, 2, 5))
>div : Symbol(JSX.IntrinsicElements.div, Decl(react16.d.ts, 2420, 114))
>key : Symbol(key, Decl(jsxJsxsCjsTransformKeyPropCustomImport.tsx, 2, 14))
>props : Symbol(props, Decl(jsxJsxsCjsTransformKeyPropCustomImport.tsx, 1, 5))
>div : Symbol(JSX.IntrinsicElements.div, Decl(react16.d.ts, 2420, 114))

const b = <div {...props} key="bar">text</div>;
>b : Symbol(b, Decl(jsxJsxsCjsTransformKeyPropCustomImport.tsx, 3, 5))
>div : Symbol(JSX.IntrinsicElements.div, Decl(react16.d.ts, 2420, 114))
>props : Symbol(props, Decl(jsxJsxsCjsTransformKeyPropCustomImport.tsx, 1, 5))
>key : Symbol(key, Decl(jsxJsxsCjsTransformKeyPropCustomImport.tsx, 3, 25))
>div : Symbol(JSX.IntrinsicElements.div, Decl(react16.d.ts, 2420, 114))

export {};

Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
=== tests/cases/conformance/jsx/jsxs/jsxJsxsCjsTransformKeyPropCustomImport.tsx ===
/// <reference path="react16.d.ts" />
const props = { answer: 42 }
>props : { answer: number; }
>{ answer: 42 } : { answer: number; }
>answer : number
>42 : 42

const a = <div key="foo" {...props}>text</div>;
>a : JSX.Element
><div key="foo" {...props}>text</div> : JSX.Element
>div : any
>key : string
>props : { answer: number; }
>div : any

const b = <div {...props} key="bar">text</div>;
>b : JSX.Element
><div {...props} key="bar">text</div> : JSX.Element
>div : any
>props : { answer: number; }
>key : string
>div : any

export {};