From c8acc007888400c95cf2e08ce512983b43d23e65 Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Mon, 3 Nov 2025 08:29:59 -0800 Subject: [PATCH 1/9] Fix unised identifier diags, LSP tag diags --- internal/checker/checker.go | 29 +++++++++++++++++++----- internal/lsp/server.go | 44 ++++++++++++++++++++++++++++++++----- 2 files changed, 63 insertions(+), 10 deletions(-) diff --git a/internal/checker/checker.go b/internal/checker/checker.go index 381df4e0bf..1c4a43bfed 100644 --- a/internal/checker/checker.go +++ b/internal/checker/checker.go @@ -2130,7 +2130,7 @@ func (c *Checker) checkSourceFile(ctx context.Context, sourceFile *ast.SourceFil } if ctx.Err() == nil { // This relies on the results of other lazy diagnostics, so must be computed after them - if !sourceFile.IsDeclarationFile && (c.compilerOptions.NoUnusedLocals.IsTrue() || c.compilerOptions.NoUnusedParameters.IsTrue()) { + if !sourceFile.IsDeclarationFile { c.checkUnusedIdentifiers(links.identifierCheckNodes) } if !sourceFile.IsDeclarationFile { @@ -6781,10 +6781,29 @@ func (c *Checker) reportUnusedVariable(location *ast.Node, diagnostic *ast.Diagn } func (c *Checker) reportUnused(location *ast.Node, kind UnusedKind, diagnostic *ast.Diagnostic) { - if location.Flags&(ast.NodeFlagsAmbient|ast.NodeFlagsThisNodeOrAnySubNodesHasError) == 0 && - (kind == UnusedKindLocal && c.compilerOptions.NoUnusedLocals.IsTrue() || - (kind == UnusedKindParameter && c.compilerOptions.NoUnusedParameters.IsTrue())) { - c.diagnostics.Add(diagnostic) + if location.Flags&(ast.NodeFlagsAmbient|ast.NodeFlagsThisNodeOrAnySubNodesHasError) == 0 { + isError := c.unusedIsError(kind, location.Flags&ast.NodeFlagsAmbient != 0) + if isError { + c.diagnostics.Add(diagnostic) + } else { + suggestion := *diagnostic + suggestion.SetCategory(diagnostics.CategorySuggestion) + c.suggestionDiagnostics.Add(&suggestion) + } + } +} + +func (c *Checker) unusedIsError(kind UnusedKind, isAmbient bool) bool { + if isAmbient { + return false + } + switch kind { + case UnusedKindLocal: + return c.compilerOptions.NoUnusedLocals.IsTrue() + case UnusedKindParameter: + return c.compilerOptions.NoUnusedParameters.IsTrue() + default: + panic("Unhandled case in unusedIsError") } } diff --git a/internal/lsp/server.go b/internal/lsp/server.go index 6e24ffd59a..3de2a93983 100644 --- a/internal/lsp/server.go +++ b/internal/lsp/server.go @@ -800,11 +800,7 @@ func (s *Server) handleSetTrace(ctx context.Context, params *lsproto.SetTracePar } func (s *Server) handleDocumentDiagnostic(ctx context.Context, ls *ls.LanguageService, params *lsproto.DocumentDiagnosticParams) (lsproto.DocumentDiagnosticResponse, error) { - var diagnosticClientCapabilities *lsproto.DiagnosticClientCapabilities - if s.initializeParams != nil && s.initializeParams.Capabilities != nil && s.initializeParams.Capabilities.TextDocument != nil { - diagnosticClientCapabilities = s.initializeParams.Capabilities.TextDocument.Diagnostic - } - return ls.ProvideDiagnostics(ctx, params.TextDocument.Uri, diagnosticClientCapabilities) + return ls.ProvideDiagnostics(ctx, params.TextDocument.Uri, getDiagnosticClientCapabilities(s.initializeParams)) } func (s *Server) handleHover(ctx context.Context, ls *ls.LanguageService, params *lsproto.HoverParams) (lsproto.HoverResponse, error) { @@ -992,3 +988,41 @@ func getTypeDefinitionClientSupportsLink(params *lsproto.InitializeParams) bool } return ptrIsTrue(params.Capabilities.TextDocument.TypeDefinition.LinkSupport) } + +func getDiagnosticClientCapabilities(params *lsproto.InitializeParams) *lsproto.DiagnosticClientCapabilities { + if params == nil || params.Capabilities == nil || params.Capabilities.TextDocument == nil { + return nil + } + + diagnosticCaps := params.Capabilities.TextDocument.Diagnostic + publishDiagnostics := params.Capabilities.TextDocument.PublishDiagnostics + + // Merge capabilities from publishDiagnostics if needed + if diagnosticCaps == nil && publishDiagnostics != nil { + // Use publishDiagnostics capabilities directly + return &lsproto.DiagnosticClientCapabilities{ + RelatedInformation: publishDiagnostics.RelatedInformation, + TagSupport: publishDiagnostics.TagSupport, + CodeDescriptionSupport: publishDiagnostics.CodeDescriptionSupport, + DataSupport: publishDiagnostics.DataSupport, + } + } else if diagnosticCaps != nil && publishDiagnostics != nil { + // Fill in missing fields from publishDiagnostics + copied := *diagnosticCaps + if copied.RelatedInformation == nil { + copied.RelatedInformation = publishDiagnostics.RelatedInformation + } + if copied.TagSupport == nil { + copied.TagSupport = publishDiagnostics.TagSupport + } + if copied.CodeDescriptionSupport == nil { + copied.CodeDescriptionSupport = publishDiagnostics.CodeDescriptionSupport + } + if copied.DataSupport == nil { + copied.DataSupport = publishDiagnostics.DataSupport + } + return &copied + } + + return diagnosticCaps +} From 7248cea1b94d7ed70f66f8a5d3b607a45a15fcae Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Mon, 3 Nov 2025 08:44:54 -0800 Subject: [PATCH 2/9] Revert and just don't test client tags --- internal/ls/diagnostics.go | 11 +++++----- internal/lsp/server.go | 44 +++++--------------------------------- 2 files changed, 11 insertions(+), 44 deletions(-) diff --git a/internal/ls/diagnostics.go b/internal/ls/diagnostics.go index 83c3f1d61d..09fdae6c88 100644 --- a/internal/ls/diagnostics.go +++ b/internal/ls/diagnostics.go @@ -2,7 +2,6 @@ package ls import ( "context" - "slices" "strings" "github.com/microsoft/typescript-go/internal/ast" @@ -18,8 +17,9 @@ func (l *LanguageService) ProvideDiagnostics(ctx context.Context, uri lsproto.Do diagnostics := make([][]*ast.Diagnostic, 0, 4) diagnostics = append(diagnostics, program.GetSyntacticDiagnostics(ctx, file)) diagnostics = append(diagnostics, program.GetSemanticDiagnostics(ctx, file)) - // !!! user preference for suggestion diagnostics; keep only unnecessary/deprecated? + // !!! user preference for suggestion diagnostics; keep only unnecessary/deprecated? // See: https://github.com/microsoft/vscode/blob/3dbc74129aaae102e5cb485b958fa5360e8d3e7a/extensions/typescript-language-features/src/languageFeatures/diagnostics.ts#L114 + // TODO: also implement reportStyleCheckAsWarnings to rewrite diags with Warning severity diagnostics = append(diagnostics, program.GetSuggestionDiagnostics(ctx, file)) if program.Options().GetEmitDeclarations() { diagnostics = append(diagnostics, program.GetDeclarationDiagnostics(ctx, file)) @@ -73,13 +73,14 @@ func (l *LanguageService) toLSPDiagnostic(clientOptions *lsproto.DiagnosticClien } } + // We do not check client capabilities for tags; the LSP spec says clients must handle unknown tags. var tags []lsproto.DiagnosticTag - if clientOptions != nil && clientOptions.TagSupport != nil && (diagnostic.ReportsUnnecessary() || diagnostic.ReportsDeprecated()) { + if diagnostic.ReportsUnnecessary() || diagnostic.ReportsDeprecated() { tags = make([]lsproto.DiagnosticTag, 0, 2) - if diagnostic.ReportsUnnecessary() && slices.Contains(clientOptions.TagSupport.ValueSet, lsproto.DiagnosticTagUnnecessary) { + if diagnostic.ReportsUnnecessary() { tags = append(tags, lsproto.DiagnosticTagUnnecessary) } - if diagnostic.ReportsDeprecated() && slices.Contains(clientOptions.TagSupport.ValueSet, lsproto.DiagnosticTagDeprecated) { + if diagnostic.ReportsDeprecated() { tags = append(tags, lsproto.DiagnosticTagDeprecated) } } diff --git a/internal/lsp/server.go b/internal/lsp/server.go index 3de2a93983..6e24ffd59a 100644 --- a/internal/lsp/server.go +++ b/internal/lsp/server.go @@ -800,7 +800,11 @@ func (s *Server) handleSetTrace(ctx context.Context, params *lsproto.SetTracePar } func (s *Server) handleDocumentDiagnostic(ctx context.Context, ls *ls.LanguageService, params *lsproto.DocumentDiagnosticParams) (lsproto.DocumentDiagnosticResponse, error) { - return ls.ProvideDiagnostics(ctx, params.TextDocument.Uri, getDiagnosticClientCapabilities(s.initializeParams)) + var diagnosticClientCapabilities *lsproto.DiagnosticClientCapabilities + if s.initializeParams != nil && s.initializeParams.Capabilities != nil && s.initializeParams.Capabilities.TextDocument != nil { + diagnosticClientCapabilities = s.initializeParams.Capabilities.TextDocument.Diagnostic + } + return ls.ProvideDiagnostics(ctx, params.TextDocument.Uri, diagnosticClientCapabilities) } func (s *Server) handleHover(ctx context.Context, ls *ls.LanguageService, params *lsproto.HoverParams) (lsproto.HoverResponse, error) { @@ -988,41 +992,3 @@ func getTypeDefinitionClientSupportsLink(params *lsproto.InitializeParams) bool } return ptrIsTrue(params.Capabilities.TextDocument.TypeDefinition.LinkSupport) } - -func getDiagnosticClientCapabilities(params *lsproto.InitializeParams) *lsproto.DiagnosticClientCapabilities { - if params == nil || params.Capabilities == nil || params.Capabilities.TextDocument == nil { - return nil - } - - diagnosticCaps := params.Capabilities.TextDocument.Diagnostic - publishDiagnostics := params.Capabilities.TextDocument.PublishDiagnostics - - // Merge capabilities from publishDiagnostics if needed - if diagnosticCaps == nil && publishDiagnostics != nil { - // Use publishDiagnostics capabilities directly - return &lsproto.DiagnosticClientCapabilities{ - RelatedInformation: publishDiagnostics.RelatedInformation, - TagSupport: publishDiagnostics.TagSupport, - CodeDescriptionSupport: publishDiagnostics.CodeDescriptionSupport, - DataSupport: publishDiagnostics.DataSupport, - } - } else if diagnosticCaps != nil && publishDiagnostics != nil { - // Fill in missing fields from publishDiagnostics - copied := *diagnosticCaps - if copied.RelatedInformation == nil { - copied.RelatedInformation = publishDiagnostics.RelatedInformation - } - if copied.TagSupport == nil { - copied.TagSupport = publishDiagnostics.TagSupport - } - if copied.CodeDescriptionSupport == nil { - copied.CodeDescriptionSupport = publishDiagnostics.CodeDescriptionSupport - } - if copied.DataSupport == nil { - copied.DataSupport = publishDiagnostics.DataSupport - } - return &copied - } - - return diagnosticCaps -} From 5d5ea64a9035914c07db114fb5ce4ca9c38b3a36 Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Mon, 3 Nov 2025 08:46:39 -0800 Subject: [PATCH 3/9] Typo --- internal/ls/diagnostics.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/ls/diagnostics.go b/internal/ls/diagnostics.go index 09fdae6c88..034eabdb08 100644 --- a/internal/ls/diagnostics.go +++ b/internal/ls/diagnostics.go @@ -17,7 +17,7 @@ func (l *LanguageService) ProvideDiagnostics(ctx context.Context, uri lsproto.Do diagnostics := make([][]*ast.Diagnostic, 0, 4) diagnostics = append(diagnostics, program.GetSyntacticDiagnostics(ctx, file)) diagnostics = append(diagnostics, program.GetSemanticDiagnostics(ctx, file)) - // !!! user preference for suggestion diagnostics; keep only unnecessary/deprecated? + // !!! user preference for suggestion diagnostics; keep only unnecessary/deprecated? // See: https://github.com/microsoft/vscode/blob/3dbc74129aaae102e5cb485b958fa5360e8d3e7a/extensions/typescript-language-features/src/languageFeatures/diagnostics.ts#L114 // TODO: also implement reportStyleCheckAsWarnings to rewrite diags with Warning severity diagnostics = append(diagnostics, program.GetSuggestionDiagnostics(ctx, file)) From dede6c2440cc361ac6f3bbc7a3d16a69f43e7237 Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Mon, 3 Nov 2025 08:57:36 -0800 Subject: [PATCH 4/9] PR feedback --- internal/checker/checker.go | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/internal/checker/checker.go b/internal/checker/checker.go index 1c4a43bfed..5267b0014c 100644 --- a/internal/checker/checker.go +++ b/internal/checker/checker.go @@ -6782,7 +6782,7 @@ func (c *Checker) reportUnusedVariable(location *ast.Node, diagnostic *ast.Diagn func (c *Checker) reportUnused(location *ast.Node, kind UnusedKind, diagnostic *ast.Diagnostic) { if location.Flags&(ast.NodeFlagsAmbient|ast.NodeFlagsThisNodeOrAnySubNodesHasError) == 0 { - isError := c.unusedIsError(kind, location.Flags&ast.NodeFlagsAmbient != 0) + isError := c.unusedIsError(kind) if isError { c.diagnostics.Add(diagnostic) } else { @@ -6793,10 +6793,7 @@ func (c *Checker) reportUnused(location *ast.Node, kind UnusedKind, diagnostic * } } -func (c *Checker) unusedIsError(kind UnusedKind, isAmbient bool) bool { - if isAmbient { - return false - } +func (c *Checker) unusedIsError(kind UnusedKind) bool { switch kind { case UnusedKindLocal: return c.compilerOptions.NoUnusedLocals.IsTrue() From 4c94c341d94029302d402d3dc8344301fcf03aec Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Mon, 3 Nov 2025 08:57:47 -0800 Subject: [PATCH 5/9] Delete more dead code --- internal/fourslash/fourslash.go | 6 ------ 1 file changed, 6 deletions(-) diff --git a/internal/fourslash/fourslash.go b/internal/fourslash/fourslash.go index 13cfa1c2bc..13dce7a650 100644 --- a/internal/fourslash/fourslash.go +++ b/internal/fourslash/fourslash.go @@ -270,12 +270,6 @@ func getCapabilitiesWithDefaults(capabilities *lsproto.ClientCapabilities) *lspr if capabilitiesWithDefaults.TextDocument.Diagnostic == nil { capabilitiesWithDefaults.TextDocument.Diagnostic = &lsproto.DiagnosticClientCapabilities{ RelatedInformation: ptrTrue, - TagSupport: &lsproto.ClientDiagnosticsTagOptions{ - ValueSet: []lsproto.DiagnosticTag{ - lsproto.DiagnosticTagUnnecessary, - lsproto.DiagnosticTagDeprecated, - }, - }, } } if capabilitiesWithDefaults.Workspace == nil { From 10fb5c444de9e5be8f04403c2395429d06b2300c Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Mon, 3 Nov 2025 10:45:50 -0800 Subject: [PATCH 6/9] Deal with client bug --- internal/fourslash/fourslash.go | 5 +++++ internal/lsp/server.go | 31 ++++++++++++++++++++++++++----- 2 files changed, 31 insertions(+), 5 deletions(-) diff --git a/internal/fourslash/fourslash.go b/internal/fourslash/fourslash.go index 13dce7a650..f0e2436762 100644 --- a/internal/fourslash/fourslash.go +++ b/internal/fourslash/fourslash.go @@ -272,6 +272,11 @@ func getCapabilitiesWithDefaults(capabilities *lsproto.ClientCapabilities) *lspr RelatedInformation: ptrTrue, } } + if capabilitiesWithDefaults.TextDocument.PublishDiagnostics == nil { + capabilitiesWithDefaults.TextDocument.PublishDiagnostics = &lsproto.PublishDiagnosticsClientCapabilities{ + RelatedInformation: ptrTrue, + } + } if capabilitiesWithDefaults.Workspace == nil { capabilitiesWithDefaults.Workspace = &lsproto.WorkspaceClientCapabilities{} } diff --git a/internal/lsp/server.go b/internal/lsp/server.go index 6e24ffd59a..b03edeccbe 100644 --- a/internal/lsp/server.go +++ b/internal/lsp/server.go @@ -800,11 +800,7 @@ func (s *Server) handleSetTrace(ctx context.Context, params *lsproto.SetTracePar } func (s *Server) handleDocumentDiagnostic(ctx context.Context, ls *ls.LanguageService, params *lsproto.DocumentDiagnosticParams) (lsproto.DocumentDiagnosticResponse, error) { - var diagnosticClientCapabilities *lsproto.DiagnosticClientCapabilities - if s.initializeParams != nil && s.initializeParams.Capabilities != nil && s.initializeParams.Capabilities.TextDocument != nil { - diagnosticClientCapabilities = s.initializeParams.Capabilities.TextDocument.Diagnostic - } - return ls.ProvideDiagnostics(ctx, params.TextDocument.Uri, diagnosticClientCapabilities) + return ls.ProvideDiagnostics(ctx, params.TextDocument.Uri, getDiagnosticClientCapabilities(s.initializeParams)) } func (s *Server) handleHover(ctx context.Context, ls *ls.LanguageService, params *lsproto.HoverParams) (lsproto.HoverResponse, error) { @@ -992,3 +988,28 @@ func getTypeDefinitionClientSupportsLink(params *lsproto.InitializeParams) bool } return ptrIsTrue(params.Capabilities.TextDocument.TypeDefinition.LinkSupport) } + +func getDiagnosticClientCapabilities(params *lsproto.InitializeParams) *lsproto.DiagnosticClientCapabilities { + if params == nil || params.Capabilities == nil || params.Capabilities.TextDocument == nil { + return nil + } + + var caps lsproto.DiagnosticClientCapabilities + if params.Capabilities.TextDocument.Diagnostic != nil { + caps = *params.Capabilities.TextDocument.Diagnostic + } + + // Some clients claim that push and pull diagnostics have different capabilities, + // including vscode-languageclient v9. Work around this by defaulting any missing + // pull diagnostic caps with the pull diagnostic equivalents. + // + // TODO: remove when we upgrade to vscode-languageclient v10, which fixes this issue. + if publish := params.Capabilities.TextDocument.PublishDiagnostics; publish != nil { + caps.RelatedInformation = core.Coalesce(caps.RelatedInformation, publish.RelatedInformation) + caps.TagSupport = core.Coalesce(caps.TagSupport, publish.TagSupport) + caps.CodeDescriptionSupport = core.Coalesce(caps.CodeDescriptionSupport, publish.CodeDescriptionSupport) + caps.DataSupport = core.Coalesce(caps.DataSupport, publish.DataSupport) + } + + return &caps +} From 08dd855ec2edcb5daf0d072ea427ab1cb41c113f Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Mon, 3 Nov 2025 10:47:31 -0800 Subject: [PATCH 7/9] Revert other change --- internal/ls/diagnostics.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/internal/ls/diagnostics.go b/internal/ls/diagnostics.go index 034eabdb08..28df9c80bb 100644 --- a/internal/ls/diagnostics.go +++ b/internal/ls/diagnostics.go @@ -2,6 +2,7 @@ package ls import ( "context" + "slices" "strings" "github.com/microsoft/typescript-go/internal/ast" @@ -73,14 +74,13 @@ func (l *LanguageService) toLSPDiagnostic(clientOptions *lsproto.DiagnosticClien } } - // We do not check client capabilities for tags; the LSP spec says clients must handle unknown tags. var tags []lsproto.DiagnosticTag - if diagnostic.ReportsUnnecessary() || diagnostic.ReportsDeprecated() { + if clientOptions != nil && clientOptions.TagSupport != nil && (diagnostic.ReportsUnnecessary() || diagnostic.ReportsDeprecated()) { tags = make([]lsproto.DiagnosticTag, 0, 2) - if diagnostic.ReportsUnnecessary() { + if diagnostic.ReportsUnnecessary() && slices.Contains(clientOptions.TagSupport.ValueSet, lsproto.DiagnosticTagUnnecessary) { tags = append(tags, lsproto.DiagnosticTagUnnecessary) } - if diagnostic.ReportsDeprecated() { + if diagnostic.ReportsDeprecated() && slices.Contains(clientOptions.TagSupport.ValueSet, lsproto.DiagnosticTagDeprecated) { tags = append(tags, lsproto.DiagnosticTagDeprecated) } } From 10308c076adef591a013487f5a8c58cc3d934e6d Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Mon, 3 Nov 2025 10:48:15 -0800 Subject: [PATCH 8/9] Add back --- internal/fourslash/fourslash.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/internal/fourslash/fourslash.go b/internal/fourslash/fourslash.go index f0e2436762..e2433a1881 100644 --- a/internal/fourslash/fourslash.go +++ b/internal/fourslash/fourslash.go @@ -270,11 +270,23 @@ func getCapabilitiesWithDefaults(capabilities *lsproto.ClientCapabilities) *lspr if capabilitiesWithDefaults.TextDocument.Diagnostic == nil { capabilitiesWithDefaults.TextDocument.Diagnostic = &lsproto.DiagnosticClientCapabilities{ RelatedInformation: ptrTrue, + TagSupport: &lsproto.ClientDiagnosticsTagOptions{ + ValueSet: []lsproto.DiagnosticTag{ + lsproto.DiagnosticTagUnnecessary, + lsproto.DiagnosticTagDeprecated, + }, + }, } } if capabilitiesWithDefaults.TextDocument.PublishDiagnostics == nil { capabilitiesWithDefaults.TextDocument.PublishDiagnostics = &lsproto.PublishDiagnosticsClientCapabilities{ RelatedInformation: ptrTrue, + TagSupport: &lsproto.ClientDiagnosticsTagOptions{ + ValueSet: []lsproto.DiagnosticTag{ + lsproto.DiagnosticTagUnnecessary, + lsproto.DiagnosticTagDeprecated, + }, + }, } } if capabilitiesWithDefaults.Workspace == nil { From 7e27486651e1c49a9c844439b4dddfe69928b154 Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Mon, 3 Nov 2025 14:29:44 -0800 Subject: [PATCH 9/9] Defer suggestion diags like Strada --- internal/checker/checker.go | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/internal/checker/checker.go b/internal/checker/checker.go index 1519cb4542..38fb708983 100644 --- a/internal/checker/checker.go +++ b/internal/checker/checker.go @@ -2130,7 +2130,7 @@ func (c *Checker) checkSourceFile(ctx context.Context, sourceFile *ast.SourceFil } if ctx.Err() == nil { // This relies on the results of other lazy diagnostics, so must be computed after them - if !sourceFile.IsDeclarationFile { + if !sourceFile.IsDeclarationFile && (c.compilerOptions.NoUnusedLocals.IsTrue() || c.compilerOptions.NoUnusedParameters.IsTrue()) { c.checkUnusedIdentifiers(links.identifierCheckNodes) } if !sourceFile.IsDeclarationFile { @@ -13417,18 +13417,30 @@ func (c *Checker) GetSuggestionDiagnostics(ctx context.Context, sourceFile *ast. func (c *Checker) getDiagnostics(ctx context.Context, sourceFile *ast.SourceFile, collection *ast.DiagnosticsCollection) []*ast.Diagnostic { c.checkNotCanceled() + isSuggestionDiagnostics := collection == &c.suggestionDiagnostics + + files := c.files if sourceFile != nil { - c.CheckSourceFile(ctx, sourceFile) - if c.wasCanceled { - return nil - } - return collection.GetDiagnosticsForFile(sourceFile.FileName()) + files = []*ast.SourceFile{sourceFile} } - for _, file := range c.files { + + for _, file := range files { c.CheckSourceFile(ctx, file) if c.wasCanceled { return nil } + + // Check unused identifiers as suggestions if we're collecting suggestion diagnostics + // and they are not configured as errors + if isSuggestionDiagnostics && !file.IsDeclarationFile && + !(c.compilerOptions.NoUnusedLocals.IsTrue() || c.compilerOptions.NoUnusedParameters.IsTrue()) { + links := c.sourceFileLinks.Get(file) + c.checkUnusedIdentifiers(links.identifierCheckNodes) + } + } + + if sourceFile != nil { + return collection.GetDiagnosticsForFile(sourceFile.FileName()) } return collection.GetDiagnostics() }