Skip to content

Commit

Permalink
Implement constantSuper, superIsCallableConstructor and `noClassC…
Browse files Browse the repository at this point in the history
…alls` assumption (#12726)

Co-authored-by: Justin Ridgewell <justin@ridgewell.name>
  • Loading branch information
nicolo-ribaudo and jridgewell committed Feb 16, 2021
1 parent f845446 commit d8392c3
Show file tree
Hide file tree
Showing 37 changed files with 465 additions and 14 deletions.
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();
}
}

0 comments on commit d8392c3

Please sign in to comment.