Private class methods stage 3 (#8654)

* Add private method syntax support

* Add private method spec support

* Add private method loose support

* Throw error if static private method is used

* Add more isStatic & isMethod checks

* Remove `writable:false` from private method inits

`writable` is false by default.

* Add private method func obj equality check

* Throw if private accessor is used

* Add check for fields === private method loose mode

* Throw buildCodeFrameErrors instead of Errors

* Move obj destructuring inside for loop

* Remove "computed" from ClassPrivateMethod type def
This commit is contained in:
Tim McClure 2018-11-28 19:20:09 -05:00 committed by Justin Ridgewell
parent 6e39b58f8a
commit 0859535b62
40 changed files with 745 additions and 37 deletions

View File

@ -140,6 +140,12 @@ export function ClassMethod(node: Object) {
this.print(node.body, node);
}
export function ClassPrivateMethod(node: Object) {
this._classMethodHead(node);
this.space();
this.print(node.body, node);
}
export function _classMethodHead(node) {
this.printJoin(node.decorators, node);

View File

@ -5,6 +5,15 @@ class Foo {
get foo() {}
set foo(bar) {}
async #foo() {}
#foo() {}
get #foo() {}
set #foo(bar) {}
* #foo() {}
async * #foo() {}
get #bar() {}
set #baz(taz) {}
static async foo() {}
static foo() {}
static ["foo"]() {}

View File

@ -0,0 +1 @@
{ "plugins": ["classPrivateMethods", "asyncGenerators"] }

View File

@ -9,6 +9,22 @@ class Foo {
set foo(bar) {}
async #foo() {}
#foo() {}
get #foo() {}
set #foo(bar) {}
*#foo() {}
async *#foo() {}
get #bar() {}
set #baz(taz) {}
static async foo() {}
static foo() {}

View File

@ -1739,3 +1739,18 @@ helpers.decorate = helper("7.1.5")`
return constructor;
}
`;
helpers.classPrivateMethodGet = helper("7.1.6")`
export default function _classPrivateMethodGet(receiver, privateSet, fn) {
if (!privateSet.has(receiver)) {
throw new TypeError("attempted to get private field on non-instance");
}
return fn;
}
`;
helpers.classPrivateMethodSet = helper("7.1.6")`
export default function _classPrivateMethodSet() {
throw new TypeError("attempted to reassign private method");
}
`;

View File

@ -39,19 +39,43 @@ export function isLoose(file, feature) {
}
export function verifyUsedFeatures(path, file) {
if (hasDecorators(path) && !hasFeature(file, FEATURES.decorators)) {
throw path.buildCodeFrameError("Decorators are not enabled.");
}
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 (path.isClassPrivateMethod()) {
if (!hasFeature(file, FEATURES.privateMethods)) {
throw path.buildCodeFrameError("Class private methods are not enabled.");
}
if (path.node.static) {
throw path.buildCodeFrameError(
"@babel/plugin-class-features doesn't support class static private methods yet.",
);
}
if (path.node.kind !== "method") {
throw path.buildCodeFrameError(
"@babel/plugin-class-features doesn't support class private accessors yet.",
);
}
}
if (hasDecorators(path) && !hasFeature(file, FEATURES.decorators)) {
throw path.buildCodeFrameError("Decorators are not enabled.");
if (
hasFeature(file, FEATURES.privateMethods) &&
hasFeature(file, FEATURES.fields) &&
isLoose(file, FEATURES.privateMethods) !== isLoose(file, FEATURES.fields)
) {
throw path.buildCodeFrameError(
"'loose' mode configuration must be the same for both @babel/plugin-proposal-class-properties " +
"and @babel/plugin-proposal-private-methods",
);
}
if (path.isProperty()) {

View File

@ -11,6 +11,10 @@ export function buildPrivateNamesMap(props) {
privateNamesMap.set(name, {
id: prop.scope.generateUidIdentifier(name),
static: !!prop.node.static,
method: prop.isClassPrivateMethod(),
methodId: prop.isClassPrivateMethod()
? prop.scope.generateUidIdentifier(name)
: undefined,
});
}
}
@ -20,20 +24,22 @@ export function buildPrivateNamesMap(props) {
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
for (const [name, value] of privateNamesMap) {
// In loose mode, both static and instance fields are transpiled using a
// secret non-enumerable property. Hence, we also need to generate that
// key (using the classPrivateFieldLooseKey helper) in loose mode.
// key (using the classPrivateFieldLooseKey helper).
// 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.
// because static fields are directly assigned to a variable in the
// buildPrivateStaticFieldInitSpec function.
const { id, static: isStatic, method: isMethod } = value;
if (loose) {
initNodes.push(
template.statement.ast`
var ${id} = ${state.addHelper("classPrivateFieldLooseKey")}("${name}")
`,
);
} else if (isMethod && !isStatic) {
initNodes.push(template.statement.ast`var ${id} = new WeakSet();`);
} else if (!isStatic) {
initNodes.push(template.statement.ast`var ${id} = new WeakMap();`);
}
@ -42,7 +48,7 @@ export function buildPrivateNamesNodes(privateNamesMap, loose, state) {
return initNodes;
}
// Traverses the class scope, handling private name references. If an inner
// 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 = {
@ -61,7 +67,9 @@ const privateNameVisitor = {
const body = path.get("body.body");
for (const prop of body) {
if (!prop.isClassPrivateProperty()) continue;
if (!prop.isPrivate()) {
continue;
}
if (!privateNamesMap.has(prop.node.key.id.name)) continue;
// This class redeclares the private name.
@ -108,13 +116,24 @@ const privateNameHandlerSpec = {
get(member) {
const { classRef, privateNamesMap, file } = this;
const { name } = member.node.property.id;
const { id, static: isStatic } = privateNamesMap.get(name);
const {
id,
static: isStatic,
method: isMethod,
methodId,
} = privateNamesMap.get(name);
if (isStatic) {
if (isStatic && !isMethod) {
return t.callExpression(
file.addHelper("classStaticPrivateFieldSpecGet"),
[this.receiver(member), t.cloneNode(classRef), t.cloneNode(id)],
);
} else if (isMethod) {
return t.callExpression(file.addHelper("classPrivateMethodGet"), [
this.receiver(member),
t.cloneNode(id),
t.cloneNode(methodId),
]);
} else {
return t.callExpression(file.addHelper("classPrivateFieldGet"), [
this.receiver(member),
@ -126,13 +145,17 @@ const privateNameHandlerSpec = {
set(member, value) {
const { classRef, privateNamesMap, file } = this;
const { name } = member.node.property.id;
const { id, static: isStatic } = privateNamesMap.get(name);
const { id, static: isStatic, method: isMethod } = privateNamesMap.get(
name,
);
if (isStatic) {
if (isStatic && !isMethod) {
return t.callExpression(
file.addHelper("classStaticPrivateFieldSpecSet"),
[this.receiver(member), t.cloneNode(classRef), t.cloneNode(id), value],
);
} else if (isMethod) {
return t.callExpression(file.addHelper("classPrivateMethodSet"), []);
} else {
return t.callExpression(file.addHelper("classPrivateFieldSet"), [
this.receiver(member),
@ -231,6 +254,25 @@ function buildPrivateStaticFieldInitSpec(prop, privateNamesMap) {
`;
}
function buildPrivateMethodInitLoose(ref, prop, privateNamesMap) {
const { methodId, id } = privateNamesMap.get(prop.node.key.id.name);
return template.statement.ast`
Object.defineProperty(${ref}, ${id}, {
// configurable is false by default
// enumerable is false by default
// writable is false by default
value: ${methodId.name}
});
`;
}
function buildPrivateInstanceMethodInitSpec(ref, prop, privateNamesMap) {
const { id } = privateNamesMap.get(prop.node.key.id.name);
return template.statement.ast`${id}.add(${ref})`;
}
function buildPublicFieldInitLoose(ref, prop) {
const { key, computed } = prop.node;
const value = prop.node.value || prop.scope.buildUndefinedNode();
@ -257,6 +299,16 @@ function buildPublicFieldInitSpec(ref, prop, state) {
);
}
function buildPrivateInstanceMethodDeclaration(prop, privateNamesMap) {
const { methodId } = privateNamesMap.get(prop.node.key.id.name);
const { params, body } = prop.node;
const methodValue = t.functionExpression(methodId, params, body);
return t.variableDeclaration("var", [
t.variableDeclarator(methodId, methodValue),
]);
}
export function buildFieldsInitNodes(
ref,
props,
@ -269,34 +321,34 @@ export function buildFieldsInitNodes(
for (const prop of props) {
const isStatic = prop.node.static;
const isPrivate = prop.isPrivate();
const isPrivateField = prop.isClassPrivateProperty();
const isPrivateMethod = prop.isClassPrivateMethod();
// Pattern matching please
switch (true) {
case isStatic && isPrivate && loose:
case isStatic && isPrivateField && loose:
staticNodes.push(
buildPrivateFieldInitLoose(t.cloneNode(ref), prop, privateNamesMap),
);
break;
case isStatic && isPrivate && !loose:
case isStatic && isPrivateField && !loose:
staticNodes.push(
buildPrivateStaticFieldInitSpec(prop, privateNamesMap),
);
break;
case isStatic && !isPrivate && loose:
case isStatic && !isPrivateField && loose:
staticNodes.push(buildPublicFieldInitLoose(t.cloneNode(ref), prop));
break;
case isStatic && !isPrivate && !loose:
case isStatic && !isPrivateField && !loose:
staticNodes.push(
buildPublicFieldInitSpec(t.cloneNode(ref), prop, state),
);
break;
case !isStatic && isPrivate && loose:
case !isStatic && isPrivateField && loose:
instanceNodes.push(
buildPrivateFieldInitLoose(t.thisExpression(), prop, privateNamesMap),
);
break;
case !isStatic && isPrivate && !loose:
case !isStatic && isPrivateField && !loose:
instanceNodes.push(
buildPrivateInstanceFieldInitSpec(
t.thisExpression(),
@ -305,10 +357,34 @@ export function buildFieldsInitNodes(
),
);
break;
case !isStatic && !isPrivate && loose:
case !isStatic && isPrivateMethod && loose:
instanceNodes.push(
buildPrivateMethodInitLoose(
t.thisExpression(),
prop,
privateNamesMap,
),
);
staticNodes.push(
buildPrivateInstanceMethodDeclaration(prop, privateNamesMap),
);
break;
case !isStatic && isPrivateMethod && !loose:
instanceNodes.push(
buildPrivateInstanceMethodInitSpec(
t.thisExpression(),
prop,
privateNamesMap,
),
);
staticNodes.push(
buildPrivateInstanceMethodDeclaration(prop, privateNamesMap),
);
break;
case !isStatic && !isPrivateField && loose:
instanceNodes.push(buildPublicFieldInitLoose(t.thisExpression(), prop));
break;
case !isStatic && !isPrivate && !loose:
case !isStatic && !isPrivateField && !loose:
instanceNodes.push(
buildPublicFieldInitSpec(t.thisExpression(), prop, state),
);

View File

@ -75,7 +75,6 @@ export default declare((api, options) => {
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) {
@ -107,7 +106,7 @@ export default declare((api, options) => {
computedPaths.push(path);
}
if (path.isClassPrivateProperty()) {
if (path.isPrivate()) {
const { name } = path.node.key.id;
if (privateNames.has(name)) {
@ -116,7 +115,7 @@ export default declare((api, options) => {
privateNames.add(name);
}
if (path.isProperty()) {
if (path.isProperty() || path.isClassPrivateMethod()) {
props.push(path);
} else if (path.isClassMethod({ kind: "constructor" })) {
constructor = path;
@ -131,7 +130,6 @@ export default declare((api, options) => {
nameFunction(path);
ref = path.scope.generateUidIdentifier("class");
} else {
// path.isClassDeclaration() && path.node.id
ref = path.node.id;
}

View File

@ -0,0 +1,3 @@
src
test
*.log

View File

@ -0,0 +1,19 @@
# @babel/plugin-proposal-private-methods
> This plugin transforms private class methods
See our website [@babel/plugin-proposal-private-methods](https://babeljs.io/docs/en/next/babel-plugin-proposal-private-methods.html) for more information.
## Install
Using npm:
```sh
npm install --save-dev @babel/plugin-proposal-private-methods
```
or using yarn:
```sh
yarn add @babel/plugin-proposal-private-methods --dev
```

View File

@ -0,0 +1,25 @@
{
"name": "@babel/plugin-proposal-private-methods",
"version": "7.1.0",
"description": "This plugin transforms private class methods",
"repository": "https://github.com/babel/babel/tree/master/packages/babel-plugin-proposal-private-methods",
"license": "MIT",
"publishConfig": {
"access": "public"
},
"main": "lib/index.js",
"keywords": [
"babel-plugin"
],
"dependencies": {
"@babel/helper-plugin-utils": "^7.0.0",
"@babel/plugin-class-features": "^7.1.4"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
},
"devDependencies": {
"@babel/core": "^7.0.0",
"@babel/helper-plugin-test-runner": "^7.0.0"
}
}

View File

@ -0,0 +1,25 @@
import { declare } from "@babel/helper-plugin-utils";
import pluginClassFeatures, {
enableFeature,
FEATURES,
} from "@babel/plugin-class-features";
export default declare((api, options) => {
api.assertVersion(7);
const { loose } = options;
return {
name: "proposal-private-methods",
inherits: pluginClassFeatures,
manipulateOptions(opts, parserOpts) {
parserOpts.plugins.push("classPrivateMethods");
},
pre() {
enableFeature(this.file, FEATURES.privateMethods, loose);
},
};
});

View File

@ -0,0 +1,11 @@
class Foo {
constructor() {
this.publicField = this.#privateMethod();
}
#privateMethod() {
return 42;
}
}
expect((new Foo).publicField).toEqual(42);

View File

@ -0,0 +1,9 @@
class Foo {
constructor() {
this.publicField = this.#privateMethod();
}
#privateMethod() {
return 42;
}
}

View File

@ -0,0 +1,15 @@
var Foo = function Foo() {
"use strict";
babelHelpers.classCallCheck(this, Foo);
Object.defineProperty(this, _privateMethod, {
value: _privateMethod2
});
this.publicField = babelHelpers.classPrivateFieldLooseBase(this, _privateMethod)[_privateMethod]();
};
var _privateMethod = babelHelpers.classPrivateFieldLooseKey("privateMethod");
var _privateMethod2 = function _privateMethod2() {
return 42;
};

View File

@ -0,0 +1,41 @@
class Foo {
constructor(status) {
this.status = status;
expect(() => this.#getStatus = null).toThrow(TypeError);
}
#getStatus() {
return this.status;
}
getCurrentStatus() {
return this.#getStatus();
}
setCurrentStatus(newStatus) {
this.status = newStatus;
}
getFakeStatus(fakeStatus) {
const getStatus = this.#getStatus;
return function () {
return getStatus.call({ status: fakeStatus });
};
}
getFakeStatusFunc() {
return {
status: 'fake-status',
getFakeStatus: this.#getStatus,
};
}
}
const f = new Foo('inactive');
expect(f.getCurrentStatus()).toBe('inactive');
f.setCurrentStatus('new-status');
expect(f.getCurrentStatus()).toBe('new-status');
expect(f.getFakeStatus('fake')()).toBe('fake');
expect(f.getFakeStatusFunc().getFakeStatus()).toBe('fake-status');

View File

@ -0,0 +1,31 @@
class Foo {
constructor(status) {
this.status = status;
}
#getStatus() {
return this.status;
}
getCurrentStatus() {
return this.#getStatus();
}
setCurrentStatus(newStatus) {
this.status = newStatus;
}
getFakeStatus(fakeStatus) {
const fakeGetStatus = this.#getStatus;
return function() {
return fakeGetStatus.call({ status: fakeStatus });
};
}
getFakeStatusFunc() {
return {
status: 'fake-status',
getFakeStatus: this.#getStatus,
};
}
}

View File

@ -0,0 +1,51 @@
var Foo =
/*#__PURE__*/
function () {
"use strict";
function Foo(status) {
babelHelpers.classCallCheck(this, Foo);
Object.defineProperty(this, _getStatus, {
value: _getStatus2
});
this.status = status;
}
babelHelpers.createClass(Foo, [{
key: "getCurrentStatus",
value: function getCurrentStatus() {
return babelHelpers.classPrivateFieldLooseBase(this, _getStatus)[_getStatus]();
}
}, {
key: "setCurrentStatus",
value: function setCurrentStatus(newStatus) {
this.status = newStatus;
}
}, {
key: "getFakeStatus",
value: function getFakeStatus(fakeStatus) {
var fakeGetStatus = babelHelpers.classPrivateFieldLooseBase(this, _getStatus)[_getStatus];
return function () {
return fakeGetStatus.call({
status: fakeStatus
});
};
}
}, {
key: "getFakeStatusFunc",
value: function getFakeStatusFunc() {
return {
status: 'fake-status',
getFakeStatus: babelHelpers.classPrivateFieldLooseBase(this, _getStatus)[_getStatus]
};
}
}]);
return Foo;
}();
var _getStatus = babelHelpers.classPrivateFieldLooseKey("getStatus");
var _getStatus2 = function _getStatus2() {
return this.status;
};

View File

@ -0,0 +1,15 @@
let exfiltrated;
class Foo {
#privateMethod() {}
constructor() {
if (exfiltrated === undefined) {
exfiltrated = this.#privateMethod;
}
expect(exfiltrated).toStrictEqual(this.#privateMethod);
}
}
new Foo();
// check for private method function object equality
new Foo();

View File

@ -0,0 +1,10 @@
let exfiltrated;
class Foo {
#privateMethod() {}
constructor() {
if (exfiltrated === undefined) {
exfiltrated = this.#privateMethod;
}
}
}

View File

@ -0,0 +1,18 @@
var exfiltrated;
var Foo = function Foo() {
"use strict";
babelHelpers.classCallCheck(this, Foo);
Object.defineProperty(this, _privateMethod, {
value: _privateMethod2
});
if (exfiltrated === undefined) {
exfiltrated = babelHelpers.classPrivateFieldLooseBase(this, _privateMethod)[_privateMethod];
}
};
var _privateMethod = babelHelpers.classPrivateFieldLooseKey("privateMethod");
var _privateMethod2 = function _privateMethod2() {};

View File

@ -0,0 +1,15 @@
{
"plugins": [
[
"external-helpers",
{
"helperVersion": "7.1.6"
}
],
["proposal-private-methods", { "loose": true }],
["proposal-class-properties", { "loose": true }],
"transform-classes",
"transform-block-scoping",
"syntax-class-properties"
]
}

View File

@ -0,0 +1,11 @@
class Foo {
constructor() {
this.publicField = this.#privateMethod();
}
#privateMethod() {
return 42;
}
}
expect((new Foo).publicField).toEqual(42);

View File

@ -0,0 +1,9 @@
class Foo {
constructor() {
this.publicField = this.#privateMethod();
}
#privateMethod() {
return 42;
}
}

View File

@ -0,0 +1,15 @@
var Foo = function Foo() {
"use strict";
babelHelpers.classCallCheck(this, Foo);
_privateMethod.add(this);
this.publicField = babelHelpers.classPrivateMethodGet(this, _privateMethod, _privateMethod2).call(this);
};
var _privateMethod = new WeakSet();
var _privateMethod2 = function _privateMethod2() {
return 42;
};

View File

@ -0,0 +1,41 @@
class Foo {
constructor(status) {
this.status = status;
expect(() => this.#getStatus = null).toThrow(TypeError);
}
#getStatus() {
return this.status;
}
getCurrentStatus() {
return this.#getStatus();
}
setCurrentStatus(newStatus) {
this.status = newStatus;
}
getFakeStatus(fakeStatus) {
const getStatus = this.#getStatus;
return function () {
return getStatus.call({ status: fakeStatus });
};
}
getFakeStatusFunc() {
return {
status: 'fake-status',
getFakeStatus: this.#getStatus,
};
}
}
const f = new Foo('inactive');
expect(f.getCurrentStatus()).toBe('inactive');
f.setCurrentStatus('new-status');
expect(f.getCurrentStatus()).toBe('new-status');
expect(f.getFakeStatus('fake')()).toBe('fake');
expect(f.getFakeStatusFunc().getFakeStatus()).toBe('fake-status');

View File

@ -0,0 +1,31 @@
class Foo {
constructor(status) {
this.status = status;
}
#getStatus() {
return this.status;
}
getCurrentStatus() {
return this.#getStatus();
}
setCurrentStatus(newStatus) {
this.status = newStatus;
}
getFakeStatus(fakeStatus) {
const fakeGetStatus = this.#getStatus;
return function() {
return fakeGetStatus.call({ status: fakeStatus });
};
}
getFakeStatusFunc() {
return {
status: 'fake-status',
getFakeStatus: this.#getStatus,
};
}
}

View File

@ -0,0 +1,50 @@
var Foo =
/*#__PURE__*/
function () {
"use strict";
function Foo(status) {
babelHelpers.classCallCheck(this, Foo);
_getStatus.add(this);
this.status = status;
}
babelHelpers.createClass(Foo, [{
key: "getCurrentStatus",
value: function getCurrentStatus() {
return babelHelpers.classPrivateMethodGet(this, _getStatus, _getStatus2).call(this);
}
}, {
key: "setCurrentStatus",
value: function setCurrentStatus(newStatus) {
this.status = newStatus;
}
}, {
key: "getFakeStatus",
value: function getFakeStatus(fakeStatus) {
var fakeGetStatus = babelHelpers.classPrivateMethodGet(this, _getStatus, _getStatus2);
return function () {
return fakeGetStatus.call({
status: fakeStatus
});
};
}
}, {
key: "getFakeStatusFunc",
value: function getFakeStatusFunc() {
return {
status: 'fake-status',
getFakeStatus: babelHelpers.classPrivateMethodGet(this, _getStatus, _getStatus2)
};
}
}]);
return Foo;
}();
var _getStatus = new WeakSet();
var _getStatus2 = function _getStatus2() {
return this.status;
};

View File

@ -0,0 +1,15 @@
let exfiltrated;
class Foo {
#privateMethod() {}
constructor() {
if (exfiltrated === undefined) {
exfiltrated = this.#privateMethod;
}
expect(exfiltrated).toStrictEqual(this.#privateMethod);
}
}
new Foo();
// check for private method function object equality
new Foo();

View File

@ -0,0 +1,10 @@
let exfiltrated;
class Foo {
#privateMethod() {}
constructor() {
if (exfiltrated === undefined) {
exfiltrated = this.#privateMethod;
}
}
}

View File

@ -0,0 +1,17 @@
var exfiltrated;
var Foo = function Foo() {
"use strict";
babelHelpers.classCallCheck(this, Foo);
_privateMethod.add(this);
if (exfiltrated === undefined) {
exfiltrated = babelHelpers.classPrivateMethodGet(this, _privateMethod, _privateMethod2);
}
};
var _privateMethod = new WeakSet();
var _privateMethod2 = function _privateMethod2() {};

View File

@ -0,0 +1,15 @@
{
"plugins": [
[
"external-helpers",
{
"helperVersion": "7.1.6"
}
],
"proposal-private-methods",
"proposal-class-properties",
"transform-classes",
"transform-block-scoping",
"syntax-class-properties"
]
}

View File

@ -0,0 +1,3 @@
import runner from "@babel/helper-plugin-test-runner";
runner(__dirname);

View File

@ -7,7 +7,11 @@ export default declare(api => {
name: "syntax-class-properties",
manipulateOptions(opts, parserOpts) {
parserOpts.plugins.push("classProperties", "classPrivateProperties");
parserOpts.plugins.push(
"classProperties",
"classPrivateProperties",
"classPrivateMethods",
);
},
};
});

View File

@ -687,6 +687,12 @@ export function assertClassPrivateProperty(
): void {
assert("ClassPrivateProperty", node, opts);
}
export function assertClassPrivateMethod(
node: Object,
opts?: Object = {},
): void {
assert("ClassPrivateMethod", node, opts);
}
export function assertImport(node: Object, opts?: Object = {}): void {
assert("Import", node, opts);
}

View File

@ -620,6 +620,10 @@ export function ClassPrivateProperty(...args: Array<any>): Object {
return builder("ClassPrivateProperty", ...args);
}
export { ClassPrivateProperty as classPrivateProperty };
export function ClassPrivateMethod(...args: Array<any>): Object {
return builder("ClassPrivateMethod", ...args);
}
export { ClassPrivateMethod as classPrivateMethod };
export function Import(...args: Array<any>): Object {
return builder("Import", ...args);
}

View File

@ -87,6 +87,7 @@ defineType("ClassBody", {
assertEach(
assertNodeType(
"ClassMethod",
"ClassPrivateMethod",
"ClassProperty",
"ClassPrivateProperty",
"TSDeclareMethod",

View File

@ -5,7 +5,10 @@ import defineType, {
assertValueType,
chain,
} from "./utils";
import { classMethodOrPropertyCommon } from "./es2015";
import {
classMethodOrPropertyCommon,
classMethodOrDeclareMethodCommon,
} from "./es2015";
defineType("AwaitExpression", {
builder: ["argument"],
@ -131,6 +134,28 @@ defineType("ClassPrivateProperty", {
},
});
defineType("ClassPrivateMethod", {
builder: ["kind", "key", "params", "body", "static"],
visitor: [
"key",
"params",
"body",
"decorators",
"returnType",
"typeParameters",
],
aliases: ["Method", "Private", "Function"],
fields: {
...classMethodOrDeclareMethodCommon,
key: {
validate: assertNodeType("PrivateName"),
},
body: {
validate: assertNodeType("BlockStatement"),
},
},
});
defineType("Import", {
aliases: ["Expression"],
});

View File

@ -2159,6 +2159,20 @@ export function isClassPrivateProperty(node: Object, opts?: Object): boolean {
return false;
}
export function isClassPrivateMethod(node: Object, opts?: Object): boolean {
if (!node) return false;
const nodeType = node.type;
if (nodeType === "ClassPrivateMethod") {
if (typeof opts === "undefined") {
return true;
} else {
return shallowEqual(node, opts);
}
}
return false;
}
export function isImport(node: Object, opts?: Object): boolean {
if (!node) return false;
@ -3492,7 +3506,8 @@ export function isFunction(node: Object, opts?: Object): boolean {
"FunctionExpression" === nodeType ||
"ObjectMethod" === nodeType ||
"ArrowFunctionExpression" === nodeType ||
"ClassMethod" === nodeType
"ClassMethod" === nodeType ||
"ClassPrivateMethod" === nodeType
) {
if (typeof opts === "undefined") {
return true;
@ -3737,7 +3752,8 @@ export function isMethod(node: Object, opts?: Object): boolean {
if (
nodeType === "Method" ||
"ObjectMethod" === nodeType ||
"ClassMethod" === nodeType
"ClassMethod" === nodeType ||
"ClassPrivateMethod" === nodeType
) {
if (typeof opts === "undefined") {
return true;
@ -4119,6 +4135,7 @@ export function isPrivate(node: Object, opts?: Object): boolean {
if (
nodeType === "Private" ||
"ClassPrivateProperty" === nodeType ||
"ClassPrivateMethod" === nodeType ||
"PrivateName" === nodeType
) {
if (typeof opts === "undefined") {

View File

@ -46,6 +46,7 @@ export default function isReferenced(node: Object, parent: Object): boolean {
// no: class { NODE() {} }
// yes: class { [NODE]() {} }
case "ClassMethod":
case "ClassPrivateMethod":
case "ObjectMethod":
if (parent.key === node) {
return !!parent.computed;