From 136f0f243956620b7d6c2aa0f45f16f8c3c2aba6 Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Wed, 19 Nov 2025 16:38:13 -0800 Subject: [PATCH 1/6] Implement reportStyleCheckAsWarnings --- internal/fourslash/fourslash.go | 3 +- .../tests/unreachableCodeDiagnostics_test.go | 21 +++++++++++ internal/ls/diagnostics.go | 3 +- internal/ls/lsconv/converters.go | 36 ++++++++++++++----- internal/ls/lsutil/userpreferences.go | 4 +++ .../unreachableCodeDiagnostics.baseline | 11 ++++++ 6 files changed, 66 insertions(+), 12 deletions(-) create mode 100644 internal/fourslash/tests/unreachableCodeDiagnostics_test.go create mode 100644 testdata/baselines/reference/fourslash/syntaxandSemanticDiagnostics/unreachableCodeDiagnostics.baseline diff --git a/internal/fourslash/fourslash.go b/internal/fourslash/fourslash.go index 8c7cd9cf09..2929023330 100644 --- a/internal/fourslash/fourslash.go +++ b/internal/fourslash/fourslash.go @@ -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) { diff --git a/internal/fourslash/tests/unreachableCodeDiagnostics_test.go b/internal/fourslash/tests/unreachableCodeDiagnostics_test.go new file mode 100644 index 0000000000..fcdf875597 --- /dev/null +++ b/internal/fourslash/tests/unreachableCodeDiagnostics_test.go @@ -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) +} diff --git a/internal/ls/diagnostics.go b/internal/ls/diagnostics.go index 39ee512a22..50d694d42b 100644 --- a/internal/ls/diagnostics.go +++ b/internal/ls/diagnostics.go @@ -29,6 +29,7 @@ func (l *LanguageService) ProvideDiagnostics(ctx context.Context, uri lsproto.Do }, nil } + func (l *LanguageService) toLSPDiagnostics(ctx context.Context, diagnostics ...[]*ast.Diagnostic) []*lsproto.Diagnostic { size := 0 for _, diagSlice := range diagnostics { @@ -37,7 +38,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().ReportStyleCheckAsWarnings)) } } return lspDiagnostics diff --git a/internal/ls/lsconv/converters.go b/internal/ls/lsconv/converters.go index 4370c2b056..420ee37938 100644 --- a/internal/ls/lsconv/converters.go +++ b/internal/ls/lsconv/converters.go @@ -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" @@ -204,30 +205,43 @@ func ptrTo[T any](v T) *T { return &v } -type diagnosticCapabilities struct { - relatedInformation bool - tagValueSet []lsproto.DiagnosticTag +type diagnosticOptions struct { + reportStyleCheckAsWarnings 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, reportStyleCheckAsWarnings 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{ + reportStyleCheckAsWarnings: reportStyleCheckAsWarnings, // !!! 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, }) } -func diagnosticToLSP(converters *Converters, diagnostic *ast.Diagnostic, caps diagnosticCapabilities) *lsproto.Diagnostic { +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, caps diagnosticOptions) *lsproto.Diagnostic { var severity lsproto.DiagnosticSeverity switch diagnostic.Category() { case diagnostics.CategorySuggestion: @@ -240,6 +254,10 @@ func diagnosticToLSP(converters *Converters, diagnostic *ast.Diagnostic, caps di severity = lsproto.DiagnosticSeverityError } + if caps.reportStyleCheckAsWarnings && severity == lsproto.DiagnosticSeverityError && styleCheckDiagnostics.Has(diagnostic.Code()) { + severity = lsproto.DiagnosticSeverityWarning + } + var relatedInformation []*lsproto.DiagnosticRelatedInformation if caps.relatedInformation { relatedInformation = make([]*lsproto.DiagnosticRelatedInformation, 0, len(diagnostic.RelatedInformation())) diff --git a/internal/ls/lsutil/userpreferences.go b/internal/ls/lsutil/userpreferences.go index 4c6dbb22f2..9c1ddbb407 100644 --- a/internal/ls/lsutil/userpreferences.go +++ b/internal/ls/lsutil/userpreferences.go @@ -19,6 +19,7 @@ func NewDefaultUserPreferences() *UserPreferences { IncludeCompletionsWithSnippetText: core.TSTrue, DisplayPartsForJSDoc: true, DisableLineTextInReferences: true, + ReportStyleCheckAsWarnings: true, } } @@ -148,6 +149,7 @@ type UserPreferences struct { DisableSuggestions bool // !!! DisableLineTextInReferences bool // !!! DisplayPartsForJSDoc bool // !!! + ReportStyleCheckAsWarnings bool } type JsxAttributeCompletionStyle string @@ -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 "reportstylecheckaswarnings": + p.ReportStyleCheckAsWarnings = parseBoolWithDefault(value, true) } } diff --git a/testdata/baselines/reference/fourslash/syntaxandSemanticDiagnostics/unreachableCodeDiagnostics.baseline b/testdata/baselines/reference/fourslash/syntaxandSemanticDiagnostics/unreachableCodeDiagnostics.baseline new file mode 100644 index 0000000000..1e9ae53f6f --- /dev/null +++ b/testdata/baselines/reference/fourslash/syntaxandSemanticDiagnostics/unreachableCodeDiagnostics.baseline @@ -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. + \ No newline at end of file From e67edcf7e1d50479c7bd7bb7b1ab55e01fd7fbd6 Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Wed, 19 Nov 2025 16:42:17 -0800 Subject: [PATCH 2/6] Forgot the link --- internal/ls/lsconv/converters.go | 1 + 1 file changed, 1 insertion(+) diff --git a/internal/ls/lsconv/converters.go b/internal/ls/lsconv/converters.go index 420ee37938..1b93b3d47d 100644 --- a/internal/ls/lsconv/converters.go +++ b/internal/ls/lsconv/converters.go @@ -230,6 +230,7 @@ func DiagnosticToLSPPush(ctx context.Context, converters *Converters, 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(), From 5ed4ae547c5ed5e3adb150720b159d358f389fcc Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Wed, 19 Nov 2025 16:47:32 -0800 Subject: [PATCH 3/6] fmt --- internal/ls/diagnostics.go | 1 - 1 file changed, 1 deletion(-) diff --git a/internal/ls/diagnostics.go b/internal/ls/diagnostics.go index 50d694d42b..da7d18e7a2 100644 --- a/internal/ls/diagnostics.go +++ b/internal/ls/diagnostics.go @@ -29,7 +29,6 @@ func (l *LanguageService) ProvideDiagnostics(ctx context.Context, uri lsproto.Do }, nil } - func (l *LanguageService) toLSPDiagnostics(ctx context.Context, diagnostics ...[]*ast.Diagnostic) []*lsproto.Diagnostic { size := 0 for _, diagSlice := range diagnostics { From 7e94d2143ea557ac6150366354c73c30ae52fce0 Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Wed, 19 Nov 2025 16:48:23 -0800 Subject: [PATCH 4/6] rename --- internal/ls/lsconv/converters.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/internal/ls/lsconv/converters.go b/internal/ls/lsconv/converters.go index 1b93b3d47d..270b0e33c4 100644 --- a/internal/ls/lsconv/converters.go +++ b/internal/ls/lsconv/converters.go @@ -242,7 +242,7 @@ var styleCheckDiagnostics = collections.NewSetFromItems( diagnostics.Not_all_code_paths_return_a_value.Code(), ) -func diagnosticToLSP(converters *Converters, diagnostic *ast.Diagnostic, caps diagnosticOptions) *lsproto.Diagnostic { +func diagnosticToLSP(converters *Converters, diagnostic *ast.Diagnostic, opts diagnosticOptions) *lsproto.Diagnostic { var severity lsproto.DiagnosticSeverity switch diagnostic.Category() { case diagnostics.CategorySuggestion: @@ -255,12 +255,12 @@ func diagnosticToLSP(converters *Converters, diagnostic *ast.Diagnostic, caps di severity = lsproto.DiagnosticSeverityError } - if caps.reportStyleCheckAsWarnings && severity == lsproto.DiagnosticSeverityError && styleCheckDiagnostics.Has(diagnostic.Code()) { + if opts.reportStyleCheckAsWarnings && 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{ @@ -274,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) } } From d6ca95a0247960edafcb97e5ab5af38c2588aef0 Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Thu, 20 Nov 2025 08:34:54 -0800 Subject: [PATCH 5/6] Fix my terrible typo --- internal/ls/diagnostics.go | 3 +-- internal/ls/lsconv/converters.go | 16 ++++++++-------- internal/ls/lsutil/userpreferences.go | 8 ++++---- 3 files changed, 13 insertions(+), 14 deletions(-) diff --git a/internal/ls/diagnostics.go b/internal/ls/diagnostics.go index da7d18e7a2..52d65512ea 100644 --- a/internal/ls/diagnostics.go +++ b/internal/ls/diagnostics.go @@ -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)) @@ -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, l.UserPreferences().ReportStyleCheckAsWarnings)) + lspDiagnostics = append(lspDiagnostics, lsconv.DiagnosticToLSPPull(ctx, l.converters, diag, l.UserPreferences().ReportStyleChecksAsWarnings)) } } return lspDiagnostics diff --git a/internal/ls/lsconv/converters.go b/internal/ls/lsconv/converters.go index 270b0e33c4..051ffcbe52 100644 --- a/internal/ls/lsconv/converters.go +++ b/internal/ls/lsconv/converters.go @@ -206,18 +206,18 @@ func ptrTo[T any](v T) *T { } type diagnosticOptions struct { - reportStyleCheckAsWarnings bool - relatedInformation bool - tagValueSet []lsproto.DiagnosticTag + 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, reportStyleCheckAsWarnings bool) *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, diagnosticOptions{ - reportStyleCheckAsWarnings: reportStyleCheckAsWarnings, // !!! get through context UserPreferences - relatedInformation: clientCaps.RelatedInformation, - tagValueSet: clientCaps.TagSupport.ValueSet, + reportStyleChecksAsWarnings: reportStyleChecksAsWarnings, // !!! get through context UserPreferences + relatedInformation: clientCaps.RelatedInformation, + tagValueSet: clientCaps.TagSupport.ValueSet, }) } @@ -255,7 +255,7 @@ func diagnosticToLSP(converters *Converters, diagnostic *ast.Diagnostic, opts di severity = lsproto.DiagnosticSeverityError } - if opts.reportStyleCheckAsWarnings && severity == lsproto.DiagnosticSeverityError && styleCheckDiagnostics.Has(diagnostic.Code()) { + if opts.reportStyleChecksAsWarnings && severity == lsproto.DiagnosticSeverityError && styleCheckDiagnostics.Has(diagnostic.Code()) { severity = lsproto.DiagnosticSeverityWarning } diff --git a/internal/ls/lsutil/userpreferences.go b/internal/ls/lsutil/userpreferences.go index 9c1ddbb407..fadcfacf61 100644 --- a/internal/ls/lsutil/userpreferences.go +++ b/internal/ls/lsutil/userpreferences.go @@ -19,7 +19,7 @@ func NewDefaultUserPreferences() *UserPreferences { IncludeCompletionsWithSnippetText: core.TSTrue, DisplayPartsForJSDoc: true, DisableLineTextInReferences: true, - ReportStyleCheckAsWarnings: true, + ReportStyleChecksAsWarnings: true, } } @@ -149,7 +149,7 @@ type UserPreferences struct { DisableSuggestions bool // !!! DisableLineTextInReferences bool // !!! DisplayPartsForJSDoc bool // !!! - ReportStyleCheckAsWarnings bool + ReportStyleChecksAsWarnings bool } type JsxAttributeCompletionStyle string @@ -630,7 +630,7 @@ func (p *UserPreferences) set(name string, value any) { p.DisableLineTextInReferences = parseBoolWithDefault(value, true) case "displaypartsforjsdoc": p.DisplayPartsForJSDoc = parseBoolWithDefault(value, true) - case "reportstylecheckaswarnings": - p.ReportStyleCheckAsWarnings = parseBoolWithDefault(value, true) + case "reportstylechecksaswarnings": + p.ReportStyleChecksAsWarnings = parseBoolWithDefault(value, true) } } From 172b9c51d85a0ec03df0a53c5e3c492d8f5e51eb Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Thu, 20 Nov 2025 08:35:33 -0800 Subject: [PATCH 6/6] TODO --- internal/ls/lsutil/userpreferences.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/ls/lsutil/userpreferences.go b/internal/ls/lsutil/userpreferences.go index fadcfacf61..b6ce0c9537 100644 --- a/internal/ls/lsutil/userpreferences.go +++ b/internal/ls/lsutil/userpreferences.go @@ -149,7 +149,7 @@ type UserPreferences struct { DisableSuggestions bool // !!! DisableLineTextInReferences bool // !!! DisplayPartsForJSDoc bool // !!! - ReportStyleChecksAsWarnings bool + ReportStyleChecksAsWarnings bool // !!! If this changes, we need to ask the client to recompute diagnostics } type JsxAttributeCompletionStyle string