kittenipc/kitcom/internal/tsgo/ls/diagnostics.go
2025-10-15 10:12:44 +03:00

110 lines
3.8 KiB
Go

package ls
import (
"context"
"strings"
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/ast"
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/diagnostics"
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/diagnosticwriter"
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/lsp/lsproto"
)
func (l *LanguageService) ProvideDiagnostics(ctx context.Context, uri lsproto.DocumentUri) (lsproto.DocumentDiagnosticResponse, error) {
program, file := l.getProgramAndFile(uri)
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?
// See: https://github.com/microsoft/vscode/blob/3dbc74129aaae102e5cb485b958fa5360e8d3e7a/extensions/typescript-language-features/src/languageFeatures/diagnostics.ts#L114
diagnostics = append(diagnostics, program.GetSuggestionDiagnostics(ctx, file))
if program.Options().GetEmitDeclarations() {
diagnostics = append(diagnostics, program.GetDeclarationDiagnostics(ctx, file))
}
return lsproto.RelatedFullDocumentDiagnosticReportOrUnchangedDocumentDiagnosticReport{
FullDocumentDiagnosticReport: &lsproto.RelatedFullDocumentDiagnosticReport{
Items: toLSPDiagnostics(l.converters, diagnostics...),
},
}, nil
}
func toLSPDiagnostics(converters *Converters, diagnostics ...[]*ast.Diagnostic) []*lsproto.Diagnostic {
size := 0
for _, diagSlice := range diagnostics {
size += len(diagSlice)
}
lspDiagnostics := make([]*lsproto.Diagnostic, 0, size)
for _, diagSlice := range diagnostics {
for _, diag := range diagSlice {
lspDiagnostics = append(lspDiagnostics, toLSPDiagnostic(converters, diag))
}
}
return lspDiagnostics
}
func toLSPDiagnostic(converters *Converters, diagnostic *ast.Diagnostic) *lsproto.Diagnostic {
var severity lsproto.DiagnosticSeverity
switch diagnostic.Category() {
case diagnostics.CategorySuggestion:
severity = lsproto.DiagnosticSeverityHint
case diagnostics.CategoryMessage:
severity = lsproto.DiagnosticSeverityInformation
case diagnostics.CategoryWarning:
severity = lsproto.DiagnosticSeverityWarning
default:
severity = lsproto.DiagnosticSeverityError
}
relatedInformation := make([]*lsproto.DiagnosticRelatedInformation, 0, len(diagnostic.RelatedInformation()))
for _, related := range diagnostic.RelatedInformation() {
relatedInformation = append(relatedInformation, &lsproto.DiagnosticRelatedInformation{
Location: lsproto.Location{
Uri: FileNameToDocumentURI(related.File().FileName()),
Range: converters.ToLSPRange(related.File(), related.Loc()),
},
Message: related.Message(),
})
}
var tags []lsproto.DiagnosticTag
if diagnostic.ReportsUnnecessary() || diagnostic.ReportsDeprecated() {
tags = make([]lsproto.DiagnosticTag, 0, 2)
if diagnostic.ReportsUnnecessary() {
tags = append(tags, lsproto.DiagnosticTagUnnecessary)
}
if diagnostic.ReportsDeprecated() {
tags = append(tags, lsproto.DiagnosticTagDeprecated)
}
}
return &lsproto.Diagnostic{
Range: converters.ToLSPRange(diagnostic.File(), diagnostic.Loc()),
Code: &lsproto.IntegerOrString{
Integer: ptrTo(diagnostic.Code()),
},
Severity: &severity,
Message: messageChainToString(diagnostic),
Source: ptrTo("ts"),
RelatedInformation: ptrToSliceIfNonEmpty(relatedInformation),
Tags: ptrToSliceIfNonEmpty(tags),
}
}
func messageChainToString(diagnostic *ast.Diagnostic) string {
if len(diagnostic.MessageChain()) == 0 {
return diagnostic.Message()
}
var b strings.Builder
diagnosticwriter.WriteFlattenedDiagnosticMessage(&b, diagnostic, "\n")
return b.String()
}
func ptrToSliceIfNonEmpty[T any](s []T) *[]T {
if len(s) == 0 {
return nil
}
return &s
}