diff --git a/src/com/google/javascript/jscomp/CheckJSDoc.java b/src/com/google/javascript/jscomp/CheckJSDoc.java index 42c53d1ec22..6b050ea8583 100644 --- a/src/com/google/javascript/jscomp/CheckJSDoc.java +++ b/src/com/google/javascript/jscomp/CheckJSDoc.java @@ -308,7 +308,7 @@ private boolean isClassDecl(Node n) { } private boolean isNameInitializeWithClass(Node n) { - return n != null && n.isName() && n.hasChildren() && isClass(n.getFirstChild()); + return n != null && n.isName() && n.hasChildren() && isClassDecl(n.getFirstChild()); } private boolean isClass(Node n) { diff --git a/src/com/google/javascript/jscomp/TypedScopeCreator.java b/src/com/google/javascript/jscomp/TypedScopeCreator.java index 2522e001d81..7e6e3c6f90c 100644 --- a/src/com/google/javascript/jscomp/TypedScopeCreator.java +++ b/src/com/google/javascript/jscomp/TypedScopeCreator.java @@ -2397,6 +2397,14 @@ && shouldUseFunctionLiteralType( } } + if (rValue != null && rValue.isAssign()) { + // Handle nested assignments. For example, TypeScript generates code like this: + // var Foo_1; + // let Foo = Foo_1 = class Foo {} + // Foo = Foo_1 = tslib_1.decorate(..., Foo); + return getDeclaredType(info, lValue, rValue.getSecondChild(), null); + } + if (info != null && FunctionTypeBuilder.isFunctionTypeDeclaration(info)) { String fnName = lValue.getQualifiedName(); return createFunctionTypeFromNodes(null, fnName, info, lValue); @@ -2406,12 +2414,6 @@ && shouldUseFunctionLiteralType( return getNativeType(JSTypeNative.NO_TYPE); } - if (rValue != null && rValue.isAssign()) { - // Handle nested assignments. For example, tsickle generates code like this: - // let Foo = Foo_1 = tslib_1.decorate(...) - return getDeclaredType(info, lValue, rValue.getSecondChild(), null); - } - return null; } diff --git a/test/com/google/javascript/jscomp/CheckJsDocTest.java b/test/com/google/javascript/jscomp/CheckJsDocTest.java index 446cd257e5c..60de8058c9d 100644 --- a/test/com/google/javascript/jscomp/CheckJsDocTest.java +++ b/test/com/google/javascript/jscomp/CheckJsDocTest.java @@ -1287,4 +1287,14 @@ public void testMangleClosureModuleExportsContentsTypes() { "/** @type {!Array} */ let x;", "/** @type {!Array} */ let x;"); } + + @Test + public void testNameDeclarationAndAssignForAbstractClass() { + testSame(lines("/** @abstract */", "let A = A_1 = class A {}")); + } + + @Test + public void testNameDeclarationAndAssignForTemplatedClass() { + testSame(lines("/** @template T */", "let A = A_1 = class A {}")); + } } diff --git a/test/com/google/javascript/jscomp/InlineAndCollapsePropertiesTest.java b/test/com/google/javascript/jscomp/InlineAndCollapsePropertiesTest.java index 77575d3dfdf..a2a43bf104f 100644 --- a/test/com/google/javascript/jscomp/InlineAndCollapsePropertiesTest.java +++ b/test/com/google/javascript/jscomp/InlineAndCollapsePropertiesTest.java @@ -2525,6 +2525,53 @@ public void testClassStaticInheritance_cantDetermineSuperclass() { "use(C.foo);")); } + @Test + public void testTypeScriptDecoratedClass() { + // TypeScript 5.1 emits this for decorated classes. + test( + lines( + "let A = class A { static getId() { return A.ID; } };", // + "A.ID = \"a\";", + "A = tslib.__decorate([], A);", + "if (false) {", + " /** @const {string} */ A.ID;", + "}", + "console.log(A.getId());"), + lines( + "let A = class A$jscomp$1 { static getId() { return A$jscomp$1.ID; } };", + "A.ID = \"a\";", + "A = tslib.__decorate([], A);", + "if (false) {", + " /** @const */ A.ID;", + "}", + "console.log(A.getId());")); + } + + @Test + public void testTypeScriptDecoratedClass_withExtraVarAssignment() { + // TypeScript 5.2 emits this for decorated classes, which reference static properties on + // themselves. + test( + lines( + "var A_1;", // + "let A = A_1 = class A { static getId() { return A_1.ID; } };", + "A.ID = \"a\";", + "A = A_1 = tslib.__decorate([], A);", + "if (false) {", + " /** @const {string} */ A.ID;", + "}", + "console.log(A.getId());"), + lines( + "var A_1;", // + "let A = A_1 = class A$jscomp$1 { static getId() { return A_1.ID; } };", + "A.ID = \"a\";", + "A = A_1 = tslib.__decorate([], A);", + "if (false) {", + " /** @const */ A.ID;", + "}", + "console.log(A.getId());")); + } + @Test public void testAliasForSuperclassNamespace() { test( diff --git a/test/com/google/javascript/jscomp/NodeUtilTest.java b/test/com/google/javascript/jscomp/NodeUtilTest.java index 5f9ec4da848..f6384e365b2 100644 --- a/test/com/google/javascript/jscomp/NodeUtilTest.java +++ b/test/com/google/javascript/jscomp/NodeUtilTest.java @@ -3589,6 +3589,9 @@ public void testGetBestJsDocInfoForClasses() { classNode = parseFirst(CLASS, "/** @export */ var Foo = class Bar {}"); assertThat(NodeUtil.getBestJSDocInfo(classNode).isExport()).isTrue(); + + classNode = parseFirst(CLASS, "var Foo_1; /** @export */ let Foo = Foo_1 = class Foo {}"); + assertThat(NodeUtil.getBestJSDocInfo(classNode).isExport()).isTrue(); } @Test diff --git a/test/com/google/javascript/jscomp/TypedScopeCreatorTest.java b/test/com/google/javascript/jscomp/TypedScopeCreatorTest.java index 5b60f943f90..a995c2f8ea7 100644 --- a/test/com/google/javascript/jscomp/TypedScopeCreatorTest.java +++ b/test/com/google/javascript/jscomp/TypedScopeCreatorTest.java @@ -2736,6 +2736,30 @@ public void testClassDeclarationWithExtends_andIncompatibleJsdoc() { assertThat(foo.isAmbiguousConstructor()).isTrue(); } + @Test + public void testAbstractClassDeclarationWithExtends_nameDeclAndAssign() { + testSame( + lines( + "var Bar_1;", // + "/** @abstract */", + "let Bar = Bar_1 = class Bar {}", + "class Foo extends Bar {}")); + + FunctionType bar1 = (FunctionType) findNameType("Bar_1", globalScope); + FunctionType bar = (FunctionType) findNameType("Bar", globalScope); + FunctionType foo = (FunctionType) findNameType("Foo", globalScope); + assertThat(bar.isAbstract()).isTrue(); + assertThat(bar).isEqualTo(bar1); + assertType(foo.getInstanceType()).isSubtypeOf(bar.getInstanceType()); + assertType(foo.getImplicitPrototype()).isEqualTo(bar); + assertScope(globalScope).declares("Bar").withTypeThat().isEqualTo(bar); + assertScope(globalScope).declares("Foo").withTypeThat().isEqualTo(foo); + + assertThat(foo.getInstanceType().loosenTypecheckingDueToForwardReferencedSupertype()).isFalse(); + assertThat(foo.loosenTypecheckingDueToForwardReferencedSupertype()).isFalse(); + assertThat(foo.isAmbiguousConstructor()).isFalse(); + } + @Test public void testClassDeclarationWithExtendsFromNamespace() { testSame(