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

jsx-development: do not emit this within ts module block #14271

Merged
merged 1 commit into from Feb 15, 2022
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
@@ -0,0 +1,5 @@
export namespace Namespaced {
export const Component = () => (
<div>Hello, world!</div>
);
}
@@ -0,0 +1,8 @@
{
"plugins": [
"transform-react-jsx-development",
["syntax-typescript", { "isTSX": true }]
],
"sourceType": "module",
"os": ["linux", "darwin"]
}
@@ -0,0 +1,11 @@
var _jsxFileName = "<CWD>/packages/babel-plugin-transform-react-jsx-development/test/fixtures/linux/within-ts-module-block/input.ts";
import { jsxDEV as _jsxDEV } from "react/jsx-dev-runtime";
export namespace Namespaced {
export const Component = () => /*#__PURE__*/_jsxDEV("div", {
children: "Hello, world!"
}, void 0, false, {
fileName: _jsxFileName,
lineNumber: 3,
columnNumber: 3
}, void 0);
}
@@ -0,0 +1,5 @@
export namespace Namespaced {
export const Component = () => (
<div>Hello, world!</div>
);
}
@@ -0,0 +1,8 @@
{
"plugins": [
"transform-react-jsx-development",
["syntax-typescript", { "isTSX": true }]
],
"sourceType": "module",
"os": ["win32"]
}
@@ -0,0 +1,11 @@
var _jsxFileName = "<CWD>\\packages\\babel-plugin-transform-react-jsx-development\\test\\fixtures\\windows\\within-ts-module-block\\input.ts";
import { jsxDEV as _jsxDEV } from "react/jsx-dev-runtime";
export namespace Namespaced {
export const Component = () => /*#__PURE__*/_jsxDEV("div", {
children: "Hello, world!"
}, void 0, false, {
fileName: _jsxFileName,
lineNumber: 3,
columnNumber: 3
}, void 0);
}
72 changes: 26 additions & 46 deletions packages/babel-plugin-transform-react-jsx/src/create-plugin.ts
Expand Up @@ -2,15 +2,13 @@ import jsx from "@babel/plugin-syntax-jsx";
import { declare } from "@babel/helper-plugin-utils";
import { types as t } from "@babel/core";
import type { PluginPass } from "@babel/core";
import type { NodePath, Visitor } from "@babel/traverse";
import type { NodePath, Scope, Visitor } from "@babel/traverse";
import { addNamed, addNamespace, isModule } from "@babel/helper-module-imports";
import annotateAsPure from "@babel/helper-annotate-as-pure";
import type {
ArrowFunctionExpression,
CallExpression,
Class,
Expression,
FunctionParent,
Identifier,
JSXAttribute,
JSXElement,
Expand All @@ -22,8 +20,6 @@ import type {
Program,
} from "@babel/types";

type Diff<T, U> = T extends U ? never : T;

const DEFAULT = {
importSource: "react",
runtime: "automatic",
Expand Down Expand Up @@ -116,7 +112,7 @@ export default function createPlugin({ name, development }) {
const injectMetaPropertiesVisitor: Visitor<PluginPass> = {
JSXOpeningElement(path, state) {
const attributes = [];
if (isThisAllowed(path)) {
if (isThisAllowed(path.scope)) {
attributes.push(
t.jsxAttribute(
t.jsxIdentifier("__self"),
Expand Down Expand Up @@ -295,52 +291,36 @@ You can set \`throwIfNamespace: false\` to bypass this warning.`,
} as Visitor<PluginPass>,
};

// Finds the closest parent function that provides `this`. Specifically, this looks for
// the first parent function that isn't an arrow function.
//
// Derived from `Scope#getFunctionParent`
function getThisFunctionParent(
path: NodePath,
): NodePath<Diff<FunctionParent, ArrowFunctionExpression>> | null {
let scope = path.scope;
do {
if (
scope.path.isFunctionParent() &&
!scope.path.isArrowFunctionExpression()
) {
// @ts-expect-error ts can not infer scope.path is Diff<FunctionParent, ArrowFunctionExpression>
return scope.path;
}
} while ((scope = scope.parent));
return null;
}

// Returns whether the class has specified a superclass.
function isDerivedClass(classPath: NodePath<Class>) {
return classPath.node.superClass !== null;
}

// Returns whether `this` is allowed at given path.
function isThisAllowed(path: NodePath<JSXOpeningElement>) {
// Returns whether `this` is allowed at given scope.
function isThisAllowed(scope: Scope) {
// This specifically skips arrow functions as they do not rewrite `this`.
const parentMethodOrFunction = getThisFunctionParent(path);
if (parentMethodOrFunction === null) {
// We are not in a method or function. It is fine to use `this`.
return true;
}
if (!parentMethodOrFunction.isMethod()) {
// If the closest parent is a regular function, `this` will be rebound, therefore it is fine to use `this`.
return true;
}
// Current node is within a method, so we need to check if the method is a constructor.
if (parentMethodOrFunction.node.kind !== "constructor") {
// We are not in a constructor, therefore it is always fine to use `this`.
return true;
}
// Now we are in a constructor. If it is a derived class, we do not reference `this`.
return !isDerivedClass(
parentMethodOrFunction.parentPath.parentPath as NodePath<Class>,
);
do {
const { path } = scope;
if (path.isFunctionParent() && !path.isArrowFunctionExpression()) {
if (!path.isMethod()) {
// If the closest parent is a regular function, `this` will be rebound, therefore it is fine to use `this`.
return true;
}
// Current node is within a method, so we need to check if the method is a constructor.
if (path.node.kind !== "constructor") {
// We are not in a constructor, therefore it is always fine to use `this`.
return true;
}
// Now we are in a constructor. If it is a derived class, we do not reference `this`.
return !isDerivedClass(path.parentPath.parentPath as NodePath<Class>);
}
if (path.isTSModuleBlock()) {
// If the closeset parent is a TS Module block, `this` will not be allowed.
return false;
}
} while ((scope = scope.parent));
// We are not in a method or function. It is fine to use `this`.
return true;
}

function call(
Expand Down