@@ -165,63 +165,22 @@ pub struct LinterConfig {
165165 /// Markdown flavor: "standard", "mkdocs", "mdx", "quarto", "obsidian", or "kramdown"
166166 pub flavor : Option < String > ,
167167
168+ /// Rules allowed to apply fixes (if specified, only these rules are fixed)
169+ pub fixable : Option < Vec < String > > ,
170+
171+ /// Rules that should never apply fixes (takes precedence over fixable)
172+ pub unfixable : Option < Vec < String > > ,
173+
168174 /// Rule-specific configurations
169175 /// Keys are rule names (e.g., "MD060", "MD013") and values are rule options
170176 #[ serde( flatten) ]
171177 pub rules : Option < std:: collections:: HashMap < String , serde_json:: Value > > ,
172178}
173179
174180impl LinterConfig {
175- /// Convert to internal Config
181+ /// Convert to internal Config (discards any config parse warnings)
176182 fn to_config ( & self ) -> Config {
177- let mut config = Config :: default ( ) ;
178-
179- // Apply disabled rules
180- if let Some ( ref disable) = self . disable {
181- config. global . disable = disable. clone ( ) ;
182- }
183-
184- // Apply enabled rules
185- if let Some ( ref enable) = self . enable {
186- config. global . enable = enable. clone ( ) ;
187- }
188-
189- // Apply extend-enable / extend-disable
190- if let Some ( ref extend_enable) = self . extend_enable {
191- config. global . extend_enable = extend_enable. clone ( ) ;
192- }
193- if let Some ( ref extend_disable) = self . extend_disable {
194- config. global . extend_disable = extend_disable. clone ( ) ;
195- }
196-
197- // Apply line length
198- if let Some ( line_length) = self . line_length {
199- config. global . line_length = LineLength :: new ( line_length as usize ) ;
200- }
201-
202- // Apply flavor
203- config. global . flavor = self . markdown_flavor ( ) ;
204-
205- // Apply rule-specific configurations
206- if let Some ( ref rules) = self . rules {
207- for ( rule_name, json_value) in rules {
208- // Only process keys that look like rule names (MD###)
209- if !is_rule_name ( rule_name) {
210- continue ;
211- }
212-
213- // Convert JSON value to RuleConfig
214- let result = json_to_rule_config_with_warnings ( json_value) ;
215- if let Some ( rule_config) = result. config {
216- config. rules . insert ( rule_name. to_ascii_uppercase ( ) , rule_config) ;
217- }
218- }
219- }
220-
221- // Per-rule `enabled = true` → extend_enable for opt-in rules
222- config. promote_enabled_to_extend_enable ( ) ;
223-
224- config
183+ self . to_config_with_warnings ( ) . 0
225184 }
226185
227186 /// Convert to internal Config, collecting any warnings about invalid configuration
@@ -234,9 +193,10 @@ impl LinterConfig {
234193 config. global . disable = disable. clone ( ) ;
235194 }
236195
237- // Apply enabled rules
196+ // Apply enabled rules (presence of `enable` key means explicit mode)
238197 if let Some ( ref enable) = self . enable {
239198 config. global . enable = enable. clone ( ) ;
199+ config. global . enable_is_explicit = true ;
240200 }
241201
242202 // Apply extend-enable / extend-disable
@@ -255,6 +215,14 @@ impl LinterConfig {
255215 // Apply flavor
256216 config. global . flavor = self . markdown_flavor ( ) ;
257217
218+ // Apply fixable / unfixable
219+ if let Some ( ref fixable) = self . fixable {
220+ config. global . fixable = fixable. clone ( ) ;
221+ }
222+ if let Some ( ref unfixable) = self . unfixable {
223+ config. global . unfixable = unfixable. clone ( ) ;
224+ }
225+
258226 // Apply rule-specific configurations
259227 if let Some ( ref rules) = self . rules {
260228 for ( rule_name, json_value) in rules {
@@ -280,16 +248,13 @@ impl LinterConfig {
280248 ( config, warnings)
281249 }
282250
283- /// Parse markdown flavor from config
251+ /// Parse markdown flavor from config, delegating to `MarkdownFlavor::from_str`
252+ /// to support all aliases (e.g., "qmd"/"rmd" → Quarto, "gfm" → Standard)
284253 fn markdown_flavor ( & self ) -> MarkdownFlavor {
285- match self . flavor . as_deref ( ) {
286- Some ( "mkdocs" ) => MarkdownFlavor :: MkDocs ,
287- Some ( "mdx" ) => MarkdownFlavor :: MDX ,
288- Some ( "quarto" ) => MarkdownFlavor :: Quarto ,
289- Some ( "obsidian" ) => MarkdownFlavor :: Obsidian ,
290- Some ( "kramdown" ) | Some ( "jekyll" ) => MarkdownFlavor :: Kramdown ,
291- _ => MarkdownFlavor :: Standard ,
292- }
254+ self . flavor
255+ . as_deref ( )
256+ . and_then ( |s| s. parse :: < MarkdownFlavor > ( ) . ok ( ) )
257+ . unwrap_or_default ( )
293258 }
294259}
295260
@@ -419,15 +384,10 @@ impl Linter {
419384 "enable" : self . config. global. enable,
420385 "extend_enable" : self . config. global. extend_enable,
421386 "extend_disable" : self . config. global. extend_disable,
387+ "fixable" : self . config. global. fixable,
388+ "unfixable" : self . config. global. unfixable,
422389 "line_length" : self . config. global. line_length. get( ) ,
423- "flavor" : match self . flavor {
424- MarkdownFlavor :: Standard => "standard" ,
425- MarkdownFlavor :: MkDocs => "mkdocs" ,
426- MarkdownFlavor :: MDX => "mdx" ,
427- MarkdownFlavor :: Quarto => "quarto" ,
428- MarkdownFlavor :: Obsidian => "obsidian" ,
429- MarkdownFlavor :: Kramdown => "kramdown" ,
430- } ,
390+ "flavor" : self . flavor. to_string( ) ,
431391 "rules" : rules_json
432392 } )
433393 . to_string ( )
@@ -499,6 +459,7 @@ mod tests {
499459 enable : None ,
500460 line_length : Some ( 100 ) ,
501461 flavor : Some ( "mkdocs" . to_string ( ) ) ,
462+ ..Default :: default ( )
502463 } ;
503464
504465 let internal = config. to_config ( ) ;
0 commit comments