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..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)) + 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 4370c2b056..051ffcbe52 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,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, }) } -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: @@ -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{ @@ -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) } } diff --git a/internal/ls/lsutil/userpreferences.go b/internal/ls/lsutil/userpreferences.go index 4c6dbb22f2..b6ce0c9537 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, + ReportStyleChecksAsWarnings: true, } } @@ -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 @@ -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) } } 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