Merge class features plugins
* Create @babel/plugin-class-features * Move class properties transformation logic to enanced-classes (#8130)
This commit is contained in:
parent
a2afb974be
commit
5979b0669b
3
packages/babel-plugin-class-features/.npmignore
Normal file
3
packages/babel-plugin-class-features/.npmignore
Normal file
@ -0,0 +1,3 @@
|
||||
src
|
||||
test
|
||||
*.log
|
||||
19
packages/babel-plugin-class-features/README.md
Normal file
19
packages/babel-plugin-class-features/README.md
Normal file
@ -0,0 +1,19 @@
|
||||
# @babel/plugin-class-features
|
||||
|
||||
> Compile class public and private fields, private methods and decorators to ES6
|
||||
|
||||
See our website [@babel/plugin-class-features](https://babeljs.io/docs/en/next/babel-plugin-class-features.html) for more information.
|
||||
|
||||
## Install
|
||||
|
||||
Using npm:
|
||||
|
||||
```sh
|
||||
npm install --save-dev @babel/plugin-class-features
|
||||
```
|
||||
|
||||
or using yarn:
|
||||
|
||||
```sh
|
||||
yarn add @babel/plugin-class-features --dev
|
||||
```
|
||||
27
packages/babel-plugin-class-features/package.json
Normal file
27
packages/babel-plugin-class-features/package.json
Normal file
@ -0,0 +1,27 @@
|
||||
{
|
||||
"name": "@babel/plugin-class-features",
|
||||
"version": "7.1.4",
|
||||
"author": "The Babel Team (https://babeljs.io/team)",
|
||||
"license": "MIT",
|
||||
"description": "Compile class public and private fields, private methods and decorators to ES6",
|
||||
"repository": "https://github.com/babel/babel/tree/master/packages/babel-plugin-class-features",
|
||||
"main": "lib/index.js",
|
||||
"keywords": [
|
||||
"babel",
|
||||
"babel-plugin"
|
||||
],
|
||||
"dependencies": {
|
||||
"@babel/helper-function-name": "^7.1.0",
|
||||
"@babel/helper-member-expression-to-functions": "^7.0.0",
|
||||
"@babel/helper-optimise-call-expression": "^7.0.0",
|
||||
"@babel/helper-plugin-utils": "^7.0.0",
|
||||
"@babel/helper-replace-supers": "^7.1.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@babel/core": "^7.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.1.0",
|
||||
"@babel/helper-plugin-test-runner": "^7.0.0"
|
||||
}
|
||||
}
|
||||
3
packages/babel-plugin-class-features/src/decorators.js
Normal file
3
packages/babel-plugin-class-features/src/decorators.js
Normal file
@ -0,0 +1,3 @@
|
||||
export function hasDecorators(path) {
|
||||
return !!(path.node.decorators && path.node.decorators.length);
|
||||
}
|
||||
62
packages/babel-plugin-class-features/src/features.js
Normal file
62
packages/babel-plugin-class-features/src/features.js
Normal file
@ -0,0 +1,62 @@
|
||||
import { hasDecorators } from "./decorators";
|
||||
|
||||
export const FEATURES = Object.freeze({
|
||||
//classes: 1 << 0,
|
||||
fields: 1 << 1,
|
||||
privateMethods: 1 << 2,
|
||||
decorators: 1 << 3,
|
||||
});
|
||||
|
||||
// We can't use a symbol because this needs to always be the same, even if
|
||||
// this package isn't deduped by npm. e.g.
|
||||
// - node_modules/
|
||||
// - @babel/plugin-class-features
|
||||
// - @babel/plugin-proposal-decorators
|
||||
// - node_modules
|
||||
// - @babel-plugin-class-features
|
||||
const featuresKey = "@babel/plugin-class-features/featuresKey";
|
||||
const looseKey = "@babel/plugin-class-features/looseKey";
|
||||
|
||||
export function enableFeature(file, feature, loose) {
|
||||
// We can't blindly enable the feature because, if it was already set,
|
||||
// "loose" can't be changed, so that
|
||||
// @babel/plugin-class-properties { loose: true }
|
||||
// @babel/plugin-class-properties { loose: false }
|
||||
// is transformed in loose mode.
|
||||
// We only enabled the feature if it was previously disabled.
|
||||
if (!hasFeature(file, feature)) {
|
||||
file.set(featuresKey, file.get(featuresKey) | feature);
|
||||
if (loose) file.set(looseKey, file.get(looseKey) | feature);
|
||||
}
|
||||
}
|
||||
|
||||
function hasFeature(file, feature) {
|
||||
return !!(file.get(featuresKey) & feature);
|
||||
}
|
||||
|
||||
export function isLoose(file, feature) {
|
||||
return !!(file.get(looseKey) & feature);
|
||||
}
|
||||
|
||||
export function verifyUsedFeatures(path, file) {
|
||||
if (hasFeature(file, FEATURES.decorators)) {
|
||||
throw new Error(
|
||||
"@babel/plugin-class-features doesn't support decorators yet.",
|
||||
);
|
||||
}
|
||||
if (hasFeature(file, FEATURES.privateMethods)) {
|
||||
throw new Error(
|
||||
"@babel/plugin-class-features doesn't support private methods yet.",
|
||||
);
|
||||
}
|
||||
|
||||
if (hasDecorators(path) && !hasFeature(file, FEATURES.decorators)) {
|
||||
throw path.buildCodeFrameError("Decorators are not enabled.");
|
||||
}
|
||||
|
||||
if (path.isProperty()) {
|
||||
if (!hasFeature(file, FEATURES.fields)) {
|
||||
throw path.buildCodeFrameError("Class fields are not enabled.");
|
||||
}
|
||||
}
|
||||
}
|
||||
322
packages/babel-plugin-class-features/src/fields.js
Normal file
322
packages/babel-plugin-class-features/src/fields.js
Normal file
@ -0,0 +1,322 @@
|
||||
import { template, traverse, types as t } from "@babel/core";
|
||||
import { environmentVisitor } from "@babel/helper-replace-supers";
|
||||
import memberExpressionToFunctions from "@babel/helper-member-expression-to-functions";
|
||||
import optimiseCall from "@babel/helper-optimise-call-expression";
|
||||
|
||||
export function buildPrivateNamesMap(props) {
|
||||
const privateNamesMap = new Map();
|
||||
for (const prop of props) {
|
||||
if (prop.isPrivate()) {
|
||||
const { name } = prop.node.key.id;
|
||||
privateNamesMap.set(name, {
|
||||
id: prop.scope.generateUidIdentifier(name),
|
||||
static: !!prop.node.static,
|
||||
});
|
||||
}
|
||||
}
|
||||
return privateNamesMap;
|
||||
}
|
||||
|
||||
export function buildPrivateNamesNodes(privateNamesMap, loose, state) {
|
||||
const initNodes = [];
|
||||
|
||||
for (const [name, { id, static: isStatic }] of privateNamesMap) {
|
||||
// In loose mode, both static and instance fields hare transpiled using a
|
||||
// secret non-enumerable property. Hence, we also need to generate that
|
||||
// key (using the classPrivateFieldLooseKey helper) in loose mode.
|
||||
// In spec mode, only instance fields need a "private name" initializer
|
||||
// (the WeakMap), becase static fields are directly assigned to a variable
|
||||
// in the buildPrivateStaticFieldInitSpec function.
|
||||
|
||||
if (loose) {
|
||||
initNodes.push(
|
||||
template.statement.ast`
|
||||
var ${id} = ${state.addHelper("classPrivateFieldLooseKey")}("${name}")
|
||||
`,
|
||||
);
|
||||
} else if (!isStatic) {
|
||||
initNodes.push(template.statement.ast`var ${id} = new WeakMap();`);
|
||||
}
|
||||
}
|
||||
|
||||
return initNodes;
|
||||
}
|
||||
|
||||
// Traverses the class scope, handling private name references. If an inner
|
||||
// class redeclares the same private name, it will hand off traversal to the
|
||||
// restricted visitor (which doesn't traverse the inner class's inner scope).
|
||||
const privateNameVisitor = {
|
||||
PrivateName(path) {
|
||||
const { privateNamesMap } = this;
|
||||
const { node, parentPath } = path;
|
||||
|
||||
if (!parentPath.isMemberExpression({ property: node })) return;
|
||||
if (!privateNamesMap.has(node.id.name)) return;
|
||||
|
||||
this.handle(parentPath);
|
||||
},
|
||||
|
||||
Class(path) {
|
||||
const { privateNamesMap } = this;
|
||||
const body = path.get("body.body");
|
||||
|
||||
for (const prop of body) {
|
||||
if (!prop.isClassPrivateProperty()) continue;
|
||||
if (!privateNamesMap.has(prop.node.key.id.name)) continue;
|
||||
|
||||
// This class redeclares the private name.
|
||||
// So, we can only evaluate the things in the outer scope.
|
||||
path.traverse(privateNameInnerVisitor, this);
|
||||
path.skip();
|
||||
break;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
// Traverses the outer portion of a class, without touching the class's inner
|
||||
// scope, for private names.
|
||||
const privateNameInnerVisitor = traverse.visitors.merge([
|
||||
{
|
||||
PrivateName: privateNameVisitor.PrivateName,
|
||||
},
|
||||
environmentVisitor,
|
||||
]);
|
||||
|
||||
const privateNameHandlerSpec = {
|
||||
memoise(member, count) {
|
||||
const { scope } = member;
|
||||
const { object } = member.node;
|
||||
|
||||
const memo = scope.maybeGenerateMemoised(object);
|
||||
if (!memo) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.memoiser.set(object, memo, count);
|
||||
},
|
||||
|
||||
receiver(member) {
|
||||
const { object } = member.node;
|
||||
|
||||
if (this.memoiser.has(object)) {
|
||||
return t.cloneNode(this.memoiser.get(object));
|
||||
}
|
||||
|
||||
return t.cloneNode(object);
|
||||
},
|
||||
|
||||
get(member) {
|
||||
const { classRef, privateNamesMap, file } = this;
|
||||
const { name } = member.node.property.id;
|
||||
const { id, static: isStatic } = privateNamesMap.get(name);
|
||||
|
||||
if (isStatic) {
|
||||
return t.callExpression(
|
||||
file.addHelper("classStaticPrivateFieldSpecGet"),
|
||||
[this.receiver(member), t.cloneNode(classRef), t.cloneNode(id)],
|
||||
);
|
||||
} else {
|
||||
return t.callExpression(file.addHelper("classPrivateFieldGet"), [
|
||||
this.receiver(member),
|
||||
t.cloneNode(id),
|
||||
]);
|
||||
}
|
||||
},
|
||||
|
||||
set(member, value) {
|
||||
const { classRef, privateNamesMap, file } = this;
|
||||
const { name } = member.node.property.id;
|
||||
const { id, static: isStatic } = privateNamesMap.get(name);
|
||||
|
||||
if (isStatic) {
|
||||
return t.callExpression(
|
||||
file.addHelper("classStaticPrivateFieldSpecSet"),
|
||||
[this.receiver(member), t.cloneNode(classRef), t.cloneNode(id), value],
|
||||
);
|
||||
} else {
|
||||
return t.callExpression(file.addHelper("classPrivateFieldSet"), [
|
||||
this.receiver(member),
|
||||
t.cloneNode(id),
|
||||
value,
|
||||
]);
|
||||
}
|
||||
},
|
||||
|
||||
call(member, args) {
|
||||
// The first access (the get) should do the memo assignment.
|
||||
this.memoise(member, 1);
|
||||
|
||||
return optimiseCall(this.get(member), this.receiver(member), args);
|
||||
},
|
||||
};
|
||||
|
||||
const privateNameHandlerLoose = {
|
||||
handle(member) {
|
||||
const { privateNamesMap, file } = this;
|
||||
const { object } = member.node;
|
||||
const { name } = member.node.property.id;
|
||||
|
||||
member.replaceWith(
|
||||
template.expression`BASE(REF, PROP)[PROP]`({
|
||||
BASE: file.addHelper("classPrivateFieldLooseBase"),
|
||||
REF: object,
|
||||
PROP: privateNamesMap.get(name).id,
|
||||
}),
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export function transformPrivateNamesUsage(
|
||||
ref,
|
||||
path,
|
||||
privateNamesMap,
|
||||
loose,
|
||||
state,
|
||||
) {
|
||||
const body = path.get("body");
|
||||
|
||||
if (loose) {
|
||||
body.traverse(privateNameVisitor, {
|
||||
privateNamesMap,
|
||||
file: state,
|
||||
...privateNameHandlerLoose,
|
||||
});
|
||||
} else {
|
||||
memberExpressionToFunctions(body, privateNameVisitor, {
|
||||
privateNamesMap,
|
||||
classRef: ref,
|
||||
file: state,
|
||||
...privateNameHandlerSpec,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function buildPrivateFieldInitLoose(ref, prop, privateNamesMap) {
|
||||
const { id } = privateNamesMap.get(prop.node.key.id.name);
|
||||
const value = prop.node.value || prop.scope.buildUndefinedNode();
|
||||
|
||||
return template.statement.ast`
|
||||
Object.defineProperty(${ref}, ${id}, {
|
||||
// configurable is false by default
|
||||
// enumerable is false by default
|
||||
writable: true,
|
||||
value: ${value}
|
||||
});
|
||||
`;
|
||||
}
|
||||
|
||||
function buildPrivateInstanceFieldInitSpec(ref, prop, privateNamesMap) {
|
||||
const { id } = privateNamesMap.get(prop.node.key.id.name);
|
||||
const value = prop.node.value || prop.scope.buildUndefinedNode();
|
||||
|
||||
return template.statement.ast`${id}.set(${ref}, {
|
||||
// configurable is always false for private elements
|
||||
// enumerable is always false for private elements
|
||||
writable: true,
|
||||
value: ${value},
|
||||
})`;
|
||||
}
|
||||
|
||||
function buildPrivateStaticFieldInitSpec(prop, privateNamesMap) {
|
||||
const { id } = privateNamesMap.get(prop.node.key.id.name);
|
||||
const value = prop.node.value || prop.scope.buildUndefinedNode();
|
||||
|
||||
return template.statement.ast`
|
||||
var ${id} = {
|
||||
// configurable is false by default
|
||||
// enumerable is false by default
|
||||
writable: true,
|
||||
value: ${value}
|
||||
};
|
||||
`;
|
||||
}
|
||||
|
||||
function buildPublicFieldInitLoose(ref, prop) {
|
||||
const { key, computed } = prop.node;
|
||||
const value = prop.node.value || prop.scope.buildUndefinedNode();
|
||||
|
||||
return t.expressionStatement(
|
||||
t.assignmentExpression(
|
||||
"=",
|
||||
t.memberExpression(ref, key, computed || t.isLiteral(key)),
|
||||
value,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
function buildPublicFieldInitSpec(ref, prop, state) {
|
||||
const { key, computed } = prop.node;
|
||||
const value = prop.node.value || prop.scope.buildUndefinedNode();
|
||||
|
||||
return t.expressionStatement(
|
||||
t.callExpression(state.addHelper("defineProperty"), [
|
||||
ref,
|
||||
computed || t.isLiteral(key) ? key : t.stringLiteral(key.name),
|
||||
value,
|
||||
]),
|
||||
);
|
||||
}
|
||||
|
||||
export function buildFieldsInitNodes(
|
||||
ref,
|
||||
props,
|
||||
privateNamesMap,
|
||||
state,
|
||||
loose,
|
||||
) {
|
||||
const staticNodes = [];
|
||||
const instanceNodes = [];
|
||||
|
||||
for (const prop of props) {
|
||||
const isStatic = prop.node.static;
|
||||
const isPrivate = prop.isPrivate();
|
||||
|
||||
// Pattern matching please
|
||||
switch (true) {
|
||||
case isStatic && isPrivate && loose:
|
||||
staticNodes.push(
|
||||
buildPrivateFieldInitLoose(t.cloneNode(ref), prop, privateNamesMap),
|
||||
);
|
||||
break;
|
||||
case isStatic && isPrivate && !loose:
|
||||
staticNodes.push(
|
||||
buildPrivateStaticFieldInitSpec(prop, privateNamesMap),
|
||||
);
|
||||
break;
|
||||
case isStatic && !isPrivate && loose:
|
||||
staticNodes.push(buildPublicFieldInitLoose(t.cloneNode(ref), prop));
|
||||
break;
|
||||
case isStatic && !isPrivate && !loose:
|
||||
staticNodes.push(
|
||||
buildPublicFieldInitSpec(t.cloneNode(ref), prop, state),
|
||||
);
|
||||
break;
|
||||
case !isStatic && isPrivate && loose:
|
||||
instanceNodes.push(
|
||||
buildPrivateFieldInitLoose(t.thisExpression(), prop, privateNamesMap),
|
||||
);
|
||||
break;
|
||||
case !isStatic && isPrivate && !loose:
|
||||
instanceNodes.push(
|
||||
buildPrivateInstanceFieldInitSpec(
|
||||
t.thisExpression(),
|
||||
prop,
|
||||
privateNamesMap,
|
||||
),
|
||||
);
|
||||
break;
|
||||
case !isStatic && !isPrivate && loose:
|
||||
instanceNodes.push(buildPublicFieldInitLoose(t.thisExpression(), prop));
|
||||
break;
|
||||
case !isStatic && !isPrivate && !loose:
|
||||
instanceNodes.push(
|
||||
buildPublicFieldInitSpec(t.thisExpression(), prop, state),
|
||||
);
|
||||
break;
|
||||
default:
|
||||
throw new Error("Unreachable.");
|
||||
}
|
||||
}
|
||||
|
||||
return { staticNodes, instanceNodes };
|
||||
}
|
||||
208
packages/babel-plugin-class-features/src/index.js
Normal file
208
packages/babel-plugin-class-features/src/index.js
Normal file
@ -0,0 +1,208 @@
|
||||
import { declare } from "@babel/helper-plugin-utils";
|
||||
import nameFunction from "@babel/helper-function-name";
|
||||
import { types as t } from "@babel/core";
|
||||
import {
|
||||
buildPrivateNamesNodes,
|
||||
buildPrivateNamesMap,
|
||||
transformPrivateNamesUsage,
|
||||
buildFieldsInitNodes,
|
||||
} from "./fields";
|
||||
import { injectInitialization, extractComputedKeys } from "./misc";
|
||||
import {
|
||||
enableFeature,
|
||||
verifyUsedFeatures,
|
||||
FEATURES,
|
||||
setLoose,
|
||||
isLoose,
|
||||
} from "./features";
|
||||
|
||||
import pkg from "../package.json";
|
||||
|
||||
export { enableFeature, FEATURES, setLoose };
|
||||
|
||||
// Note: Versions are represented as an integer. e.g. 7.1.5 is represented
|
||||
// as 70000100005. This method is easier than using a semver-parsing
|
||||
// package, but it breaks if we relese x.y.z where x, y or z are
|
||||
// greater than 99_999.
|
||||
const version = pkg.version.split(".").reduce((v, x) => v * 1e5 + +x, 0);
|
||||
const versionKey = "@babel/plugin-class-features/version";
|
||||
|
||||
const getFeatureOptions = (options, name) => {
|
||||
const value = options[name];
|
||||
|
||||
if (value === undefined || value === false) return { enabled: false };
|
||||
if (value === true) return { enabled: true, loose: false };
|
||||
|
||||
if (typeof value === "object") {
|
||||
if (
|
||||
typeof value.loose !== "undefined" &&
|
||||
typeof value.loose !== "boolean"
|
||||
) {
|
||||
throw new Error(`.${name}.loose must be a boolean or undefined.`);
|
||||
}
|
||||
|
||||
return { enabled: true, loose: !!value.loose };
|
||||
}
|
||||
|
||||
throw new Error(
|
||||
`.${name} must be a boolean, an object with a 'loose'` +
|
||||
` property or undefined.`,
|
||||
);
|
||||
};
|
||||
|
||||
export default declare((api, options) => {
|
||||
api.assertVersion(7);
|
||||
|
||||
const fields = getFeatureOptions(options, "fields");
|
||||
const privateMethods = getFeatureOptions(options, "privateMethods");
|
||||
const decorators = getFeatureOptions(options, "decorators");
|
||||
|
||||
return {
|
||||
name: "class-features",
|
||||
|
||||
manipulateOptions(opts, parserOpts) {
|
||||
if (fields) {
|
||||
parserOpts.plugins.push("classProperties", "classPrivateProperties");
|
||||
}
|
||||
},
|
||||
|
||||
pre() {
|
||||
if (!this.file.get(versionKey) || this.file.get(versionKey) < version) {
|
||||
this.file.set(versionKey, version);
|
||||
}
|
||||
|
||||
if (fields.enabled) {
|
||||
enableFeature(this.file, FEATURES.fields, fields.loose);
|
||||
}
|
||||
if (privateMethods.enabled) {
|
||||
throw new Error("Private methods are not supported yet");
|
||||
enableFeature(this.file, FEATURES.privateMethods);
|
||||
}
|
||||
if (decorators.enabled) {
|
||||
throw new Error("Decorators are not supported yet");
|
||||
enableFeature(this.file, FEATURES.decorators);
|
||||
}
|
||||
},
|
||||
|
||||
visitor: {
|
||||
Class(path, state) {
|
||||
if (this.file.get(versionKey) !== version) return;
|
||||
|
||||
verifyUsedFeatures(path, this.file);
|
||||
|
||||
// Only fields are currently supported, this needs to be moved somewhere
|
||||
// else when other features are added.
|
||||
const loose = isLoose(this.file, FEATURES.fields);
|
||||
|
||||
let constructor;
|
||||
const props = [];
|
||||
const computedPaths = [];
|
||||
const privateNames = new Set();
|
||||
const body = path.get("body");
|
||||
|
||||
for (const path of body.get("body")) {
|
||||
verifyUsedFeatures(path, this.file);
|
||||
|
||||
if (path.node.computed) {
|
||||
computedPaths.push(path);
|
||||
}
|
||||
|
||||
if (path.isClassPrivateProperty()) {
|
||||
const { name } = path.node.key.id;
|
||||
|
||||
if (privateNames.has(name)) {
|
||||
throw path.buildCodeFrameError("Duplicate private field");
|
||||
}
|
||||
privateNames.add(name);
|
||||
}
|
||||
|
||||
if (path.isProperty()) {
|
||||
props.push(path);
|
||||
} else if (path.isClassMethod({ kind: "constructor" })) {
|
||||
constructor = path;
|
||||
}
|
||||
}
|
||||
|
||||
if (!props.length) return;
|
||||
|
||||
let ref;
|
||||
|
||||
if (path.isClassExpression() || !path.node.id) {
|
||||
nameFunction(path);
|
||||
ref = path.scope.generateUidIdentifier("class");
|
||||
} else {
|
||||
// path.isClassDeclaration() && path.node.id
|
||||
ref = path.node.id;
|
||||
}
|
||||
|
||||
const keysNodes = extractComputedKeys(
|
||||
ref,
|
||||
path,
|
||||
computedPaths,
|
||||
this.file,
|
||||
);
|
||||
|
||||
const privateNamesMap = buildPrivateNamesMap(props);
|
||||
const privateNamesNodes = buildPrivateNamesNodes(
|
||||
privateNamesMap,
|
||||
loose,
|
||||
state,
|
||||
);
|
||||
|
||||
transformPrivateNamesUsage(ref, path, privateNamesMap, loose, state);
|
||||
|
||||
const { staticNodes, instanceNodes } = buildFieldsInitNodes(
|
||||
ref,
|
||||
props,
|
||||
privateNamesMap,
|
||||
state,
|
||||
loose,
|
||||
);
|
||||
if (instanceNodes.length > 0) {
|
||||
injectInitialization(
|
||||
path,
|
||||
constructor,
|
||||
instanceNodes,
|
||||
(referenceVisitor, state) => {
|
||||
for (const prop of props) {
|
||||
if (prop.node.static) continue;
|
||||
prop.traverse(referenceVisitor, state);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
for (const prop of props) {
|
||||
prop.remove();
|
||||
}
|
||||
|
||||
if (
|
||||
keysNodes.length === 0 &&
|
||||
staticNodes.length === 0 &&
|
||||
privateNamesNodes.length === 0
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (path.isClassExpression()) {
|
||||
path.scope.push({ id: ref });
|
||||
path.replaceWith(
|
||||
t.assignmentExpression("=", t.cloneNode(ref), path.node),
|
||||
);
|
||||
} else if (!path.node.id) {
|
||||
// Anonymous class declaration
|
||||
path.node.id = ref;
|
||||
}
|
||||
|
||||
path.insertBefore(keysNodes);
|
||||
path.insertAfter([...privateNamesNodes, ...staticNodes]);
|
||||
},
|
||||
|
||||
PrivateName(path) {
|
||||
if (this.file.get(versionKey) !== version) return;
|
||||
|
||||
throw path.buildCodeFrameError(`Unknown PrivateName "${path}"`);
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
111
packages/babel-plugin-class-features/src/misc.js
Normal file
111
packages/babel-plugin-class-features/src/misc.js
Normal file
@ -0,0 +1,111 @@
|
||||
import { template, traverse, types as t } from "@babel/core";
|
||||
import { environmentVisitor } from "@babel/helper-replace-supers";
|
||||
|
||||
const findBareSupers = traverse.visitors.merge([
|
||||
{
|
||||
Super(path) {
|
||||
const { node, parentPath } = path;
|
||||
if (parentPath.isCallExpression({ callee: node })) {
|
||||
this.push(parentPath);
|
||||
}
|
||||
},
|
||||
},
|
||||
environmentVisitor,
|
||||
]);
|
||||
|
||||
const referenceVisitor = {
|
||||
"TSTypeAnnotation|TypeAnnotation"(path) {
|
||||
path.skip();
|
||||
},
|
||||
|
||||
ReferencedIdentifier(path) {
|
||||
if (this.scope.hasOwnBinding(path.node.name)) {
|
||||
this.scope.rename(path.node.name);
|
||||
path.skip();
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
const classFieldDefinitionEvaluationTDZVisitor = traverse.visitors.merge([
|
||||
{
|
||||
ReferencedIdentifier(path) {
|
||||
if (
|
||||
this.classBinding &&
|
||||
this.classBinding === path.scope.getBinding(path.node.name)
|
||||
) {
|
||||
const classNameTDZError = this.file.addHelper("classNameTDZError");
|
||||
const throwNode = t.callExpression(classNameTDZError, [
|
||||
t.stringLiteral(path.node.name),
|
||||
]);
|
||||
|
||||
path.replaceWith(t.sequenceExpression([throwNode, path.node]));
|
||||
path.skip();
|
||||
}
|
||||
},
|
||||
},
|
||||
environmentVisitor,
|
||||
]);
|
||||
|
||||
export function injectInitialization(path, constructor, nodes, renamer) {
|
||||
if (!nodes.length) return;
|
||||
|
||||
const isDerived = !!path.node.superClass;
|
||||
|
||||
if (!constructor) {
|
||||
const newConstructor = t.classMethod(
|
||||
"constructor",
|
||||
t.identifier("constructor"),
|
||||
[],
|
||||
t.blockStatement([]),
|
||||
);
|
||||
|
||||
if (isDerived) {
|
||||
newConstructor.params = [t.restElement(t.identifier("args"))];
|
||||
newConstructor.body.body.push(template.statement.ast`super(...args)`);
|
||||
}
|
||||
|
||||
[constructor] = path.get("body").unshiftContainer("body", newConstructor);
|
||||
}
|
||||
|
||||
if (renamer) {
|
||||
renamer(referenceVisitor, { scope: constructor.scope });
|
||||
}
|
||||
|
||||
if (isDerived) {
|
||||
const bareSupers = [];
|
||||
constructor.traverse(findBareSupers, bareSupers);
|
||||
for (const bareSuper of bareSupers) {
|
||||
bareSuper.insertAfter(nodes);
|
||||
}
|
||||
} else {
|
||||
constructor.get("body").unshiftContainer("body", nodes);
|
||||
}
|
||||
}
|
||||
|
||||
export function extractComputedKeys(ref, path, computedPaths, file) {
|
||||
const declarations = [];
|
||||
|
||||
for (const computedPath of computedPaths) {
|
||||
computedPath.traverse(classFieldDefinitionEvaluationTDZVisitor, {
|
||||
classBinding: path.node.id && path.scope.getBinding(path.node.id.name),
|
||||
file,
|
||||
});
|
||||
|
||||
const computedNode = computedPath.node;
|
||||
// Make sure computed property names are only evaluated once (upon class definition)
|
||||
// and in the right order in combination with static properties
|
||||
if (!computedPath.get("key").isConstantExpression()) {
|
||||
const ident = path.scope.generateUidIdentifierBasedOnNode(
|
||||
computedNode.key,
|
||||
);
|
||||
declarations.push(
|
||||
t.variableDeclaration("var", [
|
||||
t.variableDeclarator(ident, computedNode.key),
|
||||
]),
|
||||
);
|
||||
computedNode.key = t.cloneNode(ident);
|
||||
}
|
||||
}
|
||||
|
||||
return declarations;
|
||||
}
|
||||
@ -0,0 +1,3 @@
|
||||
class A {
|
||||
foo;
|
||||
}
|
||||
@ -0,0 +1,6 @@
|
||||
{
|
||||
"plugins": [
|
||||
["proposal-class-properties", { "loose": true }, "name 1"],
|
||||
["proposal-class-properties", { "loose": false }, "name 2"]
|
||||
]
|
||||
}
|
||||
@ -0,0 +1,6 @@
|
||||
class A {
|
||||
constructor() {
|
||||
this.foo = void 0;
|
||||
}
|
||||
|
||||
}
|
||||
3
packages/babel-plugin-class-features/test/index.js
Normal file
3
packages/babel-plugin-class-features/test/index.js
Normal file
@ -0,0 +1,3 @@
|
||||
import runner from "@babel/helper-plugin-test-runner";
|
||||
|
||||
runner(__dirname);
|
||||
@ -12,12 +12,8 @@
|
||||
"babel-plugin"
|
||||
],
|
||||
"dependencies": {
|
||||
"@babel/helper-function-name": "^7.1.0",
|
||||
"@babel/helper-member-expression-to-functions": "^7.0.0",
|
||||
"@babel/helper-optimise-call-expression": "^7.0.0",
|
||||
"@babel/helper-plugin-utils": "^7.0.0",
|
||||
"@babel/helper-replace-supers": "^7.1.0",
|
||||
"@babel/plugin-syntax-class-properties": "^7.0.0"
|
||||
"@babel/plugin-class-features": "^7.1.4"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@babel/core": "^7.0.0-0"
|
||||
|
||||
@ -1,525 +1,25 @@
|
||||
import { declare } from "@babel/helper-plugin-utils";
|
||||
import nameFunction from "@babel/helper-function-name";
|
||||
import syntaxClassProperties from "@babel/plugin-syntax-class-properties";
|
||||
import { template, traverse, types as t } from "@babel/core";
|
||||
import { environmentVisitor } from "@babel/helper-replace-supers";
|
||||
import memberExpressionToFunctions from "@babel/helper-member-expression-to-functions";
|
||||
import optimiseCall from "@babel/helper-optimise-call-expression";
|
||||
import pluginClassFeatures, {
|
||||
enableFeature,
|
||||
FEATURES,
|
||||
} from "@babel/plugin-class-features";
|
||||
|
||||
export default declare((api, options) => {
|
||||
api.assertVersion(7);
|
||||
|
||||
const { loose } = options;
|
||||
|
||||
const findBareSupers = traverse.visitors.merge([
|
||||
{
|
||||
Super(path) {
|
||||
const { node, parentPath } = path;
|
||||
if (parentPath.isCallExpression({ callee: node })) {
|
||||
this.push(parentPath);
|
||||
}
|
||||
},
|
||||
},
|
||||
environmentVisitor,
|
||||
]);
|
||||
|
||||
const referenceVisitor = {
|
||||
"TSTypeAnnotation|TypeAnnotation"(path) {
|
||||
path.skip();
|
||||
},
|
||||
|
||||
ReferencedIdentifier(path) {
|
||||
if (this.scope.hasOwnBinding(path.node.name)) {
|
||||
this.scope.rename(path.node.name);
|
||||
path.skip();
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
const classFieldDefinitionEvaluationTDZVisitor = traverse.visitors.merge([
|
||||
{
|
||||
ReferencedIdentifier(path) {
|
||||
if (
|
||||
this.classBinding &&
|
||||
this.classBinding === path.scope.getBinding(path.node.name)
|
||||
) {
|
||||
const classNameTDZError = this.file.addHelper("classNameTDZError");
|
||||
const throwNode = t.callExpression(classNameTDZError, [
|
||||
t.stringLiteral(path.node.name),
|
||||
]);
|
||||
|
||||
path.replaceWith(t.sequenceExpression([throwNode, path.node]));
|
||||
path.skip();
|
||||
}
|
||||
},
|
||||
},
|
||||
environmentVisitor,
|
||||
]);
|
||||
|
||||
// Traverses the class scope, handling private name references. If an inner
|
||||
// class redeclares the same private name, it will hand off traversal to the
|
||||
// restricted visitor (which doesn't traverse the inner class's inner scope).
|
||||
const privateNameVisitor = {
|
||||
PrivateName(path) {
|
||||
const { name } = this;
|
||||
const { node, parentPath } = path;
|
||||
|
||||
if (!parentPath.isMemberExpression({ property: node })) return;
|
||||
if (node.id.name !== name) return;
|
||||
this.handle(parentPath);
|
||||
},
|
||||
|
||||
Class(path) {
|
||||
const { name } = this;
|
||||
const body = path.get("body.body");
|
||||
|
||||
for (const prop of body) {
|
||||
if (!prop.isClassPrivateProperty()) continue;
|
||||
if (prop.node.key.id.name !== name) continue;
|
||||
|
||||
// This class redeclares the private name.
|
||||
// So, we can only evaluate the things in the outer scope.
|
||||
path.traverse(privateNameInnerVisitor, this);
|
||||
path.skip();
|
||||
break;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
// Traverses the outer portion of a class, without touching the class's inner
|
||||
// scope, for private names.
|
||||
const privateNameInnerVisitor = traverse.visitors.merge([
|
||||
{
|
||||
PrivateName: privateNameVisitor.PrivateName,
|
||||
},
|
||||
environmentVisitor,
|
||||
]);
|
||||
|
||||
const privateNameHandlerSpec = {
|
||||
memoise(member, count) {
|
||||
const { scope } = member;
|
||||
const { object } = member.node;
|
||||
|
||||
const memo = scope.maybeGenerateMemoised(object);
|
||||
if (!memo) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.memoiser.set(object, memo, count);
|
||||
},
|
||||
|
||||
receiver(member) {
|
||||
const { object } = member.node;
|
||||
|
||||
if (this.memoiser.has(object)) {
|
||||
return t.cloneNode(this.memoiser.get(object));
|
||||
}
|
||||
|
||||
return t.cloneNode(object);
|
||||
},
|
||||
|
||||
get(member) {
|
||||
const { map, file } = this;
|
||||
|
||||
return t.callExpression(file.addHelper("classPrivateFieldGet"), [
|
||||
this.receiver(member),
|
||||
t.cloneNode(map),
|
||||
]);
|
||||
},
|
||||
|
||||
set(member, value) {
|
||||
const { map, file } = this;
|
||||
|
||||
return t.callExpression(file.addHelper("classPrivateFieldSet"), [
|
||||
this.receiver(member),
|
||||
t.cloneNode(map),
|
||||
value,
|
||||
]);
|
||||
},
|
||||
|
||||
call(member, args) {
|
||||
// The first access (the get) should do the memo assignment.
|
||||
this.memoise(member, 1);
|
||||
|
||||
return optimiseCall(this.get(member), this.receiver(member), args);
|
||||
},
|
||||
};
|
||||
|
||||
const privateNameHandlerLoose = {
|
||||
handle(member) {
|
||||
const { prop, file } = this;
|
||||
const { object } = member.node;
|
||||
|
||||
member.replaceWith(
|
||||
template.expression`BASE(REF, PROP)[PROP]`({
|
||||
BASE: file.addHelper("classPrivateFieldLooseBase"),
|
||||
REF: object,
|
||||
PROP: prop,
|
||||
}),
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
const staticPrivatePropertyHandlerSpec = {
|
||||
...privateNameHandlerSpec,
|
||||
|
||||
get(member) {
|
||||
const { file, privateId, classRef } = this;
|
||||
|
||||
return t.callExpression(
|
||||
file.addHelper("classStaticPrivateFieldSpecGet"),
|
||||
[this.receiver(member), t.cloneNode(classRef), t.cloneNode(privateId)],
|
||||
);
|
||||
},
|
||||
|
||||
set(member, value) {
|
||||
const { file, privateId, classRef } = this;
|
||||
|
||||
return t.callExpression(
|
||||
file.addHelper("classStaticPrivateFieldSpecSet"),
|
||||
[
|
||||
this.receiver(member),
|
||||
t.cloneNode(classRef),
|
||||
t.cloneNode(privateId),
|
||||
value,
|
||||
],
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
function buildClassPropertySpec(ref, path, state) {
|
||||
const { scope } = path;
|
||||
const { key, value, computed } = path.node;
|
||||
return t.expressionStatement(
|
||||
t.callExpression(state.addHelper("defineProperty"), [
|
||||
ref,
|
||||
computed || t.isLiteral(key) ? key : t.stringLiteral(key.name),
|
||||
value || scope.buildUndefinedNode(),
|
||||
]),
|
||||
);
|
||||
}
|
||||
|
||||
function buildClassPropertyLoose(ref, path) {
|
||||
const { scope } = path;
|
||||
const { key, value, computed } = path.node;
|
||||
return t.expressionStatement(
|
||||
t.assignmentExpression(
|
||||
"=",
|
||||
t.memberExpression(ref, key, computed || t.isLiteral(key)),
|
||||
value || scope.buildUndefinedNode(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
function buildClassPrivatePropertySpec(ref, path, initNodes, state) {
|
||||
const { parentPath, scope } = path;
|
||||
const { name } = path.node.key.id;
|
||||
|
||||
const map = scope.generateUidIdentifier(name);
|
||||
memberExpressionToFunctions(parentPath, privateNameVisitor, {
|
||||
name,
|
||||
map,
|
||||
file: state,
|
||||
...privateNameHandlerSpec,
|
||||
});
|
||||
|
||||
initNodes.push(
|
||||
template.statement`var MAP = new WeakMap();`({
|
||||
MAP: map,
|
||||
}),
|
||||
);
|
||||
|
||||
// Must be late evaluated in case it references another private field.
|
||||
return () =>
|
||||
template.statement`
|
||||
MAP.set(REF, {
|
||||
// configurable is always false for private elements
|
||||
// enumerable is always false for private elements
|
||||
writable: true,
|
||||
value: VALUE
|
||||
});
|
||||
`({
|
||||
MAP: map,
|
||||
REF: ref,
|
||||
VALUE: path.node.value || scope.buildUndefinedNode(),
|
||||
});
|
||||
}
|
||||
|
||||
function buildClassPrivatePropertyLooseHelper(ref, path, state) {
|
||||
const { parentPath, scope } = path;
|
||||
const { name } = path.node.key.id;
|
||||
|
||||
const prop = scope.generateUidIdentifier(name);
|
||||
|
||||
parentPath.traverse(privateNameVisitor, {
|
||||
name,
|
||||
prop,
|
||||
file: state,
|
||||
...privateNameHandlerLoose,
|
||||
});
|
||||
|
||||
return {
|
||||
keyDecl: template.statement`var PROP = HELPER(NAME);`({
|
||||
PROP: prop,
|
||||
HELPER: state.addHelper("classPrivateFieldLooseKey"),
|
||||
NAME: t.stringLiteral(name),
|
||||
}),
|
||||
// Must be late evaluated in case it references another private field.
|
||||
buildInit: () =>
|
||||
template.statement.ast`
|
||||
Object.defineProperty(${ref}, ${prop}, {
|
||||
// configurable is false by default
|
||||
// enumerable is false by default
|
||||
writable: true,
|
||||
value: ${path.node.value || scope.buildUndefinedNode()}
|
||||
});
|
||||
`,
|
||||
};
|
||||
}
|
||||
|
||||
function buildClassInstancePrivatePropertyLoose(ref, path, initNodes, state) {
|
||||
const { keyDecl, buildInit } = buildClassPrivatePropertyLooseHelper(
|
||||
ref,
|
||||
path,
|
||||
state,
|
||||
);
|
||||
|
||||
initNodes.push(keyDecl);
|
||||
return buildInit;
|
||||
}
|
||||
|
||||
function buildClassStaticPrivatePropertyLoose(ref, path, state) {
|
||||
const { keyDecl, buildInit } = buildClassPrivatePropertyLooseHelper(
|
||||
ref,
|
||||
path,
|
||||
state,
|
||||
);
|
||||
|
||||
return [keyDecl, buildInit()];
|
||||
}
|
||||
|
||||
function buildClassStaticPrivatePropertySpec(ref, path, state) {
|
||||
const { parentPath, scope } = path;
|
||||
const { name } = path.node.key.id;
|
||||
|
||||
const privateId = scope.generateUidIdentifier(name);
|
||||
memberExpressionToFunctions(parentPath, privateNameVisitor, {
|
||||
name,
|
||||
privateId,
|
||||
classRef: ref,
|
||||
file: state,
|
||||
...staticPrivatePropertyHandlerSpec,
|
||||
});
|
||||
|
||||
return [
|
||||
template.statement.ast`
|
||||
var ${privateId} = {
|
||||
// configurable is always false for private elements
|
||||
// enumerable is always false for private elements
|
||||
writable: true,
|
||||
value: ${path.node.value || scope.buildUndefinedNode()}
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
const buildClassProperty = loose
|
||||
? buildClassPropertyLoose
|
||||
: buildClassPropertySpec;
|
||||
|
||||
const buildClassPrivateProperty = loose
|
||||
? buildClassInstancePrivatePropertyLoose
|
||||
: buildClassPrivatePropertySpec;
|
||||
|
||||
const buildClassStaticPrivateProperty = loose
|
||||
? buildClassStaticPrivatePropertyLoose
|
||||
: buildClassStaticPrivatePropertySpec;
|
||||
|
||||
return {
|
||||
name: "proposal-class-properties",
|
||||
inherits: syntaxClassProperties,
|
||||
|
||||
visitor: {
|
||||
Class(path, state) {
|
||||
const isDerived = !!path.node.superClass;
|
||||
let constructor;
|
||||
const props = [];
|
||||
const computedPaths = [];
|
||||
const privateNames = new Set();
|
||||
const body = path.get("body");
|
||||
inherits: pluginClassFeatures,
|
||||
|
||||
for (const path of body.get("body")) {
|
||||
const { computed, decorators } = path.node;
|
||||
if (computed) {
|
||||
computedPaths.push(path);
|
||||
}
|
||||
if (decorators && decorators.length > 0) {
|
||||
throw path.buildCodeFrameError(
|
||||
"Decorators transform is necessary.",
|
||||
);
|
||||
}
|
||||
manipulateOptions(opts, parserOpts) {
|
||||
parserOpts.plugins.push("classProperties", "classPrivateProperties");
|
||||
},
|
||||
|
||||
if (path.isClassPrivateProperty()) {
|
||||
const {
|
||||
key: {
|
||||
id: { name },
|
||||
},
|
||||
} = path.node;
|
||||
|
||||
if (privateNames.has(name)) {
|
||||
throw path.buildCodeFrameError("Duplicate private field");
|
||||
}
|
||||
privateNames.add(name);
|
||||
}
|
||||
|
||||
if (path.isProperty()) {
|
||||
props.push(path);
|
||||
} else if (path.isClassMethod({ kind: "constructor" })) {
|
||||
constructor = path;
|
||||
}
|
||||
}
|
||||
|
||||
if (!props.length) return;
|
||||
|
||||
let ref;
|
||||
if (path.isClassExpression() || !path.node.id) {
|
||||
nameFunction(path);
|
||||
ref = path.scope.generateUidIdentifier("class");
|
||||
} else {
|
||||
// path.isClassDeclaration() && path.node.id
|
||||
ref = path.node.id;
|
||||
}
|
||||
|
||||
const computedNodes = [];
|
||||
const staticNodes = [];
|
||||
const instanceBody = [];
|
||||
|
||||
for (const computedPath of computedPaths) {
|
||||
computedPath.traverse(classFieldDefinitionEvaluationTDZVisitor, {
|
||||
classBinding:
|
||||
path.node.id && path.scope.getBinding(path.node.id.name),
|
||||
file: this.file,
|
||||
});
|
||||
|
||||
const computedNode = computedPath.node;
|
||||
// Make sure computed property names are only evaluated once (upon class definition)
|
||||
// and in the right order in combination with static properties
|
||||
if (!computedPath.get("key").isConstantExpression()) {
|
||||
const ident = path.scope.generateUidIdentifierBasedOnNode(
|
||||
computedNode.key,
|
||||
);
|
||||
computedNodes.push(
|
||||
t.variableDeclaration("var", [
|
||||
t.variableDeclarator(ident, computedNode.key),
|
||||
]),
|
||||
);
|
||||
computedNode.key = t.cloneNode(ident);
|
||||
}
|
||||
}
|
||||
|
||||
// Transform private props before publics.
|
||||
const privateMaps = [];
|
||||
const privateMapInits = [];
|
||||
for (const prop of props) {
|
||||
if (prop.isPrivate() && !prop.node.static) {
|
||||
const inits = [];
|
||||
privateMapInits.push(inits);
|
||||
|
||||
privateMaps.push(
|
||||
buildClassPrivateProperty(t.thisExpression(), prop, inits, state),
|
||||
);
|
||||
}
|
||||
}
|
||||
let p = 0;
|
||||
for (const prop of props) {
|
||||
if (prop.node.static) {
|
||||
if (prop.isPrivate()) {
|
||||
staticNodes.push(
|
||||
...buildClassStaticPrivateProperty(
|
||||
t.cloneNode(ref),
|
||||
prop,
|
||||
state,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
staticNodes.push(
|
||||
buildClassProperty(t.cloneNode(ref), prop, state),
|
||||
);
|
||||
}
|
||||
} else if (prop.isPrivate()) {
|
||||
instanceBody.push(privateMaps[p]());
|
||||
staticNodes.push(...privateMapInits[p]);
|
||||
p++;
|
||||
} else {
|
||||
instanceBody.push(
|
||||
buildClassProperty(t.thisExpression(), prop, state),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (instanceBody.length) {
|
||||
if (!constructor) {
|
||||
const newConstructor = t.classMethod(
|
||||
"constructor",
|
||||
t.identifier("constructor"),
|
||||
[],
|
||||
t.blockStatement([]),
|
||||
);
|
||||
if (isDerived) {
|
||||
newConstructor.params = [t.restElement(t.identifier("args"))];
|
||||
newConstructor.body.body.push(
|
||||
t.expressionStatement(
|
||||
t.callExpression(t.super(), [
|
||||
t.spreadElement(t.identifier("args")),
|
||||
]),
|
||||
),
|
||||
);
|
||||
}
|
||||
[constructor] = body.unshiftContainer("body", newConstructor);
|
||||
}
|
||||
|
||||
const state = { scope: constructor.scope };
|
||||
for (const prop of props) {
|
||||
if (prop.node.static) continue;
|
||||
prop.traverse(referenceVisitor, state);
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
if (isDerived) {
|
||||
const bareSupers = [];
|
||||
constructor.traverse(findBareSupers, bareSupers);
|
||||
for (const bareSuper of bareSupers) {
|
||||
bareSuper.insertAfter(instanceBody);
|
||||
}
|
||||
} else {
|
||||
constructor.get("body").unshiftContainer("body", instanceBody);
|
||||
}
|
||||
}
|
||||
|
||||
for (const prop of props) {
|
||||
prop.remove();
|
||||
}
|
||||
|
||||
if (computedNodes.length === 0 && staticNodes.length === 0) return;
|
||||
|
||||
if (path.isClassExpression()) {
|
||||
path.scope.push({ id: ref });
|
||||
path.replaceWith(
|
||||
t.assignmentExpression("=", t.cloneNode(ref), path.node),
|
||||
);
|
||||
} else if (!path.node.id) {
|
||||
// Anonymous class declaration
|
||||
path.node.id = ref;
|
||||
}
|
||||
|
||||
path.insertBefore(computedNodes);
|
||||
path.insertAfter(staticNodes);
|
||||
},
|
||||
|
||||
PrivateName(path) {
|
||||
throw path.buildCodeFrameError(`Unknown PrivateName "${path}"`);
|
||||
},
|
||||
pre() {
|
||||
enableFeature(this.file, FEATURES.fields, loose);
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
@ -18,13 +18,12 @@ class Foo {
|
||||
|
||||
var _foo = babelHelpers.classPrivateFieldLooseKey("foo");
|
||||
|
||||
var _bar = babelHelpers.classPrivateFieldLooseKey("bar");
|
||||
|
||||
Object.defineProperty(Foo, _foo, {
|
||||
writable: true,
|
||||
value: "foo"
|
||||
});
|
||||
|
||||
var _bar = babelHelpers.classPrivateFieldLooseKey("bar");
|
||||
|
||||
var f = new Foo();
|
||||
expect("foo" in Foo).toBe(false);
|
||||
expect("bar" in f).toBe(false);
|
||||
|
||||
@ -16,9 +16,9 @@ class Foo {
|
||||
|
||||
}
|
||||
|
||||
var _bar = new WeakMap();
|
||||
|
||||
var _foo = {
|
||||
writable: true,
|
||||
value: "foo"
|
||||
};
|
||||
|
||||
var _bar = new WeakMap();
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user