@@ -4,10 +4,12 @@ use oxc_diagnostics::OxcDiagnostic;
44use oxc_macros:: declare_oxc_lint;
55use oxc_span:: Span ;
66use schemars:: JsonSchema ;
7+ use serde:: Deserialize ;
78
89use crate :: {
910 context:: { ContextHost , LintContext } ,
10- rule:: Rule ,
11+ rule:: { DefaultRuleConfig , Rule } ,
12+ utils:: deserialize_required_regex_option,
1113} ;
1214
1315fn comment ( ts_comment_name : & str , span : Span ) -> OxcDiagnostic {
@@ -45,10 +47,10 @@ fn comment_description_not_match_pattern(
4547 . with_label ( span)
4648}
4749
48- #[ derive( Debug , Default , Clone ) ]
50+ #[ derive( Debug , Default , Clone , Deserialize ) ]
4951pub struct BanTsComment ( Box < BanTsCommentConfig > ) ;
5052
51- #[ derive( Debug , Clone , JsonSchema ) ]
53+ #[ derive( Debug , Clone , Deserialize , JsonSchema ) ]
5254#[ serde( rename_all = "kebab-case" , default , deny_unknown_fields) ]
5355/// This rule allows you to specify how different TypeScript directive comments
5456/// should be handled.
@@ -122,10 +124,7 @@ pub enum DirectiveConfig {
122124enum DirectiveConfigSchema {
123125 Boolean ( bool ) ,
124126 RequireDescription ( RequireDescription ) ,
125- DescriptionFormat {
126- #[ serde( rename = "descriptionFormat" ) ]
127- description_format : Option < Regex > ,
128- } ,
127+ DescriptionFormat ( DescriptionFormatConfig ) ,
129128}
130129
131130#[ derive( Debug , JsonSchema ) ]
@@ -135,25 +134,35 @@ enum RequireDescription {
135134 AllowWithDescription ,
136135}
137136
138- impl DirectiveConfig {
139- fn from_json ( value : & serde_json:: Value ) -> Option < Self > {
140- match value {
141- serde_json:: Value :: Bool ( b) => Some ( Self :: Boolean ( * b) ) ,
142- serde_json:: Value :: String ( s) => {
143- if s == "allow-with-description" {
144- Some ( Self :: RequireDescription )
145- } else {
146- None
147- }
148- }
149- serde_json:: Value :: Object ( o) => {
150- let re = o
151- . get ( "descriptionFormat" )
152- . and_then ( serde_json:: Value :: as_str)
153- . and_then ( |pattern| Regex :: new ( pattern) . ok ( ) ) ;
154- Some ( Self :: DescriptionFormat ( re) )
137+ #[ derive( Debug , Deserialize , JsonSchema ) ]
138+ #[ serde( deny_unknown_fields) ]
139+ struct DescriptionFormatConfig {
140+ #[ serde(
141+ default ,
142+ rename = "descriptionFormat" ,
143+ deserialize_with = "deserialize_required_regex_option"
144+ ) ]
145+ description_format : Option < Regex > ,
146+ }
147+
148+ impl < ' de > Deserialize < ' de > for DirectiveConfig {
149+ fn deserialize < D > ( deserializer : D ) -> Result < Self , D :: Error >
150+ where
151+ D : serde:: Deserializer < ' de > ,
152+ {
153+ use serde:: de:: Error ;
154+
155+ match serde_json:: Value :: deserialize ( deserializer) ? {
156+ serde_json:: Value :: Bool ( value) => Ok ( Self :: Boolean ( value) ) ,
157+ serde_json:: Value :: String ( value) if value == "allow-with-description" => {
158+ Ok ( Self :: RequireDescription )
155159 }
156- _ => None ,
160+ value @ serde_json:: Value :: Object ( _) => DescriptionFormatConfig :: deserialize ( value)
161+ . map ( |config| Self :: DescriptionFormat ( config. description_format ) )
162+ . map_err ( D :: Error :: custom) ,
163+ _ => Err ( D :: Error :: custom (
164+ "expected a boolean, `allow-with-description`, or a descriptionFormat object" ,
165+ ) ) ,
157166 }
158167 }
159168}
@@ -188,30 +197,7 @@ declare_oxc_lint!(
188197
189198impl Rule for BanTsComment {
190199 fn from_configuration ( value : serde_json:: Value ) -> Result < Self , serde_json:: error:: Error > {
191- let config = value. get ( 0 ) . unwrap_or_default ( ) ;
192-
193- Ok ( Self ( Box :: new ( BanTsCommentConfig {
194- ts_expect_error : config
195- . get ( "ts-expect-error" )
196- . and_then ( DirectiveConfig :: from_json)
197- . unwrap_or ( DirectiveConfig :: RequireDescription ) ,
198- ts_ignore : config
199- . get ( "ts-ignore" )
200- . and_then ( DirectiveConfig :: from_json)
201- . unwrap_or ( DirectiveConfig :: Boolean ( true ) ) ,
202- ts_nocheck : config
203- . get ( "ts-nocheck" )
204- . and_then ( DirectiveConfig :: from_json)
205- . unwrap_or ( DirectiveConfig :: Boolean ( true ) ) ,
206- ts_check : config
207- . get ( "ts-check" )
208- . and_then ( DirectiveConfig :: from_json)
209- . unwrap_or ( DirectiveConfig :: Boolean ( false ) ) ,
210- minimum_description_length : config
211- . get ( "minimumDescriptionLength" )
212- . and_then ( serde_json:: Value :: as_u64)
213- . unwrap_or ( 3 ) ,
214- } ) ) )
200+ serde_json:: from_value :: < DefaultRuleConfig < Self > > ( value) . map ( DefaultRuleConfig :: into_inner)
215201 }
216202
217203 fn run_once ( & self , ctx : & LintContext ) {
@@ -1047,3 +1033,15 @@ if (false) {
10471033 . expect_fix ( fix)
10481034 . test_and_snapshot ( ) ;
10491035}
1036+
1037+ #[ test]
1038+ fn invalid_description_format_is_rejected ( ) {
1039+ let result = BanTsComment :: from_configuration ( serde_json:: json!( [ {
1040+ "ts-expect-error" : {
1041+ "descriptionFormat" : "^(unclosed" ,
1042+ } ,
1043+ } ] ) ) ;
1044+
1045+ let error = result. expect_err ( "invalid descriptionFormat should be rejected" ) ;
1046+ assert ! ( error. to_string( ) . contains( "regex parse error" ) , "unexpected error: {error}" ) ;
1047+ }
0 commit comments