Add typings to create-class-features-plugin helper (#13570)
Co-authored-by: Nicolò Ribaudo <nicolo.ribaudo@gmail.com>
This commit is contained in:
parent
b3ab4769d0
commit
1960f23c22
@ -1,21 +1,26 @@
|
||||
import { types as t, template } from "@babel/core";
|
||||
import type { File } from "@babel/core";
|
||||
import type { NodePath } from "@babel/traverse";
|
||||
import ReplaceSupers from "@babel/helper-replace-supers";
|
||||
import nameFunction from "@babel/helper-function-name";
|
||||
|
||||
export function hasOwnDecorators(node) {
|
||||
type Decorable = Extract<t.Node, { decorators?: t.Decorator[] | null }>;
|
||||
|
||||
export function hasOwnDecorators(node: t.Node) {
|
||||
// @ts-expect-error(flow->ts) TODO: maybe we could add t.isDecoratable to make ts happy
|
||||
return !!(node.decorators && node.decorators.length);
|
||||
}
|
||||
|
||||
export function hasDecorators(node) {
|
||||
export function hasDecorators(node: t.Class) {
|
||||
return hasOwnDecorators(node) || node.body.body.some(hasOwnDecorators);
|
||||
}
|
||||
|
||||
function prop(key, value) {
|
||||
function prop(key: string, value?: t.Expression) {
|
||||
if (!value) return null;
|
||||
return t.objectProperty(t.identifier(key), value);
|
||||
}
|
||||
|
||||
function method(key, body) {
|
||||
function method(key: string, body: t.Statement[]) {
|
||||
return t.objectMethod(
|
||||
"method",
|
||||
t.identifier(key),
|
||||
@ -24,8 +29,8 @@ function method(key, body) {
|
||||
);
|
||||
}
|
||||
|
||||
function takeDecorators(node) {
|
||||
let result;
|
||||
function takeDecorators(node: Decorable) {
|
||||
let result: t.ArrayExpression | undefined;
|
||||
if (node.decorators && node.decorators.length > 0) {
|
||||
result = t.arrayExpression(
|
||||
node.decorators.map(decorator => decorator.expression),
|
||||
@ -47,7 +52,12 @@ function getKey(node) {
|
||||
|
||||
// NOTE: This function can be easily bound as .bind(file, classRef, superRef)
|
||||
// to make it easier to use it in a loop.
|
||||
function extractElementDescriptor(/* this: File, */ classRef, superRef, path) {
|
||||
function extractElementDescriptor(
|
||||
this: File,
|
||||
classRef: t.Identifier,
|
||||
superRef: t.Identifier,
|
||||
path: ClassElementPath,
|
||||
) {
|
||||
const { node, scope } = path;
|
||||
const isMethod = path.isClassMethod();
|
||||
|
||||
@ -68,17 +78,17 @@ function extractElementDescriptor(/* this: File, */ classRef, superRef, path) {
|
||||
}).replace();
|
||||
|
||||
const properties: t.ObjectExpression["properties"] = [
|
||||
prop("kind", t.stringLiteral(isMethod ? node.kind : "field")),
|
||||
prop("decorators", takeDecorators(node)),
|
||||
prop("kind", t.stringLiteral(t.isClassMethod(node) ? node.kind : "field")),
|
||||
prop("decorators", takeDecorators(node as Decorable)),
|
||||
prop("static", node.static && t.booleanLiteral(true)),
|
||||
prop("key", getKey(node)),
|
||||
].filter(Boolean);
|
||||
|
||||
if (isMethod) {
|
||||
if (t.isClassMethod(node)) {
|
||||
const id = node.computed ? null : node.key;
|
||||
t.toExpression(node);
|
||||
properties.push(prop("value", nameFunction({ node, id, scope }) || node));
|
||||
} else if (node.value) {
|
||||
} else if (t.isClassProperty(node) && node.value) {
|
||||
properties.push(
|
||||
method("value", template.statements.ast`return ${node.value}`),
|
||||
);
|
||||
@ -91,7 +101,7 @@ function extractElementDescriptor(/* this: File, */ classRef, superRef, path) {
|
||||
return t.objectExpression(properties);
|
||||
}
|
||||
|
||||
function addDecorateHelper(file) {
|
||||
function addDecorateHelper(file: File) {
|
||||
try {
|
||||
return file.addHelper("decorate");
|
||||
} catch (err) {
|
||||
@ -105,7 +115,15 @@ function addDecorateHelper(file) {
|
||||
}
|
||||
}
|
||||
|
||||
export function buildDecoratedClass(ref, path, elements, file) {
|
||||
type ClassElement = t.Class["body"]["body"][number];
|
||||
type ClassElementPath = NodePath<ClassElement>;
|
||||
|
||||
export function buildDecoratedClass(
|
||||
ref: t.Identifier,
|
||||
path: NodePath<t.Class>,
|
||||
elements: ClassElementPath[],
|
||||
file: File,
|
||||
) {
|
||||
const { node, scope } = path;
|
||||
const initializeId = scope.generateUidIdentifier("initialize");
|
||||
const isDeclaration = node.id && path.isDeclaration();
|
||||
@ -115,7 +133,7 @@ export function buildDecoratedClass(ref, path, elements, file) {
|
||||
node.type = "ClassDeclaration";
|
||||
if (!node.id) node.id = t.cloneNode(ref);
|
||||
|
||||
let superId;
|
||||
let superId: t.Identifier;
|
||||
if (superClass) {
|
||||
superId = scope.generateUidIdentifierBasedOnNode(node.superClass, "super");
|
||||
node.superClass = superId;
|
||||
@ -124,7 +142,7 @@ export function buildDecoratedClass(ref, path, elements, file) {
|
||||
const classDecorators = takeDecorators(node);
|
||||
const definitions = t.arrayExpression(
|
||||
elements
|
||||
// Ignore TypeScript's abstract methods (see #10514)
|
||||
// @ts-expect-error Ignore TypeScript's abstract methods (see #10514)
|
||||
.filter(element => !element.node.abstract)
|
||||
.map(extractElementDescriptor.bind(file, node.id, superId)),
|
||||
);
|
||||
@ -155,9 +173,9 @@ export function buildDecoratedClass(ref, path, elements, file) {
|
||||
|
||||
return {
|
||||
instanceNodes: [template.statement.ast`${t.cloneNode(initializeId)}(this)`],
|
||||
wrapClass(path) {
|
||||
wrapClass(path: NodePath<t.Class>) {
|
||||
path.replaceWith(replacement);
|
||||
return path.get(classPathDesc);
|
||||
return path.get(classPathDesc) as NodePath;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
import type { File } from "@babel/core";
|
||||
import type { NodePath } from "@babel/traverse";
|
||||
import { hasOwnDecorators } from "./decorators";
|
||||
|
||||
export const FEATURES = Object.freeze({
|
||||
@ -36,7 +38,7 @@ const looseKey = "@babel/plugin-class-features/looseKey";
|
||||
const looseLowPriorityKey =
|
||||
"@babel/plugin-class-features/looseLowPriorityKey/#__internal__@babel/preset-env__please-overwrite-loose-instead-of-throwing";
|
||||
|
||||
export function enableFeature(file, feature, loose) {
|
||||
export function enableFeature(file: File, feature: number, loose: boolean) {
|
||||
// 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 }
|
||||
@ -46,12 +48,14 @@ export function enableFeature(file, feature, loose) {
|
||||
if (!hasFeature(file, feature) || canIgnoreLoose(file, feature)) {
|
||||
file.set(featuresKey, file.get(featuresKey) | feature);
|
||||
if (
|
||||
// @ts-expect-error
|
||||
loose ===
|
||||
"#__internal__@babel/preset-env__prefer-true-but-false-is-ok-if-it-prevents-an-error"
|
||||
) {
|
||||
setLoose(file, feature, true);
|
||||
file.set(looseLowPriorityKey, file.get(looseLowPriorityKey) | feature);
|
||||
} else if (
|
||||
// @ts-expect-error
|
||||
loose ===
|
||||
"#__internal__@babel/preset-env__prefer-false-but-true-is-ok-if-it-prevents-an-error"
|
||||
) {
|
||||
@ -62,8 +66,8 @@ export function enableFeature(file, feature, loose) {
|
||||
}
|
||||
}
|
||||
|
||||
let resolvedLoose: void | true | false;
|
||||
let higherPriorityPluginName: void | string;
|
||||
let resolvedLoose: boolean | undefined;
|
||||
let higherPriorityPluginName: string | undefined;
|
||||
|
||||
for (const [mask, name] of featuresSameLoose) {
|
||||
if (!hasFeature(file, mask)) continue;
|
||||
@ -103,26 +107,26 @@ export function enableFeature(file, feature, loose) {
|
||||
}
|
||||
}
|
||||
|
||||
function hasFeature(file, feature) {
|
||||
function hasFeature(file: File, feature: number) {
|
||||
return !!(file.get(featuresKey) & feature);
|
||||
}
|
||||
|
||||
export function isLoose(file, feature) {
|
||||
export function isLoose(file: File, feature: number) {
|
||||
return !!(file.get(looseKey) & feature);
|
||||
}
|
||||
|
||||
function setLoose(file, feature, loose) {
|
||||
function setLoose(file: File, feature: number, loose: boolean) {
|
||||
if (loose) file.set(looseKey, file.get(looseKey) | feature);
|
||||
else file.set(looseKey, file.get(looseKey) & ~feature);
|
||||
|
||||
file.set(looseLowPriorityKey, file.get(looseLowPriorityKey) & ~feature);
|
||||
}
|
||||
|
||||
function canIgnoreLoose(file, feature) {
|
||||
function canIgnoreLoose(file: File, feature: number) {
|
||||
return !!(file.get(looseLowPriorityKey) & feature);
|
||||
}
|
||||
|
||||
export function verifyUsedFeatures(path, file) {
|
||||
export function verifyUsedFeatures(path: NodePath, file: File) {
|
||||
if (hasOwnDecorators(path.node)) {
|
||||
if (!hasFeature(file, FEATURES.decorators)) {
|
||||
throw path.buildCodeFrameError(
|
||||
@ -145,8 +149,8 @@ export function verifyUsedFeatures(path, file) {
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: path.isPrivateMethod() it isn't supported in <7.2.0
|
||||
if (path.isPrivateMethod?.()) {
|
||||
// NOTE: path.isClassPrivateMethod() it isn't supported in <7.2.0
|
||||
if (path.isClassPrivateMethod?.()) {
|
||||
if (!hasFeature(file, FEATURES.privateMethods)) {
|
||||
throw path.buildCodeFrameError("Class private methods are not enabled.");
|
||||
}
|
||||
|
||||
@ -1,35 +1,53 @@
|
||||
import { template, traverse, types as t } from "@babel/core";
|
||||
import type { NodePath } from "@babel/traverse";
|
||||
import type { File } from "@babel/core";
|
||||
import type { NodePath, Visitor } from "@babel/traverse";
|
||||
import ReplaceSupers, {
|
||||
environmentVisitor,
|
||||
} from "@babel/helper-replace-supers";
|
||||
import memberExpressionToFunctions from "@babel/helper-member-expression-to-functions";
|
||||
import type {
|
||||
Handler,
|
||||
HandlerState,
|
||||
} from "@babel/helper-member-expression-to-functions";
|
||||
import optimiseCall from "@babel/helper-optimise-call-expression";
|
||||
import annotateAsPure from "@babel/helper-annotate-as-pure";
|
||||
|
||||
import * as ts from "./typescript";
|
||||
|
||||
export function buildPrivateNamesMap(props) {
|
||||
const privateNamesMap = new Map();
|
||||
interface PrivateNameMetadata {
|
||||
id: t.Identifier;
|
||||
static: boolean;
|
||||
method: boolean;
|
||||
getId?: t.Identifier;
|
||||
setId?: t.Identifier;
|
||||
methodId?: t.Identifier;
|
||||
initAdded?: boolean;
|
||||
getterDeclared?: boolean;
|
||||
setterDeclared?: boolean;
|
||||
}
|
||||
|
||||
type PrivateNamesMap = Map<string, PrivateNameMetadata>;
|
||||
|
||||
export function buildPrivateNamesMap(props: PropPath[]) {
|
||||
const privateNamesMap: PrivateNamesMap = new Map();
|
||||
for (const prop of props) {
|
||||
const isPrivate = prop.isPrivate();
|
||||
const isMethod = !prop.isProperty();
|
||||
const isInstance = !prop.node.static;
|
||||
if (isPrivate) {
|
||||
if (prop.isPrivate()) {
|
||||
const { name } = prop.node.key.id;
|
||||
const update = privateNamesMap.has(name)
|
||||
const update: PrivateNameMetadata = privateNamesMap.has(name)
|
||||
? privateNamesMap.get(name)
|
||||
: {
|
||||
id: prop.scope.generateUidIdentifier(name),
|
||||
static: !isInstance,
|
||||
method: isMethod,
|
||||
static: prop.node.static,
|
||||
method: !prop.isProperty(),
|
||||
};
|
||||
if (prop.node.kind === "get") {
|
||||
update.getId = prop.scope.generateUidIdentifier(`get_${name}`);
|
||||
} else if (prop.node.kind === "set") {
|
||||
update.setId = prop.scope.generateUidIdentifier(`set_${name}`);
|
||||
} else if (prop.node.kind === "method") {
|
||||
update.methodId = prop.scope.generateUidIdentifier(name);
|
||||
if (prop.isClassPrivateMethod()) {
|
||||
if (prop.node.kind === "get") {
|
||||
update.getId = prop.scope.generateUidIdentifier(`get_${name}`);
|
||||
} else if (prop.node.kind === "set") {
|
||||
update.setId = prop.scope.generateUidIdentifier(`set_${name}`);
|
||||
} else if (prop.node.kind === "method") {
|
||||
update.methodId = prop.scope.generateUidIdentifier(name);
|
||||
}
|
||||
}
|
||||
privateNamesMap.set(name, update);
|
||||
}
|
||||
@ -38,11 +56,11 @@ export function buildPrivateNamesMap(props) {
|
||||
}
|
||||
|
||||
export function buildPrivateNamesNodes(
|
||||
privateNamesMap,
|
||||
privateFieldsAsProperties,
|
||||
privateNamesMap: PrivateNamesMap,
|
||||
privateFieldsAsProperties: boolean,
|
||||
state,
|
||||
) {
|
||||
const initNodes = [];
|
||||
const initNodes: t.Statement[] = [];
|
||||
|
||||
for (const [name, value] of privateNamesMap) {
|
||||
// When the privateFieldsAsProperties assumption is enabled,
|
||||
@ -56,7 +74,7 @@ export function buildPrivateNamesNodes(
|
||||
const isAccessor = getId || setId;
|
||||
const id = t.cloneNode(value.id);
|
||||
|
||||
let init;
|
||||
let init: t.Expression;
|
||||
|
||||
if (privateFieldsAsProperties) {
|
||||
init = t.callExpression(state.addHelper("classPrivateFieldLooseKey"), [
|
||||
@ -78,11 +96,19 @@ export function buildPrivateNamesNodes(
|
||||
return initNodes;
|
||||
}
|
||||
|
||||
interface PrivateNameVisitorState {
|
||||
privateNamesMap: PrivateNamesMap;
|
||||
privateFieldsAsProperties: boolean;
|
||||
redeclared?: string[];
|
||||
}
|
||||
|
||||
// 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).
|
||||
function privateNameVisitorFactory(visitor) {
|
||||
const privateNameVisitor = {
|
||||
function privateNameVisitorFactory<S>(
|
||||
visitor: Visitor<PrivateNameVisitorState & S>,
|
||||
) {
|
||||
const privateNameVisitor: Visitor<PrivateNameVisitorState & S> = {
|
||||
...visitor,
|
||||
|
||||
Class(path) {
|
||||
@ -134,7 +160,16 @@ function privateNameVisitorFactory(visitor) {
|
||||
return privateNameVisitor;
|
||||
}
|
||||
|
||||
const privateNameVisitor = privateNameVisitorFactory({
|
||||
interface PrivateNameState {
|
||||
privateNamesMap: PrivateNamesMap;
|
||||
classRef: t.Identifier;
|
||||
file: File;
|
||||
noDocumentAll: boolean;
|
||||
}
|
||||
|
||||
const privateNameVisitor = privateNameVisitorFactory<
|
||||
HandlerState<PrivateNameState> & PrivateNameState
|
||||
>({
|
||||
PrivateName(path, { noDocumentAll }) {
|
||||
const { privateNamesMap, redeclared } = this;
|
||||
const { node, parentPath } = path;
|
||||
@ -153,13 +188,17 @@ const privateNameVisitor = privateNameVisitorFactory({
|
||||
},
|
||||
});
|
||||
|
||||
const privateInVisitor = privateNameVisitorFactory({
|
||||
const privateInVisitor = privateNameVisitorFactory<{
|
||||
classRef: t.Identifier;
|
||||
file: File;
|
||||
}>({
|
||||
BinaryExpression(path) {
|
||||
const { operator, left, right } = path.node;
|
||||
if (operator !== "in") return;
|
||||
if (!path.get("left").isPrivateName()) return;
|
||||
if (!t.isPrivateName(left)) return;
|
||||
|
||||
const { privateFieldsAsProperties, privateNamesMap, redeclared } = this;
|
||||
|
||||
const { name } = left.id;
|
||||
|
||||
if (!privateNamesMap.has(name)) return;
|
||||
@ -184,197 +223,205 @@ const privateInVisitor = privateNameVisitorFactory({
|
||||
},
|
||||
});
|
||||
|
||||
const privateNameHandlerSpec = {
|
||||
memoise(member, count) {
|
||||
const { scope } = member;
|
||||
const { object } = member.node;
|
||||
interface Receiver {
|
||||
receiver(
|
||||
this: HandlerState<PrivateNameState> & PrivateNameState,
|
||||
member: NodePath<t.MemberExpression | t.OptionalMemberExpression>,
|
||||
): t.Expression;
|
||||
}
|
||||
|
||||
const memo = scope.maybeGenerateMemoised(object);
|
||||
if (!memo) {
|
||||
return;
|
||||
}
|
||||
const privateNameHandlerSpec: Handler<PrivateNameState & Receiver> & Receiver =
|
||||
{
|
||||
memoise(member, count) {
|
||||
const { scope } = member;
|
||||
const { object } = member.node;
|
||||
|
||||
this.memoiser.set(object, memo, count);
|
||||
},
|
||||
const memo = scope.maybeGenerateMemoised(object);
|
||||
if (!memo) {
|
||||
return;
|
||||
}
|
||||
|
||||
receiver(member) {
|
||||
const { object } = member.node;
|
||||
this.memoiser.set(object, memo, count);
|
||||
},
|
||||
|
||||
if (this.memoiser.has(object)) {
|
||||
return t.cloneNode(this.memoiser.get(object));
|
||||
}
|
||||
receiver(member) {
|
||||
const { object } = member.node;
|
||||
|
||||
return t.cloneNode(object);
|
||||
},
|
||||
if (this.memoiser.has(object)) {
|
||||
return t.cloneNode(this.memoiser.get(object) as t.Expression);
|
||||
}
|
||||
|
||||
get(member) {
|
||||
const { classRef, privateNamesMap, file } = this;
|
||||
const { name } = member.node.property.id;
|
||||
const {
|
||||
id,
|
||||
static: isStatic,
|
||||
method: isMethod,
|
||||
methodId,
|
||||
getId,
|
||||
setId,
|
||||
} = privateNamesMap.get(name);
|
||||
const isAccessor = getId || setId;
|
||||
return t.cloneNode(object);
|
||||
},
|
||||
|
||||
if (isStatic) {
|
||||
const helperName =
|
||||
isMethod && !isAccessor
|
||||
? "classStaticPrivateMethodGet"
|
||||
: "classStaticPrivateFieldSpecGet";
|
||||
get(member) {
|
||||
const { classRef, privateNamesMap, file } = this;
|
||||
const { name } = (member.node.property as t.PrivateName).id;
|
||||
const {
|
||||
id,
|
||||
static: isStatic,
|
||||
method: isMethod,
|
||||
methodId,
|
||||
getId,
|
||||
setId,
|
||||
} = privateNamesMap.get(name);
|
||||
const isAccessor = getId || setId;
|
||||
|
||||
return t.callExpression(file.addHelper(helperName), [
|
||||
this.receiver(member),
|
||||
t.cloneNode(classRef),
|
||||
t.cloneNode(id),
|
||||
]);
|
||||
}
|
||||
if (isStatic) {
|
||||
const helperName =
|
||||
isMethod && !isAccessor
|
||||
? "classStaticPrivateMethodGet"
|
||||
: "classStaticPrivateFieldSpecGet";
|
||||
|
||||
if (isMethod) {
|
||||
if (isAccessor) {
|
||||
if (!getId && setId) {
|
||||
if (file.availableHelper("writeOnlyError")) {
|
||||
return t.sequenceExpression([
|
||||
this.receiver(member),
|
||||
t.callExpression(file.addHelper("writeOnlyError"), [
|
||||
t.stringLiteral(`#${name}`),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
console.warn(
|
||||
`@babel/helpers is outdated, update it to silence this warning.`,
|
||||
);
|
||||
}
|
||||
return t.callExpression(file.addHelper("classPrivateFieldGet"), [
|
||||
return t.callExpression(file.addHelper(helperName), [
|
||||
this.receiver(member),
|
||||
t.cloneNode(classRef),
|
||||
t.cloneNode(id),
|
||||
]);
|
||||
}
|
||||
return t.callExpression(file.addHelper("classPrivateMethodGet"), [
|
||||
this.receiver(member),
|
||||
t.cloneNode(id),
|
||||
t.cloneNode(methodId),
|
||||
]);
|
||||
}
|
||||
return t.callExpression(file.addHelper("classPrivateFieldGet"), [
|
||||
this.receiver(member),
|
||||
t.cloneNode(id),
|
||||
]);
|
||||
},
|
||||
|
||||
boundGet(member) {
|
||||
this.memoise(member, 1);
|
||||
|
||||
return t.callExpression(
|
||||
t.memberExpression(this.get(member), t.identifier("bind")),
|
||||
[this.receiver(member)],
|
||||
);
|
||||
},
|
||||
|
||||
set(member, value) {
|
||||
const { classRef, privateNamesMap, file } = this;
|
||||
const { name } = member.node.property.id;
|
||||
const {
|
||||
id,
|
||||
static: isStatic,
|
||||
method: isMethod,
|
||||
setId,
|
||||
getId,
|
||||
} = privateNamesMap.get(name);
|
||||
const isAccessor = getId || setId;
|
||||
|
||||
if (isStatic) {
|
||||
const helperName =
|
||||
isMethod && !isAccessor
|
||||
? "classStaticPrivateMethodSet"
|
||||
: "classStaticPrivateFieldSpecSet";
|
||||
|
||||
return t.callExpression(file.addHelper(helperName), [
|
||||
this.receiver(member),
|
||||
t.cloneNode(classRef),
|
||||
t.cloneNode(id),
|
||||
value,
|
||||
]);
|
||||
}
|
||||
if (isMethod) {
|
||||
if (setId) {
|
||||
return t.callExpression(file.addHelper("classPrivateFieldSet"), [
|
||||
if (isMethod) {
|
||||
if (isAccessor) {
|
||||
if (!getId && setId) {
|
||||
if (file.availableHelper("writeOnlyError")) {
|
||||
return t.sequenceExpression([
|
||||
this.receiver(member),
|
||||
t.callExpression(file.addHelper("writeOnlyError"), [
|
||||
t.stringLiteral(`#${name}`),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
console.warn(
|
||||
`@babel/helpers is outdated, update it to silence this warning.`,
|
||||
);
|
||||
}
|
||||
return t.callExpression(file.addHelper("classPrivateFieldGet"), [
|
||||
this.receiver(member),
|
||||
t.cloneNode(id),
|
||||
]);
|
||||
}
|
||||
return t.callExpression(file.addHelper("classPrivateMethodGet"), [
|
||||
this.receiver(member),
|
||||
t.cloneNode(id),
|
||||
t.cloneNode(methodId),
|
||||
]);
|
||||
}
|
||||
return t.callExpression(file.addHelper("classPrivateFieldGet"), [
|
||||
this.receiver(member),
|
||||
t.cloneNode(id),
|
||||
]);
|
||||
},
|
||||
|
||||
boundGet(member) {
|
||||
this.memoise(member, 1);
|
||||
|
||||
return t.callExpression(
|
||||
t.memberExpression(this.get(member), t.identifier("bind")),
|
||||
[this.receiver(member)],
|
||||
);
|
||||
},
|
||||
|
||||
set(member, value) {
|
||||
const { classRef, privateNamesMap, file } = this;
|
||||
const { name } = (member.node.property as t.PrivateName).id;
|
||||
const {
|
||||
id,
|
||||
static: isStatic,
|
||||
method: isMethod,
|
||||
setId,
|
||||
getId,
|
||||
} = privateNamesMap.get(name);
|
||||
const isAccessor = getId || setId;
|
||||
|
||||
if (isStatic) {
|
||||
const helperName =
|
||||
isMethod && !isAccessor
|
||||
? "classStaticPrivateMethodSet"
|
||||
: "classStaticPrivateFieldSpecSet";
|
||||
|
||||
return t.callExpression(file.addHelper(helperName), [
|
||||
this.receiver(member),
|
||||
t.cloneNode(classRef),
|
||||
t.cloneNode(id),
|
||||
value,
|
||||
]);
|
||||
}
|
||||
return t.sequenceExpression([
|
||||
if (isMethod) {
|
||||
if (setId) {
|
||||
return t.callExpression(file.addHelper("classPrivateFieldSet"), [
|
||||
this.receiver(member),
|
||||
t.cloneNode(id),
|
||||
value,
|
||||
]);
|
||||
}
|
||||
return t.sequenceExpression([
|
||||
this.receiver(member),
|
||||
value,
|
||||
t.callExpression(file.addHelper("readOnlyError"), [
|
||||
t.stringLiteral(`#${name}`),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
return t.callExpression(file.addHelper("classPrivateFieldSet"), [
|
||||
this.receiver(member),
|
||||
t.cloneNode(id),
|
||||
value,
|
||||
t.callExpression(file.addHelper("readOnlyError"), [
|
||||
t.stringLiteral(`#${name}`),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
return t.callExpression(file.addHelper("classPrivateFieldSet"), [
|
||||
this.receiver(member),
|
||||
t.cloneNode(id),
|
||||
value,
|
||||
]);
|
||||
},
|
||||
},
|
||||
|
||||
destructureSet(member) {
|
||||
const { classRef, privateNamesMap, file } = this;
|
||||
const { name } = member.node.property.id;
|
||||
const { id, static: isStatic } = privateNamesMap.get(name);
|
||||
if (isStatic) {
|
||||
try {
|
||||
// classStaticPrivateFieldDestructureSet was introduced in 7.13.10
|
||||
// eslint-disable-next-line no-var
|
||||
var helper = file.addHelper("classStaticPrivateFieldDestructureSet");
|
||||
} catch {
|
||||
throw new Error(
|
||||
"Babel can not transpile `[C.#p] = [0]` with @babel/helpers < 7.13.10, \n" +
|
||||
"please update @babel/helpers to the latest version.",
|
||||
destructureSet(member) {
|
||||
const { classRef, privateNamesMap, file } = this;
|
||||
const { name } = (member.node.property as t.PrivateName).id;
|
||||
const { id, static: isStatic } = privateNamesMap.get(name);
|
||||
if (isStatic) {
|
||||
try {
|
||||
// classStaticPrivateFieldDestructureSet was introduced in 7.13.10
|
||||
// eslint-disable-next-line no-var
|
||||
var helper = file.addHelper("classStaticPrivateFieldDestructureSet");
|
||||
} catch {
|
||||
throw new Error(
|
||||
"Babel can not transpile `[C.#p] = [0]` with @babel/helpers < 7.13.10, \n" +
|
||||
"please update @babel/helpers to the latest version.",
|
||||
);
|
||||
}
|
||||
return t.memberExpression(
|
||||
t.callExpression(helper, [
|
||||
this.receiver(member),
|
||||
t.cloneNode(classRef),
|
||||
t.cloneNode(id),
|
||||
]),
|
||||
t.identifier("value"),
|
||||
);
|
||||
}
|
||||
|
||||
return t.memberExpression(
|
||||
t.callExpression(helper, [
|
||||
t.callExpression(file.addHelper("classPrivateFieldDestructureSet"), [
|
||||
this.receiver(member),
|
||||
t.cloneNode(classRef),
|
||||
t.cloneNode(id),
|
||||
]),
|
||||
t.identifier("value"),
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
return t.memberExpression(
|
||||
t.callExpression(file.addHelper("classPrivateFieldDestructureSet"), [
|
||||
this.receiver(member),
|
||||
t.cloneNode(id),
|
||||
]),
|
||||
t.identifier("value"),
|
||||
);
|
||||
},
|
||||
call(member, args: (t.Expression | t.SpreadElement)[]) {
|
||||
// The first access (the get) should do the memo assignment.
|
||||
this.memoise(member, 1);
|
||||
|
||||
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, false);
|
||||
},
|
||||
|
||||
return optimiseCall(this.get(member), this.receiver(member), args, false);
|
||||
},
|
||||
optionalCall(member, args: (t.Expression | t.SpreadElement)[]) {
|
||||
this.memoise(member, 1);
|
||||
|
||||
optionalCall(member, args) {
|
||||
this.memoise(member, 1);
|
||||
return optimiseCall(this.get(member), this.receiver(member), args, true);
|
||||
},
|
||||
};
|
||||
|
||||
return optimiseCall(this.get(member), this.receiver(member), args, true);
|
||||
},
|
||||
};
|
||||
|
||||
const privateNameHandlerLoose = {
|
||||
const privateNameHandlerLoose: Handler<PrivateNameState> = {
|
||||
get(member) {
|
||||
const { privateNamesMap, file } = this;
|
||||
const { object } = member.node;
|
||||
const { name } = member.node.property.id;
|
||||
const { name } = (member.node.property as t.PrivateName).id;
|
||||
|
||||
return template.expression`BASE(REF, PROP)[PROP]`({
|
||||
BASE: file.addHelper("classPrivateFieldLooseBase"),
|
||||
@ -383,6 +430,11 @@ const privateNameHandlerLoose = {
|
||||
});
|
||||
},
|
||||
|
||||
set() {
|
||||
// noop
|
||||
throw new Error("private name handler with loose = true don't need set()");
|
||||
},
|
||||
|
||||
boundGet(member) {
|
||||
return t.callExpression(
|
||||
t.memberExpression(this.get(member), t.identifier("bind")),
|
||||
@ -408,9 +460,9 @@ const privateNameHandlerLoose = {
|
||||
};
|
||||
|
||||
export function transformPrivateNamesUsage(
|
||||
ref,
|
||||
path,
|
||||
privateNamesMap,
|
||||
ref: t.Identifier,
|
||||
path: NodePath<t.Class>,
|
||||
privateNamesMap: PrivateNamesMap,
|
||||
{ privateFieldsAsProperties, noDocumentAll },
|
||||
state,
|
||||
) {
|
||||
@ -421,7 +473,7 @@ export function transformPrivateNamesUsage(
|
||||
? privateNameHandlerLoose
|
||||
: privateNameHandlerSpec;
|
||||
|
||||
memberExpressionToFunctions(body, privateNameVisitor, {
|
||||
memberExpressionToFunctions<PrivateNameState>(body, privateNameVisitor, {
|
||||
privateNamesMap,
|
||||
classRef: ref,
|
||||
file: state,
|
||||
@ -436,7 +488,11 @@ export function transformPrivateNamesUsage(
|
||||
});
|
||||
}
|
||||
|
||||
function buildPrivateFieldInitLoose(ref, prop, privateNamesMap) {
|
||||
function buildPrivateFieldInitLoose(
|
||||
ref: t.Expression,
|
||||
prop: NodePath<t.ClassPrivateProperty>,
|
||||
privateNamesMap: PrivateNamesMap,
|
||||
) {
|
||||
const { id } = privateNamesMap.get(prop.node.key.id.name);
|
||||
const value = prop.node.value || prop.scope.buildUndefinedNode();
|
||||
|
||||
@ -450,7 +506,11 @@ function buildPrivateFieldInitLoose(ref, prop, privateNamesMap) {
|
||||
`;
|
||||
}
|
||||
|
||||
function buildPrivateInstanceFieldInitSpec(ref, prop, privateNamesMap) {
|
||||
function buildPrivateInstanceFieldInitSpec(
|
||||
ref: t.Expression,
|
||||
prop: NodePath<t.ClassPrivateProperty>,
|
||||
privateNamesMap: PrivateNamesMap,
|
||||
) {
|
||||
const { id } = privateNamesMap.get(prop.node.key.id.name);
|
||||
const value = prop.node.value || prop.scope.buildUndefinedNode();
|
||||
|
||||
@ -462,7 +522,10 @@ function buildPrivateInstanceFieldInitSpec(ref, prop, privateNamesMap) {
|
||||
})`;
|
||||
}
|
||||
|
||||
function buildPrivateStaticFieldInitSpec(prop, privateNamesMap) {
|
||||
function buildPrivateStaticFieldInitSpec(
|
||||
prop: NodePath<t.ClassPrivateProperty>,
|
||||
privateNamesMap: PrivateNamesMap,
|
||||
) {
|
||||
const privateName = privateNamesMap.get(prop.node.key.id.name);
|
||||
const { id, getId, setId, initAdded } = privateName;
|
||||
const isAccessor = getId || setId;
|
||||
@ -497,7 +560,11 @@ function buildPrivateStaticFieldInitSpec(prop, privateNamesMap) {
|
||||
`;
|
||||
}
|
||||
|
||||
function buildPrivateMethodInitLoose(ref, prop, privateNamesMap) {
|
||||
function buildPrivateMethodInitLoose(
|
||||
ref: t.Expression,
|
||||
prop: NodePath<t.ClassPrivateMethod>,
|
||||
privateNamesMap: PrivateNamesMap,
|
||||
) {
|
||||
const privateName = privateNamesMap.get(prop.node.key.id.name);
|
||||
const { methodId, id, getId, setId, initAdded } = privateName;
|
||||
if (initAdded) return;
|
||||
@ -531,7 +598,11 @@ function buildPrivateMethodInitLoose(ref, prop, privateNamesMap) {
|
||||
}
|
||||
}
|
||||
|
||||
function buildPrivateInstanceMethodInitSpec(ref, prop, privateNamesMap) {
|
||||
function buildPrivateInstanceMethodInitSpec(
|
||||
ref: t.Expression,
|
||||
prop: NodePath<t.ClassPrivateMethod>,
|
||||
privateNamesMap: PrivateNamesMap,
|
||||
) {
|
||||
const privateName = privateNamesMap.get(prop.node.key.id.name);
|
||||
const { id, getId, setId, initAdded } = privateName;
|
||||
|
||||
@ -554,7 +625,10 @@ function buildPrivateInstanceMethodInitSpec(ref, prop, privateNamesMap) {
|
||||
return template.statement.ast`${id}.add(${ref})`;
|
||||
}
|
||||
|
||||
function buildPublicFieldInitLoose(ref, prop) {
|
||||
function buildPublicFieldInitLoose(
|
||||
ref: t.Expression,
|
||||
prop: NodePath<t.ClassProperty>,
|
||||
) {
|
||||
const { key, computed } = prop.node;
|
||||
const value = prop.node.value || prop.scope.buildUndefinedNode();
|
||||
|
||||
@ -567,20 +641,31 @@ function buildPublicFieldInitLoose(ref, prop) {
|
||||
);
|
||||
}
|
||||
|
||||
function buildPublicFieldInitSpec(ref, prop, state) {
|
||||
function buildPublicFieldInitSpec(
|
||||
ref: t.Expression,
|
||||
prop: NodePath<t.ClassProperty>,
|
||||
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),
|
||||
computed || t.isLiteral(key)
|
||||
? key
|
||||
: t.stringLiteral((key as t.Identifier).name),
|
||||
value,
|
||||
]),
|
||||
);
|
||||
}
|
||||
|
||||
function buildPrivateStaticMethodInitLoose(ref, prop, state, privateNamesMap) {
|
||||
function buildPrivateStaticMethodInitLoose(
|
||||
ref: t.Expression,
|
||||
prop: NodePath<t.ClassPrivateMethod>,
|
||||
state,
|
||||
privateNamesMap: PrivateNamesMap,
|
||||
) {
|
||||
const privateName = privateNamesMap.get(prop.node.key.id.name);
|
||||
const { id, methodId, getId, setId, initAdded } = privateName;
|
||||
|
||||
@ -615,8 +700,8 @@ function buildPrivateStaticMethodInitLoose(ref, prop, state, privateNamesMap) {
|
||||
}
|
||||
|
||||
function buildPrivateMethodDeclaration(
|
||||
prop,
|
||||
privateNamesMap,
|
||||
prop: NodePath<t.ClassPrivateMethod>,
|
||||
privateNamesMap: PrivateNamesMap,
|
||||
privateFieldsAsProperties = false,
|
||||
) {
|
||||
const privateName = privateNamesMap.get(prop.node.key.id.name);
|
||||
@ -653,6 +738,7 @@ function buildPrivateMethodDeclaration(
|
||||
|
||||
return t.functionDeclaration(
|
||||
t.cloneNode(declId),
|
||||
// @ts-expect-error params for ClassMethod has TSParameterProperty
|
||||
params,
|
||||
body,
|
||||
generator,
|
||||
@ -695,13 +781,13 @@ const innerReferencesVisitor = {
|
||||
};
|
||||
|
||||
function replaceThisContext(
|
||||
path,
|
||||
ref,
|
||||
getSuperRef,
|
||||
file,
|
||||
isStaticBlock,
|
||||
constantSuper,
|
||||
innerBindingRef,
|
||||
path: PropPath,
|
||||
ref: t.Identifier,
|
||||
getSuperRef: () => t.Identifier,
|
||||
file: File,
|
||||
isStaticBlock: boolean,
|
||||
constantSuper: boolean,
|
||||
innerBindingRef: t.Identifier,
|
||||
) {
|
||||
const state = {
|
||||
classRef: ref,
|
||||
@ -734,23 +820,29 @@ function replaceThisContext(
|
||||
return state.needsClassRef;
|
||||
}
|
||||
|
||||
export type PropNode =
|
||||
| t.ClassProperty
|
||||
| t.ClassPrivateMethod
|
||||
| t.ClassPrivateProperty;
|
||||
export type PropPath = NodePath<PropNode>;
|
||||
|
||||
export function buildFieldsInitNodes(
|
||||
ref,
|
||||
superRef,
|
||||
props,
|
||||
privateNamesMap,
|
||||
ref: t.Identifier,
|
||||
superRef: t.Expression | undefined,
|
||||
props: PropPath[],
|
||||
privateNamesMap: PrivateNamesMap,
|
||||
state,
|
||||
setPublicClassFields,
|
||||
privateFieldsAsProperties,
|
||||
constantSuper,
|
||||
innerBindingRef,
|
||||
setPublicClassFields: boolean,
|
||||
privateFieldsAsProperties: boolean,
|
||||
constantSuper: boolean,
|
||||
innerBindingRef: t.Identifier,
|
||||
) {
|
||||
let needsClassRef = false;
|
||||
let injectSuperRef;
|
||||
const staticNodes = [];
|
||||
const instanceNodes = [];
|
||||
let injectSuperRef: t.Identifier;
|
||||
const staticNodes: t.Statement[] = [];
|
||||
const instanceNodes: t.Statement[] = [];
|
||||
// These nodes are pure and can be moved to the closest statement position
|
||||
const pureStaticNodes = [];
|
||||
const pureStaticNodes: t.FunctionDeclaration[] = [];
|
||||
|
||||
const getSuperRef = t.isIdentifier(superRef)
|
||||
? () => superRef
|
||||
@ -761,7 +853,7 @@ export function buildFieldsInitNodes(
|
||||
};
|
||||
|
||||
for (const prop of props) {
|
||||
ts.assertFieldTransformed(prop);
|
||||
prop.isClassProperty() && ts.assertFieldTransformed(prop);
|
||||
|
||||
const isStatic = prop.node.static;
|
||||
const isInstance = !isStatic;
|
||||
@ -784,36 +876,47 @@ export function buildFieldsInitNodes(
|
||||
needsClassRef = needsClassRef || replaced;
|
||||
}
|
||||
|
||||
// TODO(ts): there are so many `ts-expect-error` inside cases since
|
||||
// ts can not infer type from pre-computed values (or a case test)
|
||||
// even change `isStaticBlock` to `t.isStaticBlock(prop)` will not make prop
|
||||
// a `NodePath<t.StaticBlock>`
|
||||
// this maybe a bug for ts
|
||||
switch (true) {
|
||||
case isStaticBlock:
|
||||
staticNodes.push(
|
||||
// @ts-expect-error prop is `StaticBlock` here
|
||||
template.statement.ast`(() => ${t.blockStatement(prop.node.body)})()`,
|
||||
);
|
||||
break;
|
||||
case isStatic && isPrivate && isField && privateFieldsAsProperties:
|
||||
needsClassRef = true;
|
||||
staticNodes.push(
|
||||
// @ts-expect-error checked in switch
|
||||
buildPrivateFieldInitLoose(t.cloneNode(ref), prop, privateNamesMap),
|
||||
);
|
||||
break;
|
||||
case isStatic && isPrivate && isField && !privateFieldsAsProperties:
|
||||
needsClassRef = true;
|
||||
staticNodes.push(
|
||||
// @ts-expect-error checked in switch
|
||||
buildPrivateStaticFieldInitSpec(prop, privateNamesMap),
|
||||
);
|
||||
break;
|
||||
case isStatic && isPublic && isField && setPublicClassFields:
|
||||
needsClassRef = true;
|
||||
// @ts-expect-error checked in switch
|
||||
staticNodes.push(buildPublicFieldInitLoose(t.cloneNode(ref), prop));
|
||||
break;
|
||||
case isStatic && isPublic && isField && !setPublicClassFields:
|
||||
needsClassRef = true;
|
||||
staticNodes.push(
|
||||
// @ts-expect-error checked in switch
|
||||
buildPublicFieldInitSpec(t.cloneNode(ref), prop, state),
|
||||
);
|
||||
break;
|
||||
case isInstance && isPrivate && isField && privateFieldsAsProperties:
|
||||
instanceNodes.push(
|
||||
// @ts-expect-error checked in switch
|
||||
buildPrivateFieldInitLoose(t.thisExpression(), prop, privateNamesMap),
|
||||
);
|
||||
break;
|
||||
@ -821,6 +924,7 @@ export function buildFieldsInitNodes(
|
||||
instanceNodes.push(
|
||||
buildPrivateInstanceFieldInitSpec(
|
||||
t.thisExpression(),
|
||||
// @ts-expect-error checked in switch
|
||||
prop,
|
||||
privateNamesMap,
|
||||
),
|
||||
@ -830,12 +934,14 @@ export function buildFieldsInitNodes(
|
||||
instanceNodes.unshift(
|
||||
buildPrivateMethodInitLoose(
|
||||
t.thisExpression(),
|
||||
// @ts-expect-error checked in switch
|
||||
prop,
|
||||
privateNamesMap,
|
||||
),
|
||||
);
|
||||
pureStaticNodes.push(
|
||||
buildPrivateMethodDeclaration(
|
||||
// @ts-expect-error checked in switch
|
||||
prop,
|
||||
privateNamesMap,
|
||||
privateFieldsAsProperties,
|
||||
@ -846,12 +952,14 @@ export function buildFieldsInitNodes(
|
||||
instanceNodes.unshift(
|
||||
buildPrivateInstanceMethodInitSpec(
|
||||
t.thisExpression(),
|
||||
// @ts-expect-error checked in switch
|
||||
prop,
|
||||
privateNamesMap,
|
||||
),
|
||||
);
|
||||
pureStaticNodes.push(
|
||||
buildPrivateMethodDeclaration(
|
||||
// @ts-expect-error checked in switch
|
||||
prop,
|
||||
privateNamesMap,
|
||||
privateFieldsAsProperties,
|
||||
@ -861,10 +969,12 @@ export function buildFieldsInitNodes(
|
||||
case isStatic && isPrivate && isMethod && !privateFieldsAsProperties:
|
||||
needsClassRef = true;
|
||||
staticNodes.unshift(
|
||||
// @ts-expect-error checked in switch
|
||||
buildPrivateStaticFieldInitSpec(prop, privateNamesMap),
|
||||
);
|
||||
pureStaticNodes.push(
|
||||
buildPrivateMethodDeclaration(
|
||||
// @ts-expect-error checked in switch
|
||||
prop,
|
||||
privateNamesMap,
|
||||
privateFieldsAsProperties,
|
||||
@ -876,6 +986,7 @@ export function buildFieldsInitNodes(
|
||||
staticNodes.unshift(
|
||||
buildPrivateStaticMethodInitLoose(
|
||||
t.cloneNode(ref),
|
||||
// @ts-expect-error checked in switch
|
||||
prop,
|
||||
state,
|
||||
privateNamesMap,
|
||||
@ -883,6 +994,7 @@ export function buildFieldsInitNodes(
|
||||
);
|
||||
pureStaticNodes.push(
|
||||
buildPrivateMethodDeclaration(
|
||||
// @ts-expect-error checked in switch
|
||||
prop,
|
||||
privateNamesMap,
|
||||
privateFieldsAsProperties,
|
||||
@ -890,10 +1002,12 @@ export function buildFieldsInitNodes(
|
||||
);
|
||||
break;
|
||||
case isInstance && isPublic && isField && setPublicClassFields:
|
||||
// @ts-expect-error checked in switch
|
||||
instanceNodes.push(buildPublicFieldInitLoose(t.thisExpression(), prop));
|
||||
break;
|
||||
case isInstance && isPublic && isField && !setPublicClassFields:
|
||||
instanceNodes.push(
|
||||
// @ts-expect-error checked in switch
|
||||
buildPublicFieldInitSpec(t.thisExpression(), prop, state),
|
||||
);
|
||||
break;
|
||||
@ -906,7 +1020,7 @@ export function buildFieldsInitNodes(
|
||||
staticNodes: staticNodes.filter(Boolean),
|
||||
instanceNodes: instanceNodes.filter(Boolean),
|
||||
pureStaticNodes: pureStaticNodes.filter(Boolean),
|
||||
wrapClass(path) {
|
||||
wrapClass(path: NodePath<t.Class>) {
|
||||
for (const prop of props) {
|
||||
prop.remove();
|
||||
}
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { types as t } from "@babel/core";
|
||||
import type { NodePath } from "@babel/traverse";
|
||||
import nameFunction from "@babel/helper-function-name";
|
||||
import splitExportDeclaration from "@babel/helper-split-export-declaration";
|
||||
import {
|
||||
@ -7,11 +8,8 @@ import {
|
||||
transformPrivateNamesUsage,
|
||||
buildFieldsInitNodes,
|
||||
} from "./fields";
|
||||
import {
|
||||
hasOwnDecorators,
|
||||
buildDecoratedClass,
|
||||
hasDecorators,
|
||||
} from "./decorators";
|
||||
import type { PropPath } from "./fields";
|
||||
import { buildDecoratedClass, hasDecorators } from "./decorators";
|
||||
import { injectInitialization, extractComputedKeys } from "./misc";
|
||||
import {
|
||||
enableFeature,
|
||||
@ -33,20 +31,24 @@ const version = PACKAGE_JSON.version
|
||||
.reduce((v, x) => v * 1e5 + +x, 0);
|
||||
const versionKey = "@babel/plugin-class-features/version";
|
||||
|
||||
interface Options {
|
||||
name: string;
|
||||
feature: number;
|
||||
loose?: boolean;
|
||||
// same as PluginObject.manipulateOptions
|
||||
manipulateOptions: (options: unknown, parserOpts: unknown) => void;
|
||||
// TODO(flow->ts): change to babel api
|
||||
api?: { assumption: (key?: string) => boolean | undefined };
|
||||
}
|
||||
|
||||
export function createClassFeaturePlugin({
|
||||
name,
|
||||
feature,
|
||||
loose,
|
||||
manipulateOptions,
|
||||
// TODO(Babel 8): Remove the default falue
|
||||
api = { assumption: () => {} },
|
||||
}: {
|
||||
name;
|
||||
feature;
|
||||
loose?;
|
||||
manipulateOptions;
|
||||
api?;
|
||||
}) {
|
||||
api = { assumption: () => void 0 },
|
||||
}: Options) {
|
||||
const setPublicClassFields = api.assumption("setPublicClassFields");
|
||||
const privateFieldsAsProperties = api.assumption("privateFieldsAsProperties");
|
||||
const constantSuper = api.assumption("constantSuper");
|
||||
@ -90,25 +92,29 @@ export function createClassFeaturePlugin({
|
||||
},
|
||||
|
||||
visitor: {
|
||||
Class(path, state) {
|
||||
Class(path: NodePath<t.Class>, state) {
|
||||
if (this.file.get(versionKey) !== version) return;
|
||||
|
||||
verifyUsedFeatures(path, this.file);
|
||||
|
||||
const loose = isLoose(this.file, feature);
|
||||
|
||||
let constructor;
|
||||
let isDecorated = hasOwnDecorators(path.node);
|
||||
const props = [];
|
||||
let constructor: NodePath<t.ClassMethod>;
|
||||
const isDecorated = hasDecorators(path.node);
|
||||
const props: PropPath[] = [];
|
||||
const elements = [];
|
||||
const computedPaths = [];
|
||||
const privateNames = new Set();
|
||||
const computedPaths: NodePath<t.ClassProperty | t.ClassMethod>[] = [];
|
||||
const privateNames = new Set<string>();
|
||||
const body = path.get("body");
|
||||
|
||||
for (const path of body.get("body")) {
|
||||
verifyUsedFeatures(path, this.file);
|
||||
|
||||
if (path.node.computed) {
|
||||
if (
|
||||
// check path.node.computed is enough, but ts will complain
|
||||
(path.isClassProperty() || path.isClassMethod()) &&
|
||||
path.node.computed
|
||||
) {
|
||||
computedPaths.push(path);
|
||||
}
|
||||
|
||||
@ -117,24 +123,24 @@ export function createClassFeaturePlugin({
|
||||
const getName = `get ${name}`;
|
||||
const setName = `set ${name}`;
|
||||
|
||||
if (path.node.kind === "get") {
|
||||
if (
|
||||
privateNames.has(getName) ||
|
||||
(privateNames.has(name) && !privateNames.has(setName))
|
||||
) {
|
||||
throw path.buildCodeFrameError("Duplicate private field");
|
||||
if (path.isClassPrivateMethod()) {
|
||||
if (path.node.kind === "get") {
|
||||
if (
|
||||
privateNames.has(getName) ||
|
||||
(privateNames.has(name) && !privateNames.has(setName))
|
||||
) {
|
||||
throw path.buildCodeFrameError("Duplicate private field");
|
||||
}
|
||||
privateNames.add(getName).add(name);
|
||||
} else if (path.node.kind === "set") {
|
||||
if (
|
||||
privateNames.has(setName) ||
|
||||
(privateNames.has(name) && !privateNames.has(getName))
|
||||
) {
|
||||
throw path.buildCodeFrameError("Duplicate private field");
|
||||
}
|
||||
privateNames.add(setName).add(name);
|
||||
}
|
||||
|
||||
privateNames.add(getName).add(name);
|
||||
} else if (path.node.kind === "set") {
|
||||
if (
|
||||
privateNames.has(setName) ||
|
||||
(privateNames.has(name) && !privateNames.has(getName))
|
||||
) {
|
||||
throw path.buildCodeFrameError("Duplicate private field");
|
||||
}
|
||||
|
||||
privateNames.add(setName).add(name);
|
||||
} else {
|
||||
if (
|
||||
(privateNames.has(name) &&
|
||||
@ -162,14 +168,12 @@ export function createClassFeaturePlugin({
|
||||
props.push(path);
|
||||
}
|
||||
}
|
||||
|
||||
if (!isDecorated) isDecorated = hasOwnDecorators(path.node);
|
||||
}
|
||||
|
||||
if (!props.length && !isDecorated) return;
|
||||
|
||||
const innerBinding = path.node.id;
|
||||
let ref;
|
||||
let ref: t.Identifier;
|
||||
if (!innerBinding || path.isClassExpression()) {
|
||||
nameFunction(path);
|
||||
ref = path.scope.generateUidIdentifier("class");
|
||||
@ -198,7 +202,11 @@ export function createClassFeaturePlugin({
|
||||
state,
|
||||
);
|
||||
|
||||
let keysNodes, staticNodes, pureStaticNodes, instanceNodes, wrapClass;
|
||||
let keysNodes: t.Statement[],
|
||||
staticNodes: t.Statement[],
|
||||
instanceNodes: t.Statement[],
|
||||
pureStaticNodes: t.FunctionDeclaration[],
|
||||
wrapClass: (path: NodePath<t.Class>) => NodePath;
|
||||
|
||||
if (isDecorated) {
|
||||
staticNodes = pureStaticNodes = keysNodes = [];
|
||||
@ -239,19 +247,20 @@ export function createClassFeaturePlugin({
|
||||
);
|
||||
}
|
||||
|
||||
path = wrapClass(path);
|
||||
path.insertBefore([...privateNamesNodes, ...keysNodes]);
|
||||
// rename to make ts happy
|
||||
const wrappedPath = wrapClass(path);
|
||||
wrappedPath.insertBefore([...privateNamesNodes, ...keysNodes]);
|
||||
if (staticNodes.length > 0) {
|
||||
path.insertAfter(staticNodes);
|
||||
wrappedPath.insertAfter(staticNodes);
|
||||
}
|
||||
if (pureStaticNodes.length > 0) {
|
||||
path
|
||||
wrappedPath
|
||||
.find(parent => parent.isStatement() || parent.isDeclaration())
|
||||
.insertAfter(pureStaticNodes);
|
||||
}
|
||||
},
|
||||
|
||||
PrivateName(path) {
|
||||
PrivateName(path: NodePath<t.PrivateName>) {
|
||||
if (
|
||||
this.file.get(versionKey) !== version ||
|
||||
path.parentPath.isPrivate({ key: path.node })
|
||||
@ -262,7 +271,7 @@ export function createClassFeaturePlugin({
|
||||
throw path.buildCodeFrameError(`Unknown PrivateName "${path}"`);
|
||||
},
|
||||
|
||||
ExportDefaultDeclaration(path) {
|
||||
ExportDefaultDeclaration(path: NodePath<t.ExportDefaultDeclaration>) {
|
||||
if (this.file.get(versionKey) !== version) return;
|
||||
|
||||
const decl = path.get("declaration");
|
||||
@ -276,6 +285,7 @@ export function createClassFeaturePlugin({
|
||||
} else {
|
||||
// Annyms class declarations can be
|
||||
// transformed as if they were expressions
|
||||
// @ts-expect-error
|
||||
decl.node.type = "ClassExpression";
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,9 +1,11 @@
|
||||
import { template, traverse, types as t } from "@babel/core";
|
||||
import type { File } from "@babel/core";
|
||||
import type { NodePath, Scope, Visitor, Binding } from "@babel/traverse";
|
||||
import { environmentVisitor } from "@babel/helper-replace-supers";
|
||||
|
||||
const findBareSupers = traverse.visitors.merge([
|
||||
{
|
||||
Super(path) {
|
||||
Super(path: NodePath<t.Super>) {
|
||||
const { node, parentPath } = path;
|
||||
if (parentPath.isCallExpression({ callee: node })) {
|
||||
this.push(parentPath);
|
||||
@ -14,18 +16,24 @@ const findBareSupers = traverse.visitors.merge([
|
||||
]);
|
||||
|
||||
const referenceVisitor = {
|
||||
"TSTypeAnnotation|TypeAnnotation"(path) {
|
||||
"TSTypeAnnotation|TypeAnnotation"(path: NodePath) {
|
||||
path.skip();
|
||||
},
|
||||
|
||||
ReferencedIdentifier(path) {
|
||||
ReferencedIdentifier(path: NodePath<t.Identifier>) {
|
||||
if (this.scope.hasOwnBinding(path.node.name)) {
|
||||
this.scope.rename(path.node.name);
|
||||
path.skip();
|
||||
}
|
||||
},
|
||||
};
|
||||
function handleClassTDZ(path, state) {
|
||||
function handleClassTDZ(
|
||||
path: NodePath<t.Identifier>,
|
||||
state: {
|
||||
classBinding: Binding;
|
||||
file: File;
|
||||
},
|
||||
) {
|
||||
if (
|
||||
state.classBinding &&
|
||||
state.classBinding === path.scope.getBinding(path.node.name)
|
||||
@ -44,7 +52,16 @@ const classFieldDefinitionEvaluationTDZVisitor = {
|
||||
ReferencedIdentifier: handleClassTDZ,
|
||||
};
|
||||
|
||||
export function injectInitialization(path, constructor, nodes, renamer?) {
|
||||
interface RenamerState {
|
||||
scope: Scope;
|
||||
}
|
||||
|
||||
export function injectInitialization(
|
||||
path: NodePath<t.Class>,
|
||||
constructor: NodePath<t.ClassMethod> | undefined,
|
||||
nodes: t.Statement[],
|
||||
renamer?: (visitor: Visitor<RenamerState>, state: RenamerState) => void,
|
||||
) {
|
||||
if (!nodes.length) return;
|
||||
|
||||
const isDerived = !!path.node.superClass;
|
||||
@ -62,7 +79,9 @@ export function injectInitialization(path, constructor, nodes, renamer?) {
|
||||
newConstructor.body.body.push(template.statement.ast`super(...args)`);
|
||||
}
|
||||
|
||||
[constructor] = path.get("body").unshiftContainer("body", newConstructor);
|
||||
[constructor] = path
|
||||
.get("body")
|
||||
.unshiftContainer("body", newConstructor) as NodePath<t.ClassMethod>[];
|
||||
}
|
||||
|
||||
if (renamer) {
|
||||
@ -86,8 +105,13 @@ export function injectInitialization(path, constructor, nodes, renamer?) {
|
||||
}
|
||||
}
|
||||
|
||||
export function extractComputedKeys(ref, path, computedPaths, file) {
|
||||
const declarations = [];
|
||||
export function extractComputedKeys(
|
||||
ref: t.Identifier,
|
||||
path: NodePath<t.Class>,
|
||||
computedPaths: NodePath<t.ClassProperty | t.ClassMethod>[],
|
||||
file: File,
|
||||
) {
|
||||
const declarations: t.Statement[] = [];
|
||||
const state = {
|
||||
classBinding: path.node.id && path.scope.getBinding(path.node.id.name),
|
||||
file,
|
||||
|
||||
@ -1,17 +1,18 @@
|
||||
import type { NodePath, Visitor } from "@babel/traverse";
|
||||
import * as t from "@babel/types";
|
||||
import { willPathCastToBoolean } from "./util";
|
||||
|
||||
class AssignmentMemoiser {
|
||||
private _map: WeakMap<object, any>;
|
||||
private _map: WeakMap<t.Expression, { count: number; value: t.LVal }>;
|
||||
constructor() {
|
||||
this._map = new WeakMap();
|
||||
}
|
||||
|
||||
has(key) {
|
||||
has(key: t.Expression) {
|
||||
return this._map.has(key);
|
||||
}
|
||||
|
||||
get(key) {
|
||||
get(key: t.Expression) {
|
||||
if (!this.has(key)) return;
|
||||
|
||||
const record = this._map.get(key);
|
||||
@ -26,14 +27,17 @@ class AssignmentMemoiser {
|
||||
return value;
|
||||
}
|
||||
|
||||
set(key, value, count) {
|
||||
set(key: t.Expression, value: t.LVal, count: number) {
|
||||
return this._map.set(key, { count, value });
|
||||
}
|
||||
}
|
||||
|
||||
function toNonOptional(path, base) {
|
||||
function toNonOptional(
|
||||
path: NodePath<t.Expression>,
|
||||
base: t.Expression,
|
||||
): t.Expression {
|
||||
const { node } = path;
|
||||
if (path.isOptionalMemberExpression()) {
|
||||
if (t.isOptionalMemberExpression(node)) {
|
||||
return t.memberExpression(base, node.property, node.computed);
|
||||
}
|
||||
|
||||
@ -44,15 +48,15 @@ function toNonOptional(path, base) {
|
||||
const context = path.scope.maybeGenerateMemoised(object) || object;
|
||||
callee
|
||||
.get("object")
|
||||
.replaceWith(t.assignmentExpression("=", context, object));
|
||||
.replaceWith(t.assignmentExpression("=", context as t.LVal, object));
|
||||
|
||||
return t.callExpression(t.memberExpression(base, t.identifier("call")), [
|
||||
context,
|
||||
...node.arguments,
|
||||
...path.node.arguments,
|
||||
]);
|
||||
}
|
||||
|
||||
return t.callExpression(base, node.arguments);
|
||||
return t.callExpression(base, path.node.arguments);
|
||||
}
|
||||
|
||||
return path.node;
|
||||
@ -62,7 +66,7 @@ function toNonOptional(path, base) {
|
||||
// we are iterating on a path, and replace an ancestor with a new node. Babel
|
||||
// doesn't always stop traversing the old node tree, and that can cause
|
||||
// inconsistencies.
|
||||
function isInDetachedTree(path) {
|
||||
function isInDetachedTree(path: NodePath) {
|
||||
while (path) {
|
||||
if (path.isProgram()) break;
|
||||
|
||||
@ -80,13 +84,14 @@ function isInDetachedTree(path) {
|
||||
return false;
|
||||
}
|
||||
|
||||
type Member = NodePath<t.OptionalMemberExpression | t.MemberExpression>;
|
||||
|
||||
const handle = {
|
||||
memoise() {
|
||||
// noop.
|
||||
},
|
||||
|
||||
// todo(flow->ts) member:NodePath<t.Expression>, refactor function body to avoid too many typecasts
|
||||
handle(member: any, noDocumentAll: boolean) {
|
||||
handle(this: HandlerState, member: Member, noDocumentAll: boolean) {
|
||||
const { node, parent, parentPath, scope } = member;
|
||||
|
||||
if (member.isOptionalMemberExpression()) {
|
||||
@ -102,14 +107,14 @@ const handle = {
|
||||
// Everything up to it could be skipped if it `FOO` were nullish. But
|
||||
// actually, we can consider the `baz` access to be the end. So we're
|
||||
// looking for the nearest optional chain that is `optional: true`.
|
||||
const endPath = member.find(({ node, parent, parentPath }) => {
|
||||
if (parentPath.isOptionalMemberExpression()) {
|
||||
const endPath = member.find(({ node, parent }) => {
|
||||
if (t.isOptionalMemberExpression(parent)) {
|
||||
// We need to check `parent.object` since we could be inside the
|
||||
// computed expression of a `bad?.[FOO?.BAR]`. In this case, the
|
||||
// endPath is the `FOO?.BAR` member itself.
|
||||
return parent.optional || parent.object !== node;
|
||||
}
|
||||
if (parentPath.isOptionalCallExpression()) {
|
||||
if (t.isOptionalCallExpression(parent)) {
|
||||
// Checking `parent.callee` since we could be in the arguments, eg
|
||||
// `bad?.(FOO?.BAR)`.
|
||||
// Also skip `FOO?.BAR` in `FOO?.BAR?.()` since we need to transform the optional call to ensure proper this
|
||||
@ -119,7 +124,7 @@ const handle = {
|
||||
);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}) as NodePath<t.OptionalMemberExpression>;
|
||||
|
||||
// Replace `function (a, x = a.b?.#c) {}` to `function (a, x = (() => a.b?.#c)() ){}`
|
||||
// so the temporary variable can be injected in correct scope
|
||||
@ -164,7 +169,7 @@ const handle = {
|
||||
// But actually, we can consider the `bar` access to be the start. So
|
||||
// we're looking for the nearest optional chain that is `optional: true`,
|
||||
// which is guaranteed to be somewhere in the object/callee tree.
|
||||
let startingOptional = member;
|
||||
let startingOptional: NodePath<t.Expression> = member;
|
||||
for (;;) {
|
||||
if (startingOptional.isOptionalMemberExpression()) {
|
||||
if (startingOptional.node.optional) break;
|
||||
@ -193,10 +198,16 @@ const handle = {
|
||||
const parentIsOptionalCall = parentPath.isOptionalCallExpression({
|
||||
callee: node,
|
||||
});
|
||||
// here we use a function to wrap `parentIsOptionalCall` to get type
|
||||
// for parent, do not use it anywhere else
|
||||
// See https://github.com/microsoft/TypeScript/issues/10421
|
||||
const isOptionalCall = (
|
||||
parent: t.Node,
|
||||
): parent is t.OptionalCallExpression => parentIsOptionalCall;
|
||||
// if parentIsCall is true, it implies that node.extra.parenthesized is always true
|
||||
const parentIsCall = parentPath.isCallExpression({ callee: node });
|
||||
startingOptional.replaceWith(toNonOptional(startingOptional, baseRef));
|
||||
if (parentIsOptionalCall) {
|
||||
if (isOptionalCall(parent)) {
|
||||
if (parent.optional) {
|
||||
parentPath.replaceWith(this.optionalCall(member, parent.arguments));
|
||||
} else {
|
||||
@ -209,11 +220,15 @@ const handle = {
|
||||
member.replaceWith(this.get(member));
|
||||
}
|
||||
|
||||
let regular = member.node;
|
||||
for (let current = member; current !== endPath; ) {
|
||||
const { parentPath } = current;
|
||||
let regular: t.Expression = member.node;
|
||||
for (let current: NodePath = member; current !== endPath; ) {
|
||||
const parentPath = current.parentPath as NodePath<t.Expression>;
|
||||
// skip transforming `Foo.#BAR?.call(FOO)`
|
||||
if (parentPath === endPath && parentIsOptionalCall && parent.optional) {
|
||||
if (
|
||||
parentPath === endPath &&
|
||||
isOptionalCall(parent) &&
|
||||
parent.optional
|
||||
) {
|
||||
regular = parentPath.node;
|
||||
break;
|
||||
}
|
||||
@ -221,8 +236,8 @@ const handle = {
|
||||
current = parentPath;
|
||||
}
|
||||
|
||||
let context;
|
||||
const endParentPath = endPath.parentPath;
|
||||
let context: t.Identifier;
|
||||
const endParentPath = endPath.parentPath as NodePath<t.Expression>;
|
||||
if (
|
||||
t.isMemberExpression(regular) &&
|
||||
endParentPath.isOptionalCallExpression({
|
||||
@ -237,7 +252,7 @@ const handle = {
|
||||
}
|
||||
}
|
||||
|
||||
let replacementPath = endPath;
|
||||
let replacementPath: NodePath = endPath;
|
||||
if (isDeleteOperation) {
|
||||
replacementPath = endParentPath;
|
||||
regular = endParentPath.node;
|
||||
@ -306,7 +321,7 @@ const handle = {
|
||||
|
||||
// context and isDeleteOperation can not be both truthy
|
||||
if (context) {
|
||||
const endParent = endParentPath.node;
|
||||
const endParent = endParentPath.node as t.OptionalCallExpression;
|
||||
endParentPath.replaceWith(
|
||||
t.optionalCallExpression(
|
||||
t.optionalMemberExpression(
|
||||
@ -326,7 +341,7 @@ const handle = {
|
||||
|
||||
// MEMBER++ -> _set(MEMBER, (_ref = (+_get(MEMBER))) + 1), _ref
|
||||
// ++MEMBER -> _set(MEMBER, (+_get(MEMBER)) + 1)
|
||||
if (parentPath.isUpdateExpression({ argument: node })) {
|
||||
if (t.isUpdateExpression(parent, { argument: node })) {
|
||||
if (this.simpleSet) {
|
||||
member.replaceWith(this.simpleSet(member));
|
||||
return;
|
||||
@ -340,7 +355,7 @@ const handle = {
|
||||
this.memoise(member, 2);
|
||||
|
||||
const value = t.binaryExpression(
|
||||
operator[0],
|
||||
operator[0] as "+" | "-",
|
||||
t.unaryExpression("+", this.get(member)),
|
||||
t.numericLiteral(1),
|
||||
);
|
||||
@ -375,7 +390,7 @@ const handle = {
|
||||
return;
|
||||
}
|
||||
|
||||
const { operator, right: value } = parent;
|
||||
const { operator, right: value } = parentPath.node;
|
||||
|
||||
if (operator === "=") {
|
||||
parentPath.replaceWith(this.set(member, value));
|
||||
@ -388,7 +403,7 @@ const handle = {
|
||||
this.memoise(member, 1);
|
||||
parentPath.replaceWith(
|
||||
t.logicalExpression(
|
||||
operatorTrunc,
|
||||
operatorTrunc as t.LogicalExpression["operator"],
|
||||
this.get(member),
|
||||
this.set(member, value),
|
||||
),
|
||||
@ -399,7 +414,11 @@ const handle = {
|
||||
parentPath.replaceWith(
|
||||
this.set(
|
||||
member,
|
||||
t.binaryExpression(operatorTrunc, this.get(member), value),
|
||||
t.binaryExpression(
|
||||
operatorTrunc as t.BinaryExpression["operator"],
|
||||
this.get(member),
|
||||
value,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
@ -409,7 +428,7 @@ const handle = {
|
||||
|
||||
// MEMBER(ARGS) -> _call(MEMBER, ARGS)
|
||||
if (parentPath.isCallExpression({ callee: node })) {
|
||||
parentPath.replaceWith(this.call(member, parent.arguments));
|
||||
parentPath.replaceWith(this.call(member, parentPath.node.arguments));
|
||||
return;
|
||||
}
|
||||
|
||||
@ -425,7 +444,9 @@ const handle = {
|
||||
);
|
||||
return;
|
||||
}
|
||||
parentPath.replaceWith(this.optionalCall(member, parent.arguments));
|
||||
parentPath.replaceWith(
|
||||
this.optionalCall(member, parentPath.node.arguments),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -472,6 +493,45 @@ const handle = {
|
||||
},
|
||||
};
|
||||
|
||||
export interface Handler<State> {
|
||||
memoise?(
|
||||
this: HandlerState<State> & State,
|
||||
member: Member,
|
||||
count: number,
|
||||
): void;
|
||||
destructureSet(
|
||||
this: HandlerState<State> & State,
|
||||
member: Member,
|
||||
): t.Expression;
|
||||
boundGet(this: HandlerState<State> & State, member: Member): t.Expression;
|
||||
simpleSet?(this: HandlerState<State> & State, member: Member): t.Expression;
|
||||
get(this: HandlerState<State> & State, member: Member): t.Expression;
|
||||
set(
|
||||
this: HandlerState<State> & State,
|
||||
member: Member,
|
||||
value: t.Expression,
|
||||
): t.Expression;
|
||||
call(
|
||||
this: HandlerState<State> & State,
|
||||
member: Member,
|
||||
args: t.CallExpression["arguments"],
|
||||
): t.Expression;
|
||||
optionalCall(
|
||||
this: HandlerState<State> & State,
|
||||
member: Member,
|
||||
args: t.OptionalCallExpression["arguments"],
|
||||
): t.Expression;
|
||||
}
|
||||
|
||||
export interface HandlerState<State = {}> extends Handler<State> {
|
||||
handle(
|
||||
this: HandlerState<State> & State,
|
||||
member: Member,
|
||||
noDocumentAll: boolean,
|
||||
): void;
|
||||
memoiser: AssignmentMemoiser;
|
||||
}
|
||||
|
||||
// We do not provide a default traversal visitor
|
||||
// Instead, caller passes one, and must call `state.handle` on the members
|
||||
// it wishes to be transformed.
|
||||
@ -479,7 +539,11 @@ const handle = {
|
||||
// get, set, and call methods.
|
||||
// Optionally, a memoise method may be defined on the state, which will be
|
||||
// called when the member is a self-referential update.
|
||||
export default function memberExpressionToFunctions(path, visitor, state) {
|
||||
export default function memberExpressionToFunctions<CustomState = {}>(
|
||||
path: NodePath,
|
||||
visitor: Visitor<HandlerState<CustomState>>,
|
||||
state: Handler<CustomState> & CustomState,
|
||||
) {
|
||||
path.traverse(visitor, {
|
||||
...handle,
|
||||
...state,
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import type { HubInterface, NodePath } from "@babel/traverse";
|
||||
import type { HubInterface, NodePath, Scope } from "@babel/traverse";
|
||||
import traverse from "@babel/traverse";
|
||||
import memberExpressionToFunctions from "@babel/helper-member-expression-to-functions";
|
||||
import optimiseCall from "@babel/helper-optimise-call-expression";
|
||||
@ -271,6 +271,16 @@ type ReplaceSupersOptions = ReplaceSupersOptionsBase &
|
||||
| { superRef: t.Node; getSuperRef?: undefined }
|
||||
);
|
||||
|
||||
interface ReplaceState {
|
||||
file: unknown;
|
||||
scope: Scope;
|
||||
isDerivedConstructor: boolean;
|
||||
isStatic: boolean;
|
||||
isPrivateMethod: boolean;
|
||||
getObjectRef: ReplaceSupers["getObjectRef"];
|
||||
getSuperRef: ReplaceSupers["getSuperRef"];
|
||||
}
|
||||
|
||||
export default class ReplaceSupers {
|
||||
constructor(opts: ReplaceSupersOptions) {
|
||||
const path = opts.methodPath;
|
||||
@ -317,7 +327,7 @@ export default class ReplaceSupers {
|
||||
|
||||
const handler = this.constantSuper ? looseHandlers : specHandlers;
|
||||
|
||||
memberExpressionToFunctions(this.methodPath, visitor, {
|
||||
memberExpressionToFunctions<ReplaceState>(this.methodPath, visitor, {
|
||||
file: this.file,
|
||||
scope: this.methodPath.scope,
|
||||
isDerivedConstructor: this.isDerivedConstructor,
|
||||
@ -325,6 +335,8 @@ export default class ReplaceSupers {
|
||||
isPrivateMethod: this.isPrivateMethod,
|
||||
getObjectRef: this.getObjectRef.bind(this),
|
||||
getSuperRef: this.getSuperRef.bind(this),
|
||||
// we dont need boundGet here, but memberExpressionToFunctions handler needs it.
|
||||
boundGet: handler.get,
|
||||
...handler,
|
||||
});
|
||||
}
|
||||
|
||||
@ -1,3 +1,3 @@
|
||||
{
|
||||
"plugins": ["proposal-class-properties", "transform-block-scoping"]
|
||||
"plugins": ["proposal-class-properties", "proposal-private-methods", "transform-block-scoping"]
|
||||
}
|
||||
|
||||
@ -3,10 +3,10 @@ import * as visitors from "./visitors";
|
||||
import * as t from "@babel/types";
|
||||
import * as cache from "./cache";
|
||||
import type NodePath from "./path";
|
||||
import type Scope from "./scope";
|
||||
import type { default as Scope, Binding } from "./scope";
|
||||
import type { Visitor } from "./types";
|
||||
|
||||
export type { Visitor };
|
||||
export type { Visitor, Binding };
|
||||
export { default as NodePath } from "./path";
|
||||
export { default as Scope } from "./scope";
|
||||
export { default as Hub } from "./hub";
|
||||
|
||||
@ -317,6 +317,8 @@ const collectorVisitor: Visitor<CollectVisitorState> = {
|
||||
|
||||
let uid = 0;
|
||||
|
||||
export type { Binding };
|
||||
|
||||
export default class Scope {
|
||||
uid;
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user