Skip to content

Commit de67876

Browse files
committed
feat: expose program diagnostics (#343)
1 parent 60dc0df commit de67876

File tree

11 files changed

+475
-55
lines changed

11 files changed

+475
-55
lines changed

cmd/tsgolint/headless.go

Lines changed: 87 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -59,13 +59,25 @@ type headlessSuggestion struct {
5959
Message headlessRuleMessage `json:"message"`
6060
Fixes []headlessFix `json:"fixes"`
6161
}
62+
63+
// Diagnostic kind discriminator
64+
type headlessDiagnosticKind uint8
65+
66+
const (
67+
headlessDiagnosticKindRule headlessDiagnosticKind = iota
68+
headlessDiagnosticKindTsconfig
69+
)
70+
6271
type headlessDiagnostic struct {
63-
Range headlessRange `json:"range"`
64-
Rule string `json:"rule"`
65-
Message headlessRuleMessage `json:"message"`
72+
Kind headlessDiagnosticKind `json:"kind"`
73+
Range headlessRange `json:"range"`
74+
Message headlessRuleMessage `json:"message"`
75+
FilePath *string `json:"file_path"`
76+
77+
// Only for kind="rule"
78+
Rule *string `json:"rule,omitempty"`
6679
Fixes []headlessFix `json:"fixes,omitempty"`
6780
Suggestions []headlessSuggestion `json:"suggestions,omitempty"`
68-
FilePath string `json:"file_path"`
6981
}
7082

7183
type headlessMessageType uint8
@@ -79,6 +91,20 @@ type headlessMessagePayloadError struct {
7991
Error string `json:"error"`
8092
}
8193

94+
// Unified diagnostic type for channel
95+
type anyDiagnostic struct {
96+
ruleDiagnostic *rule.RuleDiagnostic
97+
internalDiagnostic *linter.InternalDiagnostic
98+
}
99+
100+
func ruleToAny(d rule.RuleDiagnostic) anyDiagnostic {
101+
return anyDiagnostic{ruleDiagnostic: &d}
102+
}
103+
104+
func internalToAny(d linter.InternalDiagnostic) anyDiagnostic {
105+
return anyDiagnostic{internalDiagnostic: &d}
106+
}
107+
82108
func writeMessage(w io.Writer, messageType headlessMessageType, payload any) error {
83109
payloadBytes, err := json.Marshal(payload)
84110
if err != nil {
@@ -234,44 +260,71 @@ func runHeadless(args []string) int {
234260

235261
var wg sync.WaitGroup
236262

237-
diagnosticsChan := make(chan rule.RuleDiagnostic, 4096)
263+
diagnosticsChan := make(chan anyDiagnostic, 4096)
238264

265+
// Handle all diagnostics
239266
wg.Go(func() {
240267
w := bufio.NewWriterSize(os.Stdout, 4096*100)
241268
defer w.Flush()
242269
for d := range diagnosticsChan {
243-
hd := headlessDiagnostic{
244-
Range: headlessRangeFromRange(d.Range),
245-
Rule: d.RuleName,
246-
Message: headlessRuleMessageFromRuleMessage(d.Message),
247-
Fixes: nil,
248-
Suggestions: nil,
249-
FilePath: d.SourceFile.FileName(),
250-
}
251-
if fix {
252-
hd.Fixes = make([]headlessFix, len(d.Fixes()))
253-
for i, fix := range d.Fixes() {
254-
hd.Fixes[i] = headlessFix{
255-
Text: fix.Text,
256-
Range: headlessRangeFromRange(fix.Range),
257-
}
270+
var hd headlessDiagnostic
271+
272+
if d.ruleDiagnostic != nil {
273+
// Rule diagnostic
274+
rd := d.ruleDiagnostic
275+
filePath := rd.SourceFile.FileName()
276+
hd = headlessDiagnostic{
277+
Kind: headlessDiagnosticKindRule,
278+
Range: headlessRangeFromRange(rd.Range),
279+
Rule: &rd.RuleName,
280+
Message: headlessRuleMessageFromRuleMessage(rd.Message),
281+
Fixes: nil,
282+
Suggestions: nil,
283+
FilePath: &filePath,
258284
}
259-
}
260-
if fixSuggestions {
261-
hd.Suggestions = make([]headlessSuggestion, len(d.GetSuggestions()))
262-
for i, suggestion := range d.GetSuggestions() {
263-
hd.Suggestions[i] = headlessSuggestion{
264-
Message: headlessRuleMessageFromRuleMessage(d.Message),
265-
Fixes: make([]headlessFix, len(suggestion.Fixes())),
266-
}
267-
for j, fix := range suggestion.Fixes() {
268-
hd.Suggestions[i].Fixes[j] = headlessFix{
285+
286+
if fix {
287+
hd.Fixes = make([]headlessFix, len(rd.Fixes()))
288+
for i, fix := range rd.Fixes() {
289+
hd.Fixes[i] = headlessFix{
269290
Text: fix.Text,
270291
Range: headlessRangeFromRange(fix.Range),
271292
}
272293
}
273294
}
295+
if fixSuggestions {
296+
hd.Suggestions = make([]headlessSuggestion, len(rd.GetSuggestions()))
297+
for i, suggestion := range rd.GetSuggestions() {
298+
hd.Suggestions[i] = headlessSuggestion{
299+
Message: headlessRuleMessageFromRuleMessage(rd.Message),
300+
Fixes: make([]headlessFix, len(suggestion.Fixes())),
301+
}
302+
for j, fix := range suggestion.Fixes() {
303+
hd.Suggestions[i].Fixes[j] = headlessFix{
304+
Text: fix.Text,
305+
Range: headlessRangeFromRange(fix.Range),
306+
}
307+
}
308+
}
309+
}
310+
} else if d.internalDiagnostic != nil {
311+
// Internal diagnostic (tsconfig, type error, etc.)
312+
internalDiagnostic := d.internalDiagnostic
313+
314+
hd = headlessDiagnostic{
315+
Kind: headlessDiagnosticKindTsconfig,
316+
Range: headlessRange{},
317+
Rule: nil, // Internal diagnostics don't have a rule
318+
Message: headlessRuleMessage{
319+
Id: internalDiagnostic.Id,
320+
Description: internalDiagnostic.Description,
321+
},
322+
Fixes: nil,
323+
Suggestions: nil,
324+
FilePath: nil,
325+
}
274326
}
327+
275328
writeMessage(w, headlessMessageTypeDiagnostic, hd)
276329
if w.Available() < 4096 {
277330
w.Flush()
@@ -309,7 +362,10 @@ func runHeadless(args []string) int {
309362
return rules
310363
},
311364
func(d rule.RuleDiagnostic) {
312-
diagnosticsChan <- d
365+
diagnosticsChan <- ruleToAny(d)
366+
},
367+
func(d linter.InternalDiagnostic) {
368+
diagnosticsChan <- internalToAny(d)
313369
},
314370
linter.Fixes{
315371
Fix: fix,

cmd/tsgolint/main.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -402,7 +402,7 @@ func runMain() int {
402402
UseCaseSensitiveFileNames: host.FS().UseCaseSensitiveFileNames(),
403403
}
404404

405-
program, err := utils.CreateProgram(singleThreaded, fs, currentDirectory, configFileName, host)
405+
program, _, err := utils.CreateProgram(singleThreaded, fs, currentDirectory, configFileName, host)
406406
if err != nil {
407407
fmt.Fprintf(os.Stderr, "error creating TS program: %v", err)
408408
return 1

0 commit comments

Comments
 (0)