Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Consider well-known and registered symbols as literals #16342

Merged
merged 4 commits into from Mar 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
@@ -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