frame_cli/scaffold/
names.rs1use super::NewError;
5
6pub fn validate_name(name: &str) -> Result<(), NewError> {
13 let crate_error = crate_name_error(name);
14 let gleam_error = gleam_name_error(name);
15 match (crate_error, gleam_error) {
16 (None, None) => Ok(()),
17 (Some(crate_reason), None) => Err(NewError::InvalidCrateName {
18 name: name.to_owned(),
19 reason: crate_reason,
20 }),
21 (None, Some(gleam_reason)) => Err(NewError::InvalidGleamName {
22 name: name.to_owned(),
23 reason: gleam_reason,
24 }),
25 (Some(crate_reason), Some(gleam_reason)) => Err(NewError::InvalidBothNames {
26 name: name.to_owned(),
27 crate_reason,
28 gleam_reason,
29 }),
30 }
31}
32
33fn crate_name_error(name: &str) -> Option<&'static str> {
34 const KEYWORDS: &[&str] = &[
35 "Self", "abstract", "as", "async", "await", "become", "box", "break", "const", "continue",
36 "crate", "do", "dyn", "else", "enum", "extern", "false", "final", "fn", "for", "gen", "if",
37 "impl", "in", "let", "loop", "macro", "match", "mod", "move", "mut", "override", "priv",
38 "pub", "ref", "return", "self", "static", "struct", "super", "trait", "true", "try",
39 "type", "typeof", "union", "unsafe", "unsized", "use", "virtual", "where", "while",
40 "yield",
41 ];
42 let mut chars = name.chars();
43 if name == "_"
44 || !chars
45 .next()
46 .is_some_and(|first| first == '_' || first.is_ascii_alphabetic())
47 || !chars.all(|character| character == '_' || character.is_ascii_alphanumeric())
48 {
49 return Some("it must be an ASCII Rust identifier");
50 }
51 KEYWORDS
52 .contains(&name)
53 .then_some("Rust keywords are not crate identifiers")
54}
55
56fn gleam_name_error(name: &str) -> Option<&'static str> {
57 let mut chars = name.chars();
58 if !chars.next().is_some_and(|first| first.is_ascii_lowercase())
59 || !chars.all(|character| {
60 character == '_' || character.is_ascii_lowercase() || character.is_ascii_digit()
61 })
62 {
63 Some(
64 "it must start with a lowercase ASCII letter and contain only lowercase letters, digits, or underscores",
65 )
66 } else {
67 None
68 }
69}
70
71#[cfg(test)]
72mod tests {
73 use super::validate_name;
74
75 #[test]
76 fn invalid_names_report_crate_gleam_and_both_rules() {
77 let crate_error = validate_name("type").err().map(|error| error.to_string());
78 assert!(crate_error.is_some_and(|message| {
79 message.contains("Rust crate identifier") && message.contains("[a-z][a-z0-9_]*")
80 }));
81
82 let gleam_error = validate_name("Hello").err().map(|error| error.to_string());
83 assert!(gleam_error.is_some_and(|message| {
84 message.contains("Gleam project name") && message.contains("[a-z][a-z0-9_]*")
85 }));
86
87 let both_error = validate_name("bad-name")
88 .err()
89 .map(|error| error.to_string());
90 assert!(both_error.is_some_and(|message| {
91 message.contains("fails both rules")
92 && message.contains("Rust crate identifier")
93 && message.contains("Gleam project name")
94 }));
95 }
96}