fix: fix static private field shadowed by local variable (#13656)
* fix: fix static private field shadowed by local variable currently throw an error, maybe we could generate correct code fix #12960 * feat: rename local variable and add test cases * feat: add unshadow to privateIn visitor also add test cases * test: add reference to shadowed variable * refactor: apply suggested changes simplify logic and add comments
This commit is contained in:
parent
a54f041440
commit
313ecb579d
@ -1,6 +1,6 @@
|
|||||||
import { template, traverse, types as t } from "@babel/core";
|
import { template, traverse, types as t } from "@babel/core";
|
||||||
import type { File } from "@babel/core";
|
import type { File } from "@babel/core";
|
||||||
import type { NodePath, Visitor } from "@babel/traverse";
|
import type { NodePath, Visitor, Scope } from "@babel/traverse";
|
||||||
import ReplaceSupers, {
|
import ReplaceSupers, {
|
||||||
environmentVisitor,
|
environmentVisitor,
|
||||||
} from "@babel/helper-replace-supers";
|
} from "@babel/helper-replace-supers";
|
||||||
@ -58,7 +58,7 @@ export function buildPrivateNamesMap(props: PropPath[]) {
|
|||||||
export function buildPrivateNamesNodes(
|
export function buildPrivateNamesNodes(
|
||||||
privateNamesMap: PrivateNamesMap,
|
privateNamesMap: PrivateNamesMap,
|
||||||
privateFieldsAsProperties: boolean,
|
privateFieldsAsProperties: boolean,
|
||||||
state,
|
state: File,
|
||||||
) {
|
) {
|
||||||
const initNodes: t.Statement[] = [];
|
const initNodes: t.Statement[] = [];
|
||||||
|
|
||||||
@ -165,6 +165,7 @@ interface PrivateNameState {
|
|||||||
classRef: t.Identifier;
|
classRef: t.Identifier;
|
||||||
file: File;
|
file: File;
|
||||||
noDocumentAll: boolean;
|
noDocumentAll: boolean;
|
||||||
|
innerBinding?: t.Identifier;
|
||||||
}
|
}
|
||||||
|
|
||||||
const privateNameVisitor = privateNameVisitorFactory<
|
const privateNameVisitor = privateNameVisitorFactory<
|
||||||
@ -188,9 +189,28 @@ const privateNameVisitor = privateNameVisitorFactory<
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// rename all bindings that shadows innerBinding
|
||||||
|
function unshadow(
|
||||||
|
name: string,
|
||||||
|
scope: Scope,
|
||||||
|
innerBinding: t.Identifier | undefined,
|
||||||
|
) {
|
||||||
|
// in some cases, scope.getBinding(name) === undefined
|
||||||
|
// so we check hasBinding to avoid keeping looping
|
||||||
|
// see: https://github.com/babel/babel/pull/13656#discussion_r686030715
|
||||||
|
while (
|
||||||
|
scope?.hasBinding(name) &&
|
||||||
|
!scope.bindingIdentifierEquals(name, innerBinding)
|
||||||
|
) {
|
||||||
|
scope.rename(name);
|
||||||
|
scope = scope.parent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const privateInVisitor = privateNameVisitorFactory<{
|
const privateInVisitor = privateNameVisitorFactory<{
|
||||||
classRef: t.Identifier;
|
classRef: t.Identifier;
|
||||||
file: File;
|
file: File;
|
||||||
|
innerBinding?: t.Identifier;
|
||||||
}>({
|
}>({
|
||||||
BinaryExpression(path) {
|
BinaryExpression(path) {
|
||||||
const { operator, left, right } = path.node;
|
const { operator, left, right } = path.node;
|
||||||
@ -204,6 +224,10 @@ const privateInVisitor = privateNameVisitorFactory<{
|
|||||||
if (!privateNamesMap.has(name)) return;
|
if (!privateNamesMap.has(name)) return;
|
||||||
if (redeclared && redeclared.includes(name)) return;
|
if (redeclared && redeclared.includes(name)) return;
|
||||||
|
|
||||||
|
// if there are any local variable shadowing classRef, unshadow it
|
||||||
|
// see #12960
|
||||||
|
unshadow(this.classRef.name, path.scope, this.innerBinding);
|
||||||
|
|
||||||
if (privateFieldsAsProperties) {
|
if (privateFieldsAsProperties) {
|
||||||
const { id } = privateNamesMap.get(name);
|
const { id } = privateNamesMap.get(name);
|
||||||
path.replaceWith(template.expression.ast`
|
path.replaceWith(template.expression.ast`
|
||||||
@ -255,7 +279,7 @@ const privateNameHandlerSpec: Handler<PrivateNameState & Receiver> & Receiver =
|
|||||||
},
|
},
|
||||||
|
|
||||||
get(member) {
|
get(member) {
|
||||||
const { classRef, privateNamesMap, file } = this;
|
const { classRef, privateNamesMap, file, innerBinding } = this;
|
||||||
const { name } = (member.node.property as t.PrivateName).id;
|
const { name } = (member.node.property as t.PrivateName).id;
|
||||||
const {
|
const {
|
||||||
id,
|
id,
|
||||||
@ -273,6 +297,10 @@ const privateNameHandlerSpec: Handler<PrivateNameState & Receiver> & Receiver =
|
|||||||
? "classStaticPrivateMethodGet"
|
? "classStaticPrivateMethodGet"
|
||||||
: "classStaticPrivateFieldSpecGet";
|
: "classStaticPrivateFieldSpecGet";
|
||||||
|
|
||||||
|
// if there are any local variable shadowing classRef, unshadow it
|
||||||
|
// see #12960
|
||||||
|
unshadow(classRef.name, member.scope, innerBinding);
|
||||||
|
|
||||||
return t.callExpression(file.addHelper(helperName), [
|
return t.callExpression(file.addHelper(helperName), [
|
||||||
this.receiver(member),
|
this.receiver(member),
|
||||||
t.cloneNode(classRef),
|
t.cloneNode(classRef),
|
||||||
@ -463,8 +491,8 @@ export function transformPrivateNamesUsage(
|
|||||||
ref: t.Identifier,
|
ref: t.Identifier,
|
||||||
path: NodePath<t.Class>,
|
path: NodePath<t.Class>,
|
||||||
privateNamesMap: PrivateNamesMap,
|
privateNamesMap: PrivateNamesMap,
|
||||||
{ privateFieldsAsProperties, noDocumentAll },
|
{ privateFieldsAsProperties, noDocumentAll, innerBinding },
|
||||||
state,
|
state: File,
|
||||||
) {
|
) {
|
||||||
if (!privateNamesMap.size) return;
|
if (!privateNamesMap.size) return;
|
||||||
|
|
||||||
@ -479,12 +507,14 @@ export function transformPrivateNamesUsage(
|
|||||||
file: state,
|
file: state,
|
||||||
...handler,
|
...handler,
|
||||||
noDocumentAll,
|
noDocumentAll,
|
||||||
|
innerBinding,
|
||||||
});
|
});
|
||||||
body.traverse(privateInVisitor, {
|
body.traverse(privateInVisitor, {
|
||||||
privateNamesMap,
|
privateNamesMap,
|
||||||
classRef: ref,
|
classRef: ref,
|
||||||
file: state,
|
file: state,
|
||||||
privateFieldsAsProperties,
|
privateFieldsAsProperties,
|
||||||
|
innerBinding,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -770,7 +800,7 @@ const thisContextVisitor = traverse.visitors.merge([
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
const innerReferencesVisitor = {
|
const innerReferencesVisitor = {
|
||||||
ReferencedIdentifier(path, state) {
|
ReferencedIdentifier(path: NodePath<t.Identifier>, state) {
|
||||||
if (
|
if (
|
||||||
path.scope.bindingIdentifierEquals(path.node.name, state.innerBinding)
|
path.scope.bindingIdentifierEquals(path.node.name, state.innerBinding)
|
||||||
) {
|
) {
|
||||||
@ -831,7 +861,7 @@ export function buildFieldsInitNodes(
|
|||||||
superRef: t.Expression | undefined,
|
superRef: t.Expression | undefined,
|
||||||
props: PropPath[],
|
props: PropPath[],
|
||||||
privateNamesMap: PrivateNamesMap,
|
privateNamesMap: PrivateNamesMap,
|
||||||
state,
|
state: File,
|
||||||
setPublicClassFields: boolean,
|
setPublicClassFields: boolean,
|
||||||
privateFieldsAsProperties: boolean,
|
privateFieldsAsProperties: boolean,
|
||||||
constantSuper: boolean,
|
constantSuper: boolean,
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import { types as t } from "@babel/core";
|
import { types as t } from "@babel/core";
|
||||||
|
import type { File } from "@babel/core";
|
||||||
import type { NodePath } from "@babel/traverse";
|
import type { NodePath } from "@babel/traverse";
|
||||||
import nameFunction from "@babel/helper-function-name";
|
import nameFunction from "@babel/helper-function-name";
|
||||||
import splitExportDeclaration from "@babel/helper-split-export-declaration";
|
import splitExportDeclaration from "@babel/helper-split-export-declaration";
|
||||||
@ -92,7 +93,7 @@ export function createClassFeaturePlugin({
|
|||||||
},
|
},
|
||||||
|
|
||||||
visitor: {
|
visitor: {
|
||||||
Class(path: NodePath<t.Class>, state) {
|
Class(path: NodePath<t.Class>, state: File) {
|
||||||
if (this.file.get(versionKey) !== version) return;
|
if (this.file.get(versionKey) !== version) return;
|
||||||
|
|
||||||
verifyUsedFeatures(path, this.file);
|
verifyUsedFeatures(path, this.file);
|
||||||
@ -198,6 +199,7 @@ export function createClassFeaturePlugin({
|
|||||||
{
|
{
|
||||||
privateFieldsAsProperties: privateFieldsAsProperties ?? loose,
|
privateFieldsAsProperties: privateFieldsAsProperties ?? loose,
|
||||||
noDocumentAll,
|
noDocumentAll,
|
||||||
|
innerBinding,
|
||||||
},
|
},
|
||||||
state,
|
state,
|
||||||
);
|
);
|
||||||
|
|||||||
@ -0,0 +1,15 @@
|
|||||||
|
class Test {
|
||||||
|
|
||||||
|
static #x = 1
|
||||||
|
|
||||||
|
static method() {
|
||||||
|
const Test = 2;
|
||||||
|
const func = () => {
|
||||||
|
const Test = 3;
|
||||||
|
return this.#x + Test;
|
||||||
|
}
|
||||||
|
return func() + Test;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(Test.method()).toBe(6)
|
||||||
@ -0,0 +1,13 @@
|
|||||||
|
class Test {
|
||||||
|
|
||||||
|
static #x = 1
|
||||||
|
|
||||||
|
static method() {
|
||||||
|
const Test = 2;
|
||||||
|
const func = () => {
|
||||||
|
const Test = 3;
|
||||||
|
return this.#x + Test;
|
||||||
|
}
|
||||||
|
return func() + Test;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"plugins": ["proposal-class-properties"]
|
||||||
|
}
|
||||||
@ -0,0 +1,18 @@
|
|||||||
|
class Test {
|
||||||
|
static method() {
|
||||||
|
const _Test2 = 2;
|
||||||
|
|
||||||
|
const func = () => {
|
||||||
|
const _Test = 3;
|
||||||
|
return babelHelpers.classStaticPrivateFieldSpecGet(this, Test, _x) + _Test;
|
||||||
|
};
|
||||||
|
|
||||||
|
return func() + _Test2;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
var _x = {
|
||||||
|
writable: true,
|
||||||
|
value: 1
|
||||||
|
};
|
||||||
15
packages/babel-plugin-proposal-class-properties/test/fixtures/private/static-shadow/exec.js
vendored
Normal file
15
packages/babel-plugin-proposal-class-properties/test/fixtures/private/static-shadow/exec.js
vendored
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
class Test {
|
||||||
|
|
||||||
|
static #x = 1
|
||||||
|
|
||||||
|
static method() {
|
||||||
|
const Test = 2;
|
||||||
|
const func = () => {
|
||||||
|
const Test = 3;
|
||||||
|
return this.#x + Test;
|
||||||
|
}
|
||||||
|
return func() + Test;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(Test.method()).toBe(6)
|
||||||
13
packages/babel-plugin-proposal-class-properties/test/fixtures/private/static-shadow/input.js
vendored
Normal file
13
packages/babel-plugin-proposal-class-properties/test/fixtures/private/static-shadow/input.js
vendored
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
class Test {
|
||||||
|
|
||||||
|
static #x = 1
|
||||||
|
|
||||||
|
static method() {
|
||||||
|
const Test = 2;
|
||||||
|
const func = () => {
|
||||||
|
const Test = 3;
|
||||||
|
return this.#x + Test;
|
||||||
|
}
|
||||||
|
return func() + Test;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"plugins": ["proposal-class-properties"]
|
||||||
|
}
|
||||||
18
packages/babel-plugin-proposal-class-properties/test/fixtures/private/static-shadow/output.js
vendored
Normal file
18
packages/babel-plugin-proposal-class-properties/test/fixtures/private/static-shadow/output.js
vendored
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
class Test {
|
||||||
|
static method() {
|
||||||
|
const _Test2 = 2;
|
||||||
|
|
||||||
|
const func = () => {
|
||||||
|
const _Test = 3;
|
||||||
|
return babelHelpers.classStaticPrivateFieldSpecGet(this, Test, _x) + _Test;
|
||||||
|
};
|
||||||
|
|
||||||
|
return func() + _Test2;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
var _x = {
|
||||||
|
writable: true,
|
||||||
|
value: 1
|
||||||
|
};
|
||||||
@ -0,0 +1,17 @@
|
|||||||
|
class Test {
|
||||||
|
|
||||||
|
static #x = 1
|
||||||
|
|
||||||
|
method(other) {
|
||||||
|
const Test = 2;
|
||||||
|
const func = () => {
|
||||||
|
const Test = 3;
|
||||||
|
return #x in other && Test;
|
||||||
|
}
|
||||||
|
return func() + Test;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const t = new Test();
|
||||||
|
const t2 = new Test();
|
||||||
|
expect(t.method(Test)).toBe(5)
|
||||||
@ -0,0 +1,13 @@
|
|||||||
|
class Test {
|
||||||
|
|
||||||
|
static #x = 1
|
||||||
|
|
||||||
|
method(other) {
|
||||||
|
const Test = 2;
|
||||||
|
const func = () => {
|
||||||
|
const Test = 3;
|
||||||
|
return #x in other && Test;
|
||||||
|
}
|
||||||
|
return func() + Test;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"plugins": ["proposal-class-properties"]
|
||||||
|
}
|
||||||
@ -0,0 +1,18 @@
|
|||||||
|
class Test {
|
||||||
|
method(other) {
|
||||||
|
const _Test2 = 2;
|
||||||
|
|
||||||
|
const func = () => {
|
||||||
|
const _Test = 3;
|
||||||
|
return other === Test && _Test;
|
||||||
|
};
|
||||||
|
|
||||||
|
return func() + _Test2;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
var _x = {
|
||||||
|
writable: true,
|
||||||
|
value: 1
|
||||||
|
};
|
||||||
@ -0,0 +1,17 @@
|
|||||||
|
class Test {
|
||||||
|
|
||||||
|
static #x = 1
|
||||||
|
|
||||||
|
method(other) {
|
||||||
|
const Test = 2;
|
||||||
|
const func = () => {
|
||||||
|
const Test = 3;
|
||||||
|
return #x in other && Test;
|
||||||
|
}
|
||||||
|
return func() + Test;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const t = new Test();
|
||||||
|
const t2 = new Test();
|
||||||
|
expect(t.method(Test)).toBe(5)
|
||||||
@ -0,0 +1,13 @@
|
|||||||
|
class Test {
|
||||||
|
|
||||||
|
static #x = 1
|
||||||
|
|
||||||
|
method(other) {
|
||||||
|
const Test = 2;
|
||||||
|
const func = () => {
|
||||||
|
const Test = 3;
|
||||||
|
return #x in other && Test;
|
||||||
|
}
|
||||||
|
return func() + Test;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"plugins": ["proposal-class-properties"]
|
||||||
|
}
|
||||||
@ -0,0 +1,18 @@
|
|||||||
|
class Test {
|
||||||
|
method(other) {
|
||||||
|
const _Test2 = 2;
|
||||||
|
|
||||||
|
const func = () => {
|
||||||
|
const _Test = 3;
|
||||||
|
return other === Test && _Test;
|
||||||
|
};
|
||||||
|
|
||||||
|
return func() + _Test2;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
var _x = {
|
||||||
|
writable: true,
|
||||||
|
value: 1
|
||||||
|
};
|
||||||
@ -0,0 +1,17 @@
|
|||||||
|
class Test {
|
||||||
|
|
||||||
|
static #x = 1
|
||||||
|
|
||||||
|
method(other) {
|
||||||
|
const Test = 2;
|
||||||
|
const func = () => {
|
||||||
|
const Test = 3;
|
||||||
|
return #x in other && Test;
|
||||||
|
}
|
||||||
|
return func() + Test;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const t = new Test();
|
||||||
|
const t2 = new Test();
|
||||||
|
expect(t.method(Test)).toBe(5)
|
||||||
@ -0,0 +1,13 @@
|
|||||||
|
class Test {
|
||||||
|
|
||||||
|
static #x = 1
|
||||||
|
|
||||||
|
method(other) {
|
||||||
|
const Test = 2;
|
||||||
|
const func = () => {
|
||||||
|
const Test = 3;
|
||||||
|
return #x in other && Test;
|
||||||
|
}
|
||||||
|
return func() + Test;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"plugins": ["proposal-class-properties"]
|
||||||
|
}
|
||||||
@ -0,0 +1,18 @@
|
|||||||
|
class Test {
|
||||||
|
method(other) {
|
||||||
|
const _Test2 = 2;
|
||||||
|
|
||||||
|
const func = () => {
|
||||||
|
const _Test = 3;
|
||||||
|
return other === Test && _Test;
|
||||||
|
};
|
||||||
|
|
||||||
|
return func() + _Test2;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
var _x = {
|
||||||
|
writable: true,
|
||||||
|
value: 1
|
||||||
|
};
|
||||||
Loading…
x
Reference in New Issue
Block a user