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

Implement constantSuper, superIsCallableConstructor and noClassCalls assumption #12726

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
3 changes: 3 additions & 0 deletions packages/babel-core/src/config/validation/options.js
Expand Up @@ -334,11 +334,13 @@ export type NestingPath = RootPath | OverridesPath | EnvPath;
export const assumptionsNames = new Set<string>([
"arrayLikeIsIterable",
"constantReexports",
"constantSuper",
"enumerableModuleMeta",
"ignoreFunctionLength",
"ignoreToPrimitiveHint",
"iterableIsArray",
"mutableTemplateObject",
"noClassCalls",
"noDocumentAll",
"noNewArrows",
"objectRestNoSymbols",
Expand All @@ -349,6 +351,7 @@ export const assumptionsNames = new Set<string>([
"setPublicClassFields",
"setSpreadProperties",
"skipForOfIteratorClosing",
"superIsCallableConstructor",
]);

function getSource(loc: NestingPath): OptionsSource {
Expand Down
14 changes: 10 additions & 4 deletions packages/babel-helper-create-class-features-plugin/src/fields.js
Expand Up @@ -647,12 +647,12 @@ const thisContextVisitor = traverse.visitors.merge([
environmentVisitor,
]);

function replaceThisContext(path, ref, superRef, file, loose) {
function replaceThisContext(path, ref, superRef, file, constantSuper) {
const state = { classRef: ref, needsClassRef: false };

const replacer = new ReplaceSupers({
methodPath: path,
isLoose: loose,
constantSuper,
superRef,
file,
refToPreserve: ref,
Expand All @@ -678,7 +678,7 @@ export function buildFieldsInitNodes(
state,
setPublicClassFields,
privateFieldsAsProperties,
loose,
constantSuper,
) {
const staticNodes = [];
const instanceNodes = [];
Expand All @@ -695,7 +695,13 @@ export function buildFieldsInitNodes(
const isMethod = !isField;

if (isStatic || (isMethod && isPrivate)) {
const replaced = replaceThisContext(prop, ref, superRef, state, loose);
const replaced = replaceThisContext(
prop,
ref,
superRef,
state,
constantSuper,
);
needsClassRef = needsClassRef || replaced;
}

Expand Down
Expand Up @@ -41,6 +41,7 @@ export function createClassFeaturePlugin({
}) {
const setPublicClassFields = api.assumption("setPublicClassFields");
const privateFieldsAsProperties = api.assumption("privateFieldsAsProperties");
const constantSuper = api.assumption("constantSuper");
const noDocumentAll = api.assumption("noDocumentAll");

if (loose) {
Expand Down Expand Up @@ -217,7 +218,7 @@ export function createClassFeaturePlugin({
state,
setPublicClassFields ?? loose,
privateFieldsAsProperties ?? loose,
loose,
constantSuper ?? loose,
));
}

Expand Down
11 changes: 7 additions & 4 deletions packages/babel-helper-replace-supers/src/index.js
Expand Up @@ -255,7 +255,7 @@ const looseHandlers = {
type ReplaceSupersOptionsBase = {|
methodPath: NodePath,
superRef: Object,
isLoose: boolean,
constantSuper: boolean,
file: any,
// objectRef might have been shadowed in child scopes,
// in that case, we need to rename related variables.
Expand Down Expand Up @@ -284,13 +284,16 @@ export default class ReplaceSupers {

this.file = opts.file;
this.superRef = opts.superRef;
this.isLoose = opts.isLoose;
this.constantSuper = process.env.BABEL_8_BREAKING
? opts.constantSuper
: // Fallback to isLoose for backward compatibility
opts.constantSuper ?? (opts: any).isLoose;
this.opts = opts;
}

declare file: HubInterface;
declare isDerivedConstructor: boolean;
declare isLoose: boolean;
declare constantSuper: boolean;
declare isPrivateMethod: boolean;
declare isStatic: boolean;
declare methodPath: NodePath;
Expand All @@ -309,7 +312,7 @@ export default class ReplaceSupers {
});
}

const handler = this.isLoose ? looseHandlers : specHandlers;
const handler = this.constantSuper ? looseHandlers : specHandlers;

memberExpressionToFunctions(this.methodPath, visitor, {
file: this.file,
Expand Down
@@ -0,0 +1,17 @@
{
"plugins": [
[
"external-helpers",
{
"helperVersion": "7.1000.0"
}
],
"proposal-private-methods",
"proposal-class-properties",
"transform-block-scoping",
"syntax-class-properties"
],
"assumptions": {
"constantSuper": true
}
}
@@ -0,0 +1,19 @@
class Base {
superMethod() {
return 'good';
}
}

class Sub extends Base {
superMethod() {
return 'bad';
}

#privateMethod() {
return super.superMethod();
}

publicMethod() {
return this.#privateMethod();
}
}
@@ -0,0 +1,29 @@
class Base {
superMethod() {
return 'good';
}

}

var _privateMethod = new WeakSet();

class Sub extends Base {
constructor(...args) {
super(...args);

_privateMethod.add(this);
}

superMethod() {
return 'bad';
}

publicMethod() {
return babelHelpers.classPrivateMethodGet(this, _privateMethod, _privateMethod2).call(this);
}

}

var _privateMethod2 = function _privateMethod2() {
return Base.prototype.superMethod.call(this);
};
7 changes: 7 additions & 0 deletions packages/babel-plugin-transform-classes/src/index.js
Expand Up @@ -22,6 +22,10 @@ export default declare((api, options) => {
const { loose } = options;

const setClassMethods = api.assumption("setClassMethods") ?? options.loose;
const constantSuper = api.assumption("constantSuper") ?? options.loose;
const superIsCallableConstructor =
api.assumption("superIsCallableConstructor") ?? options.loose;
const noClassCalls = api.assumption("noClassCalls") ?? options.loose;

// todo: investigate traversal requeueing
const VISITED = Symbol();
Expand Down Expand Up @@ -62,6 +66,9 @@ export default declare((api, options) => {
path.replaceWith(
transformClass(path, state.file, builtinClasses, loose, {
setClassMethods,
constantSuper,
superIsCallableConstructor,
noClassCalls,
}),
);

Expand Down
15 changes: 10 additions & 5 deletions packages/babel-plugin-transform-classes/src/transformClass.js
Expand Up @@ -11,7 +11,12 @@ import addCreateSuperHelper from "./inline-createSuper-helpers";

type ReadonlySet<T> = Set<T> | { has(val: T): boolean };

type ClassAssumptions = { setClassMethods: boolean };
type ClassAssumptions = {
setClassMethods: boolean,
constantSuper: boolean,
superIsCallableConstructor: boolean,
noClassCalls: boolean,
};

function buildConstructor(classRef, constructorBody, node) {
const func = t.functionDeclaration(
Expand Down Expand Up @@ -164,7 +169,7 @@ export default function transformClass(
methodPath: path,
objectRef: classState.classRef,
superRef: classState.superName,
isLoose: classState.isLoose,
constantSuper: assumptions.constantSuper,
file: classState.file,
refToPreserve: classState.classRef,
});
Expand Down Expand Up @@ -249,7 +254,7 @@ export default function transformClass(
const bareSuperNode = bareSuper.node;
let call;

if (classState.isLoose) {
if (assumptions.superIsCallableConstructor) {
bareSuperNode.arguments.unshift(t.thisExpression());
if (
bareSuperNode.arguments.length === 2 &&
Expand Down Expand Up @@ -566,7 +571,7 @@ export default function transformClass(
// Unshift to ensure that the constructor inheritance is set up before
// any properties can be assigned to the prototype.

if (!classState.isLoose) {
if (!assumptions.superIsCallableConstructor) {
classState.body.unshift(
t.variableDeclaration("var", [
t.variableDeclarator(
Expand Down Expand Up @@ -666,7 +671,7 @@ export default function transformClass(
buildBody();

// make sure this class isn't directly called (with A() instead new A())
if (!classState.isLoose) {
if (!assumptions.noClassCalls) {
constructorBody.body.unshift(
t.expressionStatement(
t.callExpression(classState.file.addHelper("classCallCheck"), [
Expand Down
@@ -0,0 +1,13 @@
class Test extends Foo {
constructor() {
woops.super.test();
super();
super.test();

super(...arguments);
super("test", ...arguments);

super.test(...arguments);
super.test("test", ...arguments);
}
}
@@ -0,0 +1,30 @@
var Test = /*#__PURE__*/function (_Foo) {
"use strict";

babelHelpers.inherits(Test, _Foo);

var _super = babelHelpers.createSuper(Test);

function Test() {
var _Foo$prototype$test;

var _this;

babelHelpers.classCallCheck(this, Test);
woops.super.test();
_this = _super.call(this);

_Foo.prototype.test.call(babelHelpers.assertThisInitialized(_this));

_this = _super.apply(this, arguments);
_this = _super.call.apply(_super, [this, "test"].concat(Array.prototype.slice.call(arguments)));

_Foo.prototype.test.apply(babelHelpers.assertThisInitialized(_this), arguments);

(_Foo$prototype$test = _Foo.prototype.test).call.apply(_Foo$prototype$test, [babelHelpers.assertThisInitialized(_this), "test"].concat(Array.prototype.slice.call(arguments)));

return _this;
}

return Test;
}(Foo);
@@ -0,0 +1,7 @@
class Test extends Foo {
constructor() {
super();
super.test;
super.test.whatever;
}
}
@@ -0,0 +1,19 @@
var Test = /*#__PURE__*/function (_Foo) {
"use strict";

babelHelpers.inherits(Test, _Foo);

var _super = babelHelpers.createSuper(Test);

function Test() {
var _this;

babelHelpers.classCallCheck(this, Test);
_this = _super.call(this);
_Foo.prototype.test;
_Foo.prototype.test.whatever;
return _this;
}

return Test;
}(Foo);
@@ -0,0 +1,6 @@
class Test extends Foo {
constructor() {
super.foo?.bar;
super.foo?.();
}
}
@@ -0,0 +1,10 @@
{
"plugins": [
["external-helpers", { "helperVersion": "7.100.0" }],
["proposal-optional-chaining", { "loose": true }],
"transform-function-name",
["transform-classes", { "loose": true }],
["transform-spread", { "loose": true }],
"transform-block-scoping"
]
}
@@ -0,0 +1,17 @@
var Test = /*#__PURE__*/function (_Foo) {
"use strict";

babelHelpers.inheritsLoose(Test, _Foo);

function Test() {
var _Foo$prototype$foo, _Foo$prototype$foo2;

var _this;

(_Foo$prototype$foo = _Foo.prototype.foo) == null ? void 0 : _Foo$prototype$foo.bar;
(_Foo$prototype$foo2 = _Foo.prototype.foo) == null ? void 0 : _Foo$prototype$foo2.call(babelHelpers.assertThisInitialized(_this));
return babelHelpers.assertThisInitialized(_this);
}

return Test;
}(Foo);
@@ -0,0 +1,11 @@
class Test extends Foo {
constructor() {
super();
super.test.whatever();
super.test();
}

static test() {
return super.wow();
}
}