@@ -8,6 +8,10 @@ use schemars::{
88 r#gen:: SchemaGenerator ,
99 schema:: { ArrayValidation , InstanceType , Schema , SchemaObject } ,
1010} ;
11+
12+ #[ cfg( feature = "ruledocs" ) ]
13+ use schemars:: schema:: SingleOrVec ;
14+
1115use serde:: {
1216 Deserialize , Serialize , Serializer ,
1317 de:: { self , Deserializer , Visitor } ,
@@ -17,6 +21,8 @@ use smallvec::SmallVec;
1721
1822use oxc_diagnostics:: { Error , OxcDiagnostic } ;
1923
24+ #[ cfg( feature = "ruledocs" ) ]
25+ use crate :: utils:: should_skip_config_schema;
2026use crate :: {
2127 AllowWarnDeny , ExternalPluginStore , LintPlugins ,
2228 external_plugin_store:: { ExternalOptionsId , ExternalRuleId , ExternalRuleLookupError } ,
@@ -306,15 +312,173 @@ impl JsonSchema for OxlintRules {
306312 }
307313
308314 fn json_schema ( r#gen : & mut SchemaGenerator ) -> Schema {
315+ #[ cfg( feature = "ruledocs" ) ]
316+ fn resolve_references_in_schema < ' a > (
317+ schema : & ' a Schema ,
318+ r#gen : & ' a SchemaGenerator ,
319+ ) -> & ' a Schema {
320+ let mut current = schema;
321+ while let Some ( next) = r#gen. dereference ( current) {
322+ current = next;
323+ }
324+
325+ let Schema :: Object ( obj) = current else {
326+ return current;
327+ } ;
328+
329+ // reuse the defined schema if it's already an object schema,
330+ // we really only need to dereference array schemas for rule config
331+ // TODO: for the array config, the reference should be removed from the generator.
332+ if obj. object . is_some ( ) {
333+ return schema;
334+ }
335+
336+ current
337+ }
338+
339+ // we expect that rules config items does not extend 4,294,967,295 entries.
340+ #[ expect( clippy:: cast_possible_truncation) ]
341+ #[ cfg( feature = "ruledocs" ) ]
342+ fn rule_config_schema ( r : & RuleEnum , r#gen : & mut SchemaGenerator ) -> Schema {
343+ fn with_toggle_schema (
344+ config_schema : Schema ,
345+ r#gen : & mut SchemaGenerator ,
346+ ) -> Schema {
347+ SchemaObject {
348+ subschemas : Some ( Box :: new ( schemars:: schema:: SubschemaValidation {
349+ any_of : Some ( vec ! [
350+ r#gen. subschema_for:: <AllowWarnDeny >( ) ,
351+ config_schema,
352+ ] ) ,
353+ ..Default :: default ( )
354+ } ) ) ,
355+ ..Default :: default ( )
356+ }
357+ . into ( )
358+ }
359+
360+ let Some ( schema) = r. schema ( r#gen) else {
361+ return r#gen. subschema_for :: < DummyRule > ( ) ;
362+ } ;
363+
364+ let schema = resolve_references_in_schema ( & schema, r#gen) . clone ( ) ;
365+
366+ let Schema :: Object ( obj) = schema else {
367+ let array_schema = SchemaObject {
368+ instance_type : Some ( InstanceType :: Array . into ( ) ) ,
369+ array : Some ( Box :: new ( ArrayValidation {
370+ items : Some ( SingleOrVec :: Vec ( vec ! [
371+ r#gen. subschema_for:: <AllowWarnDeny >( ) ,
372+ Schema :: Bool ( true ) ,
373+ ] ) ) ,
374+ min_items : Some ( 1 ) ,
375+ max_items : Some ( 2 ) ,
376+ ..Default :: default ( )
377+ } ) ) ,
378+ ..Default :: default ( )
379+ }
380+ . into ( ) ;
381+ return with_toggle_schema ( array_schema, r#gen) ;
382+ } ;
383+
384+ debug_assert ! (
385+ ( u8 :: from( obj. array. is_some( ) )
386+ + u8 :: from( obj. object. is_some( ) )
387+ + u8 :: from( obj. reference. is_some( ) ) )
388+ <= 1 ,
389+ "Expected rule schema to be either an object, an array, or a reference, but not multiple"
390+ ) ;
391+
392+ if let Some ( reference) = obj. reference {
393+ let array_schema = SchemaObject {
394+ instance_type : Some ( InstanceType :: Array . into ( ) ) ,
395+ array : Some ( Box :: new ( ArrayValidation {
396+ items : Some ( SingleOrVec :: Vec ( vec ! [
397+ r#gen. subschema_for:: <AllowWarnDeny >( ) ,
398+ Schema :: Object ( SchemaObject {
399+ reference: Some ( reference) ,
400+ ..Default :: default ( )
401+ } ) ,
402+ ] ) ) ,
403+ min_items : Some ( 1 ) ,
404+ max_items : Some ( 2 ) ,
405+ ..Default :: default ( )
406+ } ) ) ,
407+ ..Default :: default ( )
408+ }
409+ . into ( ) ;
410+ return with_toggle_schema ( array_schema, r#gen) ;
411+ }
412+
413+ if let Some ( array) = obj. array {
414+ let items = match array. items {
415+ None => vec ! [ r#gen. subschema_for:: <AllowWarnDeny >( ) ] ,
416+ Some ( SingleOrVec :: Single ( config) ) => {
417+ vec ! [ r#gen. subschema_for:: <AllowWarnDeny >( ) , * config]
418+ }
419+ Some ( SingleOrVec :: Vec ( configs) ) => {
420+ let mut items = Vec :: with_capacity ( configs. len ( ) . saturating_add ( 1 ) ) ;
421+ items. push ( r#gen. subschema_for :: < AllowWarnDeny > ( ) ) ;
422+ items. extend ( configs) ;
423+ items
424+ }
425+ } ;
426+
427+ let config_length = items. len ( ) as u32 ;
428+
429+ let array_schema = SchemaObject {
430+ instance_type : Some ( InstanceType :: Array . into ( ) ) ,
431+ array : Some ( Box :: new ( ArrayValidation {
432+ items : Some ( SingleOrVec :: Vec ( items) ) ,
433+ min_items : Some ( 1 ) ,
434+ max_items : Some ( config_length) ,
435+ ..Default :: default ( )
436+ } ) ) ,
437+ ..Default :: default ( )
438+ }
439+ . into ( ) ;
440+ return with_toggle_schema ( array_schema, r#gen) ;
441+ }
442+
443+ let array_schema = Schema :: Object ( SchemaObject {
444+ instance_type : Some ( InstanceType :: Array . into ( ) ) ,
445+ array : Some ( Box :: new ( ArrayValidation {
446+ items : Some ( SingleOrVec :: Vec ( vec ! [
447+ r#gen. subschema_for:: <AllowWarnDeny >( ) ,
448+ Schema :: Object ( obj) ,
449+ ] ) ) ,
450+ min_items : Some ( 1 ) ,
451+ max_items : Some ( 2 ) ,
452+ ..Default :: default ( )
453+ } ) ) ,
454+ ..Default :: default ( )
455+ } ) ;
456+
457+ with_toggle_schema ( array_schema, r#gen)
458+ }
459+
460+ let dummy_schema = r#gen. subschema_for :: < DummyRule > ( ) ;
461+
309462 let rules_enum = RULES . iter ( ) . map ( |r| {
463+ #[ cfg( feature = "ruledocs" ) ]
464+ let schema = if should_skip_config_schema ( r) {
465+ r#gen. subschema_for :: < DummyRule > ( )
466+ } else {
467+ rule_config_schema ( r, r#gen)
468+ } ;
469+ #[ cfg( not( feature = "ruledocs" ) ) ]
470+ let schema = r#gen. subschema_for :: < DummyRule > ( ) ;
310471 if r. plugin_name ( ) == "eslint" {
311- r. name ( ) . to_string ( )
472+ ( r. name ( ) . to_string ( ) , schema )
312473 } else {
313- format ! (
314- "{}/{}" ,
315- // replace `jsx_a11y` with `jsx-a11y`, `react_perf` with `react-perf`.
316- r. plugin_name( ) . cow_replace( '_' , "-" ) ,
317- r. name( )
474+ (
475+ format ! (
476+ "{}/{}" ,
477+ // replace `jsx_a11y` with `jsx-a11y`, `react_perf` with `react-perf`.
478+ r. plugin_name( ) . cow_replace( '_' , "-" ) ,
479+ r. name( )
480+ ) ,
481+ schema,
318482 )
319483 }
320484 } ) ;
@@ -329,10 +493,8 @@ impl JsonSchema for OxlintRules {
329493 } ) ) ,
330494 instance_type : Some ( InstanceType :: Object . into ( ) ) ,
331495 object : Some ( Box :: new ( schemars:: schema:: ObjectValidation {
332- additional_properties : Some ( Box :: new ( r#gen. subschema_for :: < DummyRule > ( ) ) ) ,
333- properties : rules_enum
334- . map ( |rule_name| ( rule_name, r#gen. subschema_for :: < DummyRule > ( ) ) )
335- . collect ( ) ,
496+ additional_properties : Some ( Box :: new ( dummy_schema) ) ,
497+ properties : rules_enum. collect ( ) ,
336498 ..Default :: default ( )
337499 } ) ) ,
338500 ..Default :: default ( )
0 commit comments