1use indexmap::IndexMap;
2
3use super::{
4 tokenizer::{Token, Tokenizer},
5 Composer, ImportDefWithOffset, ImportDefinition,
6};
7
8pub fn parse_imports<'a>(
9 input: &'a str,
10 declared_imports: &mut IndexMap<String, Vec<String>>,
11) -> Result<(), (&'a str, usize)> {
12 let mut tokens = Tokenizer::new(input, false).peekable();
13
14 match tokens.next() {
15 Some(Token::Other('#', _)) => (),
16 Some(other) => return Err(("expected `#import`", other.pos())),
17 None => return Err(("expected #import", input.len())),
18 };
19 match tokens.next() {
20 Some(Token::Identifier("import", _)) => (),
21 Some(other) => return Err(("expected `#import`", other.pos())),
22 None => return Err(("expected `#import`", input.len())),
23 };
24
25 let mut stack = Vec::default();
26 let mut current = String::default();
27 let mut as_name = None;
28 let mut is_deprecated_itemlist = false;
29
30 loop {
31 match tokens.peek() {
32 Some(Token::Identifier(ident, _)) => {
33 current.push_str(ident);
34 tokens.next();
35
36 if tokens.peek().and_then(Token::identifier) == Some("as") {
37 let pos = tokens.next().unwrap().pos();
38 let Some(Token::Identifier(name, _)) = tokens.next() else {
39 return Err(("expected identifier after `as`", pos));
40 };
41
42 as_name = Some(name);
43 }
44
45 if let Some(Token::Identifier(..)) = tokens.peek() {
47 #[cfg(not(feature = "allow_deprecated"))]
48 tracing::warn!("item list imports are deprecated, please use `rust::style::item_imports` (or use feature `allow_deprecated`)`\n| {}", input);
49
50 is_deprecated_itemlist = true;
51 stack.push(format!("{}::", current));
52 current = String::default();
53 as_name = None;
54 }
55
56 continue;
57 }
58 Some(Token::Other('{', pos)) => {
59 if !current.ends_with("::") {
60 return Err(("open brace must follow `::`", *pos));
61 }
62 stack.push(current);
63 current = String::default();
64 as_name = None;
65 }
66 Some(Token::Other(',', _))
67 | Some(Token::Other('}', _))
68 | Some(Token::Other('\n', _))
69 | None => {
70 if !current.is_empty() {
71 let used_name = as_name.map(ToString::to_string).unwrap_or_else(|| {
72 current
73 .rsplit_once("::")
74 .map(|(_, name)| name.to_owned())
75 .unwrap_or(current.clone())
76 });
77 declared_imports.entry(used_name).or_default().push(format!(
78 "{}{}",
79 stack.join(""),
80 current
81 ));
82 current = String::default();
83 as_name = None;
84 }
85
86 if let Some(Token::Other('}', pos)) = tokens.peek() {
87 if stack.pop().is_none() {
88 return Err(("close brace without open", *pos));
89 }
90 }
91
92 if tokens.peek().is_none() {
93 break;
94 }
95 }
96 Some(Token::Other(';', _)) => {
97 tokens.next();
98 if let Some(token) = tokens.peek() {
99 return Err(("unexpected token after ';'", token.pos()));
100 }
101 }
102 Some(Token::Other(_, pos)) => return Err(("unexpected token", *pos)),
103 Some(Token::Whitespace(..)) => unreachable!(),
104 }
105
106 tokens.next();
107 }
108
109 if !(stack.is_empty() || is_deprecated_itemlist && stack.len() == 1) {
110 return Err(("missing close brace", input.len()));
111 }
112
113 Ok(())
114}
115
116pub fn substitute_identifiers(
117 input: &str,
118 offset: usize,
119 declared_imports: &IndexMap<String, Vec<String>>,
120 used_imports: &mut IndexMap<String, ImportDefWithOffset>,
121 allow_ambiguous: bool,
122) -> Result<String, usize> {
123 let tokens = Tokenizer::new(input, true);
124 let mut output = String::with_capacity(input.len());
125 let mut in_substitution_position = true;
126
127 for token in tokens {
128 match token {
129 Token::Identifier(ident, token_pos) => {
130 if in_substitution_position {
131 let (first, residual) = ident.split_once("::").unwrap_or((ident, ""));
132 let full_paths = declared_imports
133 .get(first)
134 .cloned()
135 .unwrap_or(vec![first.to_owned()]);
136
137 if !allow_ambiguous && full_paths.len() > 1 {
138 return Err(offset + token_pos);
139 }
140
141 for mut full_path in full_paths {
142 if !residual.is_empty() {
143 full_path.push_str("::");
144 full_path.push_str(residual);
145 }
146
147 if let Some((module, item)) = full_path.rsplit_once("::") {
148 used_imports
149 .entry(module.to_owned())
150 .or_insert_with(|| ImportDefWithOffset {
151 definition: ImportDefinition {
152 import: module.to_owned(),
153 ..Default::default()
154 },
155 offset: offset + token_pos,
156 })
157 .definition
158 .items
159 .push(item.to_owned());
160 output.push_str(item);
161 output.push_str(&Composer::decorate(module));
162 } else if full_path.find('"').is_some() {
163 output.push_str(ident);
168 } else {
169 output.push_str(&full_path);
173 }
174 }
175 } else {
176 output.push_str(ident);
177 }
178 }
179 Token::Other(other, _) => {
180 output.push(other);
181 if other == '.' || other == '@' {
182 in_substitution_position = false;
183 continue;
184 }
185 }
186 Token::Whitespace(ws, _) => output.push_str(ws),
187 }
188
189 in_substitution_position = true;
190 }
191
192 Ok(output)
193}
194
195#[cfg(test)]
196fn test_parse(input: &str) -> Result<IndexMap<String, Vec<String>>, (&str, usize)> {
197 let mut declared_imports = IndexMap::default();
198 parse_imports(input, &mut declared_imports)?;
199 Ok(declared_imports)
200}
201
202#[test]
203fn import_tokens() {
204 let input = r"
205 #import a::b
206 ";
207 assert_eq!(
208 test_parse(input),
209 Ok(IndexMap::from_iter([(
210 "b".to_owned(),
211 vec!("a::b".to_owned())
212 )]))
213 );
214
215 let input = r"
216 #import a::{b, c}
217 ";
218 assert_eq!(
219 test_parse(input),
220 Ok(IndexMap::from_iter([
221 ("b".to_owned(), vec!("a::b".to_owned())),
222 ("c".to_owned(), vec!("a::c".to_owned())),
223 ]))
224 );
225
226 let input = r"
227 #import a::{b as d, c}
228 ";
229 assert_eq!(
230 test_parse(input),
231 Ok(IndexMap::from_iter([
232 ("d".to_owned(), vec!("a::b".to_owned())),
233 ("c".to_owned(), vec!("a::c".to_owned())),
234 ]))
235 );
236
237 let input = r"
238 #import a::{b::{c, d}, e}
239 ";
240 assert_eq!(
241 test_parse(input),
242 Ok(IndexMap::from_iter([
243 ("c".to_owned(), vec!("a::b::c".to_owned())),
244 ("d".to_owned(), vec!("a::b::d".to_owned())),
245 ("e".to_owned(), vec!("a::e".to_owned())),
246 ]))
247 );
248
249 let input = r"
250 #import a::b::{c, d}, e
251 ";
252 assert_eq!(
253 test_parse(input),
254 Ok(IndexMap::from_iter([
255 ("c".to_owned(), vec!("a::b::c".to_owned())),
256 ("d".to_owned(), vec!("a::b::d".to_owned())),
257 ("e".to_owned(), vec!("e".to_owned())),
258 ]))
259 );
260
261 let input = r"
262 #import a, b
263 ";
264 assert_eq!(
265 test_parse(input),
266 Ok(IndexMap::from_iter([
267 ("a".to_owned(), vec!("a".to_owned())),
268 ("b".to_owned(), vec!("b".to_owned())),
269 ]))
270 );
271
272 let input = r"
273 #import a::b c, d
274 ";
275 assert_eq!(
276 test_parse(input),
277 Ok(IndexMap::from_iter([
278 ("c".to_owned(), vec!("a::b::c".to_owned())),
279 ("d".to_owned(), vec!("a::b::d".to_owned())),
280 ]))
281 );
282
283 let input = r"
284 #import a::b c
285 ";
286 assert_eq!(
287 test_parse(input),
288 Ok(IndexMap::from_iter([(
289 "c".to_owned(),
290 vec!("a::b::c".to_owned())
291 ),]))
292 );
293
294 let input = r"
295 #import a::b::{c::{d, e}, f, g::{h as i, j}}
296 ";
297 assert_eq!(
298 test_parse(input),
299 Ok(IndexMap::from_iter([
300 ("d".to_owned(), vec!("a::b::c::d".to_owned())),
301 ("e".to_owned(), vec!("a::b::c::e".to_owned())),
302 ("f".to_owned(), vec!("a::b::f".to_owned())),
303 ("i".to_owned(), vec!("a::b::g::h".to_owned())),
304 ("j".to_owned(), vec!("a::b::g::j".to_owned())),
305 ]))
306 );
307
308 let input = r"
309 #import a::b::{
310 c::{d, e},
311 f,
312 g::{
313 h as i,
314 j::k::l as m,
315 }
316 }
317 ";
318 assert_eq!(
319 test_parse(input),
320 Ok(IndexMap::from_iter([
321 ("d".to_owned(), vec!("a::b::c::d".to_owned())),
322 ("e".to_owned(), vec!("a::b::c::e".to_owned())),
323 ("f".to_owned(), vec!("a::b::f".to_owned())),
324 ("i".to_owned(), vec!("a::b::g::h".to_owned())),
325 ("m".to_owned(), vec!("a::b::g::j::k::l".to_owned())),
326 ]))
327 );
328
329 let input = r#"
330 #import "path//with\ all sorts of .stuff"::{a, b}
331 "#;
332 assert_eq!(
333 test_parse(input),
334 Ok(IndexMap::from_iter([
335 (
336 "a".to_owned(),
337 vec!(r#""path//with\ all sorts of .stuff"::a"#.to_owned())
338 ),
339 (
340 "b".to_owned(),
341 vec!(r#""path//with\ all sorts of .stuff"::b"#.to_owned())
342 ),
343 ]))
344 );
345
346 let input = r"
347 #import a::b::{
348 ";
349 assert!(test_parse(input).is_err());
350
351 let input = r"
352 #import a::b::{{c}
353 ";
354 assert!(test_parse(input).is_err());
355
356 let input = r"
357 #import a::b::{c}}
358 ";
359 assert!(test_parse(input).is_err());
360
361 let input = r"
362 #import a::b{{c,d}}
363 ";
364 assert!(test_parse(input).is_err());
365
366 let input = r"
367 #import a:b
368 ";
369 assert!(test_parse(input).is_err());
370}