|
1 | 1 | package lsconv |
2 | 2 |
|
3 | 3 | import ( |
| 4 | + "context" |
4 | 5 | "fmt" |
5 | 6 | "net/url" |
6 | 7 | "slices" |
7 | 8 | "strings" |
8 | 9 | "unicode/utf16" |
9 | 10 | "unicode/utf8" |
10 | 11 |
|
| 12 | + "github.com/microsoft/typescript-go/internal/ast" |
11 | 13 | "github.com/microsoft/typescript-go/internal/core" |
| 14 | + "github.com/microsoft/typescript-go/internal/diagnostics" |
| 15 | + "github.com/microsoft/typescript-go/internal/diagnosticwriter" |
12 | 16 | "github.com/microsoft/typescript-go/internal/lsp/lsproto" |
13 | 17 | "github.com/microsoft/typescript-go/internal/tspath" |
14 | 18 | ) |
@@ -199,3 +203,99 @@ func (c *Converters) PositionToLineAndCharacter(script Script, position core.Tex |
199 | 203 | func ptrTo[T any](v T) *T { |
200 | 204 | return &v |
201 | 205 | } |
| 206 | + |
| 207 | +type diagnosticCapabilities struct { |
| 208 | + relatedInformation bool |
| 209 | + tagValueSet []lsproto.DiagnosticTag |
| 210 | +} |
| 211 | + |
| 212 | +// DiagnosticToLSPPull converts a diagnostic for pull diagnostics (textDocument/diagnostic) |
| 213 | +func DiagnosticToLSPPull(ctx context.Context, converters *Converters, diagnostic *ast.Diagnostic) *lsproto.Diagnostic { |
| 214 | + clientCaps := lsproto.GetClientCapabilities(ctx).TextDocument.Diagnostic |
| 215 | + return diagnosticToLSP(converters, diagnostic, diagnosticCapabilities{ |
| 216 | + relatedInformation: clientCaps.RelatedInformation, |
| 217 | + tagValueSet: clientCaps.TagSupport.ValueSet, |
| 218 | + }) |
| 219 | +} |
| 220 | + |
| 221 | +// DiagnosticToLSPPush converts a diagnostic for push diagnostics (textDocument/publishDiagnostics) |
| 222 | +func DiagnosticToLSPPush(ctx context.Context, converters *Converters, diagnostic *ast.Diagnostic) *lsproto.Diagnostic { |
| 223 | + clientCaps := lsproto.GetClientCapabilities(ctx).TextDocument.PublishDiagnostics |
| 224 | + return diagnosticToLSP(converters, diagnostic, diagnosticCapabilities{ |
| 225 | + relatedInformation: clientCaps.RelatedInformation, |
| 226 | + tagValueSet: clientCaps.TagSupport.ValueSet, |
| 227 | + }) |
| 228 | +} |
| 229 | + |
| 230 | +func diagnosticToLSP(converters *Converters, diagnostic *ast.Diagnostic, caps diagnosticCapabilities) *lsproto.Diagnostic { |
| 231 | + var severity lsproto.DiagnosticSeverity |
| 232 | + switch diagnostic.Category() { |
| 233 | + case diagnostics.CategorySuggestion: |
| 234 | + severity = lsproto.DiagnosticSeverityHint |
| 235 | + case diagnostics.CategoryMessage: |
| 236 | + severity = lsproto.DiagnosticSeverityInformation |
| 237 | + case diagnostics.CategoryWarning: |
| 238 | + severity = lsproto.DiagnosticSeverityWarning |
| 239 | + default: |
| 240 | + severity = lsproto.DiagnosticSeverityError |
| 241 | + } |
| 242 | + |
| 243 | + var relatedInformation []*lsproto.DiagnosticRelatedInformation |
| 244 | + if caps.relatedInformation { |
| 245 | + relatedInformation = make([]*lsproto.DiagnosticRelatedInformation, 0, len(diagnostic.RelatedInformation())) |
| 246 | + for _, related := range diagnostic.RelatedInformation() { |
| 247 | + relatedInformation = append(relatedInformation, &lsproto.DiagnosticRelatedInformation{ |
| 248 | + Location: lsproto.Location{ |
| 249 | + Uri: FileNameToDocumentURI(related.File().FileName()), |
| 250 | + Range: converters.ToLSPRange(related.File(), related.Loc()), |
| 251 | + }, |
| 252 | + Message: related.Message(), |
| 253 | + }) |
| 254 | + } |
| 255 | + } |
| 256 | + |
| 257 | + var tags []lsproto.DiagnosticTag |
| 258 | + if len(caps.tagValueSet) > 0 && (diagnostic.ReportsUnnecessary() || diagnostic.ReportsDeprecated()) { |
| 259 | + tags = make([]lsproto.DiagnosticTag, 0, 2) |
| 260 | + if diagnostic.ReportsUnnecessary() && slices.Contains(caps.tagValueSet, lsproto.DiagnosticTagUnnecessary) { |
| 261 | + tags = append(tags, lsproto.DiagnosticTagUnnecessary) |
| 262 | + } |
| 263 | + if diagnostic.ReportsDeprecated() && slices.Contains(caps.tagValueSet, lsproto.DiagnosticTagDeprecated) { |
| 264 | + tags = append(tags, lsproto.DiagnosticTagDeprecated) |
| 265 | + } |
| 266 | + } |
| 267 | + |
| 268 | + // For diagnostics without a file (e.g., program diagnostics), use a zero range |
| 269 | + var lspRange lsproto.Range |
| 270 | + if diagnostic.File() != nil { |
| 271 | + lspRange = converters.ToLSPRange(diagnostic.File(), diagnostic.Loc()) |
| 272 | + } |
| 273 | + |
| 274 | + return &lsproto.Diagnostic{ |
| 275 | + Range: lspRange, |
| 276 | + Code: &lsproto.IntegerOrString{ |
| 277 | + Integer: ptrTo(diagnostic.Code()), |
| 278 | + }, |
| 279 | + Severity: &severity, |
| 280 | + Message: messageChainToString(diagnostic), |
| 281 | + Source: ptrTo("ts"), |
| 282 | + RelatedInformation: ptrToSliceIfNonEmpty(relatedInformation), |
| 283 | + Tags: ptrToSliceIfNonEmpty(tags), |
| 284 | + } |
| 285 | +} |
| 286 | + |
| 287 | +func messageChainToString(diagnostic *ast.Diagnostic) string { |
| 288 | + if len(diagnostic.MessageChain()) == 0 { |
| 289 | + return diagnostic.Message() |
| 290 | + } |
| 291 | + var b strings.Builder |
| 292 | + diagnosticwriter.WriteFlattenedASTDiagnosticMessage(&b, diagnostic, "\n") |
| 293 | + return b.String() |
| 294 | +} |
| 295 | + |
| 296 | +func ptrToSliceIfNonEmpty[T any](s []T) *[]T { |
| 297 | + if len(s) == 0 { |
| 298 | + return nil |
| 299 | + } |
| 300 | + return &s |
| 301 | +} |
0 commit comments