Skip to content

Commit

Permalink
improve helper-create-class-features typings (#14530)
Browse files Browse the repository at this point in the history
  • Loading branch information
JLHwung committed May 17, 2022
1 parent 9156230 commit 4d12808
Show file tree
Hide file tree
Showing 11 changed files with 160 additions and 72 deletions.
8 changes: 4 additions & 4 deletions packages/babel-core/src/config/validation/plugins.ts
Expand Up @@ -13,8 +13,8 @@ import type {
} from "./option-assertions";
import type { ParserOptions } from "@babel/parser";
import type { Visitor } from "@babel/traverse";
import type PluginPass from "../../transformation/plugin-pass";
import type { ValidatedOptions } from "./options";
import type { File, PluginPass } from "../../..";

// Note: The casts here are just meant to be static assertions to make sure
// that the assertion functions actually assert that the value's type matches
Expand Down Expand Up @@ -78,14 +78,14 @@ type VisitorHandler =
exit?: Function;
};

export type PluginObject<S = PluginPass> = {
export type PluginObject<S extends PluginPass = PluginPass> = {
name?: string;
manipulateOptions?: (
options: ValidatedOptions,
parserOpts: ParserOptions,
) => void;
pre?: Function;
post?: Function;
pre?: (this: S, file: File) => void;
post?: (this: S, file: File) => void;
inherits?: Function;
visitor?: Visitor<S>;
parserOverride?: Function;
Expand Down
4 changes: 2 additions & 2 deletions packages/babel-core/src/transformation/index.ts
Expand Up @@ -3,7 +3,7 @@ import type * as t from "@babel/types";
type SourceMap = any;
import type { Handler } from "gensync";

import type { ResolvedConfig, PluginPasses } from "../config";
import type { ResolvedConfig, Plugin, PluginPasses } from "../config";

import PluginPass from "./plugin-pass";
import loadBlockHoistPlugin from "./block-hoist-plugin";
Expand Down Expand Up @@ -79,7 +79,7 @@ export function* run(

function* transformFile(file: File, pluginPasses: PluginPasses): Handler<void> {
for (const pluginPairs of pluginPasses) {
const passPairs = [];
const passPairs: [Plugin, PluginPass][] = [];
const passes = [];
const visitors = [];

Expand Down
Expand Up @@ -40,51 +40,70 @@ function takeDecorators(node: Decorable) {
return result;
}

function getKey(node) {
type AcceptedElement = Exclude<ClassElement, t.TSIndexSignature>;
type SupportedElement = Exclude<
AcceptedElement,
| t.ClassPrivateMethod
| t.ClassPrivateProperty
| t.ClassAccessorProperty
| t.StaticBlock
>;

function getKey(node: SupportedElement) {
if (node.computed) {
return node.key;
} else if (t.isIdentifier(node.key)) {
return t.stringLiteral(node.key.name);
} else {
return t.stringLiteral(String(node.key.value));
return t.stringLiteral(
String(
// A non-identifier non-computed key
(node.key as t.StringLiteral | t.NumericLiteral | t.BigIntLiteral)
.value,
),
);
}
}

// 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,
file: File,
classRef: t.Identifier,
superRef: t.Identifier,
path: ClassElementPath,
path: NodePath<AcceptedElement>,
) {
const { node, scope } = path;
const isMethod = path.isClassMethod();

if (path.isPrivate()) {
throw path.buildCodeFrameError(
`Private ${
isMethod ? "methods" : "fields"
} in decorated classes are not supported yet.`,
);
}
if (path.node.type === "ClassAccessorProperty") {
throw path.buildCodeFrameError(
`Accessor properties are not supported in 2018-09 decorator transform, please specify { "version": "2021-12" } instead.`,
);
}
if (path.node.type === "StaticBlock") {
throw path.buildCodeFrameError(
`Static blocks are not supported in 2018-09 decorator transform, please specify { "version": "2021-12" } instead.`,
);
}

const { node, scope } = path as NodePath<SupportedElement>;

new ReplaceSupers({
methodPath: path,
objectRef: classRef,
superRef,
file: this,
file,
refToPreserve: classRef,
}).replace();

const properties: t.ObjectExpression["properties"] = [
prop("kind", t.stringLiteral(t.isClassMethod(node) ? node.kind : "field")),
prop("decorators", takeDecorators(node as Decorable)),
prop(
"static",
// @ts-expect-error: TS doesn't infer that node is not a StaticBlock
!t.isStaticBlock?.(node) && node.static && t.booleanLiteral(true),
),
prop("static", node.static && t.booleanLiteral(true)),
prop("key", getKey(node)),
].filter(Boolean);

Expand Down Expand Up @@ -146,9 +165,20 @@ export function buildDecoratedClass(
const classDecorators = takeDecorators(node);
const definitions = t.arrayExpression(
elements
// @ts-expect-error Ignore TypeScript's abstract methods (see #10514)
.filter(element => !element.node.abstract)
.map(extractElementDescriptor.bind(file, node.id, superId)),
.filter(
element =>
// @ts-expect-error Ignore TypeScript's abstract methods (see #10514)
!element.node.abstract && element.node.type !== "TSIndexSignature",
)
.map(path =>
extractElementDescriptor(
file,
node.id,
superId,
// @ts-expect-error TS can not exclude TSIndexSignature
path,
),
),
);

const wrapperCall = template.expression.ast`
Expand Down
25 changes: 16 additions & 9 deletions packages/babel-helper-create-class-features-plugin/src/fields.ts
Expand Up @@ -850,17 +850,19 @@ function buildPrivateMethodDeclaration(
);
}

const thisContextVisitor = traverse.visitors.merge<{
type ReplaceThisState = {
classRef: t.Identifier;
needsClassRef: boolean;
innerBinding: t.Identifier;
}>([
innerBinding: t.Identifier | null;
};

const thisContextVisitor = traverse.visitors.merge<ReplaceThisState>([
{
ThisExpression(path, state) {
state.needsClassRef = true;
path.replaceWith(t.cloneNode(state.classRef));
},
MetaProperty(path: NodePath<t.MetaProperty>) {
MetaProperty(path) {
const meta = path.get("meta");
const property = path.get("property");
const { scope } = path;
Expand All @@ -877,8 +879,8 @@ const thisContextVisitor = traverse.visitors.merge<{
environmentVisitor,
]);

const innerReferencesVisitor = {
ReferencedIdentifier(path: NodePath<t.Identifier>, state) {
const innerReferencesVisitor: Visitor<ReplaceThisState> = {
ReferencedIdentifier(path, state) {
if (
path.scope.bindingIdentifierEquals(path.node.name, state.innerBinding)
) {
Expand All @@ -895,9 +897,9 @@ function replaceThisContext(
file: File,
isStaticBlock: boolean,
constantSuper: boolean,
innerBindingRef: t.Identifier,
innerBindingRef: t.Identifier | null,
) {
const state = {
const state: ReplaceThisState = {
classRef: ref,
needsClassRef: false,
innerBinding: innerBindingRef,
Expand All @@ -922,7 +924,12 @@ function replaceThisContext(
path.traverse(thisContextVisitor, state);
}

if (state.classRef?.name && state.classRef.name !== innerBindingRef?.name) {
// todo: use innerBinding.referencePaths to avoid full traversal
if (
innerBindingRef != null &&
state.classRef?.name &&
state.classRef.name !== innerBindingRef?.name
) {
path.traverse(innerReferencesVisitor, state);
}

Expand Down
34 changes: 17 additions & 17 deletions packages/babel-helper-create-class-features-plugin/src/index.ts
@@ -1,5 +1,5 @@
import { types as t } from "@babel/core";
import type { File, PluginAPI, PluginObject } from "@babel/core";
import type { PluginAPI, PluginObject } from "@babel/core";
import type { NodePath } from "@babel/traverse";
import nameFunction from "@babel/helper-function-name";
import splitExportDeclaration from "@babel/helper-split-export-declaration";
Expand Down Expand Up @@ -45,7 +45,7 @@ export function createClassFeaturePlugin({
// @ts-ignore TODO(Babel 8): Remove the default value
api = { assumption: () => void 0 },
inherits,
}: Options) {
}: Options): PluginObject {
const setPublicClassFields = api.assumption("setPublicClassFields");
const privateFieldsAsProperties = api.assumption("privateFieldsAsProperties");
const constantSuper = api.assumption("constantSuper");
Expand Down Expand Up @@ -81,23 +81,23 @@ export function createClassFeaturePlugin({
manipulateOptions,
inherits,

pre() {
enableFeature(this.file, feature, loose);
pre(file) {
enableFeature(file, feature, loose);

if (!this.file.get(versionKey) || this.file.get(versionKey) < version) {
this.file.set(versionKey, version);
if (!file.get(versionKey) || file.get(versionKey) < version) {
file.set(versionKey, version);
}
},

visitor: {
Class(path: NodePath<t.Class>, state: File) {
if (this.file.get(versionKey) !== version) return;
Class(path, { file }) {
if (file.get(versionKey) !== version) return;

if (!shouldTransform(path, this.file)) return;
if (!shouldTransform(path, file)) return;

if (path.isClassDeclaration()) assertFieldTransformed(path);

const loose = isLoose(this.file, feature);
const loose = isLoose(file, feature);

let constructor: NodePath<t.ClassMethod>;
const isDecorated = hasDecorators(path.node);
Expand Down Expand Up @@ -186,7 +186,7 @@ export function createClassFeaturePlugin({
const privateNamesNodes = buildPrivateNamesNodes(
privateNamesMap,
(privateFieldsAsProperties ?? loose) as boolean,
state,
file,
);

transformPrivateNamesUsage(
Expand All @@ -198,7 +198,7 @@ export function createClassFeaturePlugin({
noDocumentAll,
innerBinding,
},
state,
file,
);

let keysNodes: t.Statement[],
Expand All @@ -213,17 +213,17 @@ export function createClassFeaturePlugin({
ref,
path,
elements,
this.file,
file,
));
} else {
keysNodes = extractComputedKeys(ref, path, computedPaths, this.file);
keysNodes = extractComputedKeys(path, computedPaths, file);
({ staticNodes, pureStaticNodes, instanceNodes, wrapClass } =
buildFieldsInitNodes(
ref,
path.node.superClass,
props,
privateNamesMap,
state,
file,
(setPublicClassFields ?? loose) as boolean,
(privateFieldsAsProperties ?? loose) as boolean,
(constantSuper ?? loose) as boolean,
Expand Down Expand Up @@ -260,8 +260,8 @@ export function createClassFeaturePlugin({
}
},

ExportDefaultDeclaration(path: NodePath<t.ExportDefaultDeclaration>) {
if (this.file.get(versionKey) !== version) return;
ExportDefaultDeclaration(path, { file }) {
if (file.get(versionKey) !== version) return;

const decl = path.get("declaration");

Expand Down
32 changes: 18 additions & 14 deletions packages/babel-helper-create-class-features-plugin/src/misc.ts
Expand Up @@ -5,7 +5,7 @@ import environmentVisitor from "@babel/helper-environment-visitor";

const findBareSupers = traverse.visitors.merge<NodePath<t.CallExpression>[]>([
{
Super(path: NodePath<t.Super>) {
Super(path) {
const { node, parentPath } = path;
if (parentPath.isCallExpression({ callee: node })) {
this.push(parentPath);
Expand All @@ -15,24 +15,29 @@ const findBareSupers = traverse.visitors.merge<NodePath<t.CallExpression>[]>([
environmentVisitor,
]);

const referenceVisitor = {
"TSTypeAnnotation|TypeAnnotation"(path: NodePath) {
const referenceVisitor: Visitor<{ scope: Scope }> = {
"TSTypeAnnotation|TypeAnnotation"(
path: NodePath<t.TSTypeAnnotation | t.TypeAnnotation>,
) {
path.skip();
},

ReferencedIdentifier(path: NodePath<t.Identifier>) {
if (this.scope.hasOwnBinding(path.node.name)) {
this.scope.rename(path.node.name);
ReferencedIdentifier(path: NodePath<t.Identifier>, { scope }) {
if (scope.hasOwnBinding(path.node.name)) {
scope.rename(path.node.name);
path.skip();
}
},
};

type HandleClassTDZState = {
classBinding: Binding;
file: File;
};

function handleClassTDZ(
path: NodePath<t.Identifier>,
state: {
classBinding: Binding;
file: File;
},
state: HandleClassTDZState,
) {
if (
state.classBinding &&
Expand All @@ -48,7 +53,7 @@ function handleClassTDZ(
}
}

const classFieldDefinitionEvaluationTDZVisitor = {
const classFieldDefinitionEvaluationTDZVisitor: Visitor<HandleClassTDZState> = {
ReferencedIdentifier: handleClassTDZ,
};

Expand Down Expand Up @@ -89,7 +94,7 @@ export function injectInitialization(
}

if (isDerived) {
const bareSupers = [];
const bareSupers: NodePath<t.CallExpression>[] = [];
constructor.traverse(findBareSupers, bareSupers);
let isFirst = true;
for (const bareSuper of bareSupers) {
Expand All @@ -106,12 +111,11 @@ export function injectInitialization(
}

export function extractComputedKeys(
ref: t.Identifier,
path: NodePath<t.Class>,
computedPaths: NodePath<t.ClassProperty | t.ClassMethod>[],
file: File,
) {
const declarations: t.Statement[] = [];
const declarations: t.ExpressionStatement[] = [];
const state = {
classBinding: path.node.id && path.scope.getBinding(path.node.id.name),
file,
Expand Down
@@ -0,0 +1,3 @@
@f class C {
accessor x;
}

0 comments on commit 4d12808

Please sign in to comment.