diff --git a/packages/babel-core/src/config/validation/options.ts b/packages/babel-core/src/config/validation/options.ts index b30b8be2015e..3c554e06a6ca 100644 --- a/packages/babel-core/src/config/validation/options.ts +++ b/packages/babel-core/src/config/validation/options.ts @@ -281,6 +281,7 @@ const knownAssumptions = [ "noDocumentAll", "noIncompleteNsImportDetection", "noNewArrows", + "noUninitializedPrivateFieldAccess", "objectRestNoSymbols", "privateFieldsAsSymbols", "privateFieldsAsProperties", 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 dbc480ceee44..1877f6ec1015 100644 --- a/packages/babel-helper-create-class-features-plugin/src/fields.ts +++ b/packages/babel-helper-create-class-features-plugin/src/fields.ts @@ -167,9 +167,7 @@ export function privateNameVisitorFactory( // Traverses the outer portion of a class, without touching the class's inner // scope, for private names. const nestedVisitor = traverse.visitors.merge([ - { - ...visitor, - }, + { ...visitor }, environmentVisitor, ]); @@ -224,6 +222,7 @@ interface PrivateNameState { classRef: t.Identifier; file: File; noDocumentAll: boolean; + noUninitializedPrivateFieldAccess: boolean; innerBinding?: t.Identifier; } @@ -361,6 +360,14 @@ function writeOnlyError(file: File, name: string) { ]); } +function buildStaticPrivateFieldAccess( + expr: N, + noUninitializedPrivateFieldAccess: boolean, +) { + if (noUninitializedPrivateFieldAccess) return expr; + return t.memberExpression(expr, t.identifier("_")); +} + const privateNameHandlerSpec: Handler & Receiver = { memoise(member, count) { @@ -386,7 +393,13 @@ const privateNameHandlerSpec: Handler & Receiver = }, get(member) { - const { classRef, privateNamesMap, file, innerBinding } = this; + const { + classRef, + privateNamesMap, + file, + innerBinding, + noUninitializedPrivateFieldAccess, + } = this; const { name } = (member.node.property as t.PrivateName).id; const { id, @@ -424,16 +437,19 @@ const privateNameHandlerSpec: Handler & Receiver = if (!isMethod) { if (skipCheck) { - return t.memberExpression(t.cloneNode(id), t.identifier("_")); + return buildStaticPrivateFieldAccess( + t.cloneNode(id), + noUninitializedPrivateFieldAccess, + ); } - return t.memberExpression( + return buildStaticPrivateFieldAccess( t.callExpression(file.addHelper("assertClassBrand"), [ receiver, t.cloneNode(classRef), t.cloneNode(id), ]), - t.identifier("_"), + noUninitializedPrivateFieldAccess, ); } @@ -515,7 +531,12 @@ const privateNameHandlerSpec: Handler & Receiver = }, set(member, value) { - const { classRef, privateNamesMap, file } = this; + const { + classRef, + privateNamesMap, + file, + noUninitializedPrivateFieldAccess, + } = this; const { name } = (member.node.property as t.PrivateName).id; const { id, @@ -574,7 +595,10 @@ const privateNameHandlerSpec: Handler & Receiver = } return t.assignmentExpression( "=", - t.memberExpression(t.cloneNode(id), t.identifier("_")), + buildStaticPrivateFieldAccess( + t.cloneNode(id), + noUninitializedPrivateFieldAccess, + ), skipCheck ? value : t.callExpression(file.addHelper("assertClassBrand"), [ @@ -615,7 +639,12 @@ const privateNameHandlerSpec: Handler & Receiver = }, destructureSet(member) { - const { classRef, privateNamesMap, file } = this; + const { + classRef, + privateNamesMap, + file, + noUninitializedPrivateFieldAccess, + } = this; const { name } = (member.node.property as t.PrivateName).id; const { id, @@ -669,7 +698,19 @@ const privateNameHandlerSpec: Handler & Receiver = } if (isStatic && !isMethod) { - return this.get(member); + const getCall = this.get(member); + if ( + !noUninitializedPrivateFieldAccess || + !t.isCallExpression(getCall) + ) { + return getCall; + } + const ref = getCall.arguments.pop(); + getCall.arguments.push(template.expression.ast`(_) => ${ref} = _`); + return t.memberExpression( + t.callExpression(file.addHelper("toSetter"), [getCall]), + t.identifier("_"), + ); } const setCall = this.set(member, t.identifier("_")); @@ -793,10 +834,12 @@ export function transformPrivateNamesUsage( privateNamesMap: PrivateNamesMap, { privateFieldsAsProperties, + noUninitializedPrivateFieldAccess, noDocumentAll, innerBinding, }: { privateFieldsAsProperties: boolean; + noUninitializedPrivateFieldAccess: boolean; noDocumentAll: boolean; innerBinding: t.Identifier; }, @@ -815,6 +858,7 @@ export function transformPrivateNamesUsage( file: state, ...handler, noDocumentAll, + noUninitializedPrivateFieldAccess, innerBinding, }); body.traverse(privateInVisitor, { @@ -888,14 +932,20 @@ function buildPrivateInstanceFieldInitSpec( function buildPrivateStaticFieldInitSpec( prop: NodePath, privateNamesMap: PrivateNamesMap, + noUninitializedPrivateFieldAccess: boolean, ) { const privateName = privateNamesMap.get(prop.node.key.id.name); - return inheritPropComments( - template.statement.ast` - var ${t.cloneNode(privateName.id)} = { + + const value = noUninitializedPrivateFieldAccess + ? prop.node.value + : template.expression.ast`{ _: ${prop.node.value || t.buildUndefinedNode()} - }; - `, + }`; + + return inheritPropComments( + t.variableDeclaration("var", [ + t.variableDeclarator(t.cloneNode(privateName.id), value), + ]), prop, ); } @@ -1368,6 +1418,7 @@ export function buildFieldsInitNodes( file: File, setPublicClassFields: boolean, privateFieldsAsProperties: boolean, + noUninitializedPrivateFieldAccess: boolean, constantSuper: boolean, innerBindingRef: t.Identifier | null, ) { @@ -1473,7 +1524,11 @@ export function buildFieldsInitNodes( ); } else { staticNodes.push( - buildPrivateStaticFieldInitSpec(prop, privateNamesMap), + buildPrivateStaticFieldInitSpec( + prop, + privateNamesMap, + noUninitializedPrivateFieldAccess, + ), ); } break; 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 a0ae9c371b4c..a49d6705654e 100644 --- a/packages/babel-helper-create-class-features-plugin/src/index.ts +++ b/packages/babel-helper-create-class-features-plugin/src/index.ts @@ -75,6 +75,8 @@ export function createClassFeaturePlugin({ const setPublicClassFields = api.assumption("setPublicClassFields"); const privateFieldsAsSymbols = api.assumption("privateFieldsAsSymbols"); const privateFieldsAsProperties = api.assumption("privateFieldsAsProperties"); + const noUninitializedPrivateFieldAccess = + api.assumption("noUninitializedPrivateFieldAccess") ?? false; const constantSuper = api.assumption("constantSuper"); const noDocumentAll = api.assumption("noDocumentAll"); @@ -256,6 +258,7 @@ export function createClassFeaturePlugin({ { privateFieldsAsProperties: privateFieldsAsSymbolsOrProperties ?? loose, + noUninitializedPrivateFieldAccess, noDocumentAll, innerBinding, }, @@ -296,6 +299,7 @@ export function createClassFeaturePlugin({ file, setPublicClassFields ?? loose, privateFieldsAsSymbolsOrProperties ?? loose, + noUninitializedPrivateFieldAccess, constantSuper ?? loose, innerBinding, )); @@ -317,6 +321,7 @@ export function createClassFeaturePlugin({ file, setPublicClassFields ?? loose, privateFieldsAsSymbolsOrProperties ?? loose, + noUninitializedPrivateFieldAccess, constantSuper ?? loose, innerBinding, )); diff --git a/packages/babel-helpers/src/helpers-generated.ts b/packages/babel-helpers/src/helpers-generated.ts index 949dd90337cf..7205ea0bcb15 100644 --- a/packages/babel-helpers/src/helpers-generated.ts +++ b/packages/babel-helpers/src/helpers-generated.ts @@ -173,10 +173,10 @@ export default Object.freeze({ "7.1.5", 'import toPrimitive from"toPrimitive";export default function toPropertyKey(t){var i=toPrimitive(t,"string");return"symbol"==typeof i?i:String(i)}', ), - // size: 134, gzip size: 134 + // size: 144, gzip size: 141 toSetter: helper( "7.22.0", - 'export default function _toSetter(t,e,n){var r=e.length++;return Object.defineProperty({},"_",{set:function(o){e[r]=o,t.apply(n,e)}})}', + 'export default function _toSetter(t,e,n){e||(e=[]);var r=e.length++;return Object.defineProperty({},"_",{set:function(o){e[r]=o,t.apply(n,e)}})}', ), // size: 289, gzip size: 165 typeof: helper( diff --git a/packages/babel-helpers/src/helpers/toSetter.ts b/packages/babel-helpers/src/helpers/toSetter.ts index 5a066d4fa526..cabdb6710762 100644 --- a/packages/babel-helpers/src/helpers/toSetter.ts +++ b/packages/babel-helpers/src/helpers/toSetter.ts @@ -1,6 +1,7 @@ /* @minVersion 7.22.0 */ export default function _toSetter(fn: Function, args: any[], thisArg: any) { + if (!args) args = []; var l = args.length++; return Object.defineProperty({}, "_", { set: function (v) { diff --git a/packages/babel-plugin-transform-class-properties/test/fixtures/assumption-noUninitializedPrivateFieldAccess/options.json b/packages/babel-plugin-transform-class-properties/test/fixtures/assumption-noUninitializedPrivateFieldAccess/options.json new file mode 100644 index 000000000000..32daf80d9e14 --- /dev/null +++ b/packages/babel-plugin-transform-class-properties/test/fixtures/assumption-noUninitializedPrivateFieldAccess/options.json @@ -0,0 +1,6 @@ +{ + "plugins": ["transform-class-properties"], + "assumptions": { + "noUninitializedPrivateFieldAccess": true + } +} diff --git a/packages/babel-plugin-transform-class-properties/test/fixtures/assumption-noUninitializedPrivateFieldAccess/static-private/exec.js b/packages/babel-plugin-transform-class-properties/test/fixtures/assumption-noUninitializedPrivateFieldAccess/static-private/exec.js new file mode 100644 index 000000000000..2cd25442fd08 --- /dev/null +++ b/packages/babel-plugin-transform-class-properties/test/fixtures/assumption-noUninitializedPrivateFieldAccess/static-private/exec.js @@ -0,0 +1,23 @@ +class A { + static #x = 0; + + static dynamicCheck() { + expect(this.#x).toBe(0); + expect(this.#x = 1).toBe(1); + expect(this.#x).toBe(1); + [this.#x] = [2]; + expect(this.#x).toBe(2); + } + + static noCheck() { + expect(A.#x).toBe(2); + expect(A.#x = 3).toBe(3); + [A.#x] = [4]; + expect(A.#x).toBe(4); + } +} + +A.dynamicCheck(); +A.noCheck(); + + diff --git a/packages/babel-plugin-transform-class-properties/test/fixtures/assumption-noUninitializedPrivateFieldAccess/static-private/input.js b/packages/babel-plugin-transform-class-properties/test/fixtures/assumption-noUninitializedPrivateFieldAccess/static-private/input.js new file mode 100644 index 000000000000..986ce029750d --- /dev/null +++ b/packages/babel-plugin-transform-class-properties/test/fixtures/assumption-noUninitializedPrivateFieldAccess/static-private/input.js @@ -0,0 +1,15 @@ +class A { + static #x = 2; + + static dynamicCheck() { + this.#x; + this.#x = 2; + [this.#x] = []; + } + + static noCheck() { + A.#x; + A.#x = 2; + [A.#x] = []; + } +} diff --git a/packages/babel-plugin-transform-class-properties/test/fixtures/assumption-noUninitializedPrivateFieldAccess/static-private/output.js b/packages/babel-plugin-transform-class-properties/test/fixtures/assumption-noUninitializedPrivateFieldAccess/static-private/output.js new file mode 100644 index 000000000000..1a4d17a8aa6a --- /dev/null +++ b/packages/babel-plugin-transform-class-properties/test/fixtures/assumption-noUninitializedPrivateFieldAccess/static-private/output.js @@ -0,0 +1,13 @@ +class A { + static dynamicCheck() { + babelHelpers.assertClassBrand(this, A, _x); + _x = babelHelpers.assertClassBrand(this, A, 2); + [babelHelpers.toSetter(babelHelpers.assertClassBrand(this, A, _ => _x = _))._] = []; + } + static noCheck() { + _x; + _x = 2; + [_x] = []; + } +} +var _x = 2;