Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions tsc/internal/ast/utilities.go
Original file line number Diff line number Diff line change
Expand Up @@ -1219,6 +1219,24 @@ func IsVarUsing(node *Node) bool {
return GetCombinedNodeFlags(node)&NodeFlagsBlockScoped == NodeFlagsUsing
}

// GetJSDocAugmentsTag returns the first @augments JSDoc tag for the given node, or nil if none exists.
func GetJSDocAugmentsTag(node *Node) *Node {
if node == nil {
return nil
}
for _, jsdoc := range node.JSDoc(nil) {
tags := jsdoc.AsJSDoc().Tags
if tags != nil {
for _, tag := range tags.Nodes {
if IsJSDocAugmentsTag(tag) {
return tag
}
}
}
}
return nil
}

// GetJSDocDeprecatedTag returns the first @deprecated JSDoc tag for the given node, or nil if none exists.
func GetJSDocDeprecatedTag(node *Node) *Node {
for _, jsdoc := range node.JSDoc(nil) {
Expand Down
38 changes: 7 additions & 31 deletions tsc/internal/checker/checker.go
Original file line number Diff line number Diff line change
Expand Up @@ -4334,14 +4334,14 @@ func (c *Checker) checkClassLikeDeclaration(node *ast.Node) {
c.checkClassForStaticPropertyNameConflicts(node)
}

baseTypeNode := ast.GetClassExtendsHeritageElement(node)
baseTypeNode := c.getEffectiveBaseTypeNode(classType)
if baseTypeNode != nil {
c.checkSourceElements(baseTypeNode.TypeArguments())
baseTypes := c.getBaseTypes(classType)
if len(baseTypes) != 0 {
baseType := baseTypes[0]
c.checkJSDocAugmentsTagMatchesExtends(node, baseTypeNode, baseType)
baseConstructorType := c.getBaseConstructorTypeOfClass(classType)
c.checkJSDocAugmentsTagMatchesExtends(node, baseTypeNode, baseType, baseConstructorType)
staticBaseType := c.getApparentType(baseConstructorType)
c.checkBaseTypeAccessibility(staticBaseType, baseTypeNode)
c.checkSourceElement(baseTypeNode.Expression())
Expand Down Expand Up @@ -4416,32 +4416,6 @@ func (c *Checker) checkClassLikeDeclaration(node *ast.Node) {
c.checkPropertyInitialization(node)
}

func (c *Checker) checkJSDocAugmentsTagMatchesExtends(node *ast.Node, baseTypeNode *ast.ExpressionWithTypeArgumentsNode, baseType *Type) {
if !ast.IsInJSFile(node) {
return
}
file := ast.GetSourceFileOfNode(node)
for _, j := range node.EagerJSDoc(file) {
if j.AsJSDoc().Tags == nil {
continue
}
for _, tag := range j.AsJSDoc().Tags.Nodes {
if tag.Kind != ast.KindJSDocAugmentsTag {
continue
}
sourceTypeNode := tag.ClassName()
if c.isTypeIdenticalTo(c.getTypeFromTypeNode(sourceTypeNode), baseType) {
continue
}
targetName := getIdentifierFromEntityNameExpression(baseTypeNode.Expression())
sourceName := getIdentifierFromEntityNameExpression(sourceTypeNode.Expression())
if targetName != nil && sourceName != nil {
c.error(sourceName, diagnostics.JSDoc_0_1_does_not_match_the_extends_2_clause, tag.TagName().Text(), sourceName.Text(), targetName.Text())
}
}
}
}

func (c *Checker) checkClassForStaticPropertyNameConflicts(node *ast.Node) {
if c.compilerOptions.GetUseDefineForClassFields() {
return
Expand Down Expand Up @@ -8653,7 +8627,9 @@ func (c *Checker) resolveCallExpression(node *ast.Node, candidatesOutArray *[]*S
if !c.isErrorType(superType) {
// In super call, the candidate signatures are the matching arity signatures of the base constructor function instantiated
// with the type arguments specified in the extends clause.
baseTypeNode := ast.GetClassExtendsHeritageElement(ast.GetContainingClass(node))
containingClass := ast.GetContainingClass(node)
classType := c.getDeclaredTypeOfSymbol(c.getSymbolOfDeclaration(containingClass))
baseTypeNode := c.getEffectiveBaseTypeNode(classType)
if baseTypeNode != nil {
baseConstructors := c.getInstantiatedConstructorsForTypeArguments(superType, baseTypeNode.TypeArguments(), baseTypeNode)
return c.resolveCall(node, baseConstructors, candidatesOutArray, checkMode, SignatureFlagsNone, nil)
Expand Down Expand Up @@ -19537,7 +19513,7 @@ func (c *Checker) resolveBaseTypesOfClass(t *Type) {
if baseConstructorType.flags&(TypeFlagsObject|TypeFlagsIntersection|TypeFlagsAny) == 0 {
return
}
baseTypeNode := getBaseTypeNodeOfClass(t)
baseTypeNode := c.getEffectiveBaseTypeNode(t)
var baseType *Type
var originalBaseType *Type
if baseConstructorType.symbol != nil {
Expand Down Expand Up @@ -21178,7 +21154,7 @@ func (c *Checker) getDefaultConstructSignatures(classType *Type) []*Signature {
flags := core.IfElse(isAbstract, SignatureFlagsConstruct|SignatureFlagsAbstract, SignatureFlagsConstruct)
return []*Signature{c.newSignature(flags, nil, classType.AsInterfaceType().LocalTypeParameters(), nil, nil, classType, nil, 0)}
}
baseTypeNode := getBaseTypeNodeOfClass(classType)
baseTypeNode := c.getEffectiveBaseTypeNode(classType)
isJavaScript := declaration != nil && ast.IsInJSFile(declaration)
typeArguments := c.getTypeArgumentsFromNode(baseTypeNode)
typeArgCount := len(typeArguments)
Expand Down
11 changes: 11 additions & 0 deletions tsc/internal/checker/emitresolver.go
Original file line number Diff line number Diff line change
Expand Up @@ -1321,3 +1321,14 @@ func (r *EmitResolver) IsThisPropertyAssignmentDeclarationRedundant(node *ast.No
}
return false
}

func (r *EmitResolver) GetEffectiveBaseTypeNode(node *ast.Node) *ast.Node {
r.checkerMu.Lock()
defer r.checkerMu.Unlock()
symbol := r.checker.getSymbolOfDeclaration(node)
if symbol == nil {
return ast.GetClassExtendsHeritageElement(node)
}
classType := r.checker.getDeclaredTypeOfSymbol(symbol)
return r.checker.getEffectiveBaseTypeNode(classType)
}
62 changes: 62 additions & 0 deletions tsc/internal/checker/jsdoc.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,3 +98,65 @@ func getAllJSDocTags(node *ast.Node) []*ast.Node {
}
return nil
}

func (c *Checker) getEffectiveBaseTypeNode(t *Type) *ast.Node {
baseTypeNode := getBaseTypeNodeOfClass(t)
if tag := c.tryGetMatchingJSDocAugmentsTag(t, baseTypeNode); tag != nil {
return tag.ClassName()
}
return baseTypeNode
}

func (c *Checker) tryGetMatchingJSDocAugmentsTag(t *Type, baseTypeNode *ast.Node) *ast.Node {
if baseTypeNode == nil || !ast.IsInJSFile(baseTypeNode) || len(baseTypeNode.TypeArguments()) >= 1 {
return nil
}
expression := ast.SkipParentheses(baseTypeNode.Expression())
if ast.IsCallExpression(expression) {
tag := ast.GetJSDocAugmentsTag(ast.GetClassLikeDeclarationOfSymbol(t.symbol))
if tag == nil || len(tag.ClassName().TypeArguments()) == 0 {
return nil
}
baseConstructorType := c.getBaseConstructorTypeOfClass(t)
if baseConstructorType.flags&TypeFlagsIntersection == 0 {
sourceType := c.getTypeFromTypeNode(tag.ClassName())
sourceSymbol := c.getMergedSymbol(getTargetType(sourceType).symbol)
baseSymbol := c.getMergedSymbol(c.getApparentType(baseConstructorType).symbol)
if sourceSymbol != nil && sourceSymbol == baseSymbol {
return tag
}
}
}
return nil
}

func (c *Checker) checkJSDocAugmentsTagMatchesExtends(node *ast.Node, baseTypeNode *ast.ExpressionWithTypeArgumentsNode, baseType *Type, baseConstructorType *Type) {
if !ast.IsInJSFile(node) {
return
}
tag := ast.GetJSDocAugmentsTag(node)
if tag == nil {
return
}
sourceTypeNode := tag.ClassName()
sourceType := c.getTypeFromTypeNode(sourceTypeNode)
sourceSymbol := c.getMergedSymbol(getTargetType(sourceType).symbol)
sourceName := getIdentifierFromEntityNameExpression(sourceTypeNode.Expression())
if sourceName != nil && ast.IsCallExpression(ast.SkipParentheses(baseTypeNode.Expression())) {
targetSymbol := c.getMergedSymbol(c.getApparentType(baseConstructorType).symbol)
if sourceSymbol != nil && targetSymbol != nil && sourceSymbol != targetSymbol {
declarationName := getIdentifierNameOfSymbolDeclaration(targetSymbol)
if declarationName != nil {
c.error(sourceName, diagnostics.JSDoc_0_1_does_not_match_the_extends_2_clause, tag.TagName().Text(), sourceName.Text(), declarationName.Text())
return
}
}
}
if c.isTypeIdenticalTo(sourceType, baseType) {
return
}
targetName := getIdentifierFromEntityNameExpression(baseTypeNode.Expression())
if targetName != nil && sourceName != nil {
c.error(sourceName, diagnostics.JSDoc_0_1_does_not_match_the_extends_2_clause, tag.TagName().Text(), sourceName.Text(), targetName.Text())
}
}
15 changes: 15 additions & 0 deletions tsc/internal/checker/utilities.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,21 @@ func NewDiagnosticChainForNode(chain *ast.Diagnostic, node *ast.Node, message *d
return NewDiagnosticForNode(node, message, args...)
}

func getIdentifierNameOfSymbolDeclaration(symbol *ast.Symbol) *ast.Node {
if symbol == nil {
return nil
}
if name := ast.GetNameOfDeclaration(symbol.ValueDeclaration); name != nil && ast.IsIdentifier(name) {
return name
}
for _, declaration := range symbol.Declarations {
if name := ast.GetNameOfDeclaration(declaration); name != nil && ast.IsIdentifier(name) {
return name
}
}
return nil
}

func findInMap[K comparable, V any](m map[K]V, predicate func(V) bool) V {
for _, value := range m {
if predicate(value) {
Expand Down
47 changes: 47 additions & 0 deletions tsc/internal/parser/parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,53 @@ function foo(options) {}`
assert.Equal(t, scanner.GetTokenPosOfNode(typeNode, file, false /*includeJSDoc*/), strings.Index(sourceText, "{{")+1)
}

func TestJSDocDoesNotAugmentCallHeritage(t *testing.T) {
t.Parallel()
sourceText := `/** @template T */
class A {
static extend() {
return this;
}
}

/** @extends {A<string>} */
class B extends A.extend() {}`
opts := ast.SourceFileParseOptions{
FileName: "/index.js",
Path: "/index.js",
}

file := parser.ParseSourceFile(opts, sourceText, core.ScriptKindJS)
statements := file.Statements.Nodes
assert.Equal(t, len(statements), 2)

classB := statements[1]
assert.Assert(t, ast.IsClassDeclaration(classB))

baseType := ast.GetClassExtendsHeritageElement(classB)
assert.Assert(t, baseType != nil)
assert.Assert(t, ast.IsCallExpression(baseType.Expression()))
assert.Equal(t, scanner.GetTextOfNode(baseType.Expression()), "A.extend()")

typeArguments := baseType.TypeArguments()
assert.Equal(t, len(typeArguments), 0)

jsDocs := classB.JSDoc(file)
assert.Equal(t, len(jsDocs), 1)

tags := jsDocs[0].AsJSDoc().Tags
assert.Assert(t, tags != nil)
assert.Equal(t, len(tags.Nodes), 1)

tag := tags.Nodes[0]
assert.Assert(t, ast.IsJSDocAugmentsTag(tag))

sourceTypeArguments := tag.ClassName().TypeArguments()
assert.Equal(t, len(sourceTypeArguments), 1)
assert.Equal(t, sourceTypeArguments[0].Kind, ast.KindStringKeyword)
assert.Equal(t, ast.GetReparsedNodeForNode(sourceTypeArguments[0]), sourceTypeArguments[0])
}

func TestSourceFilePositionMapWithNonASCIIStringLiteral(t *testing.T) {
t.Parallel()
sourceText := `const x = "─";
Expand Down
20 changes: 11 additions & 9 deletions tsc/internal/parser/reparser.go
Original file line number Diff line number Diff line change
Expand Up @@ -597,21 +597,23 @@ func (p *Parser) reparseHosted(tag *ast.Node, parent *ast.Node, jsDoc *ast.Node)
}); extendsClause != nil && len(extendsClause.AsHeritageClause().Types.Nodes) == 1 {
target := extendsClause.AsHeritageClause().Types.Nodes[0].AsExpressionWithTypeArguments()
source := tag.ClassName().AsExpressionWithTypeArguments()
if ast.HasSamePropertyAccessName(target.Expression, source.Expression) {
if target.TypeArguments == nil && source.TypeArguments != nil {
newArguments := p.nodeSliceArena.NewSlice(len(source.TypeArguments.Nodes))
for i, arg := range source.TypeArguments.Nodes {
newArguments[i] = p.addDeepCloneReparse(arg)
}
target.TypeArguments = p.newNodeList(source.TypeArguments.Loc, newArguments)
p.finishMutatedNode(target.AsNode())
}
if target.TypeArguments == nil && source.TypeArguments != nil && ast.HasSamePropertyAccessName(target.Expression, source.Expression) {
p.setReparsedTypeArguments(target, source.TypeArguments)
}
}
}
}
}

func (p *Parser) setReparsedTypeArguments(target *ast.ExpressionWithTypeArguments, source *ast.NodeList) {
typeArguments := p.nodeSliceArena.NewSlice(len(source.Nodes))
for i, typeArgument := range source.Nodes {
typeArguments[i] = p.addDeepCloneReparse(typeArgument)
}
target.TypeArguments = p.newNodeList(source.Loc, typeArguments)
p.finishMutatedNode(target.AsNode())
}

func (p *Parser) makeQuestionIfOptional(parameter *ast.JSDocParameterOrPropertyTag) *ast.Node {
var questionToken *ast.Node
if parameter.IsBracketed || parameter.TypeExpression != nil && parameter.TypeExpression.Type().Kind == ast.KindJSDocOptionalType {
Expand Down
1 change: 1 addition & 0 deletions tsc/internal/printer/emitresolver.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ type EmitResolver interface {
IsLateBound(node *ast.Node) bool
IsOptionalParameter(node *ast.Node) bool
IsThisPropertyAssignmentDeclarationRedundant(node *ast.Node) bool
GetEffectiveBaseTypeNode(node *ast.Node) *ast.Node

// isolatedDeclarations-specific declaration emit
GetPropertiesOfContainerFunction(node *ast.Node) []*ast.Symbol
Expand Down
9 changes: 7 additions & 2 deletions tsc/internal/transformers/declarations/transform.go
Original file line number Diff line number Diff line change
Expand Up @@ -1996,9 +1996,14 @@ func (tx *DeclarationTransformer) transformClassDeclaration(input *ast.ClassDecl

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

extendsClause := getEffectiveBaseTypeNode(input.AsNode())
extendsClause := ast.GetClassExtendsHeritageElement(input.AsNode())

if extendsClause != nil && !ast.IsEntityNameExpression(extendsClause.AsExpressionWithTypeArguments().Expression) && extendsClause.AsExpressionWithTypeArguments().Expression.Kind != ast.KindNullKeyword {
effectiveBaseType := tx.resolver.GetEffectiveBaseTypeNode(input.AsNode())
typeArguments := extendsClause.AsExpressionWithTypeArguments().TypeArguments
if effectiveBaseType != nil {
typeArguments = effectiveBaseType.AsExpressionWithTypeArguments().TypeArguments
}
tx.tracker.ReportInferenceFallback(extendsClause.AsExpressionWithTypeArguments().Expression) // Add an isolated declarations error on this extends clause
oldId := "default"
if ast.NodeIsPresent(input.Name()) && ast.IsIdentifier(input.Name()) && len(input.Name().Text()) > 0 {
Expand Down Expand Up @@ -2034,7 +2039,7 @@ func (tx *DeclarationTransformer) transformClassDeclaration(input *ast.ClassDecl
tx.Factory().UpdateExpressionWithTypeArguments(
extendsClause.AsExpressionWithTypeArguments(),
newId,
tx.Visitor().VisitNodes(extendsClause.AsExpressionWithTypeArguments().TypeArguments),
tx.Visitor().VisitNodes(typeArguments),
),
}),
)
Expand Down
13 changes: 0 additions & 13 deletions tsc/internal/transformers/declarations/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -161,19 +161,6 @@ func shouldEmitFunctionProperties(input *ast.FunctionDeclaration) bool {
})
}

func getEffectiveBaseTypeNode(node *ast.Node) *ast.Node {
baseType := ast.GetClassExtendsHeritageElement(node)
// !!! TODO: JSDoc support
// if (baseType && isInJSFile(node)) {
// // Prefer an @augments tag because it may have type parameters.
// const tag = getJSDocAugmentsTag(node);
// if (tag) {
// return tag.class;
// }
// }
return baseType
}

func isScopeMarker(node *ast.Node) bool {
return ast.IsExportAssignment(node) || ast.IsExportDeclaration(node)
}
Expand Down
Loading