Skip to content
Merged
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
3 changes: 1 addition & 2 deletions internal/fourslash/fourslash.go
Original file line number Diff line number Diff line change
Expand Up @@ -2568,8 +2568,7 @@ func (f *FourslashTest) getDiagnostics(t *testing.T, fileName string) []*lsproto
}

func isSuggestionDiagnostic(diag *lsproto.Diagnostic) bool {
return diag.Tags != nil && len(*diag.Tags) > 0 ||
(diag.Severity != nil && *diag.Severity == lsproto.DiagnosticSeverityHint)
return diag.Severity != nil && *diag.Severity == lsproto.DiagnosticSeverityHint
}

func (f *FourslashTest) VerifyBaselineNonSuggestionDiagnostics(t *testing.T) {
Expand Down
21 changes: 21 additions & 0 deletions internal/fourslash/tests/unreachableCodeDiagnostics_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package fourslash_test

import (
"testing"

"github.com/microsoft/typescript-go/internal/fourslash"
"github.com/microsoft/typescript-go/internal/testutil"
)

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

defer testutil.RecoverAndFail(t, "Panic on fourslash test")
const content = `// @allowUnreachableCode: false
throw new Error();

(() => {})();
`
f := fourslash.NewFourslash(t, nil /*capabilities*/, content)
f.VerifyBaselineNonSuggestionDiagnostics(t)
}
3 changes: 1 addition & 2 deletions internal/ls/diagnostics.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ 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))
Expand All @@ -37,7 +36,7 @@ func (l *LanguageService) toLSPDiagnostics(ctx context.Context, diagnostics ...[
lspDiagnostics := make([]*lsproto.Diagnostic, 0, size)
for _, diagSlice := range diagnostics {
for _, diag := range diagSlice {
lspDiagnostics = append(lspDiagnostics, lsconv.DiagnosticToLSPPull(ctx, l.converters, diag))
lspDiagnostics = append(lspDiagnostics, lsconv.DiagnosticToLSPPull(ctx, l.converters, diag, l.UserPreferences().ReportStyleChecksAsWarnings))
}
}
return lspDiagnostics
Expand Down
45 changes: 32 additions & 13 deletions internal/ls/lsconv/converters.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"unicode/utf8"

"github.com/microsoft/typescript-go/internal/ast"
"github.com/microsoft/typescript-go/internal/collections"
"github.com/microsoft/typescript-go/internal/core"
"github.com/microsoft/typescript-go/internal/diagnostics"
"github.com/microsoft/typescript-go/internal/diagnosticwriter"
Expand Down Expand Up @@ -204,30 +205,44 @@ func ptrTo[T any](v T) *T {
return &v
}

type diagnosticCapabilities struct {
relatedInformation bool
tagValueSet []lsproto.DiagnosticTag
type diagnosticOptions struct {
reportStyleChecksAsWarnings bool
relatedInformation bool
tagValueSet []lsproto.DiagnosticTag
}

// DiagnosticToLSPPull converts a diagnostic for pull diagnostics (textDocument/diagnostic)
func DiagnosticToLSPPull(ctx context.Context, converters *Converters, diagnostic *ast.Diagnostic) *lsproto.Diagnostic {
func DiagnosticToLSPPull(ctx context.Context, converters *Converters, diagnostic *ast.Diagnostic, reportStyleChecksAsWarnings bool) *lsproto.Diagnostic {
clientCaps := lsproto.GetClientCapabilities(ctx).TextDocument.Diagnostic
return diagnosticToLSP(converters, diagnostic, diagnosticCapabilities{
relatedInformation: clientCaps.RelatedInformation,
tagValueSet: clientCaps.TagSupport.ValueSet,
return diagnosticToLSP(converters, diagnostic, diagnosticOptions{
reportStyleChecksAsWarnings: reportStyleChecksAsWarnings, // !!! get through context UserPreferences
relatedInformation: clientCaps.RelatedInformation,
tagValueSet: clientCaps.TagSupport.ValueSet,
})
}

// DiagnosticToLSPPush converts a diagnostic for push diagnostics (textDocument/publishDiagnostics)
func DiagnosticToLSPPush(ctx context.Context, converters *Converters, diagnostic *ast.Diagnostic) *lsproto.Diagnostic {
clientCaps := lsproto.GetClientCapabilities(ctx).TextDocument.PublishDiagnostics
return diagnosticToLSP(converters, diagnostic, diagnosticCapabilities{
return diagnosticToLSP(converters, diagnostic, diagnosticOptions{
relatedInformation: clientCaps.RelatedInformation,
tagValueSet: clientCaps.TagSupport.ValueSet,
})
}
Comment on lines 225 to 231
Copy link

Copilot AI Nov 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The DiagnosticToLSPPush function is missing the reportStyleCheckAsWarnings parameter and doesn't pass it to diagnosticOptions. This means push diagnostics (used for textDocument/publishDiagnostics in session.go) won't respect the user preference to convert style checks to warnings. The function signature should accept a reportStyleCheckAsWarnings bool parameter like DiagnosticToLSPPull does, and pass it to diagnosticOptions.

Copilot uses AI. Check for mistakes.
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Push diags are only used for program diags which these cannot be; but this will gain the functionality through context at some point, yes


func diagnosticToLSP(converters *Converters, diagnostic *ast.Diagnostic, caps diagnosticCapabilities) *lsproto.Diagnostic {
// https://github.com/microsoft/vscode/blob/93e08afe0469712706ca4e268f778cfadf1a43ef/extensions/typescript-language-features/src/typeScriptServiceClientHost.ts#L40C7-L40C29
var styleCheckDiagnostics = collections.NewSetFromItems(
diagnostics.X_0_is_declared_but_never_used.Code(),
diagnostics.X_0_is_declared_but_its_value_is_never_read.Code(),
diagnostics.Property_0_is_declared_but_its_value_is_never_read.Code(),
diagnostics.All_imports_in_import_declaration_are_unused.Code(),
diagnostics.Unreachable_code_detected.Code(),
diagnostics.Unused_label.Code(),
diagnostics.Fallthrough_case_in_switch.Code(),
diagnostics.Not_all_code_paths_return_a_value.Code(),
)

func diagnosticToLSP(converters *Converters, diagnostic *ast.Diagnostic, opts diagnosticOptions) *lsproto.Diagnostic {
var severity lsproto.DiagnosticSeverity
switch diagnostic.Category() {
case diagnostics.CategorySuggestion:
Expand All @@ -240,8 +255,12 @@ func diagnosticToLSP(converters *Converters, diagnostic *ast.Diagnostic, caps di
severity = lsproto.DiagnosticSeverityError
}

if opts.reportStyleChecksAsWarnings && severity == lsproto.DiagnosticSeverityError && styleCheckDiagnostics.Has(diagnostic.Code()) {
severity = lsproto.DiagnosticSeverityWarning
}

var relatedInformation []*lsproto.DiagnosticRelatedInformation
if caps.relatedInformation {
if opts.relatedInformation {
relatedInformation = make([]*lsproto.DiagnosticRelatedInformation, 0, len(diagnostic.RelatedInformation()))
for _, related := range diagnostic.RelatedInformation() {
relatedInformation = append(relatedInformation, &lsproto.DiagnosticRelatedInformation{
Expand All @@ -255,12 +274,12 @@ func diagnosticToLSP(converters *Converters, diagnostic *ast.Diagnostic, caps di
}

var tags []lsproto.DiagnosticTag
if len(caps.tagValueSet) > 0 && (diagnostic.ReportsUnnecessary() || diagnostic.ReportsDeprecated()) {
if len(opts.tagValueSet) > 0 && (diagnostic.ReportsUnnecessary() || diagnostic.ReportsDeprecated()) {
tags = make([]lsproto.DiagnosticTag, 0, 2)
if diagnostic.ReportsUnnecessary() && slices.Contains(caps.tagValueSet, lsproto.DiagnosticTagUnnecessary) {
if diagnostic.ReportsUnnecessary() && slices.Contains(opts.tagValueSet, lsproto.DiagnosticTagUnnecessary) {
tags = append(tags, lsproto.DiagnosticTagUnnecessary)
}
if diagnostic.ReportsDeprecated() && slices.Contains(caps.tagValueSet, lsproto.DiagnosticTagDeprecated) {
if diagnostic.ReportsDeprecated() && slices.Contains(opts.tagValueSet, lsproto.DiagnosticTagDeprecated) {
tags = append(tags, lsproto.DiagnosticTagDeprecated)
}
}
Expand Down
4 changes: 4 additions & 0 deletions internal/ls/lsutil/userpreferences.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ func NewDefaultUserPreferences() *UserPreferences {
IncludeCompletionsWithSnippetText: core.TSTrue,
DisplayPartsForJSDoc: true,
DisableLineTextInReferences: true,
ReportStyleChecksAsWarnings: true,
}
}

Expand Down Expand Up @@ -148,6 +149,7 @@ type UserPreferences struct {
DisableSuggestions bool // !!!
DisableLineTextInReferences bool // !!!
DisplayPartsForJSDoc bool // !!!
ReportStyleChecksAsWarnings bool // !!! If this changes, we need to ask the client to recompute diagnostics
}

type JsxAttributeCompletionStyle string
Expand Down Expand Up @@ -628,5 +630,7 @@ func (p *UserPreferences) set(name string, value any) {
p.DisableLineTextInReferences = parseBoolWithDefault(value, true)
case "displaypartsforjsdoc":
p.DisplayPartsForJSDoc = parseBoolWithDefault(value, true)
case "reportstylechecksaswarnings":
p.ReportStyleChecksAsWarnings = parseBoolWithDefault(value, true)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// === Syntax and Semantic Diagnostics ===
/unreachableCodeDiagnostics.ts(3,1): warning TS7027: Unreachable code detected.


==== /unreachableCodeDiagnostics.ts (1 errors) ====
throw new Error();

(() => {})();
~~~~~~~~~~~~~
!!! warning TS7027: Unreachable code detected.