diff --git a/packages/babel-plugin-proposal-decorators/test/fixtures/2023-11-misc/symbol-key/input.js b/packages/babel-plugin-proposal-decorators/test/fixtures/2023-11-misc/symbol-key/input.js new file mode 100644 index 000000000000..38d2b6c12192 --- /dev/null +++ b/packages/babel-plugin-proposal-decorators/test/fixtures/2023-11-misc/symbol-key/input.js @@ -0,0 +1,9 @@ +let dec1, dec2, dec3; + +@dec1 +class A { + [notSymbol()] = 1; + @dec2 [Symbol.iterator] = 2; + [Symbol.for("foo")] = 3; + @dec3 [notSymbolAgain()] = 4; +} diff --git a/packages/babel-plugin-proposal-decorators/test/fixtures/2023-11-misc/symbol-key/output.js b/packages/babel-plugin-proposal-decorators/test/fixtures/2023-11-misc/symbol-key/output.js new file mode 100644 index 000000000000..6a41f8424768 --- /dev/null +++ b/packages/babel-plugin-proposal-decorators/test/fixtures/2023-11-misc/symbol-key/output.js @@ -0,0 +1,21 @@ +let _initClass, _computedKey, _init_computedKey, _init_extra_computedKey, _computedKey2, _init_computedKey2, _init_extra_computedKey2; +let dec1, dec2, dec3; +let _A; +class A { + static { + ({ + e: [_init_computedKey, _init_extra_computedKey, _init_computedKey2, _init_extra_computedKey2], + c: [_A, _initClass] + } = babelHelpers.applyDecs2311(this, [dec1], [[dec2, 0, Symbol.iterator], [dec3, 0, _computedKey2]])); + } + constructor() { + _init_extra_computedKey2(this); + } + [_computedKey = notSymbol()] = 1; + [Symbol.iterator] = _init_computedKey(this, 2); + [Symbol.for("foo")] = (_init_extra_computedKey(this), 3); + [_computedKey2 = babelHelpers.toPropertyKey(notSymbolAgain())] = _init_computedKey2(this, 4); + static { + _initClass(); + } +} diff --git a/packages/babel-plugin-transform-class-properties/test/fixtures/class-name-tdz/decorator-interop/output.js b/packages/babel-plugin-transform-class-properties/test/fixtures/class-name-tdz/decorator-interop/output.js index 8ae055f50807..c32ca6a36e7c 100644 --- a/packages/babel-plugin-transform-class-properties/test/fixtures/class-name-tdz/decorator-interop/output.js +++ b/packages/babel-plugin-transform-class-properties/test/fixtures/class-name-tdz/decorator-interop/output.js @@ -1,7 +1,6 @@ -let _Symbol$search; var _class, _descriptor; function dec() {} -let A = (_class = (_Symbol$search = Symbol.search, /*#__PURE__*/function () { +let A = (_class = /*#__PURE__*/function () { "use strict"; function A() { @@ -9,10 +8,10 @@ let A = (_class = (_Symbol$search = Symbol.search, /*#__PURE__*/function () { babelHelpers.initializerDefineProperty(this, "a", _descriptor, this); } return babelHelpers.createClass(A, [{ - key: _Symbol$search, + key: Symbol.search, value: function () {} }]); -}()), (_descriptor = babelHelpers.applyDecoratedDescriptor(_class.prototype, "a", [dec], { +}(), (_descriptor = babelHelpers.applyDecoratedDescriptor(_class.prototype, "a", [dec], { configurable: true, enumerable: true, writable: true, diff --git a/packages/babel-plugin-transform-object-rest-spread/src/index.ts b/packages/babel-plugin-transform-object-rest-spread/src/index.ts index a126c67c627f..57ed1a0c4ada 100644 --- a/packages/babel-plugin-transform-object-rest-spread/src/index.ts +++ b/packages/babel-plugin-transform-object-rest-spread/src/index.ts @@ -101,39 +101,52 @@ export default declare((api, opts: Options) => { // returns an array of all keys of an object, and a status flag indicating if all extracted keys // were converted to stringLiterals or not - // e.g. extracts {keys: ["a", "b", "3", ++x], allLiteral: false } + // e.g. extracts {keys: ["a", "b", "3", ++x], allPrimitives: false } // from ast of {a: "foo", b, 3: "bar", [++x]: "baz"} + // `allPrimitives: false` doesn't necessarily mean that there is a non-primitive, but just + // that we are not sure. function extractNormalizedKeys(node: t.ObjectPattern) { // RestElement has been removed in createObjectRest const props = node.properties as t.ObjectProperty[]; const keys: t.Expression[] = []; - let allLiteral = true; + let allPrimitives = true; let hasTemplateLiteral = false; for (const prop of props) { - if (t.isIdentifier(prop.key) && !prop.computed) { + const { key } = prop; + if (t.isIdentifier(key) && !prop.computed) { // since a key {a: 3} is equivalent to {"a": 3}, use the latter - keys.push(t.stringLiteral(prop.key.name)); - } else if (t.isTemplateLiteral(prop.key)) { - keys.push(t.cloneNode(prop.key)); + keys.push(t.stringLiteral(key.name)); + } else if (t.isTemplateLiteral(key)) { + keys.push(t.cloneNode(key)); hasTemplateLiteral = true; - } else if (t.isLiteral(prop.key)) { + } else if (t.isLiteral(key)) { keys.push( t.stringLiteral( String( // @ts-expect-error prop.key can not be a NullLiteral - prop.key.value, + key.value, ), ), ); } else { // @ts-expect-error private name has been handled by destructuring-private - keys.push(t.cloneNode(prop.key)); - allLiteral = false; + keys.push(t.cloneNode(key)); + + if ( + (t.isMemberExpression(key, { computed: false }) && + t.isIdentifier(key.object, { name: "Symbol" })) || + (t.isCallExpression(key) && + t.matchesPattern(key.callee, "Symbol.for")) + ) { + // there all return a primitive + } else { + allPrimitives = false; + } } } - return { keys, allLiteral, hasTemplateLiteral }; + return { keys, allPrimitives, hasTemplateLiteral }; } // replaces impure computed keys with new identifiers @@ -188,7 +201,7 @@ export default declare((api, opts: Options) => { path.get("properties") as NodePath[], path.scope, ); - const { keys, allLiteral, hasTemplateLiteral } = extractNormalizedKeys( + const { keys, allPrimitives, hasTemplateLiteral } = extractNormalizedKeys( path.node, ); @@ -209,7 +222,7 @@ export default declare((api, opts: Options) => { } let keyExpression; - if (!allLiteral) { + if (!allPrimitives) { // map to toPropertyKey to handle the possible non-string values keyExpression = t.callExpression( t.memberExpression(t.arrayExpression(keys), t.identifier("map")), diff --git a/packages/babel-plugin-transform-object-rest-spread/test/fixtures/object-rest/symbol/output.js b/packages/babel-plugin-transform-object-rest-spread/test/fixtures/object-rest/symbol/output.js index 6b05ad908b32..bbe925e99fd5 100644 --- a/packages/babel-plugin-transform-object-rest-spread/test/fixtures/object-rest/symbol/output.js +++ b/packages/babel-plugin-transform-object-rest-spread/test/fixtures/object-rest/symbol/output.js @@ -1,17 +1,15 @@ -var _ref3, _Symbol$for3; +var _ref3; let _ref = {}, - _Symbol$for = Symbol.for("foo"), { - [_Symbol$for]: foo + [Symbol.for("foo")]: foo } = _ref, - rest = babelHelpers.objectWithoutProperties(_ref, [_Symbol$for].map(babelHelpers.toPropertyKey)); + rest = babelHelpers.objectWithoutProperties(_ref, [Symbol.for("foo")]); var _ref2 = {}; -var _Symbol$for2 = Symbol.for("foo"); ({ - [_Symbol$for2]: foo + [Symbol.for("foo")]: foo } = _ref2); -rest = babelHelpers.objectWithoutProperties(_ref2, [_Symbol$for2].map(babelHelpers.toPropertyKey)); +rest = babelHelpers.objectWithoutProperties(_ref2, [Symbol.for("foo")]); _ref2; -if (_ref3 = {}, _Symbol$for3 = Symbol.for("foo"), ({ - [_Symbol$for3]: foo -} = _ref3), rest = babelHelpers.objectWithoutProperties(_ref3, [_Symbol$for3].map(babelHelpers.toPropertyKey)), _ref3) {} +if (_ref3 = {}, ({ + [Symbol.for("foo")]: foo +} = _ref3), rest = babelHelpers.objectWithoutProperties(_ref3, [Symbol.for("foo")]), _ref3) {} diff --git a/packages/babel-traverse/src/path/introspection.ts b/packages/babel-traverse/src/path/introspection.ts index 269540fb4a68..1f077eefe616 100644 --- a/packages/babel-traverse/src/path/introspection.ts +++ b/packages/babel-traverse/src/path/introspection.ts @@ -655,6 +655,23 @@ export function isConstantExpression(this: NodePath): boolean { ); } + if (this.isMemberExpression()) { + return ( + !this.node.computed && + this.get("object").isIdentifier({ name: "Symbol" }) && + !this.scope.hasBinding("Symbol", { noGlobals: true }) + ); + } + + if (this.isCallExpression()) { + return ( + this.node.arguments.length === 1 && + this.get("callee").matchesPattern("Symbol.for") && + !this.scope.hasBinding("Symbol", { noGlobals: true }) && + this.get("arguments")[0].isStringLiteral() + ); + } + return false; } diff --git a/packages/babel-traverse/src/scope/index.ts b/packages/babel-traverse/src/scope/index.ts index aeac3e88bf17..028bcd69f5a9 100644 --- a/packages/babel-traverse/src/scope/index.ts +++ b/packages/babel-traverse/src/scope/index.ts @@ -13,6 +13,7 @@ import { identifier, isArrayExpression, isBinary, + isCallExpression, isClass, isClassBody, isClassDeclaration, @@ -23,6 +24,7 @@ import { isIdentifier, isImportDeclaration, isLiteral, + isMemberExpression, isMethod, isModuleSpecifier, isNullLiteral, @@ -929,17 +931,33 @@ export default class Scope { return true; } else if (isUnaryExpression(node)) { return this.isPure(node.argument, constantsOnly); - } else if (isTaggedTemplateExpression(node)) { - return ( - matchesPattern(node.tag, "String.raw") && - !this.hasBinding("String", true) && - this.isPure(node.quasi, constantsOnly) - ); } else if (isTemplateLiteral(node)) { for (const expression of node.expressions) { if (!this.isPure(expression, constantsOnly)) return false; } return true; + } else if (isTaggedTemplateExpression(node)) { + return ( + matchesPattern(node.tag, "String.raw") && + !this.hasBinding("String", { noGlobals: true }) && + this.isPure(node.quasi, constantsOnly) + ); + } else if (isMemberExpression(node)) { + return ( + !node.computed && + isIdentifier(node.object) && + node.object.name === "Symbol" && + isIdentifier(node.property) && + node.property.name !== "for" && + !this.hasBinding("Symbol", { noGlobals: true }) + ); + } else if (isCallExpression(node)) { + return ( + matchesPattern(node.callee, "Symbol.for") && + !this.hasBinding("Symbol", { noGlobals: true }) && + node.arguments.length === 1 && + t.isStringLiteral(node.arguments[0]) + ); } else { return isPureish(node); } @@ -1081,9 +1099,9 @@ export default class Scope { path.isFunction() && // @ts-expect-error ArrowFunctionExpression never has a name !path.node.name && - t.isCallExpression(path.parent, { callee: path.node }) && + isCallExpression(path.parent, { callee: path.node }) && path.parent.arguments.length <= path.node.params.length && - t.isIdentifier(id) + isIdentifier(id) ) { path.pushContainer("params", id); path.scope.registerBinding(