@@ -2,8 +2,8 @@ use crate::categories::{
22 SUPPRESSION_INLINE_ACTION_CATEGORY , SUPPRESSION_TOP_LEVEL_ACTION_CATEGORY ,
33} ;
44use crate :: {
5- AnalyzerDiagnostic , AnalyzerOptions , OtherActionCategory , Queryable , RuleDiagnostic , RuleGroup ,
6- ServiceBag , SuppressionAction ,
5+ AnalyzerDiagnostic , AnalyzerOptions , FixKind , GroupCategory , OtherActionCategory , Queryable ,
6+ RuleCategory , RuleDiagnostic , RuleGroup , ServiceBag , SuppressionAction ,
77 categories:: ActionCategory ,
88 context:: RuleContext ,
99 registry:: { RuleLanguage , RuleRoot } ,
@@ -12,15 +12,81 @@ use crate::{
1212use biome_console:: { MarkupBuf , markup} ;
1313use biome_diagnostics:: { Applicability , CodeSuggestion , Error , advice:: CodeSuggestionAdvice } ;
1414use biome_rowan:: { BatchMutation , Language } ;
15+ use enumflags2:: { BitFlag , BitFlags , bitflags} ;
1516use std:: iter:: FusedIterator ;
1617use std:: marker:: PhantomData ;
1718use std:: vec:: IntoIter ;
1819
20+ /// Select which action categories should be computed by the analyzer.
21+ #[ derive( Debug , Copy , Clone , Eq , PartialEq ) ]
22+ pub struct ActionFilter ( pub BitFlags < ActionKind > ) ;
23+
24+ #[ derive( Debug , Copy , Clone , Eq , PartialEq ) ]
25+ #[ bitflags]
26+ #[ repr( u8 ) ]
27+ pub enum ActionKind {
28+ RuleFix = 1 << 0 ,
29+ InlineSuppression = 1 << 1 ,
30+ ToplevelSuppression = 1 << 2 ,
31+ }
32+
33+ impl ActionFilter {
34+ pub fn all ( ) -> Self {
35+ let all: BitFlags < ActionKind > = ActionKind :: all ( ) ;
36+ Self ( all)
37+ }
38+
39+ pub fn rule_fix ( ) -> Self {
40+ let mut filter = ActionKind :: empty ( ) ;
41+ filter. insert ( ActionKind :: RuleFix ) ;
42+ Self ( filter)
43+ }
44+
45+ pub fn inline_suppression ( ) -> Self {
46+ let mut filter = ActionKind :: empty ( ) ;
47+ filter. insert ( ActionKind :: InlineSuppression ) ;
48+ Self ( filter)
49+ }
50+
51+ pub fn toplevel_suppression ( ) -> Self {
52+ let mut filter = ActionKind :: empty ( ) ;
53+ filter. insert ( ActionKind :: ToplevelSuppression ) ;
54+ Self ( filter)
55+ }
56+
57+ pub fn has_actions ( & self ) -> bool {
58+ !self . 0 . is_empty ( )
59+ }
60+
61+ pub fn is_rule_fix ( & self ) -> bool {
62+ self . 0 . contains ( ActionKind :: RuleFix )
63+ }
64+
65+ pub fn is_inline_suppression ( & self ) -> bool {
66+ self . 0 . contains ( ActionKind :: InlineSuppression )
67+ }
68+ pub fn is_toplevel_suppression ( & self ) -> bool {
69+ self . 0 . contains ( ActionKind :: ToplevelSuppression )
70+ }
71+ }
72+
73+ /// Lightweight description of an available action, without the expensive
74+ /// [`BatchMutation`]. Used by `codeAction/resolve` to defer edit computation.
75+ #[ derive( Debug , Clone ) ]
76+ pub struct ActionMetadata {
77+ pub rule_name : Option < ( & ' static str , & ' static str ) > ,
78+ pub category : ActionCategory ,
79+ pub applicability : Applicability ,
80+ }
81+
1982/// Event raised by the analyzer when a [Rule](crate::Rule)
2083/// emits a diagnostic, a code action, or both
2184pub trait AnalyzerSignal < L : Language > {
2285 fn diagnostic ( & self ) -> Option < AnalyzerDiagnostic > ;
23- fn actions ( & self ) -> AnalyzerActionIter < L > ;
86+ fn actions ( & self , filter : ActionFilter ) -> AnalyzerActionIter < L > ;
87+ /// Returns lightweight metadata about available actions without computing
88+ /// mutations. This is cheap — no [`BatchMutation`] or tree clones.
89+ fn actions_metadata ( & self ) -> Vec < ActionMetadata > ;
2490 fn transformations ( & self ) -> AnalyzerTransformationIter < L > ;
2591}
2692
@@ -84,12 +150,17 @@ where
84150 Some ( AnalyzerDiagnostic :: from_error ( error) )
85151 }
86152
87- fn actions ( & self ) -> AnalyzerActionIter < L > {
88- if let Some ( action ) = ( self . action ) ( ) {
89- AnalyzerActionIter :: new ( [ action] )
90- } else {
91- AnalyzerActionIter :: new ( vec ! [ ] )
153+ fn actions ( & self , filter : ActionFilter ) -> AnalyzerActionIter < L > {
154+ if filter . is_rule_fix ( )
155+ && let Some ( action) = ( self . action ) ( )
156+ {
157+ return AnalyzerActionIter :: new ( [ action ] ) ;
92158 }
159+ AnalyzerActionIter :: new ( vec ! [ ] )
160+ }
161+
162+ fn actions_metadata ( & self ) -> Vec < ActionMetadata > {
163+ Vec :: new ( )
93164 }
94165
95166 fn transformations ( & self ) -> AnalyzerTransformationIter < L > {
@@ -128,10 +199,14 @@ impl<L: Language> AnalyzerSignal<L> for PluginSignal<L> {
128199 Some ( AnalyzerDiagnostic :: from ( self . diagnostic . clone ( ) ) )
129200 }
130201
131- fn actions ( & self ) -> AnalyzerActionIter < L > {
202+ fn actions ( & self , _filter : ActionFilter ) -> AnalyzerActionIter < L > {
132203 AnalyzerActionIter :: new ( vec ! [ ] )
133204 }
134205
206+ fn actions_metadata ( & self ) -> Vec < ActionMetadata > {
207+ Vec :: new ( )
208+ }
209+
135210 fn transformations ( & self ) -> AnalyzerTransformationIter < L > {
136211 AnalyzerTransformationIter :: new ( vec ! [ ] )
137212 }
@@ -429,17 +504,21 @@ where
429504 } )
430505 }
431506
432- fn actions ( & self ) -> AnalyzerActionIter < RuleLanguage < R > > {
507+ fn actions ( & self , filter : ActionFilter ) -> AnalyzerActionIter < RuleLanguage < R > > {
433508 let globals = self . options . globals ( ) ;
434509
435- let configured_applicability = if let Some ( fix_kind) = self . options . rule_fix_kind :: < R > ( ) {
436- match fix_kind {
437- crate :: FixKind :: None => {
438- // The action is disabled
439- return AnalyzerActionIter :: new ( vec ! [ ] ) ;
510+ // When fix is set to "none", disable the rule fix but still allow
511+ // suppression actions.
512+ let fix_disabled = matches ! ( self . options. rule_fix_kind:: <R >( ) , Some ( FixKind :: None ) ) ;
513+ let configured_applicability = if filter. is_rule_fix ( ) && !fix_disabled {
514+ if let Some ( fix_kind) = self . options . rule_fix_kind :: < R > ( ) {
515+ match fix_kind {
516+ FixKind :: None => None ,
517+ FixKind :: Safe => Some ( Applicability :: Always ) ,
518+ FixKind :: Unsafe => Some ( Applicability :: MaybeIncorrect ) ,
440519 }
441- crate :: FixKind :: Safe => Some ( Applicability :: Always ) ,
442- crate :: FixKind :: Unsafe => Some ( Applicability :: MaybeIncorrect ) ,
520+ } else {
521+ None
443522 }
444523 } else {
445524 None
@@ -462,16 +541,21 @@ where
462541 . ok ( ) ;
463542 let mut actions = Vec :: new ( ) ;
464543 if let Some ( ctx) = ctx {
465- if let Some ( action) = R :: action ( & ctx, & self . state ) {
544+ if filter. is_rule_fix ( )
545+ && !fix_disabled
546+ && let Some ( action) = R :: action ( & ctx, & self . state )
547+ {
466548 actions. push ( AnalyzerAction {
467549 rule_name : Some ( ( <R :: Group as RuleGroup >:: NAME , R :: METADATA . name ) ) ,
468550 applicability : configured_applicability. unwrap_or ( action. applicability ( ) ) ,
469551 category : action. category ,
470552 mutation : action. mutation ,
471553 message : action. message ,
472554 } ) ;
473- } ;
474- if let Some ( text_range) = R :: text_range ( & ctx, & self . state )
555+ }
556+
557+ if filter. is_inline_suppression ( )
558+ && let Some ( text_range) = R :: text_range ( & ctx, & self . state )
475559 && let Some ( suppression_action) = R :: inline_suppression (
476560 & ctx,
477561 & text_range,
@@ -489,8 +573,9 @@ where
489573 actions. push ( action) ;
490574 }
491575
492- if let Some ( suppression_action) =
493- R :: top_level_suppression ( & ctx, self . suppression_action )
576+ if filter. is_toplevel_suppression ( )
577+ && let Some ( suppression_action) =
578+ R :: top_level_suppression ( & ctx, self . suppression_action )
494579 {
495580 let action = AnalyzerAction {
496581 rule_name : Some ( ( <R :: Group as RuleGroup >:: NAME , R :: METADATA . name ) ) ,
@@ -508,6 +593,54 @@ where
508593 }
509594 }
510595
596+ fn actions_metadata ( & self ) -> Vec < ActionMetadata > {
597+ let rule_name = Some ( ( <R :: Group as RuleGroup >:: NAME , R :: METADATA . name ) ) ;
598+ let rule_category = <<R :: Group as RuleGroup >:: Category as GroupCategory >:: CATEGORY ;
599+ let has_suppression = matches ! (
600+ rule_category,
601+ RuleCategory :: Lint | RuleCategory :: Action | RuleCategory :: Syntax
602+ ) ;
603+
604+ let mut metadata = Vec :: new ( ) ;
605+
606+ // Rule fix metadata — only if the rule declares a fix
607+ let fix_kind = self . options . rule_fix_kind :: < R > ( ) ;
608+ let is_disabled = matches ! ( fix_kind, Some ( FixKind :: None ) ) ;
609+ if !is_disabled && R :: METADATA . fix_kind != FixKind :: None {
610+ let applicability = match fix_kind {
611+ Some ( FixKind :: Safe ) => Applicability :: Always ,
612+ Some ( FixKind :: Unsafe ) => Applicability :: MaybeIncorrect ,
613+ _ => match R :: METADATA . fix_kind {
614+ FixKind :: Safe => Applicability :: Always ,
615+ _ => Applicability :: MaybeIncorrect ,
616+ } ,
617+ } ;
618+ let action_category =
619+ R :: METADATA . action_category ( rule_category, <R :: Group as RuleGroup >:: NAME ) ;
620+ metadata. push ( ActionMetadata {
621+ rule_name,
622+ category : action_category,
623+ applicability,
624+ } ) ;
625+ }
626+
627+ // Suppression metadata
628+ if has_suppression {
629+ metadata. push ( ActionMetadata {
630+ rule_name,
631+ category : ActionCategory :: Other ( OtherActionCategory :: InlineSuppression ) ,
632+ applicability : Applicability :: Always ,
633+ } ) ;
634+ metadata. push ( ActionMetadata {
635+ rule_name,
636+ category : ActionCategory :: Other ( OtherActionCategory :: ToplevelSuppression ) ,
637+ applicability : Applicability :: Always ,
638+ } ) ;
639+ }
640+
641+ metadata
642+ }
643+
511644 fn transformations ( & self ) -> AnalyzerTransformationIter < RuleLanguage < R > > {
512645 let globals = self . options . globals ( ) ;
513646 let options = self . options . rule_options :: < R > ( ) . unwrap_or_default ( ) ;
0 commit comments