Skip to content

Commit

Permalink
Consider well-known and registered symbols as literals (#16342)
Browse files Browse the repository at this point in the history
  • Loading branch information
nicolo-ribaudo committed Mar 12, 2024
1 parent d386707 commit 69e7928
Show file tree
Hide file tree
Showing 7 changed files with 110 additions and 35 deletions.
@@ -0,0 +1,9 @@
let dec1, dec2, dec3;

@dec1
class A {
[notSymbol()] = 1;
@dec2 [Symbol.iterator] = 2;
[Symbol.for("foo")] = 3;
@dec3 [notSymbolAgain()] = 4;
}
@@ -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();
}
}
@@ -1,18 +1,17 @@
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() {
babelHelpers.classCallCheck(this, A);
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,
Expand Down
39 changes: 26 additions & 13 deletions packages/babel-plugin-transform-object-rest-spread/src/index.ts
Expand Up @@ -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
Expand Down Expand Up @@ -188,7 +201,7 @@ export default declare((api, opts: Options) => {
path.get("properties") as NodePath<t.ObjectProperty>[],
path.scope,
);
const { keys, allLiteral, hasTemplateLiteral } = extractNormalizedKeys(
const { keys, allPrimitives, hasTemplateLiteral } = extractNormalizedKeys(
path.node,
);

Expand All @@ -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")),
Expand Down
@@ -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) {}
17 changes: 17 additions & 0 deletions packages/babel-traverse/src/path/introspection.ts
Expand Up @@ -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;
}

Expand Down
34 changes: 26 additions & 8 deletions packages/babel-traverse/src/scope/index.ts
Expand Up @@ -13,6 +13,7 @@ import {
identifier,
isArrayExpression,
isBinary,
isCallExpression,
isClass,
isClassBody,
isClassDeclaration,
Expand All @@ -23,6 +24,7 @@ import {
isIdentifier,
isImportDeclaration,
isLiteral,
isMemberExpression,
isMethod,
isModuleSpecifier,
isNullLiteral,
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -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(
Expand Down

0 comments on commit 69e7928

Please sign in to comment.