@@ -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"`
66- Fixes []headlessFix `json:"fixes"`
67- Suggestions []headlessSuggestion `json:"suggestions"`
68- FilePath string `json:"file_path"`
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"`
79+ Fixes []headlessFix `json:"fixes,omitempty"`
80+ Suggestions []headlessSuggestion `json:"suggestions,omitempty"`
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 {
@@ -102,15 +128,19 @@ func runHeadless(args []string) int {
102128 logLevel := utils .GetLogLevel ()
103129
104130 var (
105- traceOut string
106- cpuprofOut string
107- heapOut string
108- allocsOut string
131+ traceOut string
132+ cpuprofOut string
133+ heapOut string
134+ allocsOut string
135+ fix bool
136+ fixSuggestions bool
109137 )
110138 flag .StringVar (& traceOut , "trace" , "" , "file to put trace to" )
111139 flag .StringVar (& cpuprofOut , "cpuprof" , "" , "file to put cpu profiling to" )
112140 flag .StringVar (& heapOut , "heap" , "" , "file to put heap profiling to" )
113141 flag .StringVar (& allocsOut , "allocs" , "" , "file to put allocs profiling to" )
142+ flag .BoolVar (& fix , "fix" , false , "generate fixes for code problems" )
143+ flag .BoolVar (& fixSuggestions , "fix-suggestions" , false , "generate suggestions for code problems" )
114144 flag .CommandLine .Parse (args )
115145
116146 log .SetOutput (os .Stderr )
@@ -138,8 +168,6 @@ func runHeadless(args []string) int {
138168 return 1
139169 }
140170
141- fs := bundled .WrapFS (cachedvfs .From (osvfs .FS ()))
142-
143171 configRaw , err := io .ReadAll (os .Stdin )
144172 if err != nil {
145173 writeErrorMessage (fmt .Sprintf ("error reading from stdin: %v" , err ))
@@ -153,6 +181,12 @@ func runHeadless(args []string) int {
153181 return 1
154182 }
155183
184+ baseFS := osvfs .FS ()
185+ if len (payload .SourceOverrides ) > 0 {
186+ baseFS = newOverlayFS (baseFS , payload .SourceOverrides )
187+ }
188+ fs := bundled .WrapFS (cachedvfs .From (baseFS ))
189+
156190 workload := linter.Workload {
157191 Programs : make (map [string ][]string ),
158192 UnmatchedFiles : []string {},
@@ -226,38 +260,71 @@ func runHeadless(args []string) int {
226260
227261 var wg sync.WaitGroup
228262
229- diagnosticsChan := make (chan rule. RuleDiagnostic , 4096 )
263+ diagnosticsChan := make (chan anyDiagnostic , 4096 )
230264
265+ // Handle all diagnostics
231266 wg .Go (func () {
232267 w := bufio .NewWriterSize (os .Stdout , 4096 * 100 )
233268 defer w .Flush ()
234269 for d := range diagnosticsChan {
235- hd := headlessDiagnostic {
236- Range : headlessRangeFromRange (d .Range ),
237- Rule : d .RuleName ,
238- Message : headlessRuleMessageFromRuleMessage (d .Message ),
239- Fixes : make ([]headlessFix , len (d .Fixes ())),
240- Suggestions : make ([]headlessSuggestion , len (d .GetSuggestions ())),
241- FilePath : d .SourceFile .FileName (),
242- }
243- for i , fix := range d .Fixes () {
244- hd .Fixes [i ] = headlessFix {
245- Text : fix .Text ,
246- Range : headlessRangeFromRange (fix .Range ),
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 ,
247284 }
248- }
249- for i , suggestion := range d .GetSuggestions () {
250- hd .Suggestions [i ] = headlessSuggestion {
251- Message : headlessRuleMessageFromRuleMessage (d .Message ),
252- Fixes : make ([]headlessFix , len (suggestion .Fixes ())),
285+
286+ if fix {
287+ hd .Fixes = make ([]headlessFix , len (rd .Fixes ()))
288+ for i , fix := range rd .Fixes () {
289+ hd .Fixes [i ] = headlessFix {
290+ Text : fix .Text ,
291+ Range : headlessRangeFromRange (fix .Range ),
292+ }
293+ }
253294 }
254- for j , fix := range suggestion .Fixes () {
255- hd .Suggestions [i ].Fixes [j ] = headlessFix {
256- Text : fix .Text ,
257- Range : headlessRangeFromRange (fix .Range ),
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+ }
258308 }
259309 }
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+ }
260326 }
327+
261328 writeMessage (w , headlessMessageTypeDiagnostic , hd )
262329 if w .Available () < 4096 {
263330 w .Flush ()
@@ -274,6 +341,7 @@ func runHeadless(args []string) int {
274341 cwd ,
275342 workload ,
276343 runtime .GOMAXPROCS (0 ),
344+ fs ,
277345 func (sourceFile * ast.SourceFile ) []linter.ConfiguredRule {
278346 cfg := fileConfigs [sourceFile .FileName ()]
279347 rules := make ([]linter.ConfiguredRule , len (cfg ))
@@ -286,15 +354,22 @@ func runHeadless(args []string) int {
286354 rules [i ] = linter.ConfiguredRule {
287355 Name : r .Name ,
288356 Run : func (ctx rule.RuleContext ) rule.RuleListeners {
289- return r .Run (ctx , nil )
357+ return r .Run (ctx , headlessRule . Options )
290358 },
291359 }
292360 }
293361
294362 return rules
295363 },
296364 func (d rule.RuleDiagnostic ) {
297- diagnosticsChan <- d
365+ diagnosticsChan <- ruleToAny (d )
366+ },
367+ func (d linter.InternalDiagnostic ) {
368+ diagnosticsChan <- internalToAny (d )
369+ },
370+ linter.Fixes {
371+ Fix : fix ,
372+ FixSuggestions : fixSuggestions ,
298373 },
299374 )
300375
0 commit comments