diff --git a/packages/babel-core/src/config/validation/plugins.ts b/packages/babel-core/src/config/validation/plugins.ts index ff7565cafcc8..9364758f153b 100644 --- a/packages/babel-core/src/config/validation/plugins.ts +++ b/packages/babel-core/src/config/validation/plugins.ts @@ -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 @@ -78,14 +78,14 @@ type VisitorHandler = exit?: Function; }; -export type PluginObject = { +export type PluginObject = { 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; parserOverride?: Function; diff --git a/packages/babel-core/src/transformation/index.ts b/packages/babel-core/src/transformation/index.ts index 2375dc128a9c..4d83285e9735 100644 --- a/packages/babel-core/src/transformation/index.ts +++ b/packages/babel-core/src/transformation/index.ts @@ -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"; @@ -79,7 +79,7 @@ export function* run( function* transformFile(file: File, pluginPasses: PluginPasses): Handler { for (const pluginPairs of pluginPasses) { - const passPairs = []; + const passPairs: [Plugin, PluginPass][] = []; const passes = []; const visitors = []; diff --git a/packages/babel-helper-create-class-features-plugin/src/decorators.ts b/packages/babel-helper-create-class-features-plugin/src/decorators.ts index 53b7124461c5..bb80db63a7db 100644 --- a/packages/babel-helper-create-class-features-plugin/src/decorators.ts +++ b/packages/babel-helper-create-class-features-plugin/src/decorators.ts @@ -40,27 +40,38 @@ function takeDecorators(node: Decorable) { return result; } -function getKey(node) { +type AcceptedElement = Exclude; +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, ) { - const { node, scope } = path; const isMethod = path.isClassMethod(); - if (path.isPrivate()) { throw path.buildCodeFrameError( `Private ${ @@ -68,23 +79,31 @@ function extractElementDescriptor( } 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; 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); @@ -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` diff --git a/packages/babel-helper-create-class-features-plugin/src/fields.ts b/packages/babel-helper-create-class-features-plugin/src/fields.ts index 3beac3c2cd95..d938e0a9ce7a 100644 --- a/packages/babel-helper-create-class-features-plugin/src/fields.ts +++ b/packages/babel-helper-create-class-features-plugin/src/fields.ts @@ -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([ { ThisExpression(path, state) { state.needsClassRef = true; path.replaceWith(t.cloneNode(state.classRef)); }, - MetaProperty(path: NodePath) { + MetaProperty(path) { const meta = path.get("meta"); const property = path.get("property"); const { scope } = path; @@ -877,8 +879,8 @@ const thisContextVisitor = traverse.visitors.merge<{ environmentVisitor, ]); -const innerReferencesVisitor = { - ReferencedIdentifier(path: NodePath, state) { +const innerReferencesVisitor: Visitor = { + ReferencedIdentifier(path, state) { if ( path.scope.bindingIdentifierEquals(path.node.name, state.innerBinding) ) { @@ -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, @@ -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); } diff --git a/packages/babel-helper-create-class-features-plugin/src/index.ts b/packages/babel-helper-create-class-features-plugin/src/index.ts index ffa7ed27a82c..4d234b121916 100644 --- a/packages/babel-helper-create-class-features-plugin/src/index.ts +++ b/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"; @@ -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"); @@ -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, 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; const isDecorated = hasDecorators(path.node); @@ -186,7 +186,7 @@ export function createClassFeaturePlugin({ const privateNamesNodes = buildPrivateNamesNodes( privateNamesMap, (privateFieldsAsProperties ?? loose) as boolean, - state, + file, ); transformPrivateNamesUsage( @@ -198,7 +198,7 @@ export function createClassFeaturePlugin({ noDocumentAll, innerBinding, }, - state, + file, ); let keysNodes: t.Statement[], @@ -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, @@ -260,8 +260,8 @@ export function createClassFeaturePlugin({ } }, - ExportDefaultDeclaration(path: NodePath) { - if (this.file.get(versionKey) !== version) return; + ExportDefaultDeclaration(path, { file }) { + if (file.get(versionKey) !== version) return; const decl = path.get("declaration"); diff --git a/packages/babel-helper-create-class-features-plugin/src/misc.ts b/packages/babel-helper-create-class-features-plugin/src/misc.ts index 3bdbc11baaa8..9352f69dad1b 100644 --- a/packages/babel-helper-create-class-features-plugin/src/misc.ts +++ b/packages/babel-helper-create-class-features-plugin/src/misc.ts @@ -5,7 +5,7 @@ import environmentVisitor from "@babel/helper-environment-visitor"; const findBareSupers = traverse.visitors.merge[]>([ { - Super(path: NodePath) { + Super(path) { const { node, parentPath } = path; if (parentPath.isCallExpression({ callee: node })) { this.push(parentPath); @@ -15,24 +15,29 @@ const findBareSupers = traverse.visitors.merge[]>([ environmentVisitor, ]); -const referenceVisitor = { - "TSTypeAnnotation|TypeAnnotation"(path: NodePath) { +const referenceVisitor: Visitor<{ scope: Scope }> = { + "TSTypeAnnotation|TypeAnnotation"( + path: NodePath, + ) { path.skip(); }, - ReferencedIdentifier(path: NodePath) { - if (this.scope.hasOwnBinding(path.node.name)) { - this.scope.rename(path.node.name); + ReferencedIdentifier(path: NodePath, { scope }) { + if (scope.hasOwnBinding(path.node.name)) { + scope.rename(path.node.name); path.skip(); } }, }; + +type HandleClassTDZState = { + classBinding: Binding; + file: File; +}; + function handleClassTDZ( path: NodePath, - state: { - classBinding: Binding; - file: File; - }, + state: HandleClassTDZState, ) { if ( state.classBinding && @@ -48,7 +53,7 @@ function handleClassTDZ( } } -const classFieldDefinitionEvaluationTDZVisitor = { +const classFieldDefinitionEvaluationTDZVisitor: Visitor = { ReferencedIdentifier: handleClassTDZ, }; @@ -89,7 +94,7 @@ export function injectInitialization( } if (isDerived) { - const bareSupers = []; + const bareSupers: NodePath[] = []; constructor.traverse(findBareSupers, bareSupers); let isFirst = true; for (const bareSuper of bareSupers) { @@ -106,12 +111,11 @@ export function injectInitialization( } export function extractComputedKeys( - ref: t.Identifier, path: NodePath, computedPaths: NodePath[], file: File, ) { - const declarations: t.Statement[] = []; + const declarations: t.ExpressionStatement[] = []; const state = { classBinding: path.node.id && path.scope.getBinding(path.node.id.name), file, diff --git a/packages/babel-plugin-proposal-decorators/test/fixtures/2018-09-integrations/class-accessor-property/input.js b/packages/babel-plugin-proposal-decorators/test/fixtures/2018-09-integrations/class-accessor-property/input.js new file mode 100644 index 000000000000..f972cefc58dc --- /dev/null +++ b/packages/babel-plugin-proposal-decorators/test/fixtures/2018-09-integrations/class-accessor-property/input.js @@ -0,0 +1,3 @@ +@f class C { + accessor x; +} diff --git a/packages/babel-plugin-proposal-decorators/test/fixtures/2018-09-integrations/class-accessor-property/options.json b/packages/babel-plugin-proposal-decorators/test/fixtures/2018-09-integrations/class-accessor-property/options.json new file mode 100644 index 000000000000..d453a9ad73fd --- /dev/null +++ b/packages/babel-plugin-proposal-decorators/test/fixtures/2018-09-integrations/class-accessor-property/options.json @@ -0,0 +1,12 @@ +{ + "plugins": [ + [ + "proposal-decorators", + { "decoratorsBeforeExport": false, "version": "2018-09" } + ] + ], + "parserOpts": { + "plugins": ["decoratorAutoAccessors"] + }, + "throws": "Accessor properties are not supported in 2018-09 decorator transform, please specify { \"version\": \"2021-12\" } instead." +} diff --git a/packages/babel-plugin-proposal-decorators/test/fixtures/2018-09-integrations/static-block/input.js b/packages/babel-plugin-proposal-decorators/test/fixtures/2018-09-integrations/static-block/input.js new file mode 100644 index 000000000000..862312a75c2c --- /dev/null +++ b/packages/babel-plugin-proposal-decorators/test/fixtures/2018-09-integrations/static-block/input.js @@ -0,0 +1,3 @@ +@f class C { + static {} +} diff --git a/packages/babel-plugin-proposal-decorators/test/fixtures/2018-09-integrations/static-block/options.json b/packages/babel-plugin-proposal-decorators/test/fixtures/2018-09-integrations/static-block/options.json new file mode 100644 index 000000000000..6316387da387 --- /dev/null +++ b/packages/babel-plugin-proposal-decorators/test/fixtures/2018-09-integrations/static-block/options.json @@ -0,0 +1,10 @@ +{ + "plugins": [ + [ + "proposal-decorators", + { "decoratorsBeforeExport": false, "version": "2018-09" } + ], + "proposal-class-static-block" + ], + "throws": "Static blocks are not supported in 2018-09 decorator transform, please specify { \"version\": \"2021-12\" } instead." +} diff --git a/packages/babel-plugin-transform-modules-systemjs/src/index.ts b/packages/babel-plugin-transform-modules-systemjs/src/index.ts index 1a4657f16ef3..c234cf87600a 100644 --- a/packages/babel-plugin-transform-modules-systemjs/src/index.ts +++ b/packages/babel-plugin-transform-modules-systemjs/src/index.ts @@ -5,7 +5,7 @@ import { getImportSource } from "babel-plugin-dynamic-import-node/utils"; import { rewriteThis, getModuleName } from "@babel/helper-module-transforms"; import type { PluginOptions } from "@babel/helper-module-transforms"; import { isIdentifierName } from "@babel/helper-validator-identifier"; -import type { NodePath } from "@babel/traverse"; +import type { NodePath, Scope, Visitor } from "@babel/traverse"; const buildTemplate = template.statement(` SYSTEM_REGISTER(MODULE_NAME, SOURCES, function (EXPORT_IDENTIFIER, CONTEXT_IDENTIFIER) { @@ -160,21 +160,31 @@ export interface Options extends PluginOptions { systemGlobal?: string; } -export default declare((api, options: Options) => { +type ReassignmentVisitorState = { + scope: Scope; + exports: any; + buildCall: (name: string, value: t.Expression) => t.ExpressionStatement; +}; + +export default declare((api, options: Options) => { api.assertVersion(7); const { systemGlobal = "System", allowTopLevelThis = false } = options; const IGNORE_REASSIGNMENT_SYMBOL = Symbol(); - const reassignmentVisitor = { - "AssignmentExpression|UpdateExpression"(path) { + const reassignmentVisitor: Visitor = { + "AssignmentExpression|UpdateExpression"( + path: NodePath, + ) { if (path.node[IGNORE_REASSIGNMENT_SYMBOL]) return; path.node[IGNORE_REASSIGNMENT_SYMBOL] = true; - const arg = path.get(path.isAssignmentExpression() ? "left" : "argument"); + const arg = path.isAssignmentExpression() + ? path.get("left") + : path.get("argument"); if (arg.isObjectPattern() || arg.isArrayPattern()) { - const exprs = [path.node]; + const exprs: t.SequenceExpression["expressions"] = [path.node]; for (const name of Object.keys(arg.getBindingIdentifiers())) { if (this.scope.getBinding(name) !== path.scope.getBinding(name)) { return; @@ -201,16 +211,25 @@ export default declare((api, options: Options) => { const exportedNames = this.exports[name]; if (!exportedNames) return; - let node = path.node; + let node: t.Expression = path.node; // if it is a non-prefix update expression (x++ etc) // then we must replace with the expression (_export('x', x + 1), x++) // in order to ensure the same update expression value - const isPostUpdateExpression = path.isUpdateExpression({ prefix: false }); + const isPostUpdateExpression = t.isUpdateExpression(node, { + prefix: false, + }); if (isPostUpdateExpression) { node = t.binaryExpression( + // @ts-expect-error node.operator[0], - t.unaryExpression("+", t.cloneNode(node.argument)), + t.unaryExpression( + "+", + t.cloneNode( + // @ts-ignore node is UpdateExpression + node.argument, + ), + ), t.numericLiteral(1), ); }