js_component_bindgen/
names.rs1use heck::ToLowerCamelCase;
2use std::collections::hash_map::RandomState;
3use std::collections::{HashMap, HashSet};
4use std::hash::{BuildHasher, Hash};
5
6#[derive(Default)]
7pub struct LocalNames {
8 local_name_ids: HashMap<u64, String>,
10 local_names: HashSet<String>,
11 random_state: RandomState,
12}
13
14impl<'a> LocalNames {
15 pub fn exclude_globals(&mut self, globals: &[&str]) {
17 for name in globals {
18 self.local_names.insert(name.to_string());
19 }
20 }
21
22 pub fn create_once(&'a mut self, goal_name: &str) -> &'a str {
24 let goal_name = if let Some(last_char) = goal_name.rfind('/') {
25 &goal_name[last_char + 1..]
26 } else {
27 goal_name
28 };
29 let mut goal = to_js_identifier(goal_name);
30 if self.local_names.contains(&goal) {
31 let mut idx = 1;
32 loop {
33 let valid_name_suffixed = format!("{goal}${idx}");
34 if !self.local_names.contains(&valid_name_suffixed) {
35 goal = valid_name_suffixed;
36 break;
37 }
38 idx += 1;
39 }
40 }
41 self.local_names.insert(goal.to_string());
42 self.local_names.get(&goal).unwrap()
43 }
44
45 pub fn get<H: Hash>(&'a self, unique_id: H) -> &'a str {
46 let hash = self.random_state.hash_one(&unique_id);
47 if !self.local_name_ids.contains_key(&hash) {
48 panic!("Internal error, no name defined in local names map");
49 }
50 &self.local_name_ids[&hash]
51 }
52
53 pub fn try_get<H: Hash>(&'a self, unique_id: H) -> Option<&'a str> {
54 let hash = self.random_state.hash_one(&unique_id);
55 if !self.local_name_ids.contains_key(&hash) {
56 return None;
57 }
58 Some(&self.local_name_ids[&hash])
59 }
60
61 pub fn get_or_create<H: Hash>(&'a mut self, unique_id: H, goal_name: &str) -> (&'a str, bool) {
66 let hash = self.random_state.hash_one(&unique_id);
67 let mut seen = true;
68 #[allow(clippy::map_entry)]
69 if !self.local_name_ids.contains_key(&hash) {
70 let goal = self.create_once(goal_name).to_string();
71 self.local_name_ids.insert(hash, goal);
72 seen = false;
73 }
74 (self.local_name_ids.get(&hash).unwrap(), seen)
75 }
76}
77
78pub fn to_js_identifier(goal_name: &str) -> String {
80 if is_js_identifier(goal_name) {
81 goal_name.to_string()
82 } else {
83 let goal = goal_name.to_lower_camel_case();
84 let mut identifier = String::new();
85 for char in goal.chars() {
86 let valid_char = if identifier.is_empty() {
87 is_js_identifier_start(char)
88 } else {
89 is_js_identifier_char(char)
90 };
91 if valid_char {
92 identifier.push(char);
93 } else {
94 identifier.push(match char {
95 '.' => '_',
96 _ => '$',
97 });
98 }
99 }
100 if !is_js_identifier(&identifier) {
101 identifier = format!("_{identifier}");
102 if !is_js_identifier(&identifier) {
103 panic!("Unable to generate valid identifier {identifier} for '{goal_name}'");
104 }
105 }
106 identifier
107 }
108}
109
110pub fn is_js_identifier(s: &str) -> bool {
111 let mut chars = s.chars();
112 if let Some(char) = chars.next() {
113 if !is_js_identifier_start(char) {
114 return false;
115 }
116 } else {
117 return false;
118 }
119 for char in chars {
120 if !is_js_identifier_char(char) {
121 return false;
122 }
123 }
124 !is_js_reserved_word(s)
125}
126
127pub fn is_js_reserved_word(s: &str) -> bool {
128 RESERVED_KEYWORDS.binary_search(&s).is_ok()
129}
130
131fn is_js_identifier_start(code: char) -> bool {
134 match code {
135 'A'..='Z' | 'a'..='z' | '$' | '_' => true,
136 _ => false,
138 }
139}
140
141fn is_js_identifier_char(code: char) -> bool {
144 match code {
145 '0'..='9' | 'A'..='Z' | 'a'..='z' | '$' | '_' => true,
146 _ => false,
148 }
149}
150
151pub fn maybe_quote_id(name: &str) -> String {
152 if is_js_identifier(name) {
153 name.to_string()
154 } else {
155 format!("'{name}'")
156 }
157}
158
159pub fn maybe_quote_member(name: &str) -> String {
160 if name == "*" {
161 "".to_string()
162 } else if is_js_identifier(name) {
163 format!(".{name}")
164 } else {
165 format!("['{name}']")
166 }
167}
168
169pub(crate) const RESERVED_KEYWORDS: &[&str] = &[
170 "await",
171 "break",
172 "case",
173 "catch",
174 "class",
175 "const",
176 "continue",
177 "debugger",
178 "default",
179 "delete",
180 "do",
181 "eval",
182 "else",
183 "enum",
184 "export",
185 "extends",
186 "false",
187 "finally",
188 "for",
189 "function",
190 "if",
191 "implements",
192 "import",
193 "in",
194 "instanceof",
195 "interface",
196 "let",
197 "new",
198 "null",
199 "package",
200 "private",
201 "protected",
202 "public",
203 "return",
204 "static",
205 "super",
206 "switch",
207 "this",
208 "throw",
209 "true",
210 "try",
211 "typeof",
212 "var",
213 "void",
214 "while",
215 "with",
216 "yield",
217];