44
55use std:: fmt;
66
7+ use crate :: hir:: types:: SourceLocation ;
8+
79/// Severity levels for compiler diagnostics.
810#[ derive( Debug , Clone , Copy , PartialEq , Eq ) ]
911pub enum DiagnosticSeverity {
@@ -17,18 +19,184 @@ pub enum DiagnosticSeverity {
1719 Invariant ,
1820}
1921
22+ /// Error category for lint rules, matching upstream's `ErrorCategory` enum.
23+ /// Each variant maps to a distinct ESLint rule name.
24+ #[ derive( Debug , Clone , Copy , PartialEq , Eq , Hash ) ]
25+ pub enum ErrorCategory {
26+ Hooks ,
27+ CapitalizedCalls ,
28+ StaticComponents ,
29+ UseMemo ,
30+ Factories ,
31+ PreserveManualMemo ,
32+ IncompatibleLibrary ,
33+ Immutability ,
34+ Globals ,
35+ Refs ,
36+ EffectDependencies ,
37+ EffectSetState ,
38+ EffectDerivationsOfState ,
39+ ErrorBoundaries ,
40+ Purity ,
41+ RenderSetState ,
42+ Invariant ,
43+ Todo ,
44+ Syntax ,
45+ UnsupportedSyntax ,
46+ Config ,
47+ Gating ,
48+ Suppression ,
49+ AutomaticEffectDependencies ,
50+ Fire ,
51+ FBT ,
52+ /// Used by the no-unused-directives rule (not an upstream ErrorCategory).
53+ UnusedDirective ,
54+ }
55+
56+ impl fmt:: Display for ErrorCategory {
57+ fn fmt ( & self , f : & mut fmt:: Formatter < ' _ > ) -> fmt:: Result {
58+ f. write_str ( self . rule_name ( ) )
59+ }
60+ }
61+
62+ impl ErrorCategory {
63+ /// Returns the kebab-case rule name matching upstream's `LintRule.name`.
64+ pub fn rule_name ( self ) -> & ' static str {
65+ match self {
66+ Self :: Hooks => "hooks" ,
67+ Self :: CapitalizedCalls => "capitalized-calls" ,
68+ Self :: StaticComponents => "static-components" ,
69+ Self :: UseMemo => "use-memo" ,
70+ Self :: Factories => "component-hook-factories" ,
71+ Self :: PreserveManualMemo => "preserve-manual-memoization" ,
72+ Self :: IncompatibleLibrary => "incompatible-library" ,
73+ Self :: Immutability => "immutability" ,
74+ Self :: Globals => "globals" ,
75+ Self :: Refs => "refs" ,
76+ Self :: EffectDependencies => "memoized-effect-dependencies" ,
77+ Self :: EffectSetState => "set-state-in-effect" ,
78+ Self :: EffectDerivationsOfState => "no-deriving-state-in-effects" ,
79+ Self :: ErrorBoundaries => "error-boundaries" ,
80+ Self :: Purity => "purity" ,
81+ Self :: RenderSetState => "set-state-in-render" ,
82+ Self :: Invariant => "invariant" ,
83+ Self :: Todo => "todo" ,
84+ Self :: Syntax => "syntax" ,
85+ Self :: UnsupportedSyntax => "unsupported-syntax" ,
86+ Self :: Config => "config" ,
87+ Self :: Gating => "gating" ,
88+ Self :: Suppression => "rule-suppression" ,
89+ Self :: AutomaticEffectDependencies => "automatic-effect-dependencies" ,
90+ Self :: Fire => "fire" ,
91+ Self :: FBT => "fbt" ,
92+ Self :: UnusedDirective => "no-unused-directives" ,
93+ }
94+ }
95+
96+ /// Returns the default ESLint severity for this category.
97+ pub fn default_severity ( self ) -> ErrorSeverity {
98+ match self {
99+ Self :: IncompatibleLibrary | Self :: UnsupportedSyntax => ErrorSeverity :: Warning ,
100+ Self :: Todo => ErrorSeverity :: Hint ,
101+ _ => ErrorSeverity :: Error ,
102+ }
103+ }
104+
105+ /// Whether this rule is included in the "recommended" preset.
106+ pub fn recommended ( self ) -> bool {
107+ matches ! (
108+ self ,
109+ Self :: StaticComponents
110+ | Self :: UseMemo
111+ | Self :: Factories
112+ | Self :: PreserveManualMemo
113+ | Self :: IncompatibleLibrary
114+ | Self :: Immutability
115+ | Self :: Globals
116+ | Self :: Refs
117+ | Self :: EffectSetState
118+ | Self :: ErrorBoundaries
119+ | Self :: Purity
120+ | Self :: RenderSetState
121+ | Self :: UnsupportedSyntax
122+ | Self :: Config
123+ | Self :: Gating
124+ | Self :: UnusedDirective
125+ )
126+ }
127+ }
128+
129+ /// ESLint-level severity for lint rules.
130+ #[ derive( Debug , Clone , Copy , PartialEq , Eq ) ]
131+ pub enum ErrorSeverity {
132+ Error ,
133+ Warning ,
134+ Hint ,
135+ Off ,
136+ }
137+
138+ impl fmt:: Display for ErrorSeverity {
139+ fn fmt ( & self , f : & mut fmt:: Formatter < ' _ > ) -> fmt:: Result {
140+ match self {
141+ Self :: Error => f. write_str ( "error" ) ,
142+ Self :: Warning => f. write_str ( "warning" ) ,
143+ Self :: Hint => f. write_str ( "hint" ) ,
144+ Self :: Off => f. write_str ( "off" ) ,
145+ }
146+ }
147+ }
148+
149+ /// A related diagnostic location with context message.
150+ #[ derive( Debug , Clone ) ]
151+ pub struct RelatedDiagnostic {
152+ pub message : String ,
153+ pub span : Option < oxc_span:: Span > ,
154+ }
155+
156+ /// An auto-fix suggestion for a diagnostic.
157+ #[ derive( Debug , Clone ) ]
158+ pub enum CompilerSuggestion {
159+ InsertBefore {
160+ description : String ,
161+ range : ( u32 , u32 ) ,
162+ text : String ,
163+ } ,
164+ InsertAfter {
165+ description : String ,
166+ range : ( u32 , u32 ) ,
167+ text : String ,
168+ } ,
169+ Remove {
170+ description : String ,
171+ range : ( u32 , u32 ) ,
172+ } ,
173+ Replace {
174+ description : String ,
175+ range : ( u32 , u32 ) ,
176+ text : String ,
177+ } ,
178+ }
179+
20180/// A compiler diagnostic.
21181#[ derive( Debug , Clone ) ]
22182pub struct CompilerDiagnostic {
23183 pub severity : DiagnosticSeverity ,
24184 pub message : String ,
185+ pub category : ErrorCategory ,
186+ pub span : Option < oxc_span:: Span > ,
187+ pub related : Vec < RelatedDiagnostic > ,
188+ pub suggestions : Vec < CompilerSuggestion > ,
25189}
26190
27191impl Default for CompilerDiagnostic {
28192 fn default ( ) -> Self {
29193 Self {
30194 severity : DiagnosticSeverity :: Invariant ,
31195 message : String :: new ( ) ,
196+ category : ErrorCategory :: Invariant ,
197+ span : None ,
198+ related : Vec :: new ( ) ,
199+ suggestions : Vec :: new ( ) ,
32200 }
33201 }
34202}
@@ -81,6 +249,8 @@ impl CompilerError {
81249 diagnostics : vec ! [ CompilerDiagnostic {
82250 severity: DiagnosticSeverity :: Invariant ,
83251 message,
252+ category: ErrorCategory :: Invariant ,
253+ ..Default :: default ( )
84254 } ] ,
85255 } )
86256 }
@@ -103,6 +273,77 @@ impl From<BailOut> for CompilerError {
103273 }
104274}
105275
276+ /// Extract the OXC span from a `SourceLocation`, if it has one.
277+ pub fn extract_span ( loc : & SourceLocation ) -> Option < oxc_span:: Span > {
278+ match loc {
279+ SourceLocation :: Source ( range) => Some ( range. original_span ) ,
280+ SourceLocation :: Generated => None ,
281+ }
282+ }
283+
284+ /// Extract the source range from a `SourceLocation`, if it has one.
285+ pub fn extract_source_range ( loc : & SourceLocation ) -> Option < & crate :: hir:: types:: SourceRange > {
286+ match loc {
287+ SourceLocation :: Source ( range) => Some ( range) ,
288+ SourceLocation :: Generated => None ,
289+ }
290+ }
291+
292+ /// A lint-related location with line:column info.
293+ #[ derive( Debug , Clone ) ]
294+ pub struct LintRelated {
295+ pub message : String ,
296+ pub start_line : u32 ,
297+ pub start_column : u32 ,
298+ pub end_line : u32 ,
299+ pub end_column : u32 ,
300+ }
301+
302+ /// A lint-level auto-fix suggestion.
303+ #[ derive( Debug , Clone ) ]
304+ pub struct LintSuggestion {
305+ pub description : String ,
306+ pub op : SuggestionOp ,
307+ pub range : ( u32 , u32 ) ,
308+ pub text : Option < String > ,
309+ }
310+
311+ /// The operation type for a suggestion.
312+ #[ derive( Debug , Clone , Copy , PartialEq , Eq ) ]
313+ pub enum SuggestionOp {
314+ InsertBefore ,
315+ InsertAfter ,
316+ Remove ,
317+ Replace ,
318+ }
319+
320+ impl fmt:: Display for SuggestionOp {
321+ fn fmt ( & self , f : & mut fmt:: Formatter < ' _ > ) -> fmt:: Result {
322+ match self {
323+ Self :: InsertBefore => f. write_str ( "insert-before" ) ,
324+ Self :: InsertAfter => f. write_str ( "insert-after" ) ,
325+ Self :: Remove => f. write_str ( "remove" ) ,
326+ Self :: Replace => f. write_str ( "replace" ) ,
327+ }
328+ }
329+ }
330+
331+ /// A structured lint diagnostic with line:column location info,
332+ /// ready to be returned via NAPI.
333+ #[ derive( Debug , Clone ) ]
334+ pub struct LintDiagnostic {
335+ pub category : ErrorCategory ,
336+ pub message : String ,
337+ pub severity : ErrorSeverity ,
338+ pub start_line : u32 ,
339+ pub start_column : u32 ,
340+ pub end_line : u32 ,
341+ pub end_column : u32 ,
342+ pub has_location : bool ,
343+ pub related : Vec < LintRelated > ,
344+ pub suggestions : Vec < LintSuggestion > ,
345+ }
346+
106347#[ cfg( test) ]
107348mod tests {
108349 use super :: * ;
@@ -121,6 +362,37 @@ mod tests {
121362 let _diag = CompilerDiagnostic {
122363 severity : DiagnosticSeverity :: InvalidReact ,
123364 message : "test" . to_string ( ) ,
365+ category : ErrorCategory :: Hooks ,
366+ ..Default :: default ( )
124367 } ;
125368 }
369+
370+ #[ test]
371+ fn test_error_category_rule_names ( ) {
372+ assert_eq ! ( ErrorCategory :: Hooks . rule_name( ) , "hooks" ) ;
373+ assert_eq ! (
374+ ErrorCategory :: RenderSetState . rule_name( ) ,
375+ "set-state-in-render"
376+ ) ;
377+ assert_eq ! (
378+ ErrorCategory :: PreserveManualMemo . rule_name( ) ,
379+ "preserve-manual-memoization"
380+ ) ;
381+ assert_eq ! (
382+ ErrorCategory :: UnusedDirective . rule_name( ) ,
383+ "no-unused-directives"
384+ ) ;
385+ }
386+
387+ #[ test]
388+ fn test_error_severity_display ( ) {
389+ assert_eq ! ( format!( "{}" , ErrorSeverity :: Error ) , "error" ) ;
390+ assert_eq ! ( format!( "{}" , ErrorSeverity :: Warning ) , "warning" ) ;
391+ }
392+
393+ #[ test]
394+ fn test_suggestion_op_display ( ) {
395+ assert_eq ! ( format!( "{}" , SuggestionOp :: InsertBefore ) , "insert-before" ) ;
396+ assert_eq ! ( format!( "{}" , SuggestionOp :: Remove ) , "remove" ) ;
397+ }
126398}
0 commit comments