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
82 changes: 67 additions & 15 deletions internal/ast/diagnostic.go
Original file line number Diff line number Diff line change
Expand Up @@ -163,29 +163,69 @@ func NewCompilerDiagnostic(message *diagnostics.Message, args ...any) *Diagnosti
type DiagnosticsCollection struct {
mu sync.Mutex
count int
fileDiagnostics map[string][]*Diagnostic
fileDiagnosticsSorted collections.Set[string]
fileDiagnostics map[*SourceFile][]*Diagnostic
fileDiagnosticsSorted collections.Set[*SourceFile]
nonFileDiagnostics []*Diagnostic
nonFileDiagnosticsSorted bool
diagnosticIndex map[diagnosticLocationKey]*Diagnostic
diagnosticCollisions map[diagnosticLocationKey][]*Diagnostic
}

func (c *DiagnosticsCollection) Add(diagnostic *Diagnostic) {
func (c *DiagnosticsCollection) Add(diagnostic *Diagnostic) *Diagnostic {
c.mu.Lock()
defer c.mu.Unlock()

key := getDiagnosticLocationKey(diagnostic)
if existing := c.diagnosticIndex[key]; existing != nil {
if EqualDiagnostics(existing, diagnostic) {
Comment on lines +178 to +180
return existing
}
for _, collision := range c.diagnosticCollisions[key] {
if EqualDiagnostics(collision, diagnostic) {
return collision
}
}
}
if c.diagnosticIndex == nil {
c.diagnosticIndex = make(map[diagnosticLocationKey]*Diagnostic)
}
if c.diagnosticIndex[key] == nil {
c.diagnosticIndex[key] = diagnostic
} else {
if c.diagnosticCollisions == nil {
c.diagnosticCollisions = make(map[diagnosticLocationKey][]*Diagnostic)
}
c.diagnosticCollisions[key] = append(c.diagnosticCollisions[key], diagnostic)
}

c.count++

if diagnostic.File() != nil {
fileName := diagnostic.File().FileName()
file := diagnostic.File()
if c.fileDiagnostics == nil {
c.fileDiagnostics = make(map[string][]*Diagnostic)
c.fileDiagnostics = make(map[*SourceFile][]*Diagnostic)
}
c.fileDiagnostics[fileName] = append(c.fileDiagnostics[fileName], diagnostic)
c.fileDiagnosticsSorted.Delete(fileName)
c.fileDiagnostics[file] = append(c.fileDiagnostics[file], diagnostic)
c.fileDiagnosticsSorted.Delete(file)
} else {
c.nonFileDiagnostics = append(c.nonFileDiagnostics, diagnostic)
c.nonFileDiagnosticsSorted = false
}
return diagnostic
}

type diagnosticLocationKey struct {
file *SourceFile
loc core.TextRange
code int32
}

func getDiagnosticLocationKey(diagnostic *Diagnostic) diagnosticLocationKey {
return diagnosticLocationKey{
file: diagnostic.File(),
loc: diagnostic.Loc(),
code: diagnostic.Code(),
}
}

func (c *DiagnosticsCollection) Lookup(diagnostic *Diagnostic) *Diagnostic {
Expand All @@ -194,7 +234,7 @@ func (c *DiagnosticsCollection) Lookup(diagnostic *Diagnostic) *Diagnostic {

var diagnostics []*Diagnostic
if diagnostic.File() != nil {
diagnostics = c.getDiagnosticsForFileLocked(diagnostic.File().FileName())
diagnostics = c.getDiagnosticsForFileLocked(diagnostic.File())
} else {
diagnostics = c.getGlobalDiagnosticsLocked()
}
Expand All @@ -219,19 +259,19 @@ func (c *DiagnosticsCollection) getGlobalDiagnosticsLocked() []*Diagnostic {
return slices.Clone(c.nonFileDiagnostics)
}

func (c *DiagnosticsCollection) GetDiagnosticsForFile(fileName string) []*Diagnostic {
func (c *DiagnosticsCollection) GetDiagnosticsForFile(file *SourceFile) []*Diagnostic {
c.mu.Lock()
defer c.mu.Unlock()

return c.getDiagnosticsForFileLocked(fileName)
return c.getDiagnosticsForFileLocked(file)
}

func (c *DiagnosticsCollection) getDiagnosticsForFileLocked(fileName string) []*Diagnostic {
if !c.fileDiagnosticsSorted.Has(fileName) {
slices.SortStableFunc(c.fileDiagnostics[fileName], CompareDiagnostics)
c.fileDiagnosticsSorted.Add(fileName)
func (c *DiagnosticsCollection) getDiagnosticsForFileLocked(file *SourceFile) []*Diagnostic {
if !c.fileDiagnosticsSorted.Has(file) {
slices.SortStableFunc(c.fileDiagnostics[file], CompareDiagnostics)
c.fileDiagnosticsSorted.Add(file)
}
return slices.Clone(c.fileDiagnostics[fileName])
return slices.Clone(c.fileDiagnostics[file])
}

func (c *DiagnosticsCollection) GetDiagnostics() []*Diagnostic {
Expand Down Expand Up @@ -269,10 +309,18 @@ func EqualDiagnosticsNoRelatedInfo(d1, d2 *Diagnostic) bool {
return getDiagnosticPath(d1) == getDiagnosticPath(d2) &&
d1.Loc() == d2.Loc() &&
d1.Code() == d2.Code() &&
getDiagnosticMessageIdentity(d1) == getDiagnosticMessageIdentity(d2) &&
slices.Equal(d1.MessageArgs(), d2.MessageArgs()) &&
slices.EqualFunc(d1.MessageChain(), d2.MessageChain(), equalMessageChain)
}

func getDiagnosticMessageIdentity(diagnostic *Diagnostic) string {
if diagnostic.message != nil && diagnostic.Code() == -1 {
return diagnostic.message.String()
}
return string(diagnostic.MessageKey())
}

func equalMessageChain(c1, c2 *Diagnostic) bool {
if c1 == c2 {
return true
Expand Down Expand Up @@ -346,6 +394,10 @@ func CompareDiagnostics(d1, d2 *Diagnostic) int {
if c != 0 {
return c
}
c = strings.Compare(getDiagnosticMessageIdentity(d1), getDiagnosticMessageIdentity(d2))
if c != 0 {
return c
}
c = slices.Compare(d1.MessageArgs(), d2.MessageArgs())
if c != 0 {
return c
Expand Down
54 changes: 54 additions & 0 deletions internal/ast/diagnostic_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package ast

import (
"testing"

"github.com/microsoft/typescript-go/internal/diagnostics"
)

func TestDiagnosticsCollectionDeduplicatesExactDiagnosticsOnAdd(t *testing.T) {
t.Parallel()

var collection DiagnosticsCollection
first := NewCompilerDiagnostic(diagnostics.Cannot_find_name_0, "x").
AddRelatedInfo(NewCompilerDiagnostic(diagnostics.X_0_is_declared_here, "first"))
second := NewCompilerDiagnostic(diagnostics.Cannot_find_name_0, "x").
AddRelatedInfo(NewCompilerDiagnostic(diagnostics.X_0_is_declared_here, "first"))
different := NewCompilerDiagnostic(diagnostics.Cannot_find_name_0, "x").
AddRelatedInfo(NewCompilerDiagnostic(diagnostics.X_0_is_declared_here, "second"))

if got := collection.Add(first); got != first {
t.Fatalf("first Add() returned %p, want %p", got, first)
}
canonical := collection.Add(second)
if canonical != first {
t.Fatalf("second Add() returned %p, want canonical %p", canonical, first)
}
if got := collection.Add(different); got != different {
t.Fatalf("different Add() returned %p, want %p", got, different)
}

canonical.AddRelatedInfo(NewCompilerDiagnostic(diagnostics.X_0_is_declared_here, "third"))
collected := collection.GetGlobalDiagnostics()
if len(collected) != 2 {
t.Fatalf("GetGlobalDiagnostics() returned %d diagnostics, want 2", len(collected))
}
if got := len(first.RelatedInformation()); got != 2 {
t.Fatalf("canonical diagnostic has %d related diagnostics, want 2", got)
}
}

func TestDiagnosticsCollectionPreservesDistinctAdHocMessages(t *testing.T) {
t.Parallel()

var collection DiagnosticsCollection
first := NewCompilerDiagnostic(diagnostics.NewAdHocMessage("first"))
second := NewCompilerDiagnostic(diagnostics.NewAdHocMessage("second"))

collection.Add(first)
collection.Add(second)
collected := collection.GetGlobalDiagnostics()
if len(collected) != 2 {
t.Fatalf("GetGlobalDiagnostics() returned %d diagnostics, want 2", len(collected))
}
}
55 changes: 30 additions & 25 deletions internal/checker/checker.go
Original file line number Diff line number Diff line change
Expand Up @@ -2497,7 +2497,7 @@ func (c *Checker) checkDeferredNodes(context *ast.SourceFile) {
}
c.checkDeferredNode(node)
}
links.deferredNodes.Clear()
links.deferredNodes = collections.OrderedSet[*ast.Node]{}
}

func (c *Checker) checkDeferredNode(node *ast.Node) {
Expand Down Expand Up @@ -2532,6 +2532,8 @@ func (c *Checker) checkDeferredNode(node *ast.Node) {
if ast.IsInstanceOfExpression(node) {
c.resolveUntypedCall(node)
}
case ast.KindObjectLiteralExpression, ast.KindJsxAttributes:
c.checkContextualDeprecations(node)
}
c.currentNode = saveCurrentNode
}
Expand Down Expand Up @@ -8762,7 +8764,7 @@ func (c *Checker) resolveDecorator(node *ast.Node, candidatesOutArray *[]*Signat
headMessage := c.getDiagnosticHeadMessageForDecoratorResolution(node)
if len(callSignatures) == 0 {
diag := ast.NewDiagnosticChain(c.invocationErrorDetails(node.Expression(), apparentType, SignatureKindCall), headMessage)
c.addDiagnostic(diag)
diag = c.addDiagnostic(diag)
c.invocationErrorRecovery(apparentType, SignatureKindCall, diag)
return c.resolveErrorCall(node)
}
Expand Down Expand Up @@ -9998,7 +10000,7 @@ func (c *Checker) invocationError(errorTarget *ast.Node, apparentType *Type, kin
if relatedInformation != nil {
diagnostic.AddRelatedInfo(relatedInformation)
}
c.addDiagnostic(diagnostic)
diagnostic = c.addDiagnostic(diagnostic)
c.invocationErrorRecovery(apparentType, kind, diagnostic)
}

Expand Down Expand Up @@ -13152,6 +13154,7 @@ func (c *Checker) checkObjectLiteral(node *ast.Node, checkMode CheckMode) *Type
// is nothing to check here.
return result //nolint:customlint // expando object literal has no property children to check
}
c.checkNodeDeferred(node)
inDestructuringPattern := ast.IsAssignmentTarget(node)
// Grammar checking
c.checkGrammarObjectLiteralExpression(node.AsObjectLiteralExpression(), inDestructuringPattern)
Expand Down Expand Up @@ -13268,9 +13271,6 @@ func (c *Checker) checkObjectLiteral(node *ast.Node, checkMode CheckMode) *Type
if allPropertiesTable != nil {
allPropertiesTable[prop.Name] = prop
}
if ast.IsIdentifier(memberDecl.Name()) {
c.checkDeprecatedProperty(memberDecl.Name(), contextualType)
}
if contextualType != nil && checkMode&CheckModeInferential != 0 && checkMode&CheckModeSkipContextSensitive == 0 && (ast.IsPropertyAssignment(memberDecl) || ast.IsMethodDeclaration(memberDecl)) && c.isContextSensitive(memberDecl) {
inferenceContext := c.getInferenceContext(node)
// In CheckMode.Inferential we should always have an inference context
Expand Down Expand Up @@ -13355,8 +13355,20 @@ func (c *Checker) checkObjectLiteral(node *ast.Node, checkMode CheckMode) *Type
return createObjectLiteralType()
}

func (c *Checker) checkDeprecatedProperty(name *ast.IdentifierNode, contextualType *Type) {
if contextualType == nil || name == nil {
func (c *Checker) checkContextualDeprecations(node *ast.Node) {
contextualType := c.getApparentTypeOfContextualType(node, ContextFlagsNone)
for _, property := range node.Properties() {
if c.isCanceled() {
return
}
if property.Name() != nil && !ast.IsComputedPropertyName(property.Name()) {
c.checkDeprecatedProperty(property.Name(), contextualType)
}
}
}

func (c *Checker) checkDeprecatedProperty(name *ast.Node, contextualType *Type) {
if contextualType == nil {
return
}
prop := c.getPropertyOfType(contextualType, name.Text())
Expand Down Expand Up @@ -13963,7 +13975,7 @@ func (c *Checker) getDiagnostics(ctx context.Context, sourceFile *ast.SourceFile
if c.wasCanceled {
return nil
}
return collection.GetDiagnosticsForFile(sourceFile.FileName())
return collection.GetDiagnosticsForFile(sourceFile)
}

func (c *Checker) GetGlobalDiagnostics() []*ast.Diagnostic {
Expand All @@ -13982,24 +13994,24 @@ func (c *Checker) produceDeferredDiagnostics() {
c.deferredDiagnosticCallbacks = nil
}

func (c *Checker) addDiagnostic(diagnostic *ast.Diagnostic) {
func (c *Checker) addDiagnostic(diagnostic *ast.Diagnostic) *ast.Diagnostic {
// Discard diagnostics created while at the maximum number of recursive TypeToString invocations.
if c.serializationLevel < maxSerializationLevel {
c.diagnostics.Add(diagnostic)
return c.diagnostics.Add(diagnostic)
}
return diagnostic
}

func (c *Checker) addSuggestionDiagnostic(diagnostic *ast.Diagnostic) {
func (c *Checker) addSuggestionDiagnostic(diagnostic *ast.Diagnostic) *ast.Diagnostic {
// Discard diagnostics created while at the maximum number of recursive TypeToString invocations.
if c.serializationLevel < maxSerializationLevel {
c.suggestionDiagnostics.Add(diagnostic)
return c.suggestionDiagnostics.Add(diagnostic)
}
return diagnostic
}

func (c *Checker) error(location *ast.Node, message *diagnostics.Message, args ...any) *ast.Diagnostic {
diagnostic := NewDiagnosticForNode(location, message, args...)
c.addDiagnostic(diagnostic)
return diagnostic
return c.addDiagnostic(NewDiagnosticForNode(location, message, args...))
}

func (c *Checker) errorSkippedOnNoEmit(location *ast.Node, message *diagnostics.Message, args ...any) *ast.Diagnostic {
Expand Down Expand Up @@ -14047,8 +14059,7 @@ func (c *Checker) addDeprecatedSuggestionWorker(declarations []*ast.Node, diagno
break
}
}
c.addSuggestionDiagnostic(diagnostic)
return diagnostic
return c.addSuggestionDiagnostic(diagnostic)
}

func (c *Checker) isDeprecatedSymbol(symbol *ast.Symbol) bool {
Expand Down Expand Up @@ -14268,13 +14279,7 @@ func getAdjustedNodeForError(node *ast.Node) *ast.Node {
}

func (c *Checker) lookupOrIssueError(location *ast.Node, message *diagnostics.Message, args ...any) *ast.Diagnostic {
diagnostic := NewDiagnosticForNode(location, message, args...)
existing := c.diagnostics.Lookup(diagnostic)
if existing != nil {
return existing
}
c.addDiagnostic(diagnostic)
return diagnostic
return c.addDiagnostic(NewDiagnosticForNode(location, message, args...))
}

func getFirstDeclaration(symbol *ast.Symbol) *ast.Node {
Expand Down
2 changes: 1 addition & 1 deletion internal/checker/grammarchecks.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ func (c *Checker) checkGrammarRegularExpressionLiteral(node *ast.RegularExpressi
lastError.AddRelatedInfo(err)
} else if lastError == nil || start != lastError.Pos() {
lastError = ast.NewDiagnostic(sourceFile, core.NewTextRange(start, start+length), message, args...)
c.addDiagnostic(lastError)
lastError = c.addDiagnostic(lastError)
}
})
c.regExpScanner.SetText(sourceFile.Text())
Expand Down
4 changes: 1 addition & 3 deletions internal/checker/jsx.go
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ func (c *Checker) checkJsxFragment(node *ast.Node) *Type {
}

func (c *Checker) checkJsxAttributes(node *ast.Node, checkMode CheckMode) *Type {
c.checkNodeDeferred(node)
return c.createJsxAttributesTypeFromAttributesProperty(node.Parent, checkMode)
}

Expand Down Expand Up @@ -756,9 +757,6 @@ func (c *Checker) createJsxAttributesTypeFromAttributesProperty(openingLikeEleme
if attributeDecl.Name().Text() == jsxChildrenPropertyName {
explicitlySpecifyChildrenAttribute = true
}
if ast.IsIdentifier(attributeDecl.Name()) {
c.checkDeprecatedProperty(attributeDecl.Name(), contextualType)
}
if contextualType != nil && checkMode&CheckModeInferential != 0 && checkMode&CheckModeSkipContextSensitive == 0 && c.isContextSensitive(attributeDecl) {
inferenceContext := c.getInferenceContext(attributes)
debug.Assert(inferenceContext != nil)
Expand Down
2 changes: 1 addition & 1 deletion internal/compiler/program.go
Original file line number Diff line number Diff line change
Expand Up @@ -706,7 +706,7 @@ func (p *Program) GetIncludeProcessorDiagnostics(sourceFile *ast.SourceFile) []*
if p.SkipTypeChecking(sourceFile, false) {
return nil
}
filtered, _ := p.getDiagnosticsWithPrecedingDirectives(sourceFile, p.includeProcessor.getDiagnostics(p).GetDiagnosticsForFile(sourceFile.FileName()))
filtered, _ := p.getDiagnosticsWithPrecedingDirectives(sourceFile, p.includeProcessor.getDiagnostics(p).GetDiagnosticsForFile(sourceFile))
return filtered
}

Expand Down
Loading