Skip to content

Commit b672e75

Browse files
Closure Teamcopybara-github
authored andcommitted
Use MakeDeclaredNamesUnique in RewriteClassMembers to remove any name shadowing in classes.
This includes the cases where the class name matches the variable declaring it and any names in the rhs of a public field, such as the parameter name in an arrow function, matching a constructor parameter name. PiperOrigin-RevId: 554505664
1 parent 70b72cc commit b672e75

File tree

2 files changed

+115
-40
lines changed

2 files changed

+115
-40
lines changed

src/com/google/javascript/jscomp/RewriteClassMembers.java

Lines changed: 12 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,10 @@ public RewriteClassMembers(AbstractCompiler compiler) {
4848

4949
@Override
5050
public void process(Node externs, Node root) {
51+
// Make all declared names unique to ensure that name shadowing can't occur in classes and
52+
// public fields don't share names with constructor parameters
53+
MakeDeclaredNamesUnique renamer = MakeDeclaredNamesUnique.builder().build();
54+
NodeTraversal.traverseRoots(compiler, renamer, externs, root);
5155
NodeTraversal.traverse(compiler, root, this);
5256
TranspilationPasses.maybeMarkFeaturesAsTranspiledAway(
5357
compiler, root, Feature.PUBLIC_CLASS_FIELDS, Feature.CLASS_STATIC_BLOCK);
@@ -66,15 +70,10 @@ public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
6670
checkState(classNameNode != null, "Class missing a name: %s", n);
6771
Node classInsertionPoint = getStatementDeclaringClass(n, classNameNode);
6872
checkState(classInsertionPoint != null, "Class was not extracted: %s", n);
69-
70-
if (!n.getFirstChild().isEmpty()
71-
&& !classNameNode.matchesQualifiedName(n.getFirstChild())) {
72-
// we do not allow `let x = class C {}` where the names inside the class can be shadowed
73-
// at this time
74-
t.report(n, TranspilationUtil.CANNOT_CONVERT_YET, "Classes with possible name shadowing");
75-
return false;
76-
}
77-
73+
checkState(
74+
n.getFirstChild().isEmpty() || classNameNode.matchesQualifiedName(n.getFirstChild()),
75+
"Class name shadows variable declaring the class: %s",
76+
n);
7877
classStack.push(new ClassRecord(n, classNameNode.getQualifiedName(), classInsertionPoint));
7978
break;
8079
case COMPUTED_FIELD_DEF:
@@ -247,13 +246,10 @@ private void rewriteInstanceMembers(NodeTraversal t, ClassRecord record) {
247246

248247
for (Node nameInRhs : record.referencedNamesByMember.get(instanceMember)) {
249248
String name = nameInRhs.getString();
250-
if (ctorDefinedNames.contains(name)) {
251-
t.report(
252-
nameInRhs,
253-
TranspilationUtil.CANNOT_CONVERT_YET,
254-
"Initializer referencing identifier '" + name + "' declared in constructor");
255-
return;
256-
}
249+
checkState(
250+
!ctorDefinedNames.contains(name),
251+
"Rhs of public field cannot use the same name as a constructor parameter: %s",
252+
name);
257253
}
258254

259255
Node thisNode = astFactory.createThisForEs6ClassMember(instanceMember);

test/com/google/javascript/jscomp/RewriteClassMembersTest.java

Lines changed: 103 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,18 @@ public void testCannotConvertYet() {
112112
"}")*/
113113
CANNOT_CONVERT_YET); // uses `this` in static block
114114

115+
testError(
116+
lines(
117+
"let C = class {",
118+
" static prop = 5;",
119+
"},",
120+
"D = class extends C {",
121+
" static {",
122+
" console.log(this.prop);",
123+
" }",
124+
"}"),
125+
CANNOT_CONVERT_YET); // uses `this` in static block
126+
115127
testError(
116128
lines(
117129
"var z = 1", //
@@ -722,7 +734,7 @@ public void testClassStaticBlocksNoFieldAssign() {
722734
"{",
723735
" let x = 5;",
724736
" class Bar {}",
725-
" {let x = 'str';}",
737+
" {let x$jscomp$1 = 'str';}",
726738
"}"));
727739
}
728740

@@ -1531,58 +1543,113 @@ public void testConstuctorAndStaticFieldDontConflict() {
15311543
lines(
15321544
"let x = 2;", //
15331545
"class C {",
1534-
" constructor(x) {}",
1546+
" constructor(x$jscomp$1) {}",
15351547
"}",
15361548
"C.y = x"));
15371549
}
15381550

15391551
@Test
15401552
public void testInstanceInitializerShadowsConstructorDeclaration() {
1541-
testError(
1553+
test(
15421554
lines(
15431555
"let x = 2;", //
15441556
"class C {",
15451557
" y = x",
15461558
" constructor(x) {}",
15471559
"}"),
1548-
CANNOT_CONVERT_YET);
1560+
lines(
1561+
"let x = 2;", //
1562+
"class C {",
1563+
" constructor(x$jscomp$1) {",
1564+
" this.y = x;",
1565+
" }",
1566+
"}"));
15491567

1550-
testError(
1568+
test(
15511569
lines(
15521570
"let x = 2;", //
15531571
"class C {",
1554-
" y = x",
1555-
" constructor() { let x;}",
1572+
" y = x;",
1573+
" constructor() { let x; }",
15561574
"}"),
1557-
CANNOT_CONVERT_YET);
1575+
lines(
1576+
"let x = 2;",
1577+
"class C {",
1578+
" constructor() {",
1579+
" this.y = x;",
1580+
" let x$jscomp$1;",
1581+
" }",
1582+
"}"));
15581583

1559-
testError(
1584+
test(
15601585
lines(
15611586
"let x = 2;", //
15621587
"class C {",
15631588
" y = x",
15641589
" constructor() { {var x;} }",
15651590
"}"),
1566-
CANNOT_CONVERT_YET);
1591+
lines(
1592+
"let x = 2;",
1593+
"class C {",
1594+
" constructor() {",
1595+
" this.y = x;",
1596+
" {",
1597+
" var x$jscomp$1;",
1598+
" }",
1599+
" }",
1600+
"}"));
15671601

1568-
testError(
1602+
test(
15691603
lines(
1570-
"function f() { return 4; };", //
1604+
"function f() { return 4; }", //
15711605
"class C {",
15721606
" y = f();",
15731607
" constructor() {function f() { return 'str'; }}",
15741608
"}"),
1575-
CANNOT_CONVERT_YET);
1609+
lines(
1610+
"function f() {",
1611+
" return 4;",
1612+
"}",
1613+
"class C {",
1614+
" constructor() {",
1615+
" this.y = f();",
1616+
" function f$jscomp$1() {",
1617+
" return 'str';",
1618+
" }",
1619+
" }",
1620+
"}"));
15761621

1577-
// TODO(b/189993301): Technically this has no shadowing, so we could inline without renaming
1578-
testError(
1622+
test(
1623+
lines(
1624+
"class Foo {", //
1625+
" constructor(x) {}",
1626+
" y = (x) => x;",
1627+
"}"),
1628+
lines(
1629+
"class Foo {",
1630+
" constructor(x) {",
1631+
" this.y = x$jscomp$1 => {",
1632+
" return x$jscomp$1;",
1633+
" };",
1634+
" }",
1635+
"}"));
1636+
1637+
test(
15791638
lines(
15801639
"let x = 2;", //
15811640
"class C {",
15821641
" y = (x) => x;",
15831642
" constructor(x) {}",
15841643
"}"),
1585-
CANNOT_CONVERT_YET);
1644+
lines(
1645+
"let x = 2;",
1646+
"class C {",
1647+
" constructor(x$jscomp$2) {",
1648+
" this.y = x$jscomp$1 => {",
1649+
" return x$jscomp$1;",
1650+
" };",
1651+
" }",
1652+
"}"));
15861653
}
15871654

15881655
@Test
@@ -1591,15 +1658,15 @@ public void testInstanceInitializerDoesntShadowConstructorDeclaration() {
15911658
lines(
15921659
"let x = 2;", //
15931660
"class C {",
1594-
" y = x",
1661+
" y = x;",
15951662
" constructor() { {let x;} }",
15961663
"}"),
15971664
lines(
15981665
"let x = 2;",
15991666
"class C {",
16001667
" constructor() {",
16011668
" this.y = x;",
1602-
" {let x;}",
1669+
" {let x$jscomp$1;}",
16031670
" }",
16041671
"}"));
16051672

@@ -1615,7 +1682,7 @@ public void testInstanceInitializerDoesntShadowConstructorDeclaration() {
16151682
"class C {",
16161683
" constructor() {",
16171684
" this.y = x;",
1618-
" () => { let x; };",
1685+
" () => { let x$jscomp$1; };",
16191686
" }",
16201687
"}"));
16211688

