1mod append_text_comment;
8pub mod bundle;
9mod call_parens;
10mod compute_expression;
11mod configuration_error;
12mod convert_index_to_field;
13mod convert_luau_number;
14mod convert_require;
15mod convert_square_root_call;
16mod empty_do;
17mod filter_early_return;
18mod global_function_to_assign;
19mod group_local;
20mod inject_value;
21mod make_assignment_local;
22mod method_def;
23mod no_local_function;
24mod remove_assertions;
25mod remove_attribute;
26mod remove_call_match;
27mod remove_comments;
28mod remove_compound_assign;
29mod remove_continue;
30mod remove_debug_profiling;
31mod remove_floor_division;
32mod remove_if_expression;
33mod remove_interpolated_string;
34mod remove_method_call;
35mod remove_nil_declarations;
36mod remove_spaces;
37mod remove_types;
38mod remove_unused_variable;
39mod rename_variables;
40mod replace_referenced_tokens;
41pub(crate) mod require;
42mod rule_property;
43mod shift_token_line;
44mod unused_if_branch;
45mod unused_while;
46
47pub use append_text_comment::*;
48pub use call_parens::*;
49pub use compute_expression::*;
50pub use configuration_error::RuleConfigurationError;
51pub use convert_index_to_field::*;
52pub use convert_luau_number::*;
53pub use convert_require::*;
54pub use convert_square_root_call::*;
55pub use empty_do::*;
56pub use filter_early_return::*;
57pub use global_function_to_assign::*;
58pub use group_local::*;
59pub use inject_value::*;
60pub use make_assignment_local::*;
61pub use method_def::*;
62pub use no_local_function::*;
63pub use remove_assertions::*;
64pub use remove_attribute::*;
65pub use remove_comments::*;
66pub use remove_compound_assign::*;
67pub use remove_continue::*;
68pub use remove_debug_profiling::*;
69pub use remove_floor_division::*;
70pub use remove_if_expression::*;
71pub use remove_interpolated_string::*;
72pub use remove_method_call::*;
73pub use remove_nil_declarations::*;
74pub use remove_spaces::*;
75pub use remove_types::*;
76pub use remove_unused_variable::*;
77pub use rename_variables::*;
78pub(crate) use replace_referenced_tokens::*;
79pub use require::PathRequireMode;
80pub use rule_property::*;
81pub(crate) use shift_token_line::*;
82pub use unused_if_branch::*;
83pub use unused_while::*;
84
85use crate::frontend::LoaderConfiguration;
86use crate::nodes::Block;
87use crate::utils::FilterPattern;
88use crate::{DarkluaError, Resources};
89
90use serde::de::{self, MapAccess, Visitor};
91use serde::ser::SerializeMap;
92use serde::{Deserialize, Deserializer, Serialize, Serializer};
93use std::collections::HashMap;
94use std::fmt;
95use std::path::{Path, PathBuf};
96use std::str::FromStr;
97
98const APPLY_TO_FILTER_PROPERTY: &str = "apply_to_files";
99const SKIP_FILTER_PROPERTY: &str = "skip_files";
100
101#[derive(Debug, Clone)]
106pub struct ContextBuilder<'a, 'resources, 'code> {
107 path: PathBuf,
108 resources: &'resources Resources,
109 original_code: &'code str,
110 blocks: HashMap<PathBuf, &'a Block>,
111 project_location: Option<PathBuf>,
112 loaders: Option<LoaderConfiguration>,
113 preferred_lua_extension: String,
114}
115
116impl<'a, 'resources, 'code> ContextBuilder<'a, 'resources, 'code> {
117 pub fn new(
119 path: impl Into<PathBuf>,
120 resources: &'resources Resources,
121 original_code: &'code str,
122 ) -> Self {
123 Self {
124 path: path.into(),
125 resources,
126 original_code,
127 blocks: Default::default(),
128 project_location: None,
129 loaders: None,
130 preferred_lua_extension: "lua".to_owned(),
131 }
132 }
133
134 pub(crate) fn with_loaders(mut self, loaders: LoaderConfiguration) -> Self {
135 self.loaders = Some(loaders);
136 self
137 }
138
139 pub fn with_project_location(mut self, path: impl Into<PathBuf>) -> Self {
141 self.project_location = Some(path.into());
142 self
143 }
144
145 pub fn with_preferred_lua_extension(mut self, extension: impl Into<String>) -> Self {
147 self.preferred_lua_extension = extension.into();
148 self
149 }
150
151 pub fn build(self) -> Context<'a, 'resources, 'code> {
153 Context {
154 path: self.path,
155 resources: self.resources,
156 original_code: self.original_code,
157 blocks: self.blocks,
158 project_location: self.project_location,
159 dependencies: Default::default(),
160 loaders: self.loaders.unwrap_or_default(),
161 preferred_lua_extension: self.preferred_lua_extension,
162 }
163 }
164
165 pub fn insert_block<'block: 'a>(&mut self, path: impl Into<PathBuf>, block: &'block Block) {
167 self.blocks.insert(path.into(), block);
168 }
169}
170
171#[derive(Debug, Clone)]
176pub struct Context<'a, 'resources, 'code> {
177 path: PathBuf,
178 resources: &'resources Resources,
179 original_code: &'code str,
180 blocks: HashMap<PathBuf, &'a Block>,
181 project_location: Option<PathBuf>,
182 dependencies: std::cell::RefCell<Vec<PathBuf>>,
183 loaders: LoaderConfiguration,
184 preferred_lua_extension: String,
185}
186
187impl Context<'_, '_, '_> {
188 pub fn block(&self, path: impl AsRef<Path>) -> Option<&Block> {
190 self.blocks.get(path.as_ref()).copied()
191 }
192
193 pub fn current_path(&self) -> &Path {
195 self.path.as_ref()
196 }
197
198 pub fn add_file_dependency(&self, path: PathBuf) {
202 if let Ok(mut dependencies) = self.dependencies.try_borrow_mut() {
203 log::trace!("add file dependency {}", path.display());
204 dependencies.push(path);
205 } else {
206 log::warn!("unable to submit file dependency (internal error)");
207 }
208 }
209
210 pub fn into_dependencies(self) -> impl Iterator<Item = PathBuf> {
212 self.dependencies.into_inner().into_iter()
213 }
214
215 pub(crate) fn loaders(&self) -> &LoaderConfiguration {
216 &self.loaders
217 }
218
219 pub(crate) fn preferred_lua_extension(&self) -> &str {
220 &self.preferred_lua_extension
221 }
222
223 fn resources(&self) -> &Resources {
224 self.resources
225 }
226
227 fn original_code(&self) -> &str {
228 self.original_code
229 }
230
231 fn project_location(&self) -> &Path {
232 self.project_location.as_deref().unwrap_or_else(|| {
233 let source = self.current_path();
234 source.parent().unwrap_or_else(|| {
235 log::warn!(
236 "unexpected file path `{}` (unable to extract parent path)",
237 source.display()
238 );
239 source
240 })
241 })
242 }
243}
244
245pub type RuleProcessResult = Result<(), String>;
247
248pub trait Rule: RuleConfiguration + fmt::Debug {
253 fn process(&self, block: &mut Block, context: &Context) -> RuleProcessResult;
257
258 fn require_content(&self, _current_source: &Path, _current_block: &Block) -> Vec<PathBuf> {
262 Vec::new()
263 }
264}
265
266pub trait RuleConfiguration {
271 fn configure(&mut self, properties: RuleProperties) -> Result<(), RuleConfigurationError>;
275
276 fn get_name(&self) -> &'static str;
278
279 fn serialize_to_properties(&self) -> RuleProperties;
283
284 fn has_properties(&self) -> bool {
286 !self.serialize_to_properties().is_empty()
287 }
288
289 fn set_metadata(&mut self, metadata: RuleMetadata);
290
291 fn metadata(&self) -> &RuleMetadata;
292}
293
294#[derive(Debug, Clone, Default, PartialEq, Eq)]
299pub struct RuleMetadata {
300 apply_to_filters: Vec<FilterPattern>,
301 skip_filters: Vec<FilterPattern>,
302}
303
304impl RuleMetadata {
305 pub fn with_apply_to_filter(mut self, filter: String) -> Result<Self, DarkluaError> {
306 self.push_apply_to_filter(filter)?;
307 Ok(self)
308 }
309
310 pub fn push_apply_to_filter(&mut self, filter: String) -> Result<(), DarkluaError> {
311 let pattern = FilterPattern::new(filter)?;
312 self.apply_to_filters.push(pattern);
313 Ok(())
314 }
315
316 pub fn with_skip_filter(mut self, filter: String) -> Result<Self, DarkluaError> {
317 self.push_skip_filter(filter)?;
318 Ok(self)
319 }
320
321 pub fn push_skip_filter(&mut self, filter: String) -> Result<(), DarkluaError> {
322 let pattern = FilterPattern::new(filter)?;
323 self.skip_filters.push(pattern);
324 Ok(())
325 }
326
327 pub(crate) fn should_apply(&self, path: &Path) -> bool {
328 if !self.apply_to_filters.is_empty()
329 && self.apply_to_filters.iter().all(|f| !f.matches(path))
330 {
331 return false;
332 }
333
334 if !self.skip_filters.is_empty() && self.skip_filters.iter().any(|f| f.matches(path)) {
335 return false;
336 }
337
338 true
339 }
340}
341
342pub trait FlawlessRule {
347 fn flawless_process(&self, block: &mut Block, context: &Context);
349}
350
351impl<T: FlawlessRule + RuleConfiguration + fmt::Debug> Rule for T {
352 fn process(&self, block: &mut Block, context: &Context) -> RuleProcessResult {
353 self.flawless_process(block, context);
354 Ok(())
355 }
356}
357
358pub fn get_default_rules() -> Vec<Box<dyn Rule>> {
363 vec![
364 Box::<RemoveSpaces>::default(),
365 Box::<RemoveComments>::default(),
366 Box::<ComputeExpression>::default(),
367 Box::<RemoveUnusedIfBranch>::default(),
368 Box::<RemoveUnusedWhile>::default(),
369 Box::<FilterAfterEarlyReturn>::default(),
370 Box::<RemoveEmptyDo>::default(),
371 Box::<RemoveUnusedVariable>::default(),
372 Box::<RemoveMethodDefinition>::default(),
373 Box::<ConvertIndexToField>::default(),
374 Box::<RemoveNilDeclaration>::default(),
375 Box::<RenameVariables>::default(),
376 Box::<RemoveFunctionCallParens>::default(),
377 ]
378}
379
380pub fn get_all_rule_names() -> Vec<&'static str> {
384 vec![
385 APPEND_TEXT_COMMENT_RULE_NAME,
386 COMPUTE_EXPRESSIONS_RULE_NAME,
387 CONVERT_FUNCTION_TO_ASSIGNMENT_RULE_NAME,
388 CONVERT_INDEX_TO_FIELD_RULE_NAME,
389 CONVERT_LOCAL_FUNCTION_TO_ASSIGN_RULE_NAME,
390 CONVERT_LUAU_NUMBER_RULE_NAME,
391 CONVERT_REQUIRE_RULE_NAME,
392 CONVERT_SQUARE_ROOT_CALL_RULE_NAME,
393 FILTER_AFTER_EARLY_RETURN_RULE_NAME,
394 GROUP_LOCAL_ASSIGNMENT_RULE_NAME,
395 INJECT_GLOBAL_VALUE_RULE_NAME,
396 MAKE_ASSIGNMENT_LOCAL_RULE_NAME,
397 REMOVE_ASSERTIONS_RULE_NAME,
398 REMOVE_ATTRIBUTE_RULE_NAME,
399 REMOVE_COMMENTS_RULE_NAME,
400 REMOVE_COMPOUND_ASSIGNMENT_RULE_NAME,
401 REMOVE_DEBUG_PROFILING_RULE_NAME,
402 REMOVE_EMPTY_DO_RULE_NAME,
403 REMOVE_FLOOR_DIVISION_RULE_NAME,
404 REMOVE_FUNCTION_CALL_PARENS_RULE_NAME,
405 REMOVE_INTERPOLATED_STRING_RULE_NAME,
406 REMOVE_METHOD_CALL_RULE_NAME,
407 REMOVE_METHOD_DEFINITION_RULE_NAME,
408 REMOVE_NIL_DECLARATION_RULE_NAME,
409 REMOVE_SPACES_RULE_NAME,
410 REMOVE_TYPES_RULE_NAME,
411 REMOVE_UNUSED_IF_BRANCH_RULE_NAME,
412 REMOVE_UNUSED_VARIABLE_RULE_NAME,
413 REMOVE_UNUSED_WHILE_RULE_NAME,
414 RENAME_VARIABLES_RULE_NAME,
415 REMOVE_IF_EXPRESSION_RULE_NAME,
416 REMOVE_CONTINUE_RULE_NAME,
417 ]
418}
419
420impl FromStr for Box<dyn Rule> {
421 type Err = String;
422
423 fn from_str(string: &str) -> Result<Self, Self::Err> {
424 let rule: Box<dyn Rule> = match string {
425 APPEND_TEXT_COMMENT_RULE_NAME => Box::<AppendTextComment>::default(),
426 COMPUTE_EXPRESSIONS_RULE_NAME => Box::<ComputeExpression>::default(),
427 CONVERT_FUNCTION_TO_ASSIGNMENT_RULE_NAME => Box::<ConvertFunctionToAssign>::default(),
428 CONVERT_INDEX_TO_FIELD_RULE_NAME => Box::<ConvertIndexToField>::default(),
429 CONVERT_LOCAL_FUNCTION_TO_ASSIGN_RULE_NAME => {
430 Box::<ConvertLocalFunctionToAssign>::default()
431 }
432 CONVERT_LUAU_NUMBER_RULE_NAME => Box::<ConvertLuauNumber>::default(),
433 CONVERT_REQUIRE_RULE_NAME => Box::<ConvertRequire>::default(),
434 CONVERT_SQUARE_ROOT_CALL_RULE_NAME => Box::<ConvertSquareRootCall>::default(),
435 FILTER_AFTER_EARLY_RETURN_RULE_NAME => Box::<FilterAfterEarlyReturn>::default(),
436 GROUP_LOCAL_ASSIGNMENT_RULE_NAME => Box::<GroupLocalAssignment>::default(),
437 INJECT_GLOBAL_VALUE_RULE_NAME => Box::<InjectGlobalValue>::default(),
438 MAKE_ASSIGNMENT_LOCAL_RULE_NAME => Box::<MakeAssignmentLocal>::default(),
439 REMOVE_ASSERTIONS_RULE_NAME => Box::<RemoveAssertions>::default(),
440 REMOVE_ATTRIBUTE_RULE_NAME => Box::<RemoveAttribute>::default(),
441 REMOVE_COMMENTS_RULE_NAME => Box::<RemoveComments>::default(),
442 REMOVE_COMPOUND_ASSIGNMENT_RULE_NAME => Box::<RemoveCompoundAssignment>::default(),
443 REMOVE_DEBUG_PROFILING_RULE_NAME => Box::<RemoveDebugProfiling>::default(),
444 REMOVE_EMPTY_DO_RULE_NAME => Box::<RemoveEmptyDo>::default(),
445 REMOVE_FLOOR_DIVISION_RULE_NAME => Box::<RemoveFloorDivision>::default(),
446 REMOVE_FUNCTION_CALL_PARENS_RULE_NAME => Box::<RemoveFunctionCallParens>::default(),
447 REMOVE_INTERPOLATED_STRING_RULE_NAME => Box::<RemoveInterpolatedString>::default(),
448 REMOVE_METHOD_CALL_RULE_NAME => Box::<RemoveMethodCall>::default(),
449 REMOVE_METHOD_DEFINITION_RULE_NAME => Box::<RemoveMethodDefinition>::default(),
450 REMOVE_NIL_DECLARATION_RULE_NAME => Box::<RemoveNilDeclaration>::default(),
451 REMOVE_SPACES_RULE_NAME => Box::<RemoveSpaces>::default(),
452 REMOVE_TYPES_RULE_NAME => Box::<RemoveTypes>::default(),
453 REMOVE_UNUSED_IF_BRANCH_RULE_NAME => Box::<RemoveUnusedIfBranch>::default(),
454 REMOVE_UNUSED_VARIABLE_RULE_NAME => Box::<RemoveUnusedVariable>::default(),
455 REMOVE_UNUSED_WHILE_RULE_NAME => Box::<RemoveUnusedWhile>::default(),
456 RENAME_VARIABLES_RULE_NAME => Box::<RenameVariables>::default(),
457 REMOVE_IF_EXPRESSION_RULE_NAME => Box::<RemoveIfExpression>::default(),
458 REMOVE_CONTINUE_RULE_NAME => Box::<RemoveContinue>::default(),
459 _ => return Err(format!("invalid rule name: {}", string)),
460 };
461
462 Ok(rule)
463 }
464}
465
466impl Serialize for dyn Rule {
467 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
468 let properties = self.serialize_to_properties();
469 let property_count = properties.len();
470 let rule_name = self.get_name();
471
472 if property_count == 0 {
473 serializer.serialize_str(rule_name)
474 } else {
475 let mut map = serializer.serialize_map(Some(property_count + 1))?;
476
477 map.serialize_entry("rule", rule_name)?;
478
479 let metadata = self.metadata();
480
481 if !metadata.apply_to_filters.is_empty() {
482 let filters = metadata
483 .apply_to_filters
484 .iter()
485 .map(|f| f.original())
486 .collect::<Vec<_>>();
487
488 if filters.len() == 1 {
489 map.serialize_entry(APPLY_TO_FILTER_PROPERTY, &filters[0])?;
490 } else {
491 map.serialize_entry(APPLY_TO_FILTER_PROPERTY, &filters)?;
492 }
493 }
494
495 if !metadata.apply_to_filters.is_empty() {
496 let filters = metadata
497 .skip_filters
498 .iter()
499 .map(|f| f.original())
500 .collect::<Vec<_>>();
501
502 if filters.len() == 1 {
503 map.serialize_entry(SKIP_FILTER_PROPERTY, &filters[0])?;
504 } else {
505 map.serialize_entry(SKIP_FILTER_PROPERTY, &filters)?;
506 }
507 }
508
509 let mut ordered: Vec<(String, RulePropertyValue)> = properties.into_iter().collect();
510
511 ordered.sort_by(|a, b| a.0.cmp(&b.0));
512
513 for (key, value) in ordered {
514 map.serialize_entry(&key, &value)?;
515 }
516
517 map.end()
518 }
519 }
520}
521
522#[derive(Debug, Clone, Serialize, Deserialize)]
523#[serde(untagged)]
524enum OneOrMany<T> {
525 One(T),
526 Many(Vec<T>),
527}
528
529impl<T> OneOrMany<T> {
530 fn into_vec(self) -> Vec<T> {
531 match self {
532 OneOrMany::One(value) => vec![value],
533 OneOrMany::Many(values) => values,
534 }
535 }
536}
537
538impl<'de> Deserialize<'de> for Box<dyn Rule> {
539 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Box<dyn Rule>, D::Error> {
540 struct StringOrStruct;
541
542 impl<'de> Visitor<'de> for StringOrStruct {
543 type Value = Box<dyn Rule>;
544
545 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
546 formatter.write_str("rule name or rule object")
547 }
548
549 fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
550 where
551 E: de::Error,
552 {
553 let mut rule: Self::Value = FromStr::from_str(value).map_err(de::Error::custom)?;
554
555 rule.configure(RuleProperties::new())
556 .map_err(de::Error::custom)?;
557
558 Ok(rule)
559 }
560
561 fn visit_map<M>(self, mut map: M) -> Result<Self::Value, M::Error>
562 where
563 M: MapAccess<'de>,
564 {
565 let mut rule_name: Option<String> = None;
566 let mut properties = HashMap::new();
567 let mut only_patterns: Option<Vec<_>> = None;
568 let mut skip_patterns: Option<Vec<_>> = None;
569
570 while let Some(key) = map.next_key::<String>()? {
571 match key.as_str() {
572 "rule" => {
573 if rule_name.is_none() {
574 rule_name = Some(map.next_value::<String>()?);
575 } else {
576 return Err(de::Error::duplicate_field("rule"));
577 }
578 }
579 APPLY_TO_FILTER_PROPERTY => {
580 if only_patterns.is_none() {
581 let value =
582 map.next_value::<OneOrMany<FilterPattern>>()?.into_vec();
583
584 only_patterns = Some(value);
585 } else {
586 return Err(de::Error::duplicate_field(APPLY_TO_FILTER_PROPERTY));
587 }
588 }
589 SKIP_FILTER_PROPERTY => {
590 if skip_patterns.is_none() {
591 let value =
592 map.next_value::<OneOrMany<FilterPattern>>()?.into_vec();
593
594 skip_patterns = Some(value);
595 } else {
596 return Err(de::Error::duplicate_field(SKIP_FILTER_PROPERTY));
597 }
598 }
599 property => {
600 let value = map.next_value::<RulePropertyValue>()?;
601
602 if properties.insert(property.to_owned(), value).is_some() {
603 return Err(de::Error::custom(format!(
604 "duplicate field {} in rule object",
605 property
606 )));
607 }
608 }
609 }
610 }
611
612 if let Some(rule_name) = rule_name {
613 let mut rule: Self::Value =
614 FromStr::from_str(&rule_name).map_err(de::Error::custom)?;
615
616 rule.configure(properties).map_err(de::Error::custom)?;
617
618 let metadata = RuleMetadata {
619 apply_to_filters: only_patterns.unwrap_or_default(),
620 skip_filters: skip_patterns.unwrap_or_default(),
621 };
622 rule.set_metadata(metadata);
623
624 Ok(rule)
625 } else {
626 Err(de::Error::missing_field("rule"))
627 }
628 }
629 }
630
631 deserializer.deserialize_any(StringOrStruct)
632 }
633}
634
635fn verify_no_rule_properties(properties: &RuleProperties) -> Result<(), RuleConfigurationError> {
636 if let Some((key, _value)) = properties.iter().next() {
637 return Err(RuleConfigurationError::UnexpectedProperty(key.to_owned()));
638 }
639 Ok(())
640}
641
642fn verify_required_properties(
643 properties: &RuleProperties,
644 names: &[&str],
645) -> Result<(), RuleConfigurationError> {
646 for name in names.iter() {
647 if !properties.contains_key(*name) {
648 return Err(RuleConfigurationError::MissingProperty(name.to_string()));
649 }
650 }
651 Ok(())
652}
653
654fn verify_required_any_properties(
655 properties: &RuleProperties,
656 names: &[&str],
657) -> Result<(), RuleConfigurationError> {
658 if names.iter().any(|name| properties.contains_key(*name)) {
659 Ok(())
660 } else {
661 Err(RuleConfigurationError::MissingAnyProperty(
662 names.iter().map(ToString::to_string).collect(),
663 ))
664 }
665}
666
667fn verify_property_collisions(
668 properties: &RuleProperties,
669 names: &[&str],
670) -> Result<(), RuleConfigurationError> {
671 let mut exists: Option<&str> = None;
672 for name in names.iter() {
673 if properties.contains_key(*name) {
674 if let Some(existing_name) = &exists {
675 return Err(RuleConfigurationError::PropertyCollision(vec![
676 existing_name.to_string(),
677 name.to_string(),
678 ]));
679 } else {
680 exists = Some(*name);
681 }
682 }
683 }
684 Ok(())
685}
686
687#[cfg(test)]
688mod test {
689 use super::*;
690
691 use insta::assert_json_snapshot;
692
693 #[test]
694 fn snapshot_default_rules() {
695 let rules = get_default_rules();
696
697 assert_json_snapshot!("default_rules", rules);
698 }
699
700 #[test]
701 fn snapshot_all_rules() {
702 let rule_names = get_all_rule_names();
703
704 assert_json_snapshot!("all_rule_names", rule_names);
705 }
706
707 #[test]
708 fn verify_no_rule_properties_is_ok_when_empty() {
709 let empty_properties = RuleProperties::default();
710
711 assert_eq!(verify_no_rule_properties(&empty_properties), Ok(()));
712 }
713
714 #[test]
715 fn verify_no_rule_properties_is_unexpected_rule_err() {
716 let mut properties = RuleProperties::default();
717 let some_rule_name = "rule name";
718 properties.insert(some_rule_name.to_owned(), RulePropertyValue::None);
719
720 assert_eq!(
721 verify_no_rule_properties(&properties),
722 Err(RuleConfigurationError::UnexpectedProperty(
723 some_rule_name.to_owned()
724 ))
725 );
726 }
727
728 #[test]
729 fn get_all_rule_names_are_deserializable() {
730 for name in get_all_rule_names() {
731 let rule: Result<Box<dyn Rule>, _> = name.parse();
732 assert!(rule.is_ok(), "unable to deserialize `{}`", name);
733 }
734 }
735
736 #[test]
737 fn get_all_rule_names_are_serializable() {
738 for name in get_all_rule_names() {
739 let rule: Box<dyn Rule> = name
740 .parse()
741 .unwrap_or_else(|_| panic!("unable to deserialize `{}`", name));
742 assert!(json5::to_string(&rule).is_ok());
743 }
744 }
745}