Skip to content

Commit 929b1bc

Browse files
committed
resolve jsdoc @extends calls before applying type arguments
1 parent 7850856 commit 929b1bc

22 files changed

Lines changed: 585 additions & 73 deletions

‎tsc/internal/ast/utilities.go‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1219,6 +1219,24 @@ func IsVarUsing(node *Node) bool {
12191219
return GetCombinedNodeFlags(node)&NodeFlagsBlockScoped == NodeFlagsUsing
12201220
}
12211221

1222+
// GetJSDocAugmentsTag returns the first @augments JSDoc tag for the given node, or nil if none exists.
1223+
func GetJSDocAugmentsTag(node *Node) *Node {
1224+
if node == nil {
1225+
return nil
1226+
}
1227+
for _, jsdoc := range node.JSDoc(nil) {
1228+
tags := jsdoc.AsJSDoc().Tags
1229+
if tags != nil {
1230+
for _, tag := range tags.Nodes {
1231+
if IsJSDocAugmentsTag(tag) {
1232+
return tag
1233+
}
1234+
}
1235+
}
1236+
}
1237+
return nil
1238+
}
1239+
12221240
// GetJSDocDeprecatedTag returns the first @deprecated JSDoc tag for the given node, or nil if none exists.
12231241
func GetJSDocDeprecatedTag(node *Node) *Node {
12241242
for _, jsdoc := range node.JSDoc(nil) {

‎tsc/internal/checker/checker.go‎

Lines changed: 7 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -4321,14 +4321,14 @@ func (c *Checker) checkClassLikeDeclaration(node *ast.Node) {
43214321
c.checkClassForStaticPropertyNameConflicts(node)
43224322
}
43234323

4324-
baseTypeNode := ast.GetClassExtendsHeritageElement(node)
4324+
baseTypeNode := c.getEffectiveBaseTypeNode(classType)
43254325
if baseTypeNode != nil {
43264326
c.checkSourceElements(baseTypeNode.TypeArguments())
43274327
baseTypes := c.getBaseTypes(classType)
43284328
if len(baseTypes) != 0 {
43294329
baseType := baseTypes[0]
4330-
c.checkJSDocAugmentsTagMatchesExtends(node, baseTypeNode, baseType)
43314330
baseConstructorType := c.getBaseConstructorTypeOfClass(classType)
4331+
c.checkJSDocAugmentsTagMatchesExtends(node, baseTypeNode, baseType, baseConstructorType)
43324332
staticBaseType := c.getApparentType(baseConstructorType)
43334333
c.checkBaseTypeAccessibility(staticBaseType, baseTypeNode)
43344334
c.checkSourceElement(baseTypeNode.Expression())
@@ -4403,41 +4403,6 @@ func (c *Checker) checkClassLikeDeclaration(node *ast.Node) {
44034403
c.checkPropertyInitialization(node)
44044404
}
44054405

4406-
func (c *Checker) checkJSDocAugmentsTagMatchesExtends(node *ast.Node, baseTypeNode *ast.ExpressionWithTypeArgumentsNode, baseType *Type) {
4407-
if !ast.IsInJSFile(node) {
4408-
return
4409-
}
4410-
file := ast.GetSourceFileOfNode(node)
4411-
for _, j := range node.EagerJSDoc(file) {
4412-
if j.AsJSDoc().Tags == nil {
4413-
continue
4414-
}
4415-
for _, tag := range j.AsJSDoc().Tags.Nodes {
4416-
if tag.Kind != ast.KindJSDocAugmentsTag {
4417-
continue
4418-
}
4419-
sourceTypeNode := tag.ClassName()
4420-
sourceType := c.getTypeFromTypeNode(sourceTypeNode)
4421-
sourceName := getIdentifierFromEntityNameExpression(sourceTypeNode.Expression())
4422-
if sourceName != nil && ast.IsCallExpression(ast.SkipParentheses(baseTypeNode.Expression())) {
4423-
sourceSymbol := c.getMergedSymbol(getTargetType(sourceType).symbol)
4424-
targetSymbol := c.getMergedSymbol(getTargetType(baseType).symbol)
4425-
if sourceSymbol != nil && targetSymbol != nil && sourceSymbol != targetSymbol {
4426-
c.error(sourceName, diagnostics.JSDoc_0_1_does_not_match_the_extends_2_clause, tag.TagName().Text(), sourceName.Text(), targetSymbol.Name)
4427-
continue
4428-
}
4429-
}
4430-
if c.isTypeIdenticalTo(sourceType, baseType) {
4431-
continue
4432-
}
4433-
targetName := getIdentifierFromEntityNameExpression(baseTypeNode.Expression())
4434-
if targetName != nil && sourceName != nil {
4435-
c.error(sourceName, diagnostics.JSDoc_0_1_does_not_match_the_extends_2_clause, tag.TagName().Text(), sourceName.Text(), targetName.Text())
4436-
}
4437-
}
4438-
}
4439-
}
4440-
44414406
func (c *Checker) checkClassForStaticPropertyNameConflicts(node *ast.Node) {
44424407
if c.compilerOptions.GetUseDefineForClassFields() {
44434408
return
@@ -8582,7 +8547,9 @@ func (c *Checker) resolveCallExpression(node *ast.Node, candidatesOutArray *[]*S
85828547
if !c.isErrorType(superType) {
85838548
// In super call, the candidate signatures are the matching arity signatures of the base constructor function instantiated
85848549
// with the type arguments specified in the extends clause.
8585-
baseTypeNode := ast.GetClassExtendsHeritageElement(ast.GetContainingClass(node))
8550+
containingClass := ast.GetContainingClass(node)
8551+
classType := c.getDeclaredTypeOfSymbol(c.getSymbolOfDeclaration(containingClass))
8552+
baseTypeNode := c.getEffectiveBaseTypeNode(classType)
85868553
if baseTypeNode != nil {
85878554
baseConstructors := c.getInstantiatedConstructorsForTypeArguments(superType, baseTypeNode.TypeArguments(), baseTypeNode)
85888555
return c.resolveCall(node, baseConstructors, candidatesOutArray, checkMode, SignatureFlagsNone, nil)
@@ -19348,7 +19315,7 @@ func (c *Checker) resolveBaseTypesOfClass(t *Type) {
1934819315
if baseConstructorType.flags&(TypeFlagsObject|TypeFlagsIntersection|TypeFlagsAny) == 0 {
1934919316
return
1935019317
}
19351-
baseTypeNode := getBaseTypeNodeOfClass(t)
19318+
baseTypeNode := c.getEffectiveBaseTypeNode(t)
1935219319
var baseType *Type
1935319320
var originalBaseType *Type
1935419321
if baseConstructorType.symbol != nil {
@@ -20989,7 +20956,7 @@ func (c *Checker) getDefaultConstructSignatures(classType *Type) []*Signature {
2098920956
flags := core.IfElse(isAbstract, SignatureFlagsConstruct|SignatureFlagsAbstract, SignatureFlagsConstruct)
2099020957
return []*Signature{c.newSignature(flags, nil, classType.AsInterfaceType().LocalTypeParameters(), nil, nil, classType, nil, 0)}
2099120958
}
20992-
baseTypeNode := getBaseTypeNodeOfClass(classType)
20959+
baseTypeNode := c.getEffectiveBaseTypeNode(classType)
2099320960
isJavaScript := declaration != nil && ast.IsInJSFile(declaration)
2099420961
typeArguments := c.getTypeArgumentsFromNode(baseTypeNode)
2099520962
typeArgCount := len(typeArguments)

‎tsc/internal/checker/emitresolver.go‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1328,3 +1328,14 @@ func (r *EmitResolver) IsThisPropertyAssignmentDeclarationRedundant(node *ast.No
13281328
}
13291329
return false
13301330
}
1331+
1332+
func (r *EmitResolver) GetEffectiveBaseTypeNode(node *ast.Node) *ast.Node {
1333+
r.checkerMu.Lock()
1334+
defer r.checkerMu.Unlock()
1335+
symbol := r.checker.getSymbolOfDeclaration(node)
1336+
if symbol == nil {
1337+
return ast.GetClassExtendsHeritageElement(node)
1338+
}
1339+
classType := r.checker.getDeclaredTypeOfSymbol(symbol)
1340+
return r.checker.getEffectiveBaseTypeNode(classType)
1341+
}

‎tsc/internal/checker/jsdoc.go‎

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,3 +98,65 @@ func getAllJSDocTags(node *ast.Node) []*ast.Node {
9898
}
9999
return nil
100100
}
101+
102+
func (c *Checker) getEffectiveBaseTypeNode(t *Type) *ast.Node {
103+
baseTypeNode := getBaseTypeNodeOfClass(t)
104+
if tag := c.tryGetMatchingJSDocAugmentsTag(t, baseTypeNode); tag != nil {
105+
return tag.ClassName()
106+
}
107+
return baseTypeNode
108+
}
109+
110+
func (c *Checker) tryGetMatchingJSDocAugmentsTag(t *Type, baseTypeNode *ast.Node) *ast.Node {
111+
if baseTypeNode == nil || !ast.IsInJSFile(baseTypeNode) || len(baseTypeNode.TypeArguments()) >= 1 {
112+
return nil
113+
}
114+
expression := ast.SkipParentheses(baseTypeNode.Expression())
115+
if ast.IsCallExpression(expression) {
116+
tag := ast.GetJSDocAugmentsTag(ast.GetClassLikeDeclarationOfSymbol(t.symbol))
117+
if tag == nil || len(tag.ClassName().TypeArguments()) == 0 {
118+
return nil
119+
}
120+
baseConstructorType := c.getBaseConstructorTypeOfClass(t)
121+
if baseConstructorType.flags&TypeFlagsIntersection == 0 {
122+
sourceType := c.getTypeFromTypeNode(tag.ClassName())
123+
sourceSymbol := c.getMergedSymbol(getTargetType(sourceType).symbol)
124+
baseSymbol := c.getMergedSymbol(c.getApparentType(baseConstructorType).symbol)
125+
if sourceSymbol != nil && sourceSymbol == baseSymbol {
126+
return tag
127+
}
128+
}
129+
}
130+
return nil
131+
}
132+
133+
func (c *Checker) checkJSDocAugmentsTagMatchesExtends(node *ast.Node, baseTypeNode *ast.ExpressionWithTypeArgumentsNode, baseType *Type, baseConstructorType *Type) {
134+
if !ast.IsInJSFile(node) {
135+
return
136+
}
137+
tag := ast.GetJSDocAugmentsTag(node)
138+
if tag == nil {
139+
return
140+
}
141+
sourceTypeNode := tag.ClassName()
142+
sourceType := c.getTypeFromTypeNode(sourceTypeNode)
143+
sourceSymbol := c.getMergedSymbol(getTargetType(sourceType).symbol)
144+
sourceName := getIdentifierFromEntityNameExpression(sourceTypeNode.Expression())
145+
if sourceName != nil && ast.IsCallExpression(ast.SkipParentheses(baseTypeNode.Expression())) {
146+
targetSymbol := c.getMergedSymbol(c.getApparentType(baseConstructorType).symbol)
147+
if sourceSymbol != nil && targetSymbol != nil && sourceSymbol != targetSymbol {
148+
declarationName := getIdentifierNameOfSymbolDeclaration(targetSymbol)
149+
if declarationName != nil {
150+
c.error(sourceName, diagnostics.JSDoc_0_1_does_not_match_the_extends_2_clause, tag.TagName().Text(), sourceName.Text(), declarationName.Text())
151+
return
152+
}
153+
}
154+
}
155+
if c.isTypeIdenticalTo(sourceType, baseType) {
156+
return
157+
}
158+
targetName := getIdentifierFromEntityNameExpression(baseTypeNode.Expression())
159+
if targetName != nil && sourceName != nil {
160+
c.error(sourceName, diagnostics.JSDoc_0_1_does_not_match_the_extends_2_clause, tag.TagName().Text(), sourceName.Text(), targetName.Text())
161+
}
162+
}

‎tsc/internal/checker/utilities.go‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,21 @@ func NewDiagnosticChainForNode(chain *ast.Diagnostic, node *ast.Node, message *d
3636
return NewDiagnosticForNode(node, message, args...)
3737
}
3838

39+
func getIdentifierNameOfSymbolDeclaration(symbol *ast.Symbol) *ast.Node {
40+
if symbol == nil {
41+
return nil
42+
}
43+
if name := ast.GetNameOfDeclaration(symbol.ValueDeclaration); name != nil && ast.IsIdentifier(name) {
44+
return name
45+
}
46+
for _, declaration := range symbol.Declarations {
47+
if name := ast.GetNameOfDeclaration(declaration); name != nil && ast.IsIdentifier(name) {
48+
return name
49+
}
50+
}
51+
return nil
52+
}
53+
3954
func findInMap[K comparable, V any](m map[K]V, predicate func(V) bool) V {
4055
for _, value := range m {
4156
if predicate(value) {

‎tsc/internal/parser/parser_test.go‎

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -308,7 +308,7 @@ function foo(options) {}`
308308
assert.Equal(t, scanner.GetTokenPosOfNode(typeNode, file, false /*includeJSDoc*/), strings.Index(sourceText, "{{")+1)
309309
}
310310

311-
func TestJSDocAugmentsCallHeritage(t *testing.T) {
311+
func TestJSDocDoesNotAugmentCallHeritage(t *testing.T) {
312312
t.Parallel()
313313
sourceText := `/** @template T */
314314
class A {
@@ -337,11 +337,7 @@ class B extends A.extend() {}`
337337
assert.Equal(t, scanner.GetTextOfNode(baseType.Expression()), "A.extend()")
338338

339339
typeArguments := baseType.TypeArguments()
340-
assert.Equal(t, len(typeArguments), 1)
341-
342-
typeArgument := typeArguments[0]
343-
assert.Equal(t, typeArgument.Kind, ast.KindStringKeyword)
344-
assert.Assert(t, typeArgument.Flags&ast.NodeFlagsReparsed != 0)
340+
assert.Equal(t, len(typeArguments), 0)
345341

346342
jsDocs := classB.JSDoc(file)
347343
assert.Equal(t, len(jsDocs), 1)
@@ -355,7 +351,8 @@ class B extends A.extend() {}`
355351

356352
sourceTypeArguments := tag.ClassName().TypeArguments()
357353
assert.Equal(t, len(sourceTypeArguments), 1)
358-
assert.Equal(t, ast.GetReparsedNodeForNode(sourceTypeArguments[0]), typeArgument)
354+
assert.Equal(t, sourceTypeArguments[0].Kind, ast.KindStringKeyword)
355+
assert.Equal(t, ast.GetReparsedNodeForNode(sourceTypeArguments[0]), sourceTypeArguments[0])
359356
}
360357

361358
func TestSourceFilePositionMapWithNonASCIIStringLiteral(t *testing.T) {

‎tsc/internal/parser/reparser.go‎

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -597,10 +597,8 @@ func (p *Parser) reparseHosted(tag *ast.Node, parent *ast.Node, jsDoc *ast.Node)
597597
}); extendsClause != nil && len(extendsClause.AsHeritageClause().Types.Nodes) == 1 {
598598
target := extendsClause.AsHeritageClause().Types.Nodes[0].AsExpressionWithTypeArguments()
599599
source := tag.ClassName().AsExpressionWithTypeArguments()
600-
if target.TypeArguments == nil && source.TypeArguments != nil {
601-
if ast.IsCallExpression(ast.SkipParentheses(target.Expression)) || ast.HasSamePropertyAccessName(target.Expression, source.Expression) {
602-
p.setReparsedTypeArguments(target, source.TypeArguments)
603-
}
600+
if target.TypeArguments == nil && source.TypeArguments != nil && ast.HasSamePropertyAccessName(target.Expression, source.Expression) {
601+
p.setReparsedTypeArguments(target, source.TypeArguments)
604602
}
605603
}
606604
}

‎tsc/internal/printer/emitresolver.go‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ type EmitResolver interface {
112112
IsLateBound(node *ast.Node) bool
113113
IsOptionalParameter(node *ast.Node) bool
114114
IsThisPropertyAssignmentDeclarationRedundant(node *ast.Node) bool
115+
GetEffectiveBaseTypeNode(node *ast.Node) *ast.Node
115116

116117
// isolatedDeclarations-specific declaration emit
117118
GetPropertiesOfContainerFunction(node *ast.Node) []*ast.Symbol

‎tsc/internal/transformers/declarations/transform.go‎

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2002,9 +2002,14 @@ func (tx *DeclarationTransformer) transformClassDeclaration(input *ast.ClassDecl
20022002

20032003
members := tx.buildClassMembers(input.AsNode(), extraMembers...)
20042004

2005-
extendsClause := getEffectiveBaseTypeNode(input.AsNode())
2005+
extendsClause := ast.GetClassExtendsHeritageElement(input.AsNode())
20062006

20072007
if extendsClause != nil && !ast.IsEntityNameExpression(extendsClause.AsExpressionWithTypeArguments().Expression) && extendsClause.AsExpressionWithTypeArguments().Expression.Kind != ast.KindNullKeyword {
2008+
effectiveBaseType := tx.resolver.GetEffectiveBaseTypeNode(input.AsNode())
2009+
typeArguments := extendsClause.AsExpressionWithTypeArguments().TypeArguments
2010+
if effectiveBaseType != nil {
2011+
typeArguments = effectiveBaseType.AsExpressionWithTypeArguments().TypeArguments
2012+
}
20082013
tx.tracker.ReportInferenceFallback(extendsClause.AsExpressionWithTypeArguments().Expression) // Add an isolated declarations error on this extends clause
20092014
oldId := "default"
20102015
if ast.NodeIsPresent(input.Name()) && ast.IsIdentifier(input.Name()) && len(input.Name().Text()) > 0 {
@@ -2040,7 +2045,7 @@ func (tx *DeclarationTransformer) transformClassDeclaration(input *ast.ClassDecl
20402045
tx.Factory().UpdateExpressionWithTypeArguments(
20412046
extendsClause.AsExpressionWithTypeArguments(),
20422047
newId,
2043-
tx.Visitor().VisitNodes(extendsClause.AsExpressionWithTypeArguments().TypeArguments),
2048+
tx.Visitor().VisitNodes(typeArguments),
20442049
),
20452050
}),
20462051
)

‎tsc/internal/transformers/declarations/util.go‎

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -161,19 +161,6 @@ func shouldEmitFunctionProperties(input *ast.FunctionDeclaration) bool {
161161
})
162162
}
163163

164-
func getEffectiveBaseTypeNode(node *ast.Node) *ast.Node {
165-
baseType := ast.GetClassExtendsHeritageElement(node)
166-
// !!! TODO: JSDoc support
167-
// if (baseType && isInJSFile(node)) {
168-
// // Prefer an @augments tag because it may have type parameters.
169-
// const tag = getJSDocAugmentsTag(node);
170-
// if (tag) {
171-
// return tag.class;
172-
// }
173-
// }
174-
return baseType
175-
}
176-
177164
func isScopeMarker(node *ast.Node) bool {
178165
return ast.IsExportAssignment(node) || ast.IsExportDeclaration(node)
179166
}

0 commit comments

Comments
 (0)