@@ -1631,7 +1698,7 @@ public void testInstanceInitializerDoesntShadowConstructorDeclaration() {
16311698
"class C {",
16321699
" constructor() {",
16331700
" this.y = x;",
1634-
" (x) => { return 3; };",
1701+
" (x$jscomp$1) => { return 3; };",
16351702
" }",
16361703
"}"));
16371704
}
@@ -1656,16 +1723,28 @@ public void testInstanceFieldInitializersDontBleedOut() {
16561723

16571724
@Test
16581725
public void testNestedClassesWithShadowingInstanceFields() {
1659-
testError(
1726+
test(
16601727
lines(
1661-
"let x = 2;", //
1728+
"let x = 2;",
16621729
"class C {",
16631730
" y = () => {",
1664-
" class Foo { z = x };",
1731+
" class Foo { z = x }",
16651732
" };",
16661733
" constructor(x) {}",
16671734
"}"),
1668-
CANNOT_CONVERT_YET);
1735+
lines(
1736+
"let x = 2;",
1737+
"class C {",
1738+
" constructor(x$jscomp$1) {",
1739+
" this.y = () => {",
1740+
" class Foo {",
1741+
" constructor() {",
1742+
" this.z = x;",
1743+
" }",
1744+
" }",
1745+
" };",
1746+
" }",
1747+
"}"));
16691748
}
16701749

16711750
// Added when fixing transpilation of real-world code that passed a class expression to a

0 commit comments

Comments
 (0)