sway_error/
warning.rs

1use crate::{
2    diagnostic::{Code, Diagnostic, Hint, Issue, Reason, ToDiagnostic},
3    formatting::{
4        did_you_mean_help, num_to_str, sequence_to_list, sequence_to_str, Enclosing, Indent,
5    },
6};
7
8use core::fmt;
9
10use either::Either;
11
12use sway_types::{Ident, IdentUnique, SourceId, Span, Spanned};
13
14// TODO: since moving to using Idents instead of strings,
15// the warning_content will usually contain a duplicate of the span.
16#[derive(Debug, Clone, PartialEq, Eq, Hash)]
17pub struct CompileWarning {
18    pub span: Span,
19    pub warning_content: Warning,
20}
21
22impl Spanned for CompileWarning {
23    fn span(&self) -> Span {
24        self.span.clone()
25    }
26}
27
28impl CompileWarning {
29    pub fn to_friendly_warning_string(&self) -> String {
30        self.warning_content.to_string()
31    }
32
33    pub fn source_id(&self) -> Option<SourceId> {
34        self.span.source_id().cloned()
35    }
36}
37
38#[derive(Debug, Clone, PartialEq, Eq, Hash)]
39pub enum Warning {
40    NonClassCaseStructName {
41        struct_name: Ident,
42    },
43    NonClassCaseTypeParameter {
44        name: Ident,
45    },
46    NonClassCaseTraitName {
47        name: Ident,
48    },
49    NonClassCaseEnumName {
50        enum_name: Ident,
51    },
52    NonClassCaseEnumVariantName {
53        variant_name: Ident,
54    },
55    NonSnakeCaseStructFieldName {
56        field_name: Ident,
57    },
58    NonSnakeCaseFunctionName {
59        name: Ident,
60    },
61    NonScreamingSnakeCaseConstName {
62        name: Ident,
63    },
64    UnusedReturnValue {
65        r#type: String,
66    },
67    SimilarMethodFound {
68        lib: Ident,
69        module: Ident,
70        name: Ident,
71    },
72    ShadowsOtherSymbol {
73        name: Ident,
74    },
75    AsmBlockIsEmpty,
76    UninitializedAsmRegShadowsItem {
77        /// Text "Constant" or "Configurable" or "Variable".
78        /// Denotes the type of the `item` that shadows the uninitialized ASM register.
79        constant_or_configurable_or_variable: &'static str,
80        /// The name of the item that shadows the register, that points to the name in
81        /// the item declaration.
82        item: IdentUnique,
83    },
84    OverridingTraitImplementation,
85    DeadDeclaration,
86    DeadEnumDeclaration,
87    DeadFunctionDeclaration,
88    DeadStructDeclaration,
89    DeadTrait,
90    UnreachableCode,
91    DeadEnumVariant {
92        variant_name: Ident,
93    },
94    DeadMethod,
95    StructFieldNeverRead,
96    ShadowingReservedRegister {
97        reg_name: Ident,
98    },
99    DeadStorageDeclaration,
100    DeadStorageDeclarationForFunction {
101        unneeded_attrib: String,
102    },
103    MatchExpressionUnreachableArm {
104        match_value: Span,
105        match_type: String,
106        // Either preceding non catch-all arms or a single interior catch-all arm.
107        preceding_arms: Either<Vec<Span>, Span>,
108        unreachable_arm: Span,
109        is_last_arm: bool,
110        is_catch_all_arm: bool,
111    },
112    UnknownAttribute {
113        attribute: IdentUnique,
114        known_attributes: &'static [&'static str],
115    },
116    UnknownAttributeArg {
117        attribute: Ident,
118        arg: IdentUnique,
119        expected_args: Vec<&'static str>,
120    },
121    EffectAfterInteraction {
122        effect: String,
123        effect_in_suggestion: String,
124        block_name: Ident,
125    },
126    UsingDeprecated {
127        deprecated_element: DeprecatedElement,
128        deprecated_element_name: String,
129        help: Option<String>,
130    },
131    DuplicatedStorageKey {
132        first_field: IdentUnique,
133        first_field_full_name: String,
134        first_field_key_is_compiler_generated: bool,
135        second_field: IdentUnique,
136        second_field_full_name: String,
137        second_field_key_is_compiler_generated: bool,
138        key: String,
139    },
140    ErrorTypeEmptyEnum {
141        enum_name: IdentUnique,
142    },
143    ErrorEmptyErrorMessage {
144        enum_name: Ident,
145        enum_variant_name: Ident,
146    },
147    ErrorDuplicatedErrorMessage {
148        last_occurrence: Span,
149        previous_occurrences: Vec<Span>,
150    },
151}
152
153/// Elements that can be deprecated.
154#[derive(Debug, Clone, PartialEq, Eq, Hash)]
155pub enum DeprecatedElement {
156    Struct,
157    StructField,
158    Enum,
159    EnumVariant,
160    Function,
161    Const,
162    Configurable,
163}
164
165impl fmt::Display for DeprecatedElement {
166    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
167        match self {
168            Self::Struct => write!(f, "Struct"),
169            Self::StructField => write!(f, "Struct field"),
170            Self::Enum => write!(f, "Enum"),
171            Self::EnumVariant => write!(f, "Enum variant"),
172            Self::Function => write!(f, "Function"),
173            Self::Const => write!(f, "Constant"),
174            Self::Configurable => write!(f, "Configurable"),
175        }
176    }
177}
178
179impl fmt::Display for Warning {
180    // This trait requires `fmt` with this exact signature.
181    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
182        use sway_types::style::*;
183        use Warning::*;
184        match self {
185            NonClassCaseStructName { struct_name } => {
186                write!(f,
187                "Struct name \"{}\" is not idiomatic. Structs should have a ClassCase name, like \
188                 \"{}\".",
189                struct_name,
190                to_upper_camel_case(struct_name.as_str())
191            )
192            }
193            NonClassCaseTypeParameter { name } => {
194                write!(f,
195                "Type parameter \"{}\" is not idiomatic. TypeParameters should have a ClassCase name, like \
196                 \"{}\".",
197                name,
198                to_upper_camel_case(name.as_str())
199            )
200            }
201            NonClassCaseTraitName { name } => {
202                write!(f,
203                "Trait name \"{}\" is not idiomatic. Traits should have a ClassCase name, like \
204                 \"{}\".",
205                name,
206                to_upper_camel_case(name.as_str())
207            )
208            }
209            NonClassCaseEnumName { enum_name } => write!(
210                f,
211                "Enum \"{}\"'s capitalization is not idiomatic. Enums should have a ClassCase \
212                 name, like \"{}\".",
213                enum_name,
214                to_upper_camel_case(enum_name.as_str())
215            ),
216            NonSnakeCaseStructFieldName { field_name } => write!(
217                f,
218                "Struct field name \"{}\" is not idiomatic. Struct field names should have a \
219                 snake_case name, like \"{}\".",
220                field_name,
221                to_snake_case(field_name.as_str())
222            ),
223            NonClassCaseEnumVariantName { variant_name } => write!(
224                f,
225                "Enum variant name \"{}\" is not idiomatic. Enum variant names should be \
226                 ClassCase, like \"{}\".",
227                variant_name,
228                to_upper_camel_case(variant_name.as_str())
229            ),
230            NonSnakeCaseFunctionName { name } => {
231                write!(f,
232                "Function name \"{}\" is not idiomatic. Function names should be snake_case, like \
233                 \"{}\".",
234                name,
235                to_snake_case(name.as_str())
236            )
237            }
238            NonScreamingSnakeCaseConstName { name } => {
239                write!(
240                    f,
241                    "Constant name \"{name}\" is not idiomatic. Constant names should be SCREAMING_SNAKE_CASE, like \
242                    \"{}\".",
243                    to_screaming_snake_case(name.as_str()),
244                )
245            },
246            UnusedReturnValue { r#type } => write!(
247                f,
248                "This returns a value of type {type}, which is not assigned to anything and is \
249                 ignored."
250            ),
251            SimilarMethodFound { lib, module, name } => write!(
252                f,
253                "A method with the same name was found for type {name} in dependency \"{lib}::{module}\". \
254                 Traits must be in scope in order to access their methods. "
255            ),
256            ShadowsOtherSymbol { name } => write!(
257                f,
258                "This shadows another symbol in this scope with the same name \"{name}\"."
259            ),
260            AsmBlockIsEmpty => write!(
261                f,
262                "This ASM block is empty."
263            ),
264            UninitializedAsmRegShadowsItem { constant_or_configurable_or_variable, item } => write!(
265                f,
266                "This uninitialized register is shadowing a {}. You probably meant to also initialize it, like \"{item}: {item}\".",
267                constant_or_configurable_or_variable.to_ascii_lowercase(),
268            ),
269            OverridingTraitImplementation => write!(
270                f,
271                "This trait implementation overrides another one that was previously defined."
272            ),
273            DeadDeclaration => write!(f, "This declaration is never used."),
274            DeadEnumDeclaration => write!(f, "This enum is never used."),
275            DeadStructDeclaration => write!(f, "This struct is never used."),
276            DeadFunctionDeclaration => write!(f, "This function is never called."),
277            UnreachableCode => write!(f, "This code is unreachable."),
278            DeadEnumVariant { variant_name } => {
279                write!(f, "Enum variant {variant_name} is never constructed.")
280            }
281            DeadTrait => write!(f, "This trait is never implemented."),
282            DeadMethod => write!(f, "This method is never called."),
283            StructFieldNeverRead => write!(f, "This struct field is never accessed."),
284            ShadowingReservedRegister { reg_name } => write!(
285                f,
286                "This register declaration shadows the reserved register, \"{reg_name}\"."
287            ),
288            DeadStorageDeclaration => write!(
289                f,
290                "This storage declaration is never accessed and can be removed."
291            ),
292            DeadStorageDeclarationForFunction { unneeded_attrib } => write!(
293                f,
294                "This function's storage attributes declaration does not match its \
295                 actual storage access pattern: '{unneeded_attrib}' attribute(s) can be removed."
296            ),
297            MatchExpressionUnreachableArm { .. } => write!(f, "This match arm is unreachable."),
298            UnknownAttribute { attribute, .. } => write!(f, "Unknown attribute \"{attribute}\"."),
299            UnknownAttributeArg { attribute, arg, expected_args } => write!(
300                f,
301                "\"{arg}\" is an unknown argument for attribute \"{attribute}\". Known arguments are: {}.", sequence_to_str(expected_args, Enclosing::DoubleQuote, usize::MAX)
302            ),
303            EffectAfterInteraction {effect, effect_in_suggestion, block_name} =>
304                write!(f, "{effect} after external contract interaction in function or method \"{block_name}\". \
305                          Consider {effect_in_suggestion} before calling another contract"),
306            UsingDeprecated { deprecated_element_name, deprecated_element, help } =>
307                write!(f, "{deprecated_element} \"{deprecated_element_name}\" is deprecated. {}", help.as_ref().unwrap_or(&"".into())),
308            DuplicatedStorageKey { first_field_full_name, second_field_full_name, key, .. } =>
309                write!(f, "Two storage fields have the same storage key.\nFirst field: {first_field_full_name}\nSecond field: {second_field_full_name}\nKey: {key}"),
310            ErrorTypeEmptyEnum { enum_name } =>
311                write!(f, "Empty error type enum \"{enum_name}\" can never be instantiated and used in `panic` expressions."),
312            ErrorEmptyErrorMessage { enum_name, enum_variant_name } =>
313                write!(f, "Error enum variant \"{enum_name}::{enum_variant_name}\" has an empty error message. Consider adding a helpful error message."),
314            ErrorDuplicatedErrorMessage { previous_occurrences, .. } =>
315                write!(f, "This error message is duplicated{}. Consider using a unique error message for every error variant.",
316                    if previous_occurrences.len() == 1 {
317                        "".to_string()
318                    } else {
319                        format!(" {} times", num_to_str(previous_occurrences.len()))
320                    }
321                ),
322        }
323    }
324}
325
326#[allow(dead_code)]
327const FUTURE_HARD_ERROR_HELP: &str =
328    "In future versions of Sway this warning will become a hard error.";
329
330impl ToDiagnostic for CompileWarning {
331    fn to_diagnostic(&self, source_engine: &sway_types::SourceEngine) -> Diagnostic {
332        let code = Code::warnings;
333        use sway_types::style::*;
334        use Warning::*;
335        match &self.warning_content {
336            NonScreamingSnakeCaseConstName { name } => Diagnostic {
337                reason: Some(Reason::new(code(1), "Constant name is not idiomatic".to_string())),
338                issue: Issue::warning(
339                    source_engine,
340                    name.span(),
341                    format!("Constant \"{name}\" should be SCREAMING_SNAKE_CASE."),
342                ),
343                hints: vec![
344                    Hint::help(
345                        source_engine,
346                        name.span(),
347                        format!("Consider renaming it to, e.g., \"{}\".", to_screaming_snake_case(name.as_str())),
348                    ),
349                ],
350                help: vec![
351                    format!("In Sway, ABIs, structs, traits, and enums are CapitalCase."),
352                    format!("Modules, variables, and functions are snake_case, while constants are SCREAMING_SNAKE_CASE."),
353                ],
354            },
355            MatchExpressionUnreachableArm { match_value, match_type, preceding_arms, unreachable_arm, is_last_arm, is_catch_all_arm } => Diagnostic {
356                reason: Some(Reason::new(code(1), "Match arm is unreachable".to_string())),
357                issue: Issue::warning(
358                    source_engine,
359                    unreachable_arm.clone(),
360                    match (*is_last_arm, *is_catch_all_arm) {
361                        (true, true) => format!("Last catch-all match arm `{}` is unreachable.", unreachable_arm.as_str()),
362                        _ => format!("Match arm `{}` is unreachable.", unreachable_arm.as_str())
363                    }
364                ),
365                hints: vec![
366                    Hint::info(
367                        source_engine,
368                        match_value.clone(),
369                        format!("The expression to match on is of type \"{match_type}\".")
370                    ),
371                    if preceding_arms.is_right() {
372                        Hint::help(
373                            source_engine,
374                            preceding_arms.as_ref().unwrap_right().clone(),
375                            format!("Catch-all arm `{}` makes all match arms below it unreachable.", preceding_arms.as_ref().unwrap_right().as_str())
376                        )
377                    }
378                    else {
379                        Hint::info(
380                            source_engine,
381                            Span::join_all(preceding_arms.as_ref().unwrap_left().clone()),
382                            if *is_last_arm {
383                                format!("Preceding match arms already match all possible values of `{}`.", match_value.as_str())
384                            }
385                            else {
386                                format!("Preceding match arms already match all the values that `{}` can match.", unreachable_arm.as_str())
387                            }
388                        )
389                    }
390                ],
391                help: if preceding_arms.is_right() {
392                    let catch_all_arm = preceding_arms.as_ref().unwrap_right().as_str();
393                    vec![
394                        format!("Catch-all patterns make sense only in last match arms."),
395                        format!("Consider removing the catch-all arm `{catch_all_arm}` or making it the last arm."),
396                        format!("Consider removing the unreachable arms below the `{catch_all_arm}` arm."),
397                    ]
398                }
399                else if *is_last_arm && *is_catch_all_arm {
400                    vec![
401                        format!("Catch-all patterns are often used in last match arms."),
402                        format!("But in this case, the preceding arms already match all possible values of `{}`.", match_value.as_str()),
403                        format!("Consider removing the unreachable last catch-all arm."),
404                    ]
405                }
406                else {
407                    vec![
408                        format!("Consider removing the unreachable arm."),
409                    ]
410                }
411            },
412            UninitializedAsmRegShadowsItem { constant_or_configurable_or_variable, item } => Diagnostic {
413                reason: Some(Reason::new(code(1), format!("Uninitialized ASM register is shadowing a {}", constant_or_configurable_or_variable.to_ascii_lowercase()))),
414                issue: Issue::warning(
415                    source_engine,
416                    self.span(),
417                    format!("Uninitialized register \"{item}\" is shadowing a {} of the same name.", constant_or_configurable_or_variable.to_ascii_lowercase()),
418                ),
419                hints: {
420                    let mut hints = vec![
421                        Hint::info(
422                            source_engine,
423                            item.span(),
424                            format!("{constant_or_configurable_or_variable} \"{item}\" is declared here.")
425                        ),
426                    ];
427
428                    hints.append(&mut Hint::multi_help(
429                        source_engine,
430                        &self.span(),
431                        vec![
432                            format!("Are you trying to initialize the register to the value of the {}?", constant_or_configurable_or_variable.to_ascii_lowercase()),
433                            format!("In that case, you must do it explicitly: `{item}: {item}`."),
434                            format!("Otherwise, to avoid the confusion with the shadowed {}, consider renaming the register \"{item}\".", constant_or_configurable_or_variable.to_ascii_lowercase()),
435                        ]
436                    ));
437
438                    hints
439                },
440                help: vec![],
441            },
442            AsmBlockIsEmpty => Diagnostic {
443                reason: Some(Reason::new(code(1), "ASM block is empty".to_string())),
444                issue: Issue::warning(
445                    source_engine,
446                    self.span(),
447                    "This ASM block is empty.".to_string(),
448                ),
449                hints: vec![],
450                help: vec![
451                    "Consider adding assembly instructions or a return register to the ASM block, or removing the block altogether.".to_string(),
452                ],
453            },
454            DuplicatedStorageKey { first_field, first_field_full_name, first_field_key_is_compiler_generated, second_field, second_field_full_name, second_field_key_is_compiler_generated, key } => Diagnostic {
455                reason: Some(Reason::new(code(1), "Two storage fields have the same storage key".to_string())),
456                issue: Issue::warning(
457                    source_engine,
458                    first_field.span(),
459                    format!("\"{first_field_full_name}\" has the same storage key as \"{second_field_full_name}\"."),
460                ),
461                hints: vec![
462                    Hint::info(
463                        source_engine,
464                        second_field.span(),
465                        format!("\"{second_field_full_name}\" is declared here."),
466                    ),
467                ],
468                help: vec![
469                    if *first_field_key_is_compiler_generated || *second_field_key_is_compiler_generated {
470                        format!("The key of \"{}\" is generated by the compiler using the following formula:",
471                            if *first_field_key_is_compiler_generated {
472                                first_field_full_name
473                            } else {
474                                second_field_full_name
475                            }
476                        )
477                    } else {
478                        "Both keys are explicitly defined by using the `in` keyword.".to_string()
479                    },
480                    if *first_field_key_is_compiler_generated || *second_field_key_is_compiler_generated {
481                        format!("{}sha256((0u8, \"{}\"))",
482                            Indent::Single,
483                            if *first_field_key_is_compiler_generated {
484                                first_field_full_name
485                            } else {
486                                second_field_full_name
487                            }
488                        )
489                    } else {
490                        Diagnostic::help_none()
491                    },
492                    format!("The common key is: {key}.")
493                ],
494            },
495            UnknownAttribute { attribute, known_attributes } => Diagnostic {
496                reason: Some(Reason::new(code(1), "Attribute is unknown".to_string())),
497                issue: Issue::warning(
498                    source_engine,
499                    attribute.span(),
500                    format!("\"{attribute}\" attribute is unknown.")
501                ),
502                hints: vec![did_you_mean_help(source_engine, attribute.span(), known_attributes.iter(), 2, Enclosing::DoubleQuote)],
503                help: vec![
504                    "Unknown attributes are allowed and can be used by third-party tools,".to_string(),
505                    "but the compiler ignores them.".to_string(),
506                ],
507            },
508            UnknownAttributeArg { attribute, arg, expected_args } => Diagnostic {
509                reason: Some(Reason::new(code(1), "Attribute argument is unknown".to_string())),
510                issue: Issue::warning(
511                    source_engine,
512                    arg.span(),
513                    format!("\"{arg}\" is an unknown argument for attribute \"{attribute}\".")
514                ),
515                hints: {
516                    let mut hints = vec![did_you_mean_help(source_engine, arg.span(), expected_args, 2, Enclosing::DoubleQuote)];
517                    if expected_args.len() == 1 {
518                        hints.push(Hint::help(source_engine, arg.span(), format!("The only known argument is \"{}\".", expected_args[0])));
519                    } else if expected_args.len() <= 3 {
520                        hints.push(Hint::help(source_engine, arg.span(), format!("Known arguments are {}.", sequence_to_str(expected_args, Enclosing::DoubleQuote, usize::MAX))));
521                    } else {
522                        hints.push(Hint::help(source_engine, arg.span(), "Known arguments are:".to_string()));
523                        hints.append(&mut Hint::multi_help(source_engine, &arg.span(), sequence_to_list(expected_args, Indent::Single, usize::MAX)))
524                    }
525                    hints
526                },
527                help: vec![
528                    format!("Unknown attribute arguments are allowed for some attributes like \"{attribute}\"."),
529                    "They can be used by third-party tools, but the compiler ignores them.".to_string(),
530                ],
531            },
532            UsingDeprecated { deprecated_element, deprecated_element_name, help } => Diagnostic {
533                reason: Some(Reason::new(code(1), format!("{deprecated_element} is deprecated"))),
534                issue: Issue::warning(
535                    source_engine,
536                    self.span(),
537                    format!("{deprecated_element} \"{deprecated_element_name}\" is deprecated."),
538                ),
539                hints: help.as_ref().map_or(vec![], |help| vec![
540                    Hint::help(
541                        source_engine,
542                        self.span(),
543                        help.clone(),
544                    ),
545                ]),
546                help: vec![],
547            },
548            ErrorTypeEmptyEnum { enum_name } => Diagnostic {
549                reason: Some(Reason::new(code(1), "Empty error type enum cannot be used in `panic` expressions".to_string())),
550                issue: Issue::warning(
551                    source_engine,
552                    enum_name.span(),
553                    format!("Error type enum \"{enum_name}\" is empty and can never be used in `panic` expressions."),
554                ),
555                hints: vec![],
556                help: vec![
557                    "Empty enums with no enum variants can never be instantiated.".to_string(),
558                    "Thus, they cannot have instances to use as arguments in `panic` expressions.".to_string(),
559                    Diagnostic::help_empty_line(),
560                    format!("Consider adding enum variants to \"{enum_name}\" and attributing them"),
561                    "with the `#[error]` attribute.".to_string(),
562                ],
563            },
564            ErrorEmptyErrorMessage { enum_name, enum_variant_name } => Diagnostic {
565                reason: Some(Reason::new(code(1), "Error message is empty".to_string())),
566                issue: Issue::warning(
567                    source_engine,
568                    self.span(),
569                    format!("Error enum variant \"{enum_name}::{enum_variant_name}\" has an empty error message."),
570                ),
571                hints: vec![
572                    Hint::help(
573                        source_engine,
574                        self.span(),
575                        "Consider adding a helpful error message here.".to_string(),
576                    )
577                ],
578                help: vec![],
579            },
580            ErrorDuplicatedErrorMessage { last_occurrence, previous_occurrences } => Diagnostic {
581                reason: Some(Reason::new(code(1), "Error message is duplicated".to_string())),
582                issue: Issue::error(
583                    source_engine,
584                    last_occurrence.clone(),
585                    "This error message is duplicated.".to_string(),
586                ),
587                hints: {
588                    let (first_occurrence, other_occurrences) = previous_occurrences.split_first().expect("there is at least one previous occurrence in `previous_occurrences`");
589                    let mut hints = vec![Hint::info(source_engine, first_occurrence.clone(), "It is already used here.".to_string())];
590                    other_occurrences.iter().for_each(|occurrence| hints.push(Hint::info(source_engine, occurrence.clone(), "And here.".to_string())));
591                    hints.push(Hint::help(source_engine, last_occurrence.clone(), "Consider using a unique error message for every error variant.".to_string()));
592                    hints
593                },
594                help: vec![],
595            },
596           _ => Diagnostic {
597                    // TODO: Temporarily we use self here to achieve backward compatibility.
598                    //       In general, self must not be used and will not be used once we
599                    //       switch to our own #[error] macro. All the values for the formatting
600                    //       of a diagnostic must come from the enum variant parameters.
601                    issue: Issue::warning(source_engine, self.span(), format!("{}", self.warning_content)),
602                    ..Default::default()
603                }
604        }
605    }
606}
607
608#[cfg(test)]
609mod test {
610    use sway_types::style::*;
611
612    #[test]
613    fn detect_styles() {
614        let snake_cases = [
615            "hello",
616            "__hello",
617            "blah32",
618            "some_words_here",
619            "___some_words_here",
620        ];
621        let screaming_snake_cases = ["SOME_WORDS_HERE", "___SOME_WORDS_HERE"];
622        let upper_camel_cases = [
623            "Hello",
624            "__Hello",
625            "Blah32",
626            "SomeWordsHere",
627            "___SomeWordsHere",
628        ];
629        let screaming_snake_case_or_upper_camel_case_idents = ["HELLO", "__HELLO", "BLAH32"];
630        let styleless_idents = ["Mix_Of_Things", "__Mix_Of_Things", "FooBar_123"];
631        for name in &snake_cases {
632            assert!(is_snake_case(name));
633            assert!(!is_screaming_snake_case(name));
634            assert!(!is_upper_camel_case(name));
635        }
636        for name in &screaming_snake_cases {
637            assert!(!is_snake_case(name));
638            assert!(is_screaming_snake_case(name));
639            assert!(!is_upper_camel_case(name));
640        }
641        for name in &upper_camel_cases {
642            assert!(!is_snake_case(name));
643            assert!(!is_screaming_snake_case(name));
644            assert!(is_upper_camel_case(name));
645        }
646        for name in &screaming_snake_case_or_upper_camel_case_idents {
647            assert!(!is_snake_case(name));
648            assert!(is_screaming_snake_case(name));
649            assert!(is_upper_camel_case(name));
650        }
651        for name in &styleless_idents {
652            assert!(!is_snake_case(name));
653            assert!(!is_screaming_snake_case(name));
654            assert!(!is_upper_camel_case(name));
655        }
656    }
657
658    #[test]
659    fn convert_to_snake_case() {
660        assert_eq!("hello", to_snake_case("HELLO"));
661        assert_eq!("___hello", to_snake_case("___HELLO"));
662        assert_eq!("blah32", to_snake_case("BLAH32"));
663        assert_eq!("some_words_here", to_snake_case("SOME_WORDS_HERE"));
664        assert_eq!("___some_words_here", to_snake_case("___SOME_WORDS_HERE"));
665        assert_eq!("hello", to_snake_case("Hello"));
666        assert_eq!("___hello", to_snake_case("___Hello"));
667        assert_eq!("blah32", to_snake_case("Blah32"));
668        assert_eq!("some_words_here", to_snake_case("SomeWordsHere"));
669        assert_eq!("___some_words_here", to_snake_case("___SomeWordsHere"));
670        assert_eq!("some_words_here", to_snake_case("someWordsHere"));
671        assert_eq!("___some_words_here", to_snake_case("___someWordsHere"));
672        assert_eq!("mix_of_things", to_snake_case("Mix_Of_Things"));
673        assert_eq!("__mix_of_things", to_snake_case("__Mix_Of_Things"));
674        assert_eq!("foo_bar_123", to_snake_case("FooBar_123"));
675    }
676
677    #[test]
678    fn convert_to_screaming_snake_case() {
679        assert_eq!("HELLO", to_screaming_snake_case("hello"));
680        assert_eq!("___HELLO", to_screaming_snake_case("___hello"));
681        assert_eq!("BLAH32", to_screaming_snake_case("blah32"));
682        assert_eq!(
683            "SOME_WORDS_HERE",
684            to_screaming_snake_case("some_words_here")
685        );
686        assert_eq!(
687            "___SOME_WORDS_HERE",
688            to_screaming_snake_case("___some_words_here")
689        );
690        assert_eq!("HELLO", to_screaming_snake_case("Hello"));
691        assert_eq!("___HELLO", to_screaming_snake_case("___Hello"));
692        assert_eq!("BLAH32", to_screaming_snake_case("Blah32"));
693        assert_eq!("SOME_WORDS_HERE", to_screaming_snake_case("SomeWordsHere"));
694        assert_eq!(
695            "___SOME_WORDS_HERE",
696            to_screaming_snake_case("___SomeWordsHere")
697        );
698        assert_eq!("SOME_WORDS_HERE", to_screaming_snake_case("someWordsHere"));
699        assert_eq!(
700            "___SOME_WORDS_HERE",
701            to_screaming_snake_case("___someWordsHere")
702        );
703        assert_eq!("MIX_OF_THINGS", to_screaming_snake_case("Mix_Of_Things"));
704        assert_eq!(
705            "__MIX_OF_THINGS",
706            to_screaming_snake_case("__Mix_Of_Things")
707        );
708        assert_eq!("FOO_BAR_123", to_screaming_snake_case("FooBar_123"));
709    }
710
711    #[test]
712    fn convert_to_upper_camel_case() {
713        assert_eq!("Hello", to_upper_camel_case("hello"));
714        assert_eq!("___Hello", to_upper_camel_case("___hello"));
715        assert_eq!("Blah32", to_upper_camel_case("blah32"));
716        assert_eq!("SomeWordsHere", to_upper_camel_case("some_words_here"));
717        assert_eq!(
718            "___SomeWordsHere",
719            to_upper_camel_case("___some_words_here")
720        );
721        assert_eq!("Hello", to_upper_camel_case("HELLO"));
722        assert_eq!("___Hello", to_upper_camel_case("___HELLO"));
723        assert_eq!("Blah32", to_upper_camel_case("BLAH32"));
724        assert_eq!("SomeWordsHere", to_upper_camel_case("SOME_WORDS_HERE"));
725        assert_eq!(
726            "___SomeWordsHere",
727            to_upper_camel_case("___SOME_WORDS_HERE")
728        );
729        assert_eq!("SomeWordsHere", to_upper_camel_case("someWordsHere"));
730        assert_eq!("___SomeWordsHere", to_upper_camel_case("___someWordsHere"));
731        assert_eq!("MixOfThings", to_upper_camel_case("Mix_Of_Things"));
732        assert_eq!("__MixOfThings", to_upper_camel_case("__Mix_Of_Things"));
733        assert_eq!("FooBar123", to_upper_camel_case("FooBar_123"));
734    }
735}