diff --git a/internal/checker/checker.go b/internal/checker/checker.go index ccd4968c9d..38fb708983 100644 --- a/internal/checker/checker.go +++ b/internal/checker/checker.go @@ -6781,10 +6781,26 @@ 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) + if isError { + c.diagnostics.Add(diagnostic) + } else { + suggestion := *diagnostic + suggestion.SetCategory(diagnostics.CategorySuggestion) + c.suggestionDiagnostics.Add(&suggestion) + } + } +} + +func (c *Checker) unusedIsError(kind UnusedKind) bool { + switch kind { + case UnusedKindLocal: + return c.compilerOptions.NoUnusedLocals.IsTrue() + case UnusedKindParameter: + return c.compilerOptions.NoUnusedParameters.IsTrue() + default: + panic("Unhandled case in unusedIsError") } } @@ -13401,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() } diff --git a/internal/fourslash/fourslash.go b/internal/fourslash/fourslash.go index 842e43d52d..1b78246b62 100644 --- a/internal/fourslash/fourslash.go +++ b/internal/fourslash/fourslash.go @@ -282,6 +282,17 @@ func getCapabilitiesWithDefaults(capabilities *lsproto.ClientCapabilities) *lspr }, } } + 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 { capabilitiesWithDefaults.Workspace = &lsproto.WorkspaceClientCapabilities{} } diff --git a/internal/ls/diagnostics.go b/internal/ls/diagnostics.go index 83c3f1d61d..28df9c80bb 100644 --- a/internal/ls/diagnostics.go +++ b/internal/ls/diagnostics.go @@ -20,6 +20,7 @@ func (l *LanguageService) ProvideDiagnostics(ctx context.Context, uri lsproto.Do diagnostics = append(diagnostics, program.GetSemanticDiagnostics(ctx, file)) // !!! 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)) diff --git a/internal/lsp/server.go b/internal/lsp/server.go index 0f086229b6..cecd426747 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) { @@ -1037,3 +1033,28 @@ func getSignatureHelpDocumentationFormat(params *lsproto.InitializeParams) lspro // Return the first (most preferred) format return formats[0] } + +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 +}