@@ -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+
6271type 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
7183type 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+
82108func 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 ,
0 commit comments