Skip to content

Commit ef10c64

Browse files
authored
jsx-development: do not emit this within ts module block (#14271)
1 parent 020a7b0 commit ef10c64

7 files changed

Lines changed: 74 additions & 46 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export namespace Namespaced {
2+
export const Component = () => (
3+
<div>Hello, world!</div>
4+
);
5+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"plugins": [
3+
"transform-react-jsx-development",
4+
["syntax-typescript", { "isTSX": true }]
5+
],
6+
"sourceType": "module",
7+
"os": ["linux", "darwin"]
8+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
var _jsxFileName = "<CWD>/packages/babel-plugin-transform-react-jsx-development/test/fixtures/linux/within-ts-module-block/input.ts";
2+
import { jsxDEV as _jsxDEV } from "react/jsx-dev-runtime";
3+
export namespace Namespaced {
4+
export const Component = () => /*#__PURE__*/_jsxDEV("div", {
5+
children: "Hello, world!"
6+
}, void 0, false, {
7+
fileName: _jsxFileName,
8+
lineNumber: 3,
9+
columnNumber: 3
10+
}, void 0);
11+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export namespace Namespaced {
2+
export const Component = () => (
3+
<div>Hello, world!</div>
4+
);
5+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"plugins": [
3+
"transform-react-jsx-development",
4+
["syntax-typescript", { "isTSX": true }]
5+
],
6+
"sourceType": "module",
7+
"os": ["win32"]
8+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
var _jsxFileName = "<CWD>\\packages\\babel-plugin-transform-react-jsx-development\\test\\fixtures\\windows\\within-ts-module-block\\input.ts";
2+
import { jsxDEV as _jsxDEV } from "react/jsx-dev-runtime";
3+
export namespace Namespaced {
4+
export const Component = () => /*#__PURE__*/_jsxDEV("div", {
5+
children: "Hello, world!"
6+
}, void 0, false, {
7+
fileName: _jsxFileName,
8+
lineNumber: 3,
9+
columnNumber: 3
10+
}, void 0);
11+
}

packages/babel-plugin-transform-react-jsx/src/create-plugin.ts

Lines changed: 26 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,13 @@ import jsx from "@babel/plugin-syntax-jsx";
22
import { declare } from "@babel/helper-plugin-utils";
33
import { types as t } from "@babel/core";
44
import type { PluginPass } from "@babel/core";
5-
import type { NodePath, Visitor } from "@babel/traverse";
5+
import type { NodePath, Scope, Visitor } from "@babel/traverse";
66
import { addNamed, addNamespace, isModule } from "@babel/helper-module-imports";
77
import annotateAsPure from "@babel/helper-annotate-as-pure";
88
import type {
9-
ArrowFunctionExpression,
109
CallExpression,
1110
Class,
1211
Expression,
13-
FunctionParent,
1412
Identifier,
1513
JSXAttribute,
1614
JSXElement,
@@ -22,8 +20,6 @@ import type {
2220
Program,
2321
} from "@babel/types";
2422

25-
type Diff<T, U> = T extends U ? never : T;
26-
2723
const DEFAULT = {
2824
importSource: "react",
2925
runtime: "automatic",
@@ -116,7 +112,7 @@ export default function createPlugin({ name, development }) {
116112
const injectMetaPropertiesVisitor: Visitor<PluginPass> = {
117113
JSXOpeningElement(path, state) {
118114
const attributes = [];
119-
if (isThisAllowed(path)) {
115+
if (isThisAllowed(path.scope)) {
120116
attributes.push(
121117
t.jsxAttribute(
122118
t.jsxIdentifier("__self"),
@@ -295,52 +291,36 @@ You can set \`throwIfNamespace: false\` to bypass this warning.`,
295291
} as Visitor<PluginPass>,
296292
};
297293

298-
// Finds the closest parent function that provides `this`. Specifically, this looks for
299-
// the first parent function that isn't an arrow function.
300-
//
301-
// Derived from `Scope#getFunctionParent`
302-
function getThisFunctionParent(
303-
path: NodePath,
304-
): NodePath<Diff<FunctionParent, ArrowFunctionExpression>> | null {
305-
let scope = path.scope;
306-
do {
307-
if (
308-
scope.path.isFunctionParent() &&
309-
!scope.path.isArrowFunctionExpression()
310-
) {
311-
// @ts-expect-error ts can not infer scope.path is Diff<FunctionParent, ArrowFunctionExpression>
312-
return scope.path;
313-
}
314-
} while ((scope = scope.parent));
315-
return null;
316-
}
317-
318294
// Returns whether the class has specified a superclass.
319295
function isDerivedClass(classPath: NodePath<Class>) {
320296
return classPath.node.superClass !== null;
321297
}
322298

323-
// Returns whether `this` is allowed at given path.
324-
function isThisAllowed(path: NodePath<JSXOpeningElement>) {
299+
// Returns whether `this` is allowed at given scope.
300+
function isThisAllowed(scope: Scope) {
325301
// This specifically skips arrow functions as they do not rewrite `this`.
326-
const parentMethodOrFunction = getThisFunctionParent(path);
327-
if (parentMethodOrFunction === null) {
328-
// We are not in a method or function. It is fine to use `this`.
329-
return true;
330-
}
331-
if (!parentMethodOrFunction.isMethod()) {
332-
// If the closest parent is a regular function, `this` will be rebound, therefore it is fine to use `this`.
333-
return true;
334-
}
335-
// Current node is within a method, so we need to check if the method is a constructor.
336-
if (parentMethodOrFunction.node.kind !== "constructor") {
337-
// We are not in a constructor, therefore it is always fine to use `this`.
338-
return true;
339-
}
340-
// Now we are in a constructor. If it is a derived class, we do not reference `this`.
341-
return !isDerivedClass(
342-
parentMethodOrFunction.parentPath.parentPath as NodePath<Class>,
343-
);
302+
do {
303+
const { path } = scope;
304+
if (path.isFunctionParent() && !path.isArrowFunctionExpression()) {
305+
if (!path.isMethod()) {
306+
// If the closest parent is a regular function, `this` will be rebound, therefore it is fine to use `this`.
307+
return true;
308+
}
309+
// Current node is within a method, so we need to check if the method is a constructor.
310+
if (path.node.kind !== "constructor") {
311+
// We are not in a constructor, therefore it is always fine to use `this`.
312+
return true;
313+
}
314+
// Now we are in a constructor. If it is a derived class, we do not reference `this`.
315+
return !isDerivedClass(path.parentPath.parentPath as NodePath<Class>);
316+
}
317+
if (path.isTSModuleBlock()) {
318+
// If the closeset parent is a TS Module block, `this` will not be allowed.
319+
return false;
320+
}
321+
} while ((scope = scope.parent));
322+
// We are not in a method or function. It is fine to use `this`.
323+
return true;
344324
}
345325

346326
function call(

0 commit comments

Comments
 (0)