1// Copyright © SixtyFPS GmbH <info@slint.dev>
2// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0
3
4use std::path::{Path, PathBuf};
5use std::rc::Rc;
6
7use crate::{common, util};
8
9use i_slint_compiler::diagnostics::Spanned;
10use i_slint_compiler::parser::{syntax_nodes, SyntaxKind, SyntaxNode, SyntaxToken};
11use lsp_types::Url;
12use smol_str::SmolStr;
13
14#[cfg(target_arch = "wasm32")]
15use crate::wasm_prelude::*;
16
17pub fn main_identifier(input: &SyntaxNode) -> Option<SyntaxToken> {
18 input.child_token(kind:SyntaxKind::Identifier)
19}
20
21fn is_symbol_name_exported(
22 document_cache: &common::DocumentCache,
23 document_node: &syntax_nodes::Document,
24 query: &DeclarationNodeQuery,
25) -> Option<SmolStr> {
26 let ti = query.parent_token_info();
27
28 for export in document_node.ExportsList() {
29 for specifier in export.ExportSpecifier() {
30 let external = specifier
31 .ExportName()
32 .and_then(|en| i_slint_compiler::parser::identifier_text(&en));
33
34 let export_id = specifier.ExportIdentifier();
35 let export_id_str = i_slint_compiler::parser::identifier_text(&export_id);
36
37 if export_id_str.as_ref() == Some(&ti.name)
38 && ti.is_same_symbol(document_cache, main_identifier(&export_id).unwrap())
39 {
40 return external.or(export_id_str);
41 }
42 }
43 if let Some(component) = export.Component() {
44 let identifier = component.DeclaredIdentifier();
45 let identifier_str = i_slint_compiler::parser::identifier_text(&identifier);
46
47 if identifier_str.as_ref() == Some(&ti.name)
48 && ti.is_same_symbol(document_cache, main_identifier(&identifier).unwrap())
49 {
50 return identifier_str;
51 }
52 }
53 for structs in export.StructDeclaration() {
54 let identifier = structs.DeclaredIdentifier();
55 let identifier_str = i_slint_compiler::parser::identifier_text(&identifier);
56
57 if identifier_str.as_ref() == Some(&ti.name)
58 && ti.is_same_symbol(document_cache, main_identifier(&identifier).unwrap())
59 {
60 return identifier_str;
61 }
62 }
63 for enums in export.EnumDeclaration() {
64 let enum_name = i_slint_compiler::parser::identifier_text(&enums.DeclaredIdentifier());
65 if enum_name.as_ref() == Some(&ti.name) {
66 return enum_name;
67 }
68 }
69 }
70
71 None
72}
73
74fn fix_imports(
75 document_cache: &common::DocumentCache,
76 query: &DeclarationNodeQuery,
77 exporter_path: &Path,
78 new_type: &str,
79 edits: &mut Vec<common::SingleTextEdit>,
80) {
81 let Ok(exporter_url: Url) = Url::from_file_path(exporter_path) else {
82 return;
83 };
84 for (url: Url, doc: &Document) in document_cache.all_url_documents() {
85 if url.scheme() == "builtin" || url.path() == exporter_url.path() {
86 continue;
87 }
88
89 fix_import_in_document(document_cache, query, document_node:doc, exporter_path, new_type, edits);
90 }
91}
92
93fn import_path(document_directory: &Path, specifier: &SyntaxNode) -> Option<PathBuf> {
94 assert!([SyntaxKind::ImportSpecifier, SyntaxKind::ExportModule].contains(&specifier.kind()));
95
96 let import: SmolStr = specifierOption
97 .child_token(kind:SyntaxKind::StringLiteral)
98 .and_then(|t: SyntaxToken| i_slint_compiler::literals::unescape_string(t.text()))?;
99
100 if import == "std-widgets.slint" || import.starts_with("@") {
101 return None; // No need to ever look at this!
102 }
103
104 // Do not bother with the TypeLoader: It will check the FS, which we do not use:-/
105 Some(i_slint_compiler::pathutils::clean_path(&document_directory.join(path:import)))
106}
107
108/// Fix up `Type as OtherType` like specifiers found in import and export lists
109fn fix_specifier(
110 document_cache: &common::DocumentCache,
111 query: &DeclarationNodeQuery,
112 new_type: &str,
113 type_name: SyntaxToken,
114 renamed_to: Option<SyntaxToken>,
115 edits: &mut Vec<common::SingleTextEdit>,
116) -> Option<DeclarationNodeQuery> {
117 fn replace_x_as_y_with_newtype(
118 document_cache: &common::DocumentCache,
119 source_file: &i_slint_compiler::diagnostics::SourceFile,
120 x: &SyntaxToken,
121 y: &SyntaxToken,
122 new_type: &str,
123 ) -> common::SingleTextEdit {
124 let start_position = util::text_size_to_lsp_position(source_file, x.text_range().start());
125 let end_position = util::text_size_to_lsp_position(source_file, y.text_range().end());
126 common::SingleTextEdit::from_path(
127 document_cache,
128 source_file.path(),
129 lsp_types::TextEdit {
130 range: lsp_types::Range::new(start_position, end_position),
131 new_text: new_type.to_string(),
132 },
133 )
134 .expect("URL conversion can not fail here")
135 }
136
137 let ti = query.parent_token_info();
138 let source_file = type_name.source_file()?;
139
140 if i_slint_compiler::parser::normalize_identifier(type_name.text()) == ti.name {
141 if let Some(renamed_to) = renamed_to {
142 if i_slint_compiler::parser::normalize_identifier(renamed_to.text())
143 == i_slint_compiler::parser::normalize_identifier(new_type)
144 {
145 if query.has_parent_token_info() {
146 return query.update_parent_token_info(renamed_to);
147 }
148
149 // `Old as New` => `New`
150 edits.push(replace_x_as_y_with_newtype(
151 document_cache,
152 source_file,
153 &type_name,
154 &renamed_to,
155 new_type,
156 ));
157 } else {
158 if query.has_parent_token_info() {
159 return query.update_parent_token_info(renamed_to);
160 }
161
162 // `Old as Foo` => `New as Foo`
163 edits.push(
164 common::SingleTextEdit::from_path(
165 document_cache,
166 source_file.path(),
167 lsp_types::TextEdit {
168 range: util::token_to_lsp_range(&type_name),
169 new_text: new_type.to_string(),
170 },
171 )
172 .expect("URL conversion can not fail here"),
173 );
174 }
175 // Nothing else to change: We still use the old name everywhere.
176 return None;
177 } else {
178 if query.has_parent_token_info() {
179 return Some(query.clone());
180 }
181
182 // `Old` => `New`
183 edits.push(
184 common::SingleTextEdit::from_path(
185 document_cache,
186 source_file.path(),
187 lsp_types::TextEdit {
188 range: util::token_to_lsp_range(&type_name),
189 new_text: new_type.to_string(),
190 },
191 )
192 .expect("URL conversion can not fail here"),
193 );
194 }
195
196 return query.update_parent_token_info(type_name);
197 }
198 if let Some(renamed_to) = renamed_to {
199 if i_slint_compiler::parser::normalize_identifier(renamed_to.text()) == ti.name {
200 if i_slint_compiler::parser::normalize_identifier(type_name.text())
201 == i_slint_compiler::parser::normalize_identifier(new_type)
202 {
203 if query.has_parent_token_info() {
204 return Some(query.clone());
205 }
206
207 // `New as Old` => `New`
208 edits.push(replace_x_as_y_with_newtype(
209 document_cache,
210 source_file,
211 &type_name,
212 &renamed_to,
213 new_type,
214 ));
215 } else {
216 // `Foo as Old` => `Foo as New`
217 if query.has_parent_token_info() {
218 return None;
219 }
220
221 edits.push(
222 common::SingleTextEdit::from_path(
223 document_cache,
224 source_file.path(),
225 lsp_types::TextEdit {
226 range: util::token_to_lsp_range(&renamed_to),
227 new_text: new_type.to_string(),
228 },
229 )
230 .expect("URL conversion can not fail here"),
231 );
232 }
233 return query.update_parent_token_info(renamed_to);
234 }
235 }
236
237 None
238}
239
240fn fix_import_in_document(
241 document_cache: &common::DocumentCache,
242 query: &DeclarationNodeQuery,
243 document_node: &syntax_nodes::Document,
244 exporter_path: &Path,
245 new_type: &str,
246 edits: &mut Vec<common::SingleTextEdit>,
247) {
248 let Some(document_directory) =
249 document_node.source_file().and_then(|sf| sf.path().parent()).map(|p| p.to_owned())
250 else {
251 return;
252 };
253
254 for import_specifier in document_node.ImportSpecifier() {
255 let Some(import_path) = import_path(&document_directory, &import_specifier) else {
256 continue;
257 };
258
259 if import_path != exporter_path {
260 continue;
261 }
262
263 let Some(list) = import_specifier.ImportIdentifierList() else {
264 continue;
265 };
266
267 for identifier in list.ImportIdentifier() {
268 if let Some(sub_query) = fix_specifier(
269 document_cache,
270 query,
271 new_type,
272 main_identifier(&identifier.ExternalName()).unwrap(),
273 identifier.InternalName().map(|internal| main_identifier(&internal).unwrap()),
274 edits,
275 ) {
276 // Change exports
277 fix_export_lists(document_cache, document_node, &sub_query, new_type, edits);
278
279 // Change all local usages:
280 rename_local_symbols(document_cache, document_node, &sub_query, new_type, edits);
281 }
282 }
283 }
284
285 // Find export modules!
286 for export_item in document_node.ExportsList() {
287 let Some(module) = export_item.ExportModule() else {
288 continue;
289 };
290
291 let Some(import_path) = import_path(&document_directory, &module) else {
292 continue;
293 };
294
295 if import_path != exporter_path {
296 continue;
297 }
298
299 if module.child_token(SyntaxKind::Star).is_some() {
300 // Change upstream imports
301 fix_imports(document_cache, query, document_node.source_file.path(), new_type, edits);
302 } else {
303 for specifier in export_item.ExportSpecifier() {
304 if let Some(sub_query) = fix_specifier(
305 document_cache,
306 query,
307 new_type,
308 main_identifier(&specifier.ExportIdentifier()).unwrap(),
309 specifier.ExportName().map(|export| main_identifier(&export).unwrap()),
310 edits,
311 ) {
312 // Change upstream imports
313 fix_imports(
314 document_cache,
315 &sub_query,
316 document_node.source_file.path(),
317 new_type,
318 edits,
319 );
320
321 // Change all local usages:
322 rename_local_symbols(
323 document_cache,
324 document_node,
325 &sub_query,
326 new_type,
327 edits,
328 );
329 }
330 }
331 }
332 }
333}
334
335fn fix_export_lists(
336 document_cache: &common::DocumentCache,
337 document_node: &syntax_nodes::Document,
338 query: &DeclarationNodeQuery,
339 new_type: &str,
340 edits: &mut Vec<common::SingleTextEdit>,
341) -> Option<SmolStr> {
342 for export: ExportsList in document_node.ExportsList() {
343 if export.ExportModule().is_some() {
344 // Import already covers these!
345 continue;
346 }
347
348 for specifier: ExportSpecifier in export.ExportSpecifier() {
349 if let Some(sub_query: DeclarationNodeQuery) = fix_specifier(
350 document_cache,
351 query,
352 new_type,
353 type_name:main_identifier(&specifier.ExportIdentifier()).unwrap(),
354 renamed_to:specifier.ExportName().and_then(|en: ExportName| main_identifier(&en)),
355 edits,
356 ) {
357 let my_path: &Path = document_node.source_file.path();
358 fix_imports(document_cache, &sub_query, exporter_path:my_path, new_type, edits);
359 return Some(sub_query.token_info.name.clone());
360 }
361 }
362 }
363 None
364}
365
366/// Rename all local non import/export related identifiers
367fn rename_local_symbols(
368 document_cache: &common::DocumentCache,
369 document_node: &syntax_nodes::Document,
370 query: &DeclarationNodeQuery,
371 new_type: &str,
372 edits: &mut Vec<common::SingleTextEdit>,
373) {
374 let ti = &query.token_info;
375
376 let mut current_token = document_node.first_token();
377 while let Some(current) = current_token {
378 if current.kind() == SyntaxKind::Identifier
379 && i_slint_compiler::parser::normalize_identifier(current.text()) == ti.name
380 && ![
381 SyntaxKind::ExternalName,
382 SyntaxKind::InternalName,
383 SyntaxKind::ExportIdentifier,
384 SyntaxKind::ExportName,
385 SyntaxKind::PropertyDeclaration,
386 ]
387 .contains(&current.parent().kind())
388 && ti.is_same_symbol(document_cache, current.clone())
389 {
390 edits.push(
391 common::SingleTextEdit::from_path(
392 document_cache,
393 current.source_file.path(),
394 lsp_types::TextEdit {
395 range: util::token_to_lsp_range(&current),
396 new_text: new_type.to_string(),
397 },
398 )
399 .expect("URL conversion can not fail here"),
400 )
401 }
402
403 current_token = current.next_token();
404 }
405}
406
407/// Rename an InternalName in an impoort statement
408///
409/// The ExternalName is different from our name, which is why we ended up here.
410///
411/// Change the InternalName, fix up local usage and then fix up exports. If exports
412/// change something, also fix all the necessary imports.
413fn rename_internal_name(
414 document_cache: &common::DocumentCache,
415 query: &DeclarationNodeQuery,
416 internal_name: &syntax_nodes::InternalName,
417 new_type: &str,
418) -> lsp_types::WorkspaceEdit {
419 let Some(document) = document_cache.get_document_for_source_file(&internal_name.source_file)
420 else {
421 return Default::default();
422 };
423 let Some(document_node) = &document.node else {
424 return Default::default();
425 };
426
427 let mut edits = vec![];
428
429 let parent: syntax_nodes::ImportIdentifier = internal_name.parent().unwrap().into();
430 let external_name = parent.ExternalName();
431
432 if let Some(sub_query) = fix_specifier(
433 document_cache,
434 query,
435 new_type,
436 main_identifier(&external_name).unwrap(),
437 main_identifier(internal_name),
438 &mut edits,
439 ) {
440 rename_local_symbols(document_cache, document_node, &sub_query, new_type, &mut edits);
441 fix_export_lists(document_cache, document_node, &sub_query, new_type, &mut edits);
442 }
443
444 common::create_workspace_edit_from_single_text_edits(edits)
445}
446
447/// We ended up in an ExportName that we need to rename.
448///
449/// The internal name is different, otherwise we would not have ended up here:-)
450/// So we need to rename the export itself and then fix up imports.
451fn rename_export_name(
452 document_cache: &common::DocumentCache,
453 query: &DeclarationNodeQuery,
454 export_name: &syntax_nodes::ExportName,
455 new_type: &str,
456) -> lsp_types::WorkspaceEdit {
457 let mut edits = vec![];
458
459 let specifier: syntax_nodes::ExportSpecifier = export_name.parent().unwrap().into();
460 let Some(internal_name) = main_identifier(&specifier.ExportIdentifier()) else {
461 return Default::default();
462 };
463
464 if let Some(sub_query) = fix_specifier(
465 document_cache,
466 query,
467 new_type,
468 internal_name,
469 main_identifier(export_name),
470 &mut edits,
471 ) {
472 // Change exports
473 fix_imports(
474 document_cache,
475 &sub_query,
476 export_name.source_file.path(),
477 new_type,
478 &mut edits,
479 );
480 };
481
482 common::create_workspace_edit_from_single_text_edits(edits)
483}
484
485#[derive(Clone, Debug)]
486pub enum DeclarationNodeKind {
487 DeclaredIdentifier(syntax_nodes::DeclaredIdentifier),
488 InternalName(syntax_nodes::InternalName),
489 ExportName(syntax_nodes::ExportName),
490}
491
492#[derive(Clone, Debug)]
493pub struct DeclarationNode {
494 kind: DeclarationNodeKind,
495 query: DeclarationNodeQuery,
496}
497
498pub fn find_declaration_node(
499 document_cache: &common::DocumentCache,
500 token: &SyntaxToken,
501) -> Option<DeclarationNode> {
502 if token.kind() != SyntaxKind::Identifier {
503 return None;
504 }
505
506 DeclarationNodeQuery::new(document_cache, token.clone())?.find_declaration_node(document_cache)
507}
508
509impl DeclarationNode {
510 pub fn rename(
511 &self,
512 document_cache: &common::DocumentCache,
513 new_type: &str,
514 ) -> crate::Result<lsp_types::WorkspaceEdit> {
515 match &self.kind {
516 DeclarationNodeKind::DeclaredIdentifier(id: &DeclaredIdentifier) => {
517 rename_declared_identifier(document_cache, &self.query, declared_identifier:id, new_type)
518 }
519 DeclarationNodeKind::InternalName(internal: &InternalName) => {
520 Ok(rename_internal_name(document_cache, &self.query, internal, new_type))
521 }
522 DeclarationNodeKind::ExportName(export: &ExportName) => {
523 Ok(rename_export_name(document_cache, &self.query, export, new_type))
524 }
525 }
526 }
527}
528
529fn find_last_declared_identifier_at_or_before(
530 token: SyntaxToken,
531 type_name: &SmolStr,
532) -> Option<SyntaxNode> {
533 let mut token: Option = Some(token);
534
535 while let Some(t: SyntaxToken) = token {
536 if t.kind() == SyntaxKind::Identifier {
537 let node: SyntaxNode = t.parent();
538 if node.kind() == SyntaxKind::DeclaredIdentifier
539 && i_slint_compiler::parser::identifier_text(&node).as_ref() == Some(type_name)
540 {
541 return Some(node);
542 }
543 }
544 token = t.prev_token();
545 }
546
547 None
548}
549
550#[derive(Clone, Debug)]
551struct TokenInformation {
552 info: common::token_info::TokenInfo,
553 name: SmolStr,
554 token: SyntaxToken,
555}
556
557impl TokenInformation {
558 fn is_same_symbol(&self, document_cache: &common::DocumentCache, token: SyntaxToken) -> bool {
559 let Some(info) = common::token_info::token_info(document_cache, token.clone()) else {
560 return false;
561 };
562
563 match (&self.info, &info) {
564 (common::token_info::TokenInfo::Type(s), common::token_info::TokenInfo::Type(o)) => {
565 s == o
566 }
567 (
568 common::token_info::TokenInfo::ElementType(s),
569 common::token_info::TokenInfo::ElementType(o),
570 ) => s == o,
571 (
572 common::token_info::TokenInfo::ElementRc(s),
573 common::token_info::TokenInfo::ElementRc(o),
574 ) => Rc::ptr_eq(s, o),
575 (
576 common::token_info::TokenInfo::LocalProperty(s),
577 common::token_info::TokenInfo::LocalProperty(o),
578 ) => Rc::ptr_eq(&s.source_file, &o.source_file) && s.text_range() == o.text_range(),
579 (
580 common::token_info::TokenInfo::NamedReference(nl),
581 common::token_info::TokenInfo::NamedReference(nr),
582 ) => Rc::ptr_eq(&nl.element(), &nr.element()) && nl.name() == nr.name(),
583 (
584 common::token_info::TokenInfo::ElementType(
585 i_slint_compiler::langtype::ElementType::Component(c),
586 ),
587 common::token_info::TokenInfo::ElementRc(e),
588 )
589 | (
590 common::token_info::TokenInfo::ElementRc(e),
591 common::token_info::TokenInfo::ElementType(
592 i_slint_compiler::langtype::ElementType::Component(c),
593 ),
594 ) => {
595 if let Some(ce) = c.node.as_ref().and_then(|cn| cn.child_node(SyntaxKind::Element))
596 {
597 e.borrow().debug.iter().any(|di| {
598 Some(di.node.source_file.path()) == ce.source_file().map(|sf| sf.path())
599 && di.node.text_range() == ce.text_range()
600 })
601 } else {
602 false
603 }
604 }
605 (
606 common::token_info::TokenInfo::NamedReference(nr),
607 common::token_info::TokenInfo::LocalProperty(s),
608 )
609 | (
610 common::token_info::TokenInfo::LocalProperty(s),
611 common::token_info::TokenInfo::NamedReference(nr),
612 ) => nr.element().borrow().debug.iter().any(|di| {
613 di.node.source_file.path() == s.source_file.path()
614 && di.node.PropertyDeclaration().any(|pd| pd.text_range() == s.text_range())
615 }),
616 (
617 common::token_info::TokenInfo::LocalProperty(s),
618 common::token_info::TokenInfo::IncompleteNamedReference(nr1, nr2),
619 )
620 | (
621 common::token_info::TokenInfo::IncompleteNamedReference(nr1, nr2),
622 common::token_info::TokenInfo::LocalProperty(s),
623 ) => {
624 matches!(nr1, i_slint_compiler::langtype::ElementType::Component(c)
625 if c.node.as_ref().map(|n| Rc::ptr_eq(&n.source_file, &s.source_file)
626 && Some(n.text_range()) == s.parent().and_then(|p| p.parent()).map(|gp| gp.text_range())).unwrap_or_default())
627 && Some(nr2)
628 == i_slint_compiler::parser::identifier_text(&s.DeclaredIdentifier())
629 .as_ref()
630 }
631 (_, _) => false,
632 }
633 }
634}
635
636#[derive(Clone, Debug)]
637struct DeclarationNodeQuery {
638 token_info: TokenInformation,
639 parent_token_info: Option<TokenInformation>,
640}
641
642impl DeclarationNodeQuery {
643 fn new(document_cache: &common::DocumentCache, token: SyntaxToken) -> Option<Self> {
644 let info = common::token_info::token_info(document_cache, token.clone())?;
645 let name = i_slint_compiler::parser::normalize_identifier(token.text());
646
647 let node = token.parent();
648
649 fn property_parent(
650 document_cache: &common::DocumentCache,
651 node: &SyntaxNode,
652 ) -> Option<TokenInformation> {
653 let element = node.parent()?;
654 assert_eq!(element.kind(), SyntaxKind::Element);
655 let component = element.parent()?;
656 assert_eq!(component.kind(), SyntaxKind::Component);
657
658 let declared_identifier = component.child_node(SyntaxKind::DeclaredIdentifier)?;
659
660 let token = main_identifier(&declared_identifier)?;
661 let info = common::token_info::token_info(document_cache, token.clone())?;
662 let name = i_slint_compiler::parser::normalize_identifier(token.text());
663
664 Some(TokenInformation { info, name, token })
665 }
666
667 let parent_node = node.parent()?;
668
669 let parent_query = match parent_node.kind() {
670 SyntaxKind::PropertyDeclaration => property_parent(document_cache, &parent_node),
671 _ => None,
672 };
673
674 Some(DeclarationNodeQuery {
675 token_info: TokenInformation { info, name, token },
676 parent_token_info: parent_query,
677 })
678 }
679
680 fn update_parent_token_info(&self, token: SyntaxToken) -> Option<Self> {
681 let name = i_slint_compiler::parser::normalize_identifier(token.text());
682
683 let mut query = self.token_info.clone();
684 let mut parent_query = self.parent_token_info.clone();
685
686 if let Some(pq) = &mut parent_query {
687 pq.token = token;
688 pq.name = name;
689 } else {
690 query.token = token;
691 query.name = name;
692 }
693
694 Some(DeclarationNodeQuery { token_info: query, parent_token_info: parent_query })
695 }
696
697 fn is_export_identifier_or_external_name(&self) -> bool {
698 self.token_info.token.kind() == SyntaxKind::Identifier
699 && [SyntaxKind::ExportIdentifier, SyntaxKind::ExternalName]
700 .contains(&self.token_info.token.parent().kind())
701 }
702
703 fn start_token(&self) -> Option<SyntaxToken> {
704 if self.is_export_identifier_or_external_name() {
705 None
706 } else {
707 Some(self.token_info.token.clone())
708 }
709 }
710
711 /// Find the declaration node we should rename
712 fn find_declaration_node(
713 self,
714 document_cache: &common::DocumentCache,
715 ) -> Option<DeclarationNode> {
716 let node = self.token_info.token.parent();
717
718 match node.kind() {
719 SyntaxKind::DeclaredIdentifier => Some(DeclarationNode {
720 kind: DeclarationNodeKind::DeclaredIdentifier(node.into()),
721 query: self,
722 }),
723 SyntaxKind::InternalName => Some(DeclarationNode {
724 kind: DeclarationNodeKind::InternalName(node.into()),
725 query: self,
726 }),
727 SyntaxKind::ExportName => Some(DeclarationNode {
728 kind: DeclarationNodeKind::ExportName(node.into()),
729 query: self,
730 }),
731 _ => {
732 fn find_declared_identifier_in_element(
733 query: &DeclarationNodeQuery,
734 element: &syntax_nodes::Element,
735 ) -> Option<syntax_nodes::DeclaredIdentifier> {
736 for prop in element.PropertyDeclaration() {
737 let identifier = prop.DeclaredIdentifier();
738 if i_slint_compiler::parser::identifier_text(&identifier).as_ref()
739 == Some(&query.token_info.name)
740 {
741 return Some(identifier);
742 }
743 }
744 None
745 }
746
747 let declared_identifier = match &self.token_info.info {
748 common::token_info::TokenInfo::NamedReference(nr) => {
749 if i_slint_compiler::parser::normalize_identifier(nr.name())
750 == self.token_info.name
751 {
752 nr.element()
753 .borrow()
754 .debug
755 .iter()
756 .filter_map(|di| {
757 find_declared_identifier_in_element(&self, &di.node)
758 })
759 .next()
760 } else {
761 None
762 }
763 }
764 common::token_info::TokenInfo::IncompleteNamedReference(element_type, name) => {
765 if name == &self.token_info.name {
766 match &element_type {
767 i_slint_compiler::langtype::ElementType::Component(component) => {
768 find_declared_identifier_in_element(
769 &self,
770 &syntax_nodes::Component::from(
771 component.node.as_ref()?.clone(),
772 )
773 .Element(),
774 )
775 }
776 _ => None,
777 }
778 } else {
779 None
780 }
781 }
782 _ => None,
783 };
784 if let Some(declared_identifier) = declared_identifier {
785 let token = main_identifier(&declared_identifier)?;
786
787 return Some(DeclarationNode {
788 query: DeclarationNodeQuery::new(document_cache, token)?,
789 kind: DeclarationNodeKind::DeclaredIdentifier(declared_identifier),
790 });
791 }
792
793 // Find the element/type manually so exports/imports get resolved
794 let document = document_cache
795 .get_document_by_path(self.token_info.token.source_file.path())?;
796 let document_node = document.node.clone()?;
797 let start_token = self.start_token();
798
799 find_declaration_node_impl(document_cache, &document_node, start_token, self)
800 }
801 }
802 }
803
804 fn has_parent_token_info(&self) -> bool {
805 self.parent_token_info.is_some()
806 }
807
808 fn parent_token_info(&self) -> &TokenInformation {
809 match &self.parent_token_info {
810 Some(ti) => ti,
811 None => &self.token_info,
812 }
813 }
814}
815
816fn find_declaration_node_impl(
817 document_cache: &common::DocumentCache,
818 document_node: &syntax_nodes::Document,
819 start_token: Option<SyntaxToken>,
820 query: DeclarationNodeQuery,
821) -> Option<DeclarationNode> {
822 let pti = query.parent_token_info();
823 let ti = &query.token_info;
824
825 // Exported under a custom name?
826 if start_token.is_none() {
827 for export_item in document_node.ExportsList() {
828 if export_item.ExportModule().is_some() {
829 continue;
830 }
831
832 for specifier in export_item.ExportSpecifier() {
833 if let Some(export_name) = specifier.ExportName() {
834 if i_slint_compiler::parser::identifier_text(&export_name).as_ref()
835 == Some(&pti.name)
836 {
837 return Some(DeclarationNode {
838 kind: DeclarationNodeKind::ExportName(export_name),
839 query,
840 });
841 }
842 }
843 }
844 }
845 }
846
847 let mut token = document_node.last_token();
848
849 while let Some(t) = token {
850 if let Some(declared_identifier) =
851 find_last_declared_identifier_at_or_before(t.clone(), &ti.name)
852 {
853 if ti.is_same_symbol(document_cache, main_identifier(&declared_identifier).unwrap()) {
854 return Some(DeclarationNode {
855 kind: DeclarationNodeKind::DeclaredIdentifier(declared_identifier.into()),
856 query,
857 });
858 }
859
860 token = declared_identifier.first_token().and_then(|t| t.prev_token());
861 } else {
862 token = None;
863 }
864 }
865
866 // Imported?
867 let document_path = document_node.source_file.path();
868 let document_dir = document_path.parent()?;
869
870 for import_spec in document_node.ImportSpecifier() {
871 if let Some(import_id) = import_spec.ImportIdentifierList() {
872 for id in import_id.ImportIdentifier() {
873 let external = i_slint_compiler::parser::identifier_text(&id.ExternalName());
874 let internal =
875 id.InternalName().and_then(|i| i_slint_compiler::parser::identifier_text(&i));
876
877 if internal.as_ref() == Some(&pti.name) {
878 return Some(DeclarationNode {
879 kind: DeclarationNodeKind::InternalName(id.InternalName().unwrap()),
880 query,
881 });
882 }
883
884 if external.as_ref() == Some(&pti.name) {
885 let path = import_path(document_dir, &import_spec)?;
886 let import_doc = document_cache.get_document_by_path(&path)?;
887 let import_doc_node = import_doc.node.as_ref()?;
888
889 return find_declaration_node_impl(
890 document_cache,
891 import_doc_node,
892 None,
893 query,
894 );
895 }
896 }
897 }
898 }
899
900 // Find export modules!
901 for export_item in document_node.ExportsList() {
902 let Some(module) = export_item.ExportModule() else {
903 continue;
904 };
905
906 let path = import_path(document_dir, &module)?;
907 let import_doc = document_cache.get_document_by_path(&path)?;
908 let import_doc_node = import_doc.node.as_ref()?;
909
910 if module.child_token(SyntaxKind::Star).is_some() {
911 if let Some(declaration_node) =
912 find_declaration_node_impl(document_cache, import_doc_node, None, query.clone())
913 {
914 return Some(declaration_node);
915 } else {
916 continue;
917 }
918 } else {
919 for specifier in export_item.ExportSpecifier() {
920 if let Some(export_name) = specifier.ExportName() {
921 if i_slint_compiler::parser::identifier_text(&export_name).as_ref()
922 == Some(&pti.name)
923 {
924 return Some(DeclarationNode {
925 kind: DeclarationNodeKind::ExportName(export_name),
926 query,
927 });
928 }
929 }
930
931 let identifier =
932 i_slint_compiler::parser::identifier_text(&specifier.ExportIdentifier());
933
934 if identifier.as_ref() == Some(&pti.name) {
935 return find_declaration_node_impl(
936 document_cache,
937 import_doc_node,
938 None,
939 query,
940 );
941 }
942 }
943 }
944 }
945
946 None
947}
948
949/// Rename a `DeclaredIdentifier`.
950///
951/// This is a locally defined thing.
952///
953/// Fix up local usages, fix exports and any imports elsewhere if the exports changed
954fn rename_declared_identifier(
955 document_cache: &common::DocumentCache,
956 query: &DeclarationNodeQuery,
957 declared_identifier: &syntax_nodes::DeclaredIdentifier,
958 new_type: &str,
959) -> crate::Result<lsp_types::WorkspaceEdit> {
960 let ti = &query.token_info;
961
962 let source_file = &declared_identifier.source_file;
963 let document = document_cache
964 .get_document_for_source_file(source_file)
965 .expect("Identifier is in unknown document");
966
967 let Some(document_node) = &document.node else {
968 return Err("No document found".into());
969 };
970
971 let parent = declared_identifier.parent().unwrap();
972
973 let normalized_new_type = i_slint_compiler::parser::normalize_identifier(new_type);
974
975 if parent.kind() != SyntaxKind::Component
976 && document.local_registry.lookup(normalized_new_type.as_str())
977 != i_slint_compiler::langtype::Type::Invalid
978 {
979 return Err(format!("{new_type} is already a registered type").into());
980 }
981 if parent.kind() == SyntaxKind::Component
982 && document.local_registry.lookup_element(normalized_new_type.as_str()).is_ok()
983 {
984 return Err(format!("{new_type} is already a registered element").into());
985 }
986
987 let old_type = &ti.name;
988
989 if *old_type == normalized_new_type {
990 return Ok(lsp_types::WorkspaceEdit::default());
991 }
992
993 let mut edits = vec![];
994
995 // Change all local usages:
996 rename_local_symbols(document_cache, document_node, query, new_type, &mut edits);
997
998 if is_symbol_name_exported(document_cache, document_node, query).is_some()
999 && fix_export_lists(document_cache, document_node, query, new_type, &mut edits).is_none()
1000 {
1001 fix_imports(document_cache, query, source_file.path(), new_type, &mut edits);
1002 }
1003
1004 Ok(common::create_workspace_edit_from_single_text_edits(edits))
1005}
1006
1007#[cfg(all(test, feature = "preview-engine"))]
1008mod tests {
1009 use lsp_types::Url;
1010
1011 use super::*;
1012
1013 use std::collections::HashMap;
1014
1015 use crate::common::test;
1016 use crate::common::text_edit;
1017
1018 #[track_caller]
1019 fn find_token_by_comment(
1020 document_cache: &common::DocumentCache,
1021 document_path: &Path,
1022 suffix: &str,
1023 ) -> SyntaxToken {
1024 let document = document_cache.get_document_by_path(document_path).unwrap();
1025 let document = document.node.as_ref().unwrap();
1026
1027 let offset =
1028 document.text().to_string().find(&format!("<- TEST_ME{suffix} ")).unwrap() as u32;
1029 let comment = document.token_at_offset(offset.into()).next().unwrap();
1030 assert_eq!(comment.kind(), SyntaxKind::Comment);
1031 let mut token = comment.prev_token();
1032
1033 while let Some(t) = &token {
1034 if ![SyntaxKind::Comment, SyntaxKind::Eof, SyntaxKind::Whitespace].contains(&t.kind()) {
1035 break;
1036 }
1037 token = t.prev_token();
1038 }
1039 token.unwrap()
1040 }
1041
1042 #[track_caller]
1043 fn find_node_by_comment(
1044 document_cache: &common::DocumentCache,
1045 document_path: &Path,
1046 suffix: &str,
1047 ) -> SyntaxNode {
1048 find_token_by_comment(document_cache, document_path, suffix).parent()
1049 }
1050
1051 #[track_caller]
1052 fn apply_text_changes(
1053 document_cache: &common::DocumentCache,
1054 edit: &lsp_types::WorkspaceEdit,
1055 ) -> Vec<text_edit::EditedText> {
1056 eprintln!("Edit:");
1057 for it in text_edit::EditIterator::new(edit) {
1058 eprintln!(" {} => {:?}", it.0.uri, it.1);
1059 }
1060 eprintln!("*** All edits reported ***");
1061
1062 let changed_text = text_edit::apply_workspace_edit(document_cache, edit).unwrap();
1063 assert!(!changed_text.is_empty()); // there was a change!
1064
1065 eprintln!("After changes were applied:");
1066 for ct in &changed_text {
1067 eprintln!("File {}:", ct.url);
1068 for (count, line) in ct.contents.split('\n').enumerate() {
1069 eprintln!(" {:3}: {line}", count + 1);
1070 }
1071 eprintln!("=========");
1072 }
1073 eprintln!("*** All changes reported ***");
1074
1075 changed_text
1076 }
1077
1078 #[track_caller]
1079 fn compile_test_changes(
1080 document_cache: &common::DocumentCache,
1081 edit: &lsp_types::WorkspaceEdit,
1082 allow_warnings: bool,
1083 ) -> Vec<text_edit::EditedText> {
1084 let changed_text = apply_text_changes(document_cache, edit);
1085
1086 let code = {
1087 let mut map: HashMap<Url, String> = document_cache
1088 .all_url_documents()
1089 .map(|(url, dn)| (url, dn.source_file.as_ref()))
1090 .map(|(url, sf)| (url, sf.source().unwrap().to_string()))
1091 .collect();
1092 for ct in &changed_text {
1093 map.insert(ct.url.clone(), ct.contents.clone());
1094 }
1095 map
1096 };
1097
1098 // changed code compiles fine:
1099 let _ = test::recompile_test_with_sources("fluent", code, allow_warnings);
1100
1101 changed_text
1102 }
1103
1104 #[track_caller]
1105 pub fn rename_tester_with_new_name(
1106 document_cache: &common::DocumentCache,
1107 document_path: &Path,
1108 suffix: &str,
1109 new_name: &str,
1110 ) -> Vec<text_edit::EditedText> {
1111 let edit = find_declaration_node(
1112 document_cache,
1113 &find_token_by_comment(document_cache, document_path, suffix),
1114 )
1115 .unwrap()
1116 .rename(document_cache, new_name)
1117 .unwrap();
1118 compile_test_changes(document_cache, &edit, false)
1119 }
1120
1121 #[track_caller]
1122 pub fn rename_tester(
1123 document_cache: &common::DocumentCache,
1124 document_path: &Path,
1125 suffix: &str,
1126 ) -> Vec<text_edit::EditedText> {
1127 rename_tester_with_new_name(document_cache, document_path, suffix, "XxxYyyZzz")
1128 }
1129
1130 #[test]
1131 fn test_rename_redefined_component() {
1132 let document_cache = test::compile_test_with_sources(
1133 "fluent",
1134 HashMap::from([(
1135 Url::from_file_path(test::main_test_file_name()).unwrap(),
1136 r#"
1137component Foo /* <- TEST_ME_1 */ { @children }
1138
1139export { Foo /* 2 */ }
1140
1141component Bar {
1142 Foo /* 1.1 */ { }
1143}
1144
1145component Foo /* <- TEST_ME_2 */ inherits Foo /* 1.2 */ {
1146 Foo /* 1.3 */ { }
1147}
1148 "#
1149 .to_string(),
1150 )]),
1151 true, // Component `Foo` is replacing a component with the same name
1152 );
1153
1154 // Can not rename the first one...
1155 assert!(find_declaration_node(
1156 &document_cache,
1157 &find_token_by_comment(&document_cache, &test::main_test_file_name(), "_1"),
1158 )
1159 .is_none(),);
1160
1161 let edit = find_declaration_node(
1162 &document_cache,
1163 &find_token_by_comment(&document_cache, &test::main_test_file_name(), "_2"),
1164 )
1165 .unwrap()
1166 .rename(&document_cache, "XxxYyyZzz")
1167 .unwrap();
1168
1169 let edited_text = apply_text_changes(&document_cache, &edit); // DO NOT COMPILE, THAT WILL FAIL!
1170
1171 assert_eq!(edited_text.len(), 1);
1172
1173 assert!(edited_text[0].contents.contains("component Foo /* <- TEST_ME_1 "));
1174 // The *last* Foo gets exported
1175 assert!(edited_text[0].contents.contains("export { XxxYyyZzz /* 2 */ }"));
1176
1177 // All the following are wrong:
1178 assert!(edited_text[0].contents.contains("XxxYyyZzz /* 1.1 "));
1179 assert!(edited_text[0].contents.contains("inherits XxxYyyZzz /* 1.2 "));
1180 assert!(edited_text[0].contents.contains("XxxYyyZzz /* 1.3 "));
1181 }
1182
1183 #[test]
1184 fn test_rename_redefined_enum() {
1185 let document_cache = test::compile_test_with_sources(
1186 "fluent",
1187 HashMap::from([(
1188 Url::from_file_path(test::main_test_file_name()).unwrap(),
1189 r#"
1190enum Foo /* <- TEST_ME_1 */ { test1 }
1191
1192export { Foo /* 2 */ }
1193
1194struct Bar {
1195 bar_test: Foo
1196}
1197
1198enum Foo /* <- TEST_ME_2 */ {
1199 test2
1200}
1201
1202export struct Baz {
1203 baz_test: Foo
1204}
1205 "#
1206 .to_string(),
1207 )]),
1208 true, // Component `Foo` is replacing a component with the same name
1209 );
1210
1211 // Can not rename the first one...
1212 assert!(find_declaration_node(
1213 &document_cache,
1214 &find_token_by_comment(&document_cache, &test::main_test_file_name(), "_1"),
1215 )
1216 .is_none(),);
1217
1218 let edit = find_declaration_node(
1219 &document_cache,
1220 &find_token_by_comment(&document_cache, &test::main_test_file_name(), "_2"),
1221 )
1222 .unwrap()
1223 .rename(&document_cache, "XxxYyyZzz")
1224 .unwrap();
1225
1226 let edited_text = apply_text_changes(&document_cache, &edit); // DO NOT COMPILE, THAT WILL FAIL!
1227
1228 assert_eq!(edited_text.len(), 1);
1229
1230 assert!(edited_text[0].contents.contains("enum Foo /* <- TEST_ME_1 "));
1231 // The *last* Foo gets exported!
1232 assert!(edited_text[0].contents.contains("export { XxxYyyZzz /* 2 */ }"));
1233 assert!(edited_text[0].contents.contains("baz_test: XxxYyyZzz"));
1234
1235 // All the following are wrong:
1236 assert!(edited_text[0].contents.contains("bar_test: XxxYyyZzz"));
1237 }
1238
1239 #[test]
1240 fn test_rename_redefined_struct() {
1241 let document_cache = test::compile_test_with_sources(
1242 "fluent",
1243 HashMap::from([(
1244 Url::from_file_path(test::main_test_file_name()).unwrap(),
1245 r#"
1246struct Foo /* <- TEST_ME_1 */ { test: bool }
1247
1248export { Foo /* 2 */ }
1249
1250struct Bar {
1251 bar_test: Foo
1252}
1253
1254struct Foo /* <- TEST_ME_2 */ {
1255 foo_test: Foo
1256}
1257 "#
1258 .to_string(),
1259 )]),
1260 true, // Component `Foo` is replacing a component with the same name
1261 );
1262
1263 // Can not rename the first one...
1264 assert!(find_declaration_node(
1265 &document_cache,
1266 &find_token_by_comment(&document_cache, &test::main_test_file_name(), "_1"),
1267 )
1268 .is_none(),);
1269
1270 let edit = find_declaration_node(
1271 &document_cache,
1272 &find_token_by_comment(&document_cache, &test::main_test_file_name(), "_2"),
1273 )
1274 .unwrap()
1275 .rename(&document_cache, "XxxYyyZzz")
1276 .unwrap();
1277
1278 let edited_text = apply_text_changes(&document_cache, &edit); // DO NOT COMPILE, THAT WILL FAIL!
1279
1280 assert_eq!(edited_text.len(), 1);
1281
1282 assert!(edited_text[0].contents.contains("struct Foo /* <- TEST_ME_1 "));
1283 // The *last* Foo gets exported!
1284 assert!(edited_text[0].contents.contains("export { XxxYyyZzz /* 2 */ }"));
1285
1286 // All the following are wrong:
1287 assert!(edited_text[0].contents.contains("bar_test: XxxYyyZzz"));
1288 assert!(edited_text[0].contents.contains("foo_test: XxxYyyZzz"));
1289 }
1290
1291 #[test]
1292 fn test_rename_component_from_definition() {
1293 let document_cache = test::compile_test_with_sources(
1294 "fluent",
1295 HashMap::from([(
1296 Url::from_file_path(test::main_test_file_name()).unwrap(),
1297 r#"
1298export { Foo }
1299
1300enum Xyz { Foo, Bar }
1301
1302struct Abc { Foo: Xyz }
1303
1304component Foo /* <- TEST_ME_1 */ inherits Rectangle {
1305 @children
1306}
1307
1308component Baz {
1309 Foo /* <- TEST_ME_2 */ { }
1310}
1311
1312struct Foo {
1313 bar: bool,
1314}
1315
1316export component Bar inherits Foo /* <- TEST_ME_3 */ {
1317 Foo /* <- TEST_ME_4 */ { }
1318 Rectangle {
1319 Foo /* <- TEST_ME_5 */ { }
1320 Foo := Baz { }
1321 }
1322
1323 if true: Rectangle {
1324 Foo /* <- TEST_ME_6 */ { }
1325 }
1326
1327 if false: Rectangle {
1328 Foo /* <- TEST_ME_7 */ { }
1329 }
1330
1331 function Foo(Foo: int) { Foo + 1; }
1332 function F() { self.Foo(42); }
1333
1334 for i in [1, 2, 3]: Foo /* <- TEST_ME_8 */ { }
1335}
1336 "#
1337 .to_string(),
1338 )]),
1339 true, // Component `Foo` is replacing a component with the same name
1340 );
1341
1342 let edited_text = rename_tester(&document_cache, &test::main_test_file_name(), "_1");
1343 assert_eq!(edited_text.len(), 1);
1344
1345 assert_eq!(edited_text.len(), 1);
1346 assert!(edited_text[0].contents.contains("export { XxxYyyZzz }"));
1347 assert!(edited_text[0].contents.contains("enum Xyz { Foo,"));
1348 assert!(edited_text[0].contents.contains("struct Abc { Foo:"));
1349 assert!(edited_text[0].contents.contains("component XxxYyyZzz /* <- TEST_ME_1 "));
1350 assert!(edited_text[0].contents.contains("TEST_ME_1 */ inherits Rectangle "));
1351 assert!(edited_text[0].contents.contains("XxxYyyZzz /* <- TEST_ME_2 "));
1352 assert!(edited_text[0].contents.contains("struct Foo {"));
1353 assert!(edited_text[0]
1354 .contents
1355 .contains("component Bar inherits XxxYyyZzz /* <- TEST_ME_3 "));
1356 assert!(edited_text[0].contents.contains("XxxYyyZzz /* <- TEST_ME_4 "));
1357 assert!(edited_text[0].contents.contains("XxxYyyZzz /* <- TEST_ME_5 "));
1358 assert!(edited_text[0].contents.contains("Foo := Baz "));
1359 assert!(edited_text[0].contents.contains("XxxYyyZzz /* <- TEST_ME_6 "));
1360 assert!(edited_text[0].contents.contains("XxxYyyZzz /* <- TEST_ME_7 "));
1361 assert!(edited_text[0].contents.contains("function Foo(Foo:"));
1362 assert!(edited_text[0].contents.contains("F() { self.Foo("));
1363 assert!(edited_text[0].contents.contains("XxxYyyZzz /* <- TEST_ME_8 "));
1364 }
1365
1366 #[test]
1367 fn test_rename_component_from_definition_live_preview_rename() {
1368 let document_cache = test::compile_test_with_sources(
1369 "fluent",
1370 HashMap::from([(
1371 Url::from_file_path(test::main_test_file_name()).unwrap(),
1372 "component Foo/* <- TEST_ME_1 */{ }\nexport component _SLINT_LivePreview inherits Foo { /* @lsp:ignore-node */ }\n".to_string()
1373 )]),
1374 true,
1375 );
1376
1377 let edit = find_declaration_node(
1378 &document_cache,
1379 &find_token_by_comment(&document_cache, &test::main_test_file_name(), "_1"),
1380 )
1381 .unwrap()
1382 .rename(&document_cache, "XxxYyyZzz")
1383 .unwrap();
1384
1385 assert_eq!(text_edit::EditIterator::new(&edit).count(), 1);
1386
1387 // This does not compile as the type was not changed in the _SLINT_LivePreview part.
1388 // This is inteneded as that code does not really exist in the first place!
1389 }
1390
1391 #[test]
1392 fn test_rename_component_from_definition_with_renaming_export() {
1393 let document_cache = test::compile_test_with_sources(
1394 "fluent",
1395 HashMap::from([
1396 (
1397 Url::from_file_path(test::main_test_file_name()).unwrap(),
1398 r#"
1399import { FExport} from "source.slint";
1400
1401export component Foo {
1402 FExport { }
1403}
1404 "#
1405 .to_string(),
1406 ),
1407 (
1408 Url::from_file_path(test::test_file_name("source.slint")).unwrap(),
1409 r#"
1410enum Foo {
1411 foo, bar
1412}
1413
1414component Foo /* <- TEST_ME_1 */ {
1415 property <Foo> test-property: Foo.bar;
1416}
1417
1418export { Foo as FExport }
1419 "#
1420 .to_string(),
1421 ),
1422 ]),
1423 false,
1424 );
1425
1426 let edited_text =
1427 rename_tester(&document_cache, &test::test_file_name("source.slint"), "_1");
1428
1429 assert_eq!(edited_text.len(), 1);
1430 assert_eq!(
1431 edited_text[0].url.to_file_path().unwrap(),
1432 test::test_file_name("source.slint")
1433 );
1434 assert!(edited_text[0].contents.contains("enum Foo {"));
1435 assert!(edited_text[0].contents.contains("component XxxYyyZzz /* <- TEST_ME_1 "));
1436 assert!(edited_text[0].contents.contains("property <Foo> test-property"));
1437 assert!(edited_text[0].contents.contains("test-property: Foo.bar;"));
1438 assert!(edited_text[0].contents.contains("export { XxxYyyZzz as FExport }"));
1439 }
1440
1441 #[test]
1442 fn test_rename_component_from_definition_with_export() {
1443 let document_cache = test::compile_test_with_sources(
1444 "fluent",
1445 HashMap::from([
1446 (
1447 Url::from_file_path(test::main_test_file_name()).unwrap(),
1448 r#"
1449import { Foo } from "source.slint";
1450import { UserComponent } from "user.slint";
1451import { User2Component } from "user2.slint";
1452import { Foo as User3Fxx } from "user3.slint";
1453import { User4Fxx } from "user4.slint";
1454
1455export component Main {
1456 Foo { }
1457 UserComponent { }
1458 User2Component { }
1459}
1460 "#
1461 .to_string(),
1462 ),
1463 (
1464 Url::from_file_path(test::test_file_name("source.slint")).unwrap(),
1465 r#"
1466export component Foo /* <- TEST_ME_1 */ { }
1467 "#
1468 .to_string(),
1469 ),
1470 (
1471 Url::from_file_path(test::test_file_name("user.slint")).unwrap(),
1472 r#"
1473import { Foo as Bar } from "source.slint";
1474
1475export component UserComponent {
1476 Bar { }
1477}
1478
1479export { Bar }
1480 "#
1481 .to_string(),
1482 ),
1483 (
1484 Url::from_file_path(test::test_file_name("user2.slint")).unwrap(),
1485 r#"
1486import { Foo as XxxYyyZzz } from "source.slint";
1487
1488export component User2Component {
1489 XxxYyyZzz { }
1490}
1491 "#
1492 .to_string(),
1493 ),
1494 (
1495 Url::from_file_path(test::test_file_name("user3.slint")).unwrap(),
1496 r#"
1497import { Foo } from "source.slint";
1498
1499export { Foo }
1500 "#
1501 .to_string(),
1502 ),
1503 (
1504 Url::from_file_path(test::test_file_name("user4.slint")).unwrap(),
1505 r#"
1506import { Foo } from "source.slint";
1507
1508export { Foo as User4Fxx }
1509 "#
1510 .to_string(),
1511 ),
1512 ]),
1513 false,
1514 );
1515
1516 let edited_text =
1517 rename_tester(&document_cache, &test::test_file_name("source.slint"), "_1");
1518
1519 for ed in &edited_text {
1520 let ed_path = ed.url.to_file_path().unwrap();
1521 if ed_path == test::main_test_file_name() {
1522 assert!(ed.contents.contains("XxxYyyZzz"));
1523 assert!(!ed.contents.contains("Foo"));
1524 assert!(ed.contents.contains("UserComponent"));
1525 assert!(ed.contents.contains("import { XxxYyyZzz as User3Fxx }"));
1526 assert!(ed.contents.contains("import { User4Fxx }"));
1527 } else if ed_path == test::test_file_name("source.slint") {
1528 assert!(ed.contents.contains("export component XxxYyyZzz /* <- TEST_ME_1 "));
1529 assert!(!ed.contents.contains("Foo"));
1530 } else if ed_path == test::test_file_name("user.slint") {
1531 assert!(ed.contents.contains("{ XxxYyyZzz as Bar }"));
1532 assert!(ed.contents.contains("Bar { }"));
1533 assert!(!ed.contents.contains("Foo"));
1534 } else if ed_path == test::test_file_name("user2.slint") {
1535 assert!(ed.contents.contains("import { XxxYyyZzz }"));
1536 assert!(ed.contents.contains("XxxYyyZzz { }"));
1537 } else if ed_path == test::test_file_name("user3.slint") {
1538 assert!(ed.contents.contains("import { XxxYyyZzz }"));
1539 assert!(ed.contents.contains("export { XxxYyyZzz }"));
1540 } else if ed_path == test::test_file_name("user4.slint") {
1541 assert!(ed.contents.contains("import { XxxYyyZzz }"));
1542 assert!(ed.contents.contains("export { XxxYyyZzz as User4Fxx }"));
1543 } else {
1544 unreachable!();
1545 }
1546 }
1547 }
1548
1549 #[test]
1550 fn test_rename_component_from_definition_with_export_and_relative_paths() {
1551 let document_cache = test::compile_test_with_sources(
1552 "fluent",
1553 HashMap::from([
1554 (
1555 Url::from_file_path(test::main_test_file_name()).unwrap(),
1556 r#"
1557import { Foo } from "s/source.slint";
1558import { UserComponent } from "u/user.slint";
1559import { User2Component } from "u/user2.slint";
1560import { Foo as User3Fxx } from "u/user3.slint";
1561import { User4Fxx } from "u/user4.slint";
1562
1563export component Main {
1564 Foo { }
1565 UserComponent { }
1566 User2Component { }
1567}
1568 "#
1569 .to_string(),
1570 ),
1571 (
1572 Url::from_file_path(test::test_file_name("s/source.slint")).unwrap(),
1573 r#"
1574export component Foo /* <- TEST_ME_1 */ { }
1575 "#
1576 .to_string(),
1577 ),
1578 (
1579 Url::from_file_path(test::test_file_name("u/user.slint")).unwrap(),
1580 r#"
1581import { Foo as Bar } from "../s/source.slint";
1582
1583export component UserComponent {
1584 Bar { }
1585}
1586
1587export { Bar }
1588 "#
1589 .to_string(),
1590 ),
1591 (
1592 Url::from_file_path(test::test_file_name("u/user2.slint")).unwrap(),
1593 r#"
1594import { Foo as XxxYyyZzz } from "../s/source.slint";
1595
1596export component User2Component {
1597 XxxYyyZzz { }
1598}
1599 "#
1600 .to_string(),
1601 ),
1602 (
1603 Url::from_file_path(test::test_file_name("u/user3.slint")).unwrap(),
1604 r#"
1605import { Foo } from "../s/source.slint";
1606
1607export { Foo }
1608 "#
1609 .to_string(),
1610 ),
1611 (
1612 Url::from_file_path(test::test_file_name("u/user4.slint")).unwrap(),
1613 r#"
1614import { Foo } from "../s/source.slint";
1615
1616export { Foo as User4Fxx }
1617 "#
1618 .to_string(),
1619 ),
1620 ]),
1621 false,
1622 );
1623
1624 let edited_text =
1625 rename_tester(&document_cache, &test::test_file_name("s/source.slint"), "_1");
1626
1627 for ed in &edited_text {
1628 let ed_path = ed.url.to_file_path().unwrap();
1629 if ed_path == test::main_test_file_name() {
1630 assert!(ed.contents.contains("XxxYyyZzz"));
1631 assert!(!ed.contents.contains("Foo"));
1632 assert!(ed.contents.contains("UserComponent"));
1633 assert!(ed.contents.contains("import { XxxYyyZzz as User3Fxx }"));
1634 assert!(ed.contents.contains("import { User4Fxx }"));
1635 } else if ed_path == test::test_file_name("s/source.slint") {
1636 assert!(ed.contents.contains("export component XxxYyyZzz /* <- TEST_ME_1 "));
1637 assert!(!ed.contents.contains("Foo"));
1638 } else if ed_path == test::test_file_name("u/user.slint") {
1639 assert!(ed.contents.contains("{ XxxYyyZzz as Bar }"));
1640 assert!(ed.contents.contains("Bar { }"));
1641 assert!(!ed.contents.contains("Foo"));
1642 } else if ed_path == test::test_file_name("u/user2.slint") {
1643 assert!(ed.contents.contains("import { XxxYyyZzz }"));
1644 assert!(ed.contents.contains("XxxYyyZzz { }"));
1645 } else if ed_path == test::test_file_name("u/user3.slint") {
1646 assert!(ed.contents.contains("import { XxxYyyZzz }"));
1647 assert!(ed.contents.contains("export { XxxYyyZzz }"));
1648 } else if ed_path == test::test_file_name("u/user4.slint") {
1649 assert!(ed.contents.contains("import { XxxYyyZzz }"));
1650 assert!(ed.contents.contains("export { XxxYyyZzz as User4Fxx }"));
1651 } else {
1652 unreachable!();
1653 }
1654 }
1655 }
1656
1657 #[test]
1658 fn test_rename_component_from_definition_import_confusion() {
1659 let document_cache = test::compile_test_with_sources(
1660 "fluent",
1661 HashMap::from([
1662 (
1663 Url::from_file_path(test::main_test_file_name()).unwrap(),
1664 r#"
1665import { Foo as User1Fxx } from "user1.slint";
1666import { Foo as User2Fxx } from "user2.slint";
1667
1668export component Main {
1669 User1Fxx { }
1670 User2Fxx { }
1671}
1672 "#
1673 .to_string(),
1674 ),
1675 (
1676 Url::from_file_path(test::test_file_name("user1.slint")).unwrap(),
1677 r#"
1678export component Foo /* <- TEST_ME_1 */ { }
1679 "#
1680 .to_string(),
1681 ),
1682 (
1683 Url::from_file_path(test::test_file_name("user2.slint")).unwrap(),
1684 r#"
1685export component Foo /* <- TEST_ME_2 */ { }
1686 "#
1687 .to_string(),
1688 ),
1689 ]),
1690 false,
1691 );
1692
1693 let edited_text =
1694 rename_tester(&document_cache, &test::test_file_name("user1.slint"), "_1");
1695
1696 for ed in &edited_text {
1697 let ed_path = ed.url.to_file_path().unwrap();
1698 if ed_path == test::main_test_file_name() {
1699 assert!(ed.contents.contains("import { XxxYyyZzz as User1Fxx }"));
1700 assert!(ed.contents.contains("import { Foo as User2Fxx }"));
1701 } else if ed_path == test::test_file_name("user1.slint") {
1702 assert!(ed.contents.contains("export component XxxYyyZzz /* <- TEST_ME_1 "));
1703 assert!(!ed.contents.contains("Foo"));
1704 } else {
1705 unreachable!();
1706 }
1707 }
1708
1709 let edited_text =
1710 rename_tester(&document_cache, &test::test_file_name("user2.slint"), "_2");
1711
1712 for ed in &edited_text {
1713 let ed_path = ed.url.to_file_path().unwrap();
1714 if ed_path == test::main_test_file_name() {
1715 assert!(ed.contents.contains("import { XxxYyyZzz as User2Fxx }"));
1716 assert!(ed.contents.contains("import { Foo as User1Fxx }"));
1717 } else if ed_path == test::test_file_name("user2.slint") {
1718 assert!(ed.contents.contains("export component XxxYyyZzz /* <- TEST_ME_2 "));
1719 assert!(!ed.contents.contains("Foo"));
1720 } else {
1721 unreachable!();
1722 }
1723 }
1724 }
1725
1726 #[test]
1727 fn test_rename_component_from_definition_redefinition_error() {
1728 let document_cache = test::compile_test_with_sources(
1729 "fluent",
1730 HashMap::from([(
1731 Url::from_file_path(test::main_test_file_name()).unwrap(),
1732 r#"
1733struct UsedStruct { value: int, }
1734enum UsedEnum { x, y }
1735
1736component Foo /* <- TEST_ME_1 */ { }
1737
1738component Baz {
1739 Foo { }
1740}
1741
1742export component Bar {
1743 Foo { }
1744 Rectangle {
1745 Foo { }
1746 Baz { }
1747 }
1748}
1749 "#
1750 .to_string(),
1751 )]),
1752 false,
1753 );
1754
1755 let dn = find_declaration_node(
1756 &document_cache,
1757 &find_token_by_comment(&document_cache, &test::main_test_file_name(), "_1"),
1758 )
1759 .unwrap();
1760
1761 assert!(dn.rename(&document_cache, "Foo").is_err());
1762 assert!(dn.rename(&document_cache, "UsedStruct").is_ok());
1763 assert!(dn.rename(&document_cache, "UsedEnum").is_ok());
1764 assert!(dn.rename(&document_cache, "Baz").is_err());
1765 assert!(dn.rename(&document_cache, "HorizontalLayout").is_err());
1766 }
1767
1768 #[test]
1769 fn test_rename_struct_from_definition() {
1770 let document_cache = test::compile_test_with_sources(
1771 "fluent",
1772 HashMap::from([(
1773 Url::from_file_path(test::main_test_file_name()).unwrap(),
1774 r#"
1775export struct Foo /* <- TEST_ME_1 */ {
1776 test-me: bool,
1777}
1778
1779component Baz {
1780 in-out property <Foo> baz-prop;
1781}
1782
1783export component Bar {
1784 in-out property <Foo> bar-prop <=> baz.baz-prop;
1785
1786 baz := Baz {}
1787}
1788 "#
1789 .to_string(),
1790 )]),
1791 false,
1792 );
1793
1794 let edited_text = rename_tester(&document_cache, &test::main_test_file_name(), "_1");
1795
1796 assert_eq!(edited_text.len(), 1);
1797 assert!(edited_text[0].contents.contains("struct XxxYyyZzz /* <- TEST_ME_1 "));
1798 assert!(edited_text[0].contents.contains("property <XxxYyyZzz> baz-prop"));
1799 assert!(edited_text[0].contents.contains("property <XxxYyyZzz> bar-prop"));
1800 }
1801
1802 #[test]
1803 fn test_rename_struct_from_definition_with_dash() {
1804 let document_cache = test::compile_test_with_sources(
1805 "fluent",
1806 HashMap::from([(
1807 Url::from_file_path(test::main_test_file_name()).unwrap(),
1808 r#"
1809export struct F-oo /* <- TEST_ME_1 */ {
1810 test-me: bool,
1811}
1812
1813component Baz {
1814 in-out property <F_oo> baz-prop;
1815}
1816
1817export component Bar {
1818 in-out property <F-oo> bar-prop <=> baz.baz-prop;
1819
1820 baz := Baz {}
1821}
1822 "#
1823 .to_string(),
1824 )]),
1825 false,
1826 );
1827
1828 let edited_text = rename_tester_with_new_name(
1829 &document_cache,
1830 &test::main_test_file_name(),
1831 "_1",
1832 "Xxx_Yyy-Zzz",
1833 );
1834
1835 assert_eq!(edited_text.len(), 1);
1836 assert!(edited_text[0].contents.contains("export struct Xxx_Yyy-Zzz /* <- TEST_ME_1 "));
1837 assert!(edited_text[0].contents.contains("<Xxx_Yyy-Zzz> baz-prop"));
1838 assert!(edited_text[0].contents.contains("<Xxx_Yyy-Zzz> bar-prop"));
1839 }
1840
1841 #[test]
1842 fn test_rename_struct_from_definition_with_underscore() {
1843 let document_cache = test::compile_test_with_sources(
1844 "fluent",
1845 HashMap::from([(
1846 Url::from_file_path(test::main_test_file_name()).unwrap(),
1847 r#"
1848export struct F_oo /* <- TEST_ME_1 */ {
1849 test-me: bool,
1850}
1851
1852component Baz {
1853 in-out property <F_oo> baz-prop;
1854}
1855
1856export component Bar {
1857 in-out property <F-oo> bar-prop <=> baz.baz-prop;
1858
1859 baz := Baz {}
1860}
1861 "#
1862 .to_string(),
1863 )]),
1864 false,
1865 );
1866
1867 let edited_text = rename_tester_with_new_name(
1868 &document_cache,
1869 &test::main_test_file_name(),
1870 "_1",
1871 "Xxx_Yyy-Zzz",
1872 );
1873
1874 assert_eq!(edited_text.len(), 1);
1875 assert!(edited_text[0].contents.contains("export struct Xxx_Yyy-Zzz /* <- TEST_ME_1 "));
1876 assert!(edited_text[0].contents.contains("<Xxx_Yyy-Zzz> baz-prop"));
1877 assert!(edited_text[0].contents.contains("<Xxx_Yyy-Zzz> bar-prop"));
1878 }
1879
1880 #[test]
1881 fn test_rename_struct_from_definition_with_renaming_export() {
1882 let document_cache = test::compile_test_with_sources(
1883 "fluent",
1884 HashMap::from([
1885 (
1886 Url::from_file_path(test::main_test_file_name()).unwrap(),
1887 r#"
1888import { FExport} from "source.slint";
1889
1890export component Foo {
1891 property <FExport> foo-prop;
1892}
1893 "#
1894 .to_string(),
1895 ),
1896 (
1897 Url::from_file_path(test::test_file_name("source.slint")).unwrap(),
1898 r#"
1899struct Foo /* <- TEST_ME_1 */ {
1900 test-me: bool,
1901}
1902
1903export { Foo as FExport }
1904 "#
1905 .to_string(),
1906 ),
1907 ]),
1908 false,
1909 );
1910
1911 let edited_text =
1912 rename_tester(&document_cache, &test::test_file_name("source.slint"), "_1");
1913
1914 assert_eq!(edited_text.len(), 1);
1915 assert_eq!(
1916 edited_text[0].url.to_file_path().unwrap(),
1917 test::test_file_name("source.slint")
1918 );
1919 assert!(edited_text[0].contents.contains("struct XxxYyyZzz /* <- TEST_ME_1 "));
1920 assert!(edited_text[0].contents.contains("export { XxxYyyZzz as FExport }"));
1921 }
1922
1923 #[test]
1924 fn test_rename_struct_from_definition_with_export() {
1925 let document_cache = test::compile_test_with_sources(
1926 "fluent",
1927 HashMap::from([
1928 (
1929 Url::from_file_path(test::main_test_file_name()).unwrap(),
1930 r#"
1931import { Foo } from "source.slint";
1932import { UserComponent } from "user.slint";
1933import { User2Struct } from "user2.slint";
1934import { Foo as User3Fxx } from "user3.slint";
1935import { User4Fxx } from "user4.slint";
1936
1937export component Main {
1938 property <Foo> main-prop;
1939 property <User3Fxx> main-prop2;
1940 property <User2Struct> main-prop3;
1941 property <User3Fxx> main-prop4 <=> uc.user-component-prop;
1942
1943 uc := UserComponent { }
1944}
1945 "#
1946 .to_string(),
1947 ),
1948 (
1949 Url::from_file_path(test::test_file_name("source.slint")).unwrap(),
1950 r#"
1951export struct Foo /* <- TEST_ME_1 */ { test-me: bool, }
1952 "#
1953 .to_string(),
1954 ),
1955 (
1956 Url::from_file_path(test::test_file_name("user.slint")).unwrap(),
1957 r#"
1958import { Foo as Bar } from "source.slint";
1959
1960export component UserComponent {
1961 in-out property <Bar> user-component-prop;
1962}
1963
1964export { Bar }
1965 "#
1966 .to_string(),
1967 ),
1968 (
1969 Url::from_file_path(test::test_file_name("user2.slint")).unwrap(),
1970 r#"
1971import { Foo as XxxYyyZzz } from "source.slint";
1972
1973export struct User2Struct {
1974 member: XxxYyyZzz,
1975}
1976 "#
1977 .to_string(),
1978 ),
1979 (
1980 Url::from_file_path(test::test_file_name("user3.slint")).unwrap(),
1981 r#"
1982import { Foo } from "source.slint";
1983
1984export { Foo }
1985 "#
1986 .to_string(),
1987 ),
1988 (
1989 Url::from_file_path(test::test_file_name("user4.slint")).unwrap(),
1990 r#"
1991import { Foo } from "source.slint";
1992
1993export { Foo as User4Fxx }
1994 "#
1995 .to_string(),
1996 ),
1997 ]),
1998 false,
1999 );
2000
2001 let edited_text =
2002 rename_tester(&document_cache, &test::test_file_name("source.slint"), "_1");
2003
2004 for ed in &edited_text {
2005 let ed_path = ed.url.to_file_path().unwrap();
2006 if ed_path == test::main_test_file_name() {
2007 assert!(ed.contents.contains("XxxYyyZzz"));
2008 assert!(!ed.contents.contains("Foo"));
2009 assert!(ed.contents.contains("UserComponent"));
2010 assert!(ed.contents.contains("import { XxxYyyZzz as User3Fxx }"));
2011 assert!(ed.contents.contains("import { User4Fxx }"));
2012 } else if ed_path == test::test_file_name("source.slint") {
2013 assert!(ed.contents.contains("export struct XxxYyyZzz /* <- TEST_ME_1 "));
2014 assert!(!ed.contents.contains("Foo"));
2015 } else if ed_path == test::test_file_name("user.slint") {
2016 assert!(ed.contents.contains("{ XxxYyyZzz as Bar }"));
2017 assert!(ed.contents.contains("property <Bar> user-component-prop"));
2018 assert!(!ed.contents.contains("Foo"));
2019 } else if ed_path == test::test_file_name("user2.slint") {
2020 assert!(ed.contents.contains("import { XxxYyyZzz }"));
2021 assert!(ed.contents.contains("member: XxxYyyZzz,"));
2022 } else if ed_path == test::test_file_name("user3.slint") {
2023 assert!(ed.contents.contains("import { XxxYyyZzz }"));
2024 assert!(ed.contents.contains("export { XxxYyyZzz }"));
2025 } else if ed_path == test::test_file_name("user4.slint") {
2026 assert!(ed.contents.contains("import { XxxYyyZzz }"));
2027 assert!(ed.contents.contains("export { XxxYyyZzz as User4Fxx }"));
2028 } else {
2029 unreachable!();
2030 }
2031 }
2032 }
2033
2034 #[test]
2035 fn test_rename_enum_from_definition() {
2036 let document_cache = test::compile_test_with_sources(
2037 "fluent",
2038 HashMap::from([(
2039 Url::from_file_path(test::main_test_file_name()).unwrap(),
2040 r#"
2041 export { Foo }
2042
2043 enum Foo /* <- TEST_ME_1 */ {
2044 test,
2045 }
2046
2047 component Baz {
2048 in-out property <Foo> baz-prop: Foo.test;
2049 }
2050
2051 export component Bar {
2052 in-out property <Foo> bar-prop <=> baz.baz-prop;
2053
2054 baz := Baz {}
2055 }
2056 "#
2057 .to_string(),
2058 )]),
2059 true, // redefinition of type warning
2060 );
2061
2062 let edited_text = rename_tester(&document_cache, &test::main_test_file_name(), "_1");
2063
2064 assert_eq!(edited_text.len(), 1);
2065 assert!(edited_text[0].contents.contains("export { XxxYyyZzz }"));
2066 assert!(edited_text[0].contents.contains("enum XxxYyyZzz /* <- TEST_ME_1 "));
2067 assert!(edited_text[0].contents.contains("test,"));
2068 assert!(edited_text[0].contents.contains("property <XxxYyyZzz> baz-prop"));
2069 assert!(edited_text[0].contents.contains("baz-prop: XxxYyyZzz.test;"));
2070 assert!(edited_text[0].contents.contains("property <XxxYyyZzz> bar-prop"));
2071 }
2072
2073 #[test]
2074 fn test_rename_enum_from_definition_with_struct() {
2075 let document_cache = test::compile_test_with_sources(
2076 "fluent",
2077 HashMap::from([(
2078 Url::from_file_path(test::main_test_file_name()).unwrap(),
2079 r#"
2080 enum Foo /* <- TEST_ME_1 */ {
2081 M1, M2,
2082 }
2083
2084 export { Foo }
2085
2086 component Baz {
2087 in-out property <Foo> baz-prop;
2088 }
2089
2090 export component Bar {
2091 in-out property <Foo> bar-prop <=> baz.baz-prop;
2092
2093 baz := Baz {}
2094 }
2095 "#
2096 .to_string(),
2097 )]),
2098 true, // redefinition of type warning
2099 );
2100
2101 let edited_text = rename_tester(&document_cache, &test::main_test_file_name(), "_1");
2102
2103 assert_eq!(edited_text.len(), 1);
2104 assert!(edited_text[0].contents.contains("enum XxxYyyZzz /* <- TEST_ME_1 */ "));
2105 assert!(edited_text[0].contents.contains("export { XxxYyyZzz }"));
2106 assert!(edited_text[0].contents.contains("property <XxxYyyZzz> baz-prop"));
2107 assert!(edited_text[0].contents.contains("property <XxxYyyZzz> bar-prop"));
2108 }
2109
2110 #[test]
2111 fn test_rename_enum_from_definition_with_renaming_export() {
2112 let document_cache = test::compile_test_with_sources(
2113 "fluent",
2114 HashMap::from([
2115 (
2116 Url::from_file_path(test::main_test_file_name()).unwrap(),
2117 r#"
2118 import { FExport} from "source.slint";
2119
2120 export enum Foo {
2121 M1, M2
2122 }
2123 "#
2124 .to_string(),
2125 ),
2126 (
2127 Url::from_file_path(test::test_file_name("source.slint")).unwrap(),
2128 r#"
2129 export enum Foo /* <- TEST_ME_1 */ {
2130 OM1, OM2,
2131 }
2132
2133 export { Foo as FExport }
2134 "#
2135 .to_string(),
2136 ),
2137 ]),
2138 false,
2139 );
2140
2141 let edited_text =
2142 rename_tester(&document_cache, &test::test_file_name("source.slint"), "_1");
2143
2144 assert_eq!(edited_text.len(), 1);
2145 assert_eq!(
2146 edited_text[0].url.to_file_path().unwrap(),
2147 test::test_file_name("source.slint")
2148 );
2149 assert!(edited_text[0].contents.contains("export enum XxxYyyZzz /* <- TEST_ME_1 "));
2150 assert!(edited_text[0].contents.contains("export { XxxYyyZzz as FExport"));
2151 assert!(!edited_text[0].contents.contains("Foo"));
2152 }
2153
2154 #[test]
2155 fn test_rename_enum_from_definition_with_export() {
2156 let document_cache = test::compile_test_with_sources(
2157 "fluent",
2158 HashMap::from([
2159 (
2160 Url::from_file_path(test::main_test_file_name()).unwrap(),
2161 r#"
2162 import { F_o_o } from "source.slint";
2163 import { UserComponent } from "user.slint";
2164 import { User2Struct } from "user2.slint";
2165 import { F-o-o as User3Fxx } from "user3.slint";
2166 import { User4Fxx } from "user4.slint";
2167
2168 export component Main {
2169 property <F-o_o> main-prop: F_o_o.M1;
2170 property <User3Fxx> main-prop2: User3Fxx.M1;
2171 property <User2Struct> main-prop3;
2172 property <User3Fxx> main-prop4 <=> uc.user-component-prop;
2173
2174 uc := UserComponent { }
2175 }
2176 "#
2177 .to_string(),
2178 ),
2179 (
2180 Url::from_file_path(test::test_file_name("source.slint")).unwrap(),
2181 r#"
2182 export enum F_o-o /* <- TEST_ME_1 */ { M1, M2, }
2183 "#
2184 .to_string(),
2185 ),
2186 (
2187 Url::from_file_path(test::test_file_name("user.slint")).unwrap(),
2188 r#"
2189 import { F-o_o as B_a-r } from "source.slint";
2190
2191 export component UserComponent {
2192 in-out property <B_a-r> user-component-prop: B-a-r.M1;
2193 }
2194
2195 export { B-a-r }
2196 "#
2197 .to_string(),
2198 ),
2199 (
2200 Url::from_file_path(test::test_file_name("user2.slint")).unwrap(),
2201 r#"
2202 import { F_o_o as XxxYyyZzz } from "source.slint";
2203
2204 export struct User2Struct {
2205 member: XxxYyyZzz,
2206 }
2207 "#
2208 .to_string(),
2209 ),
2210 (
2211 Url::from_file_path(test::test_file_name("user3.slint")).unwrap(),
2212 r#"
2213 import { F-o-o } from "source.slint";
2214
2215 export { F_o_o }
2216 "#
2217 .to_string(),
2218 ),
2219 (
2220 Url::from_file_path(test::test_file_name("user4.slint")).unwrap(),
2221 r#"
2222 import { F-o-o } from "source.slint";
2223
2224 export { F_o_o as User4Fxx }
2225 "#
2226 .to_string(),
2227 ),
2228 ]),
2229 false,
2230 );
2231
2232 let edited_text =
2233 rename_tester(&document_cache, &test::test_file_name("source.slint"), "_1");
2234
2235 for ed in &edited_text {
2236 let ed_path = ed.url.to_file_path().unwrap();
2237 if ed_path == test::main_test_file_name() {
2238 assert!(ed.contents.contains("import { XxxYyyZzz } from \"source.slint\""));
2239 assert!(ed.contents.contains("import { UserComponent } from \"user.slint\""));
2240 assert!(ed.contents.contains("import { User2Struct } from \"user2.slint\""));
2241 assert!(ed
2242 .contents
2243 .contains("import { XxxYyyZzz as User3Fxx } from \"user3.slint\""));
2244 assert!(ed.contents.contains("import { User4Fxx } from \"user4.slint\""));
2245 assert!(ed.contents.contains("property <XxxYyyZzz> main-prop: XxxYyyZzz.M1"));
2246 assert!(ed.contents.contains("property <User3Fxx> main-prop2: User3Fxx.M1"));
2247 assert!(ed.contents.contains("property <User2Struct> main-prop3;"));
2248 assert!(ed
2249 .contents
2250 .contains("property <User3Fxx> main-prop4 <=> uc.user-component-prop;"));
2251 assert!(ed.contents.contains("uc := UserComponent"));
2252 } else if ed_path == test::test_file_name("source.slint") {
2253 assert!(ed.contents.contains("export enum XxxYyyZzz /* <- TEST_ME_1 "));
2254 } else if ed_path == test::test_file_name("user.slint") {
2255 assert!(ed.contents.contains("import { XxxYyyZzz as B_a-r }"));
2256 assert!(ed.contents.contains("property <B_a-r> user-component-prop"));
2257 assert!(ed.contents.contains("> user-component-prop: B-a-r.M1;"));
2258 assert!(ed.contents.contains("export { B-a-r }"));
2259 } else if ed_path == test::test_file_name("user2.slint") {
2260 assert!(ed.contents.contains("import { XxxYyyZzz } from \"source.slint\""));
2261 assert!(ed.contents.contains("export struct User2Struct {"));
2262 assert!(ed.contents.contains("member: XxxYyyZzz,"));
2263 } else if ed_path == test::test_file_name("user3.slint") {
2264 assert!(ed.contents.contains("import { XxxYyyZzz }"));
2265 assert!(ed.contents.contains("export { XxxYyyZzz }"));
2266 } else if ed_path == test::test_file_name("user4.slint") {
2267 assert!(ed.contents.contains("import { XxxYyyZzz }"));
2268 assert!(ed.contents.contains("export { XxxYyyZzz as User4Fxx }"));
2269 } else {
2270 unreachable!();
2271 }
2272 }
2273 }
2274
2275 #[track_caller]
2276 fn find_declaration_node_by_comment(
2277 document_cache: &common::DocumentCache,
2278 document_path: &Path,
2279 suffix: &str,
2280 ) -> DeclarationNode {
2281 let name = find_node_by_comment(document_cache, document_path, suffix);
2282 find_declaration_node(document_cache, &main_identifier(&name).unwrap()).unwrap()
2283 }
2284
2285 #[test]
2286 fn test_rename_component_from_use() {
2287 let document_cache = test::compile_test_with_sources(
2288 "fluent",
2289 HashMap::from([(
2290 Url::from_file_path(test::main_test_file_name()).unwrap(),
2291 r#"
2292export { Foo /* <- TEST_ME_1 */ }
2293
2294enum Xyz { Foo, Bar }
2295
2296struct Abc { Foo: Xyz }
2297
2298component Foo /* <- TEST_ME_TARGET */ inherits Rectangle {
2299 @children
2300}
2301
2302component Baz {
2303 Foo /* <- TEST_ME_2 */ { }
2304}
2305
2306struct Foo {
2307 bar: bool,
2308}
2309
2310export component Bar inherits Foo /* <- TEST_ME_3 */ {
2311 Foo /* <- TEST_ME_4 */ { }
2312 Rectangle {
2313 Foo /* <- TEST_ME_5 */ { }
2314 Foo := Baz { }
2315 }
2316
2317 if true: Rectangle {
2318 Foo /* <- TEST_ME_6 */ { }
2319 }
2320
2321 if false: Rectangle {
2322 Foo /* <- TEST_ME_7 */ { }
2323 }
2324
2325 function Foo(Foo: int) { Foo + 1; }
2326 function F() { self.Foo(42); }
2327
2328 for i in [1, 2, 3]: Foo /* <- TEST_ME_8 */ { }
2329}
2330 "#
2331 .to_string(),
2332 )]),
2333 true, // Component `Foo` is replacing a component with the same name
2334 );
2335
2336 let target =
2337 find_token_by_comment(&document_cache, &test::main_test_file_name(), "_TARGET");
2338
2339 let id =
2340 find_declaration_node_by_comment(&document_cache, &test::main_test_file_name(), "_1");
2341 id.query.token_info.is_same_symbol(&document_cache, target.clone());
2342
2343 let id =
2344 find_declaration_node_by_comment(&document_cache, &test::main_test_file_name(), "_2");
2345 id.query.token_info.is_same_symbol(&document_cache, target.clone());
2346
2347 let id =
2348 find_declaration_node_by_comment(&document_cache, &test::main_test_file_name(), "_3");
2349 id.query.token_info.is_same_symbol(&document_cache, target.clone());
2350
2351 let id =
2352 find_declaration_node_by_comment(&document_cache, &test::main_test_file_name(), "_4");
2353 id.query.token_info.is_same_symbol(&document_cache, target.clone());
2354
2355 let id =
2356 find_declaration_node_by_comment(&document_cache, &test::main_test_file_name(), "_5");
2357 id.query.token_info.is_same_symbol(&document_cache, target.clone());
2358
2359 let id =
2360 find_declaration_node_by_comment(&document_cache, &test::main_test_file_name(), "_6");
2361 id.query.token_info.is_same_symbol(&document_cache, target.clone());
2362
2363 let id =
2364 find_declaration_node_by_comment(&document_cache, &test::main_test_file_name(), "_7");
2365 id.query.token_info.is_same_symbol(&document_cache, target.clone());
2366
2367 let id =
2368 find_declaration_node_by_comment(&document_cache, &test::main_test_file_name(), "_8");
2369 id.query.token_info.is_same_symbol(&document_cache, target);
2370 }
2371
2372 #[test]
2373 fn test_rename_struct_from_use() {
2374 let document_cache = test::compile_test_with_sources(
2375 "fluent",
2376 HashMap::from([(
2377 Url::from_file_path(test::main_test_file_name()).unwrap(),
2378 r#"
2379struct Foo /* <- TEST_ME_DECL */ {
2380 test: bool,
2381}
2382
2383struct Bar {
2384 bar-test: Foo /* <- TEST_ME_1 */,
2385}
2386
2387export component Bar {
2388 property <Foo /* <- TEST_ME_2 */> bar-prop: { test: false };
2389}
2390 "#
2391 .to_string(),
2392 )]),
2393 false,
2394 );
2395
2396 let declaration = find_declaration_node(
2397 &document_cache,
2398 &find_token_by_comment(&document_cache, &test::main_test_file_name(), "_DECL"),
2399 )
2400 .unwrap()
2401 .query
2402 .token_info
2403 .token
2404 .clone();
2405
2406 let id =
2407 find_declaration_node_by_comment(&document_cache, &test::main_test_file_name(), "_1");
2408 id.query.token_info.is_same_symbol(&document_cache, declaration.clone());
2409
2410 let id =
2411 find_declaration_node_by_comment(&document_cache, &test::main_test_file_name(), "_2");
2412 id.query.token_info.is_same_symbol(&document_cache, declaration);
2413 }
2414
2415 #[test]
2416 fn test_rename_component_from_use_with_export() {
2417 let document_cache = test::compile_test_with_sources(
2418 "fluent",
2419 HashMap::from([
2420 (
2421 Url::from_file_path(test::main_test_file_name()).unwrap(),
2422 r#"
2423 import { F-o_o /* <- TEST_ME_IMPORT1 */ } from "source.slint";
2424 import { UserComponent } from "user.slint";
2425 import { User2Component } from "user2.slint";
2426 import { F-o-o /* <- TEST_ME_IMPORT2 */ as User3Fxx /* <- TEST_ME_IN1 */ } from "user3.slint";
2427 import { User4Fxx } from "user4.slint";
2428
2429 export component Main {
2430 F_o_o /* <- TEST_ME_1 */ { }
2431 UserComponent { }
2432 User2Component { }
2433 User3Fxx /* <- TEST_ME_2 */ { }
2434 User4Fxx /* <- TEST_ME_3 */ { }
2435 }
2436 "#
2437 .to_string(),
2438 ),
2439 (
2440 Url::from_file_path(test::test_file_name("source.slint")).unwrap(),
2441 r#"
2442 export component F_o-o /* <- TEST_ME_DEF1 */ { @children }
2443 "#
2444 .to_string(),
2445 ),
2446 (
2447 Url::from_file_path(test::test_file_name("user.slint")).unwrap(),
2448 r#"
2449 import { F-o-o /* <- TEST_ME_IMPORT3 */ as Bar } from "source.slint";
2450
2451 export component UserComponent {
2452 Bar /* <- TEST_ME_4 */ { }
2453 }
2454
2455 export { Bar }
2456 "#
2457 .to_string(),
2458 ),
2459 (
2460 Url::from_file_path(test::test_file_name("user2.slint")).unwrap(),
2461 r#"
2462 import { F_o_o /* <- TEST_ME_IMPORT4 */ as XxxYyyZzz } from "source.slint";
2463
2464 export component User2Component {
2465 XxxYyyZzz /* <- TEST_ME_5 */ { }
2466 }
2467 "#
2468 .to_string(),
2469 ),
2470 (
2471 Url::from_file_path(test::test_file_name("user3.slint")).unwrap(),
2472 r#"
2473 import { F-o_o /* <- TEST_ME_IMPORT5 */ } from "source.slint";
2474
2475 export { F_o-o /* <- TEST_ME_EXPORT1 */ }
2476 "#
2477 .to_string(),
2478 ),
2479 (
2480 Url::from_file_path(test::test_file_name("user4.slint")).unwrap(),
2481 r#"
2482 import { F-o_o /* <- TEST_ME_IMPORT6 */ } from "source.slint";
2483
2484 export { F_o-o /* <- TEST_ME_EXPORT2 */ as User4Fxx /* <- TEST_ME_EXT1 */}
2485 "#
2486 .to_string(),
2487 ),
2488 ]),
2489 false,
2490 );
2491
2492 let declaration =
2493 find_token_by_comment(&document_cache, &test::test_file_name("source.slint"), "_DEF1");
2494
2495 let id =
2496 find_declaration_node_by_comment(&document_cache, &test::main_test_file_name(), "_1");
2497 id.query.token_info.is_same_symbol(&document_cache, declaration.clone());
2498
2499 let id =
2500 find_declaration_node_by_comment(&document_cache, &test::main_test_file_name(), "_2");
2501 let internal_name =
2502 find_token_by_comment(&document_cache, &test::main_test_file_name(), "_IN1");
2503 id.query.token_info.is_same_symbol(&document_cache, internal_name);
2504
2505 let export_name =
2506 find_token_by_comment(&document_cache, &test::test_file_name("user4.slint"), "_EXT1");
2507
2508 let id =
2509 find_declaration_node_by_comment(&document_cache, &test::main_test_file_name(), "_3");
2510 id.query.token_info.is_same_symbol(&document_cache, export_name);
2511 }
2512
2513 #[test]
2514 fn test_rename_struct_from_use_with_export() {
2515 let document_cache = test::compile_test_with_sources(
2516 "fluent",
2517 HashMap::from([
2518 (
2519 Url::from_file_path(test::main_test_file_name()).unwrap(),
2520 r#"
2521import { Foo /* <- TEST_ME_IMPORT1 */ } from "source.slint";
2522import { UserComponent } from "user.slint";
2523import { User2Struct } from "user2.slint";
2524import { Foo /* <- TEST_ME_IMPORT2 */ as User3Fxx /* <- TEST_ME_IN1 */} from "user3.slint";
2525import { User4Fxx } from "user4.slint";
2526
2527export component Main {
2528 property <Foo /* <- TEST_ME_1 */> main-prop;
2529 property <User3Fxx /* <- TEST_ME_2 */> main-prop2;
2530 property <User2Struct> main-prop3;
2531 property <User4Fxx /* <- TEST_ME_3 */> main-prop4 <=> uc.user-component-prop;
2532
2533 property <bool> test: main-prop3.member.test_me;
2534
2535 uc := UserComponent { }
2536}
2537 "#
2538 .to_string(),
2539 ),
2540 (
2541 Url::from_file_path(test::test_file_name("source.slint")).unwrap(),
2542 r#"
2543export struct Foo /* <- TEST_ME_DEF1 */ { test-me: bool, }
2544 "#
2545 .to_string(),
2546 ),
2547 (
2548 Url::from_file_path(test::test_file_name("user.slint")).unwrap(),
2549 r#"
2550import { Foo /* <- TEST_ME_IMPORT1 */ as Bar } from "source.slint";
2551
2552
2553export component UserComponent {
2554 in-out property <Bar> user-component-prop;
2555}
2556
2557export { Bar }
2558 "#
2559 .to_string(),
2560 ),
2561 (
2562 Url::from_file_path(test::test_file_name("user2.slint")).unwrap(),
2563 r#"
2564import { Foo /* <- TEST_ME_IMPORT2 */ as XxxYyyZzz } from "source.slint";
2565
2566export struct User2Struct {
2567 member: XxxYyyZzz,
2568}
2569 "#
2570 .to_string(),
2571 ),
2572 (
2573 Url::from_file_path(test::test_file_name("user3.slint")).unwrap(),
2574 r#"
2575import { Foo /* <- TEST_ME_IMPORT3 */} from "source.slint";
2576
2577export { Foo /* <- TEST_ME_EXPORT1 */}
2578 "#
2579 .to_string(),
2580 ),
2581 (
2582 Url::from_file_path(test::test_file_name("user4.slint")).unwrap(),
2583 r#"
2584import { Foo /* <- TEST_ME_IMPORT4 */ } from "source.slint";
2585
2586export { Foo /* <- TEST_ME_EXPORT2 */ as User4Fxx /* <- TEST_ME_EN1 */}
2587 "#
2588 .to_string(),
2589 ),
2590 ]),
2591 false,
2592 );
2593
2594 let declaration =
2595 find_token_by_comment(&document_cache, &test::test_file_name("source.slint"), "_DEF1");
2596
2597 let id =
2598 find_declaration_node_by_comment(&document_cache, &test::main_test_file_name(), "_1");
2599 id.query.token_info.is_same_symbol(&document_cache, declaration);
2600
2601 let id =
2602 find_declaration_node_by_comment(&document_cache, &test::main_test_file_name(), "_2");
2603 let internal_name =
2604 find_token_by_comment(&document_cache, &test::main_test_file_name(), "_IN1");
2605 id.query.token_info.is_same_symbol(&document_cache, internal_name);
2606
2607 let export_name =
2608 find_token_by_comment(&document_cache, &test::test_file_name("user4.slint"), "_EN1");
2609
2610 let id =
2611 find_declaration_node_by_comment(&document_cache, &test::main_test_file_name(), "_3");
2612 id.query.token_info.is_same_symbol(&document_cache, export_name);
2613 }
2614
2615 #[test]
2616 fn test_rename_enum_from_use_with_export() {
2617 let document_cache = test::compile_test_with_sources(
2618 "fluent",
2619 HashMap::from([
2620 (
2621 Url::from_file_path(test::main_test_file_name()).unwrap(),
2622 r#"
2623import { Foo } from "source.slint";
2624import { UserComponent } from "user.slint";
2625import { User2Struct } from "user2.slint";
2626import { Foo as User3Fxx /* <- TEST_ME_IN1 */} from "user3.slint";
2627import { User4Fxx } from "user4.slint";
2628
2629export component Main {
2630 property <Foo /* <- TEST_ME_1 */> main-prop;
2631 property <User3Fxx /* <- TEST_ME_2 */> main-prop2;
2632 property <User2Struct> main-prop3;
2633 property <User4Fxx /* <- TEST_ME_3 */> main-prop4 <=> uc.user-component-prop;
2634
2635 property <bool> test: main-prop3.member == Foo/* <- TEST_ME_4 */.test;
2636
2637 uc := UserComponent { }
2638}
2639 "#
2640 .to_string(),
2641 ),
2642 (
2643 Url::from_file_path(test::test_file_name("source.slint")).unwrap(),
2644 r#"
2645export enum Foo /* <- TEST_ME_DEF1 */ { test, }
2646 "#
2647 .to_string(),
2648 ),
2649 (
2650 Url::from_file_path(test::test_file_name("user.slint")).unwrap(),
2651 r#"
2652import { Foo /* <- TEST_ME_IMPORT1 */ as Bar } from "source.slint";
2653
2654
2655export component UserComponent {
2656 in-out property <Bar> user-component-prop;
2657}
2658
2659export { Bar }
2660 "#
2661 .to_string(),
2662 ),
2663 (
2664 Url::from_file_path(test::test_file_name("user2.slint")).unwrap(),
2665 r#"
2666import { Foo /* <- TEST_ME_IMPORT2 */ as XxxYyyZzz } from "source.slint";
2667
2668export struct User2Struct {
2669 member: XxxYyyZzz,
2670}
2671 "#
2672 .to_string(),
2673 ),
2674 (
2675 Url::from_file_path(test::test_file_name("user3.slint")).unwrap(),
2676 r#"
2677import { Foo /* <- TEST_ME_IMPORT3 */} from "source.slint";
2678
2679export { Foo /* <- TEST_ME_EXPORT1 */}
2680 "#
2681 .to_string(),
2682 ),
2683 (
2684 Url::from_file_path(test::test_file_name("user4.slint")).unwrap(),
2685 r#"
2686import { Foo /* <- TEST_ME_IMPORT4 */ } from "source.slint";
2687
2688export { Foo /* <- TEST_ME_EXPORT2 */ as User4Fxx /* <- TEST_ME_EN1 */}
2689 "#
2690 .to_string(),
2691 ),
2692 ]),
2693 false,
2694 );
2695
2696 let declaration =
2697 find_token_by_comment(&document_cache, &test::test_file_name("source.slint"), "_DEF1");
2698
2699 let internal_name =
2700 find_token_by_comment(&document_cache, &test::main_test_file_name(), "_IN1");
2701
2702 let export_name =
2703 find_token_by_comment(&document_cache, &test::test_file_name("user4.slint"), "_EN1");
2704
2705 let id =
2706 find_declaration_node_by_comment(&document_cache, &test::main_test_file_name(), "_1");
2707 id.query.token_info.is_same_symbol(&document_cache, declaration);
2708
2709 let id =
2710 find_declaration_node_by_comment(&document_cache, &test::main_test_file_name(), "_2");
2711 id.query.token_info.is_same_symbol(&document_cache, internal_name);
2712
2713 let id =
2714 find_declaration_node_by_comment(&document_cache, &test::main_test_file_name(), "_3");
2715 id.query.token_info.is_same_symbol(&document_cache, export_name.clone());
2716
2717 let id =
2718 find_declaration_node_by_comment(&document_cache, &test::main_test_file_name(), "_4");
2719 id.query.token_info.is_same_symbol(&document_cache, export_name);
2720 }
2721
2722 #[test]
2723 fn test_rename_import_from_internal_name() {
2724 let document_cache = test::compile_test_with_sources(
2725 "fluent",
2726 HashMap::from([
2727 (
2728 Url::from_file_path(test::main_test_file_name()).unwrap(),
2729 r#"
2730import { Foo as Bar /* <- TEST_ME_1 */ } from "source.slint";
2731
2732export component Main {
2733 Bar { }
2734}
2735 "#
2736 .to_string(),
2737 ),
2738 (
2739 Url::from_file_path(test::test_file_name("source.slint")).unwrap(),
2740 r#"
2741export component Foo { }
2742 "#
2743 .to_string(),
2744 ),
2745 ]),
2746 false,
2747 );
2748
2749 let edited_text =
2750 rename_tester_with_new_name(&document_cache, &test::main_test_file_name(), "_1", "Baz");
2751
2752 assert_eq!(edited_text.len(), 1);
2753 assert!(edited_text[0]
2754 .contents
2755 .contains("import { Foo as Baz /* <- TEST_ME_1 */ } from \"source.slint\";"));
2756 assert!(edited_text[0].contents.contains("component Main {"));
2757 assert!(edited_text[0].contents.contains(" Baz { "));
2758
2759 let edited_text =
2760 rename_tester_with_new_name(&document_cache, &test::main_test_file_name(), "_1", "Foo");
2761
2762 assert_eq!(edited_text.len(), 1);
2763 assert!(edited_text[0]
2764 .contents
2765 .contains("import { Foo /* <- TEST_ME_1 */ } from \"source.slint\";"));
2766 assert!(edited_text[0].contents.contains("component Main {"));
2767 assert!(edited_text[0].contents.contains(" Foo { "));
2768 }
2769
2770 #[test]
2771 fn test_rename_import_from_external_name() {
2772 let document_cache = test::compile_test_with_sources(
2773 "fluent",
2774 HashMap::from([
2775 (
2776 Url::from_file_path(test::main_test_file_name()).unwrap(),
2777 r#"
2778import { Foo /* <- TEST_ME_1 */ as Bar } from "source.slint";
2779
2780export component Main {
2781 Bar { }
2782}
2783 "#
2784 .to_string(),
2785 ),
2786 (
2787 Url::from_file_path(test::test_file_name("source.slint")).unwrap(),
2788 r#"
2789export component Foo { }
2790 "#
2791 .to_string(),
2792 ),
2793 ]),
2794 false,
2795 );
2796
2797 let edited_text =
2798 rename_tester_with_new_name(&document_cache, &test::main_test_file_name(), "_1", "Baz");
2799
2800 assert_eq!(edited_text.len(), 2);
2801
2802 for ed in &edited_text {
2803 let ed_path = ed.url.to_file_path().unwrap();
2804 if ed_path == test::main_test_file_name() {
2805 assert!(ed
2806 .contents
2807 .contains("import { Baz /* <- TEST_ME_1 */ as Bar } from \"source.slint\";"));
2808 assert!(ed.contents.contains("component Main {"));
2809 assert!(ed.contents.contains(" Bar { "));
2810 } else if ed_path == test::test_file_name("source.slint") {
2811 assert!(ed.contents.contains("export component Baz { }"));
2812 } else {
2813 panic!("Unexpected file!");
2814 }
2815 }
2816
2817 let edited_text =
2818 rename_tester_with_new_name(&document_cache, &test::main_test_file_name(), "_1", "Bar");
2819
2820 assert_eq!(edited_text.len(), 2);
2821 for ed in &edited_text {
2822 let ed_path = ed.url.to_file_path().unwrap();
2823 if ed_path == test::main_test_file_name() {
2824 assert!(ed.contents.contains("import { Bar } from \"source.slint\";"));
2825 assert!(ed.contents.contains("component Main {"));
2826 assert!(ed.contents.contains(" Bar { "));
2827 } else if ed_path == test::test_file_name("source.slint") {
2828 assert!(ed.contents.contains("export component Bar { }"));
2829 } else {
2830 panic!("Unexpected file!");
2831 }
2832 }
2833 }
2834
2835 #[test]
2836 fn test_rename_import_from_external_name_with_export_renaming() {
2837 let document_cache = test::compile_test_with_sources(
2838 "fluent",
2839 HashMap::from([
2840 (
2841 Url::from_file_path(test::main_test_file_name()).unwrap(),
2842 r#"
2843import { Foo /* <- TEST_ME_1 */ as Bar } from "source.slint";
2844
2845export component Main {
2846 Bar { }
2847}
2848 "#
2849 .to_string(),
2850 ),
2851 (
2852 Url::from_file_path(test::test_file_name("source.slint")).unwrap(),
2853 r#"
2854component XxxYyyZzz { }
2855
2856export { XxxYyyZzz as Foo }
2857 "#
2858 .to_string(),
2859 ),
2860 ]),
2861 false,
2862 );
2863
2864 let edited_text = rename_tester_with_new_name(
2865 &document_cache,
2866 &test::main_test_file_name(),
2867 "_1",
2868 "XFooX",
2869 );
2870
2871 assert_eq!(edited_text.len(), 2);
2872
2873 for ed in &edited_text {
2874 let ed_path = ed.url.to_file_path().unwrap();
2875 if ed_path == test::main_test_file_name() {
2876 assert!(ed
2877 .contents
2878 .contains("import { XFooX /* <- TEST_ME_1 */ as Bar } from \"source.slint\";"));
2879 assert!(ed.contents.contains("component Main {"));
2880 assert!(ed.contents.contains(" Bar { "));
2881 } else if ed_path == test::test_file_name("source.slint") {
2882 assert!(ed.contents.contains("component XxxYyyZzz { }"));
2883 assert!(ed.contents.contains("export { XxxYyyZzz as XFooX }"));
2884 } else {
2885 panic!("Unexpected file!");
2886 }
2887 }
2888
2889 let edited_text =
2890 rename_tester_with_new_name(&document_cache, &test::main_test_file_name(), "_1", "Bar");
2891
2892 assert_eq!(edited_text.len(), 2);
2893
2894 for ed in &edited_text {
2895 let ed_path = ed.url.to_file_path().unwrap();
2896 if ed_path == test::main_test_file_name() {
2897 assert!(ed.contents.contains("import { Bar } from \"source.slint\";"));
2898 assert!(ed.contents.contains("component Main {"));
2899 assert!(ed.contents.contains(" Bar { "));
2900 } else if ed_path == test::test_file_name("source.slint") {
2901 assert!(ed.contents.contains("component XxxYyyZzz { }"));
2902 assert!(ed.contents.contains("export { XxxYyyZzz as Bar }"));
2903 } else {
2904 panic!("Unexpected file!");
2905 }
2906 }
2907
2908 let edited_text = rename_tester(&document_cache, &test::main_test_file_name(), "_1");
2909
2910 assert_eq!(edited_text.len(), 2);
2911 for ed in &edited_text {
2912 let ed_path = ed.url.to_file_path().unwrap();
2913 if ed_path == test::main_test_file_name() {
2914 assert!(ed.contents.contains(
2915 "import { XxxYyyZzz /* <- TEST_ME_1 */ as Bar } from \"source.slint\";"
2916 ));
2917 assert!(ed.contents.contains("component Main {"));
2918 assert!(ed.contents.contains(" Bar { "));
2919 } else if ed_path == test::test_file_name("source.slint") {
2920 assert!(ed.contents.contains("component XxxYyyZzz { }"));
2921 assert!(ed.contents.contains("export { XxxYyyZzz }"));
2922 } else {
2923 panic!("Unexpected file!");
2924 }
2925 }
2926 }
2927
2928 #[test]
2929 fn test_rename_property_from_definition() {
2930 let document_cache = test::compile_test_with_sources(
2931 "fluent",
2932 HashMap::from([(
2933 Url::from_file_path(test::main_test_file_name()).unwrap(),
2934 r#"
2935component re_name-me {
2936 property <bool> re_name-me /* <- TEST_ME_1 */: true;
2937
2938 function re_name-me_(re-name_me: int) { /* 1 */ self.re-name_me = re-name_me >= 42; }
2939}
2940
2941export component Bar {
2942 property <bool> re_name-me /* <- TEST_ME_2 */: true;
2943
2944 function re_name-me_(re-name_me: int) { /* 2 */ self.re-name_me = re-name_me >= 42; }
2945
2946 re_name-me { }
2947}
2948 "#
2949 .to_string(),
2950 )]),
2951 true, // Component `Foo` is replacing a component with the same name
2952 );
2953
2954 let edited_text = rename_tester(&document_cache, &test::main_test_file_name(), "_1");
2955 assert_eq!(edited_text.len(), 1);
2956
2957 assert_eq!(edited_text.len(), 1);
2958 assert!(edited_text[0].contents.contains("component re_name-me {"));
2959 assert!(edited_text[0].contents.contains("property <bool> XxxYyyZzz /* <- TEST_ME_1 "));
2960 assert!(edited_text[0].contents.contains(" <- TEST_ME_1 */: true;"));
2961 assert!(edited_text[0]
2962 .contents
2963 .contains("function re_name-me_(re-name_me: int) { /* 1 */"));
2964 assert!(edited_text[0].contents.contains("/* 1 */ self.XxxYyyZzz = re-name_me >= 42;"));
2965
2966 assert!(edited_text[0].contents.contains("export component Bar {"));
2967 assert!(edited_text[0].contents.contains("property <bool> re_name-me /* <- TEST_ME_2 "));
2968 assert!(edited_text[0].contents.contains(" <- TEST_ME_2 */: true;"));
2969 assert!(edited_text[0]
2970 .contents
2971 .contains("function re_name-me_(re-name_me: int) { /* 2 */"));
2972 assert!(edited_text[0].contents.contains("/* 2 */ self.re-name_me = re-name_me >= 42;"));
2973 assert!(edited_text[0].contents.contains("re_name-me { }"));
2974
2975 let edited_text = rename_tester(&document_cache, &test::main_test_file_name(), "_2");
2976 assert_eq!(edited_text.len(), 1);
2977
2978 assert_eq!(edited_text.len(), 1);
2979 assert!(edited_text[0].contents.contains("component re_name-me {"));
2980 assert!(edited_text[0].contents.contains("property <bool> re_name-me /* <- TEST_ME_1 "));
2981 assert!(edited_text[0].contents.contains(" <- TEST_ME_1 */: true;"));
2982 assert!(edited_text[0]
2983 .contents
2984 .contains("function re_name-me_(re-name_me: int) { /* 1 */"));
2985 assert!(edited_text[0].contents.contains("/* 1 */ self.re-name_me = re-name_me >= 42;"));
2986
2987 assert!(edited_text[0].contents.contains("export component Bar {"));
2988 assert!(edited_text[0].contents.contains("property <bool> XxxYyyZzz /* <- TEST_ME_2 "));
2989 assert!(edited_text[0].contents.contains(" <- TEST_ME_2 */: true;"));
2990 assert!(edited_text[0]
2991 .contents
2992 .contains("function re_name-me_(re-name_me: int) { /* 2 */"));
2993 assert!(edited_text[0].contents.contains("/* 2 */ self.XxxYyyZzz = re-name_me >= 42;"));
2994 assert!(edited_text[0].contents.contains("re_name-me { }"));
2995 }
2996
2997 #[test]
2998 fn test_rename_property_from_use() {
2999 let document_cache = test::compile_test_with_sources(
3000 "fluent",
3001 HashMap::from([(
3002 Url::from_file_path(test::main_test_file_name()).unwrap(),
3003 r#"
3004component re_name-me {
3005 property <bool> re_name-me /* 1 */: true;
3006
3007 function re_name-me_(re-name_me: int) { /* 1 */ self.re-name_me /* <- TEST_ME_1 */ = re-name_me >= 42; }
3008}
3009
3010export component Bar {
3011 property <bool> re_name-me /* 2 */: true;
3012
3013 function re_name-me_(re-name_me: int) { /* 2 */ self.re-name_me /* <- TEST_ME_2 */ = re-name_me >= 42; }
3014
3015 re_name-me { }
3016}
3017 "#
3018 .to_string(),
3019 )]),
3020 true, // Component `Foo` is replacing a component with the same name
3021 );
3022
3023 let edited_text = rename_tester(&document_cache, &test::main_test_file_name(), "_1");
3024 assert_eq!(edited_text.len(), 1);
3025
3026 assert_eq!(edited_text.len(), 1);
3027 assert!(edited_text[0].contents.contains("component re_name-me {"));
3028 assert!(edited_text[0].contents.contains("property <bool> XxxYyyZzz /* 1 */"));
3029 assert!(edited_text[0].contents.contains(" 1 */: true;"));
3030 assert!(edited_text[0]
3031 .contents
3032 .contains("function re_name-me_(re-name_me: int) { /* 1 */"));
3033 assert!(edited_text[0]
3034 .contents
3035 .contains("/* 1 */ self.XxxYyyZzz /* <- TEST_ME_1 */ = re-name_me >= 42;"));
3036
3037 assert!(edited_text[0].contents.contains("export component Bar {"));
3038 assert!(edited_text[0].contents.contains("property <bool> re_name-me /* 2 "));
3039 assert!(edited_text[0].contents.contains(" 2 */: true;"));
3040 assert!(edited_text[0]
3041 .contents
3042 .contains("function re_name-me_(re-name_me: int) { /* 2 */"));
3043 assert!(edited_text[0]
3044 .contents
3045 .contains("/* 2 */ self.re-name_me /* <- TEST_ME_2 */ = re-name_me >= 42;"));
3046 assert!(edited_text[0].contents.contains("re_name-me { }"));
3047
3048 let edited_text = rename_tester(&document_cache, &test::main_test_file_name(), "_2");
3049 assert_eq!(edited_text.len(), 1);
3050
3051 assert_eq!(edited_text.len(), 1);
3052 assert!(edited_text[0].contents.contains("component re_name-me {"));
3053 assert!(edited_text[0].contents.contains("property <bool> re_name-me /* 1 "));
3054 assert!(edited_text[0].contents.contains(" 1 */: true;"));
3055 assert!(edited_text[0]
3056 .contents
3057 .contains("function re_name-me_(re-name_me: int) { /* 1 */"));
3058 assert!(edited_text[0]
3059 .contents
3060 .contains("/* 1 */ self.re-name_me /* <- TEST_ME_1 */ = re-name_me >= 42;"));
3061
3062 assert!(edited_text[0].contents.contains("export component Bar {"));
3063 assert!(edited_text[0].contents.contains("property <bool> XxxYyyZzz /* 2 "));
3064 assert!(edited_text[0].contents.contains(" 2 */: true;"));
3065 assert!(edited_text[0]
3066 .contents
3067 .contains("function re_name-me_(re-name_me: int) { /* 2 */"));
3068 assert!(edited_text[0]
3069 .contents
3070 .contains("/* 2 */ self.XxxYyyZzz /* <- TEST_ME_2 */ = re-name_me >= 42;"));
3071 assert!(edited_text[0].contents.contains("re_name-me { }"));
3072 }
3073
3074 #[test]
3075 fn test_rename_property_from_definition_with_export() {
3076 let document_cache = test::compile_test_with_sources(
3077 "fluent",
3078 HashMap::from([
3079 (
3080 Url::from_file_path(test::main_test_file_name()).unwrap(),
3081 r#"
3082import { re_name-me } from "source.slint";
3083
3084export component Bar {
3085 property <bool> re_name-me /* 1 */ : true;
3086
3087 function re_name-me_(re_name-me: int) { /* 2 */ self.re-name_me = re-name_me >= 42; }
3088
3089 re_name-me {
3090 re_name-me/* 3 */: false;
3091 }
3092}
3093 "#
3094 .to_string(),
3095 ),
3096 (
3097 Url::from_file_path(test::test_file_name("source.slint")).unwrap(),
3098 r#"
3099export component re_name-me {
3100 in-out property <bool> re_name-me /* <- TEST_ME_1 */: true;
3101
3102 function re_name-me_(re_name-me: int) { /* 4 */ self.re-name_me = re-name_me >= 42; }
3103}
3104 "#
3105 .to_string(),
3106 ),
3107 ]),
3108 true, // Component `Foo` is replacing a component with the same name
3109 );
3110
3111 let edited_text =
3112 rename_tester(&document_cache, &test::test_file_name("source.slint"), "_1");
3113
3114 assert_eq!(edited_text.len(), 2);
3115 for ed in &edited_text {
3116 let ed_path = ed.url.to_file_path().unwrap();
3117 if ed_path == test::main_test_file_name() {
3118 assert!(ed.contents.contains("import { re_name-me } from \"source.slint\";"));
3119 assert!(ed.contents.contains("export component Bar {"));
3120 assert!(ed.contents.contains("property <bool> re_name-me /* 1 */"));
3121 assert!(ed.contents.contains("function re_name-me_(re_name-me: int) { /* 2 */"));
3122 assert!(ed.contents.contains("/* 2 */ self.re-name_me = re-name_me >= 42;"));
3123 assert!(ed.contents.contains("re_name-me {"));
3124 assert!(ed.contents.contains("XxxYyyZzz/* 3 */:"));
3125 } else if ed_path == test::test_file_name("source.slint") {
3126 assert!(ed.contents.contains("export component re_name-me {"));
3127 assert!(ed.contents.contains("in-out property <bool> XxxYyyZzz /* <- TEST_ME_1"));
3128 assert!(ed.contents.contains("function re_name-me_(re_name-me: int) { /* 4 */"));
3129 assert!(ed.contents.contains("/* 4 */ self.XxxYyyZzz = re-name_me >= 42;"));
3130 } else {
3131 panic!("Unexpected file!");
3132 }
3133 }
3134 }
3135
3136 #[test]
3137 fn test_rename_property_from_use_with_export() {
3138 let document_cache = test::compile_test_with_sources(
3139 "fluent",
3140 HashMap::from([
3141 (
3142 Url::from_file_path(test::main_test_file_name()).unwrap(),
3143 r#"
3144import { re_name-me } from "source.slint";
3145
3146export component Bar {
3147 property <bool> re_name-me /* 1 */ : true;
3148
3149 function re_name-me_(re_name-me: int) { /* 2 */ self.re-name_me = re-name_me >= 42; }
3150
3151 re_name-me {
3152 re_name-me/* <- TEST_ME_1 */: false;
3153 }
3154}
3155 "#
3156 .to_string(),
3157 ),
3158 (
3159 Url::from_file_path(test::test_file_name("source.slint")).unwrap(),
3160 r#"
3161export component re_name-me {
3162 in-out property <bool> re_name-me /* 3 */: true;
3163
3164 function re_name-me_(re_name-me: int) { /* 4 */ self.re-name_me = re_name-me >= 42; }
3165}
3166 "#
3167 .to_string(),
3168 ),
3169 ]),
3170 true, // Component `Foo` is replacing a component with the same name
3171 );
3172
3173 let edited_text = rename_tester(&document_cache, &test::main_test_file_name(), "_1");
3174
3175 assert_eq!(edited_text.len(), 2);
3176 for ed in &edited_text {
3177 let ed_path = ed.url.to_file_path().unwrap();
3178 if ed_path == test::main_test_file_name() {
3179 assert!(ed.contents.contains("import { re_name-me } from \"source.slint\";"));
3180 assert!(ed.contents.contains("export component Bar {"));
3181 assert!(ed.contents.contains("property <bool> re_name-me /* 1 */"));
3182 assert!(ed.contents.contains("function re_name-me_(re_name-me: int) "));
3183 assert!(ed.contents.contains("{ /* 2 */ self.re-name_me = re-name_me >= 42;"));
3184 assert!(ed.contents.contains("re_name-me {"));
3185 assert!(ed.contents.contains("XxxYyyZzz/* <- TEST_ME_1 */:"));
3186 } else if ed_path == test::test_file_name("source.slint") {
3187 assert!(ed.contents.contains("export component re_name-me {"));
3188 assert!(ed.contents.contains("in-out property <bool> XxxYyyZzz /* 3 */"));
3189 assert!(ed.contents.contains("function re_name-me_(re_name-me: int) { /* 4 */"));
3190 assert!(ed.contents.contains("/* 4 */ self.XxxYyyZzz = re_name-me >= 42;"));
3191 } else {
3192 panic!("Unexpected file!");
3193 }
3194 }
3195 }
3196
3197 #[test]
3198 fn test_rename_globals_from_definition() {
3199 let document_cache = test::compile_test_with_sources(
3200 "fluent",
3201 HashMap::from([(
3202 Url::from_file_path(test::main_test_file_name()).unwrap(),
3203 r#"
3204export { Foo }
3205
3206global Foo /* <- TEST_ME_1 */ {
3207 in property <bool> test-property: true;
3208}
3209
3210export component Bar {
3211 function baz(bar: int) -> bool { return Foo.test_property && bar >= 42; }
3212}
3213 "#
3214 .to_string(),
3215 )]),
3216 true, // Component `Foo` is replacing a component with the same name
3217 );
3218
3219 let edited_text = rename_tester(&document_cache, &test::main_test_file_name(), "_1");
3220
3221 assert_eq!(edited_text.len(), 1);
3222 assert!(edited_text[0].contents.contains("export { XxxYyyZzz }"));
3223 assert!(edited_text[0].contents.contains("global XxxYyyZzz /* <- TEST_ME_1 "));
3224 assert!(edited_text[0].contents.contains("in property <bool> test-property: true;"));
3225 assert!(edited_text[0].contents.contains("function baz(bar: int)"));
3226 assert!(edited_text[0]
3227 .contents
3228 .contains("int) -> bool { return XxxYyyZzz.test_property && bar >= 42"));
3229 }
3230
3231 #[test]
3232 fn test_rename_globals_from_use() {
3233 let document_cache = test::compile_test_with_sources(
3234 "fluent",
3235 HashMap::from([(
3236 Url::from_file_path(test::main_test_file_name()).unwrap(),
3237 r#"
3238export { Foo }
3239
3240global Foo {
3241 in property <bool> test-property: true;
3242}
3243
3244export component Bar {
3245 function baz(bar: int) -> bool { return Foo /* <- TEST_ME_1 */.test_property && bar >= 42; }
3246}
3247 "#
3248 .to_string(),
3249 )]),
3250 true, // Component `Foo` is replacing a component with the same name
3251 );
3252
3253 let edited_text = rename_tester(&document_cache, &test::main_test_file_name(), "_1");
3254
3255 assert_eq!(edited_text.len(), 1);
3256 assert!(edited_text[0].contents.contains("export { XxxYyyZzz }"));
3257 assert!(edited_text[0].contents.contains("global XxxYyyZzz {"));
3258 assert!(edited_text[0].contents.contains("in property <bool> test-property: true;"));
3259 assert!(edited_text[0].contents.contains("function baz(bar: int)"));
3260 assert!(edited_text[0].contents.contains(
3261 "int) -> bool { return XxxYyyZzz /* <- TEST_ME_1 */.test_property && bar >= 42"
3262 ));
3263 }
3264
3265 #[test]
3266 fn test_rename_globals_from_use_with_export() {
3267 let document_cache = test::compile_test_with_sources(
3268 "fluent",
3269 HashMap::from([
3270 (
3271 Url::from_file_path(test::main_test_file_name()).unwrap(),
3272 r#"
3273import { Foo } from "source.slint";
3274
3275export component Bar {
3276 function baz(bar: int) -> bool { return Foo /* <- TEST_ME_1 */.test_property && bar >= 42; }
3277}
3278 "#
3279 .to_string(),
3280 ),
3281 (
3282 Url::from_file_path(test::test_file_name("source.slint")).unwrap(),
3283 r#"
3284export { Foo }
3285
3286global Foo {
3287 in property <bool> test-property: true;
3288}
3289 "#
3290 .to_string(),
3291 ),
3292 ]),
3293 true, // Component `Foo` is replacing a component with the same name
3294 );
3295
3296 let edited_text = rename_tester(&document_cache, &test::main_test_file_name(), "_1");
3297
3298 assert_eq!(edited_text.len(), 2);
3299 for ed in &edited_text {
3300 let ed_path = ed.url.to_file_path().unwrap();
3301 if ed_path == test::main_test_file_name() {
3302 assert!(ed.contents.contains("import { XxxYyyZzz } from \"source.slint\";"));
3303 assert!(ed.contents.contains("function baz(bar: int)"));
3304 assert!(ed.contents.contains(
3305 "int) -> bool { return XxxYyyZzz /* <- TEST_ME_1 */.test_property && bar >= 42"
3306 ));
3307 } else if ed_path == test::test_file_name("source.slint") {
3308 assert!(ed.contents.contains("export { XxxYyyZzz }"));
3309 assert!(ed.contents.contains("global XxxYyyZzz {"));
3310 assert!(ed.contents.contains("in property <bool> test-property: true;"));
3311 } else {
3312 panic!("Unexpected file!");
3313 }
3314 }
3315 }
3316
3317 #[test]
3318 fn test_rename_globals_from_use_with_export_module() {
3319 let document_cache = test::compile_test_with_sources(
3320 "fluent",
3321 HashMap::from([
3322 (
3323 Url::from_file_path(test::main_test_file_name()).unwrap(),
3324 r#"
3325import { Foo } from "reexport.slint";
3326
3327export component Bar {
3328 function baz(bar: int) -> bool { return Foo /* <- TEST_ME_1 */.test_property && bar >= 42; }
3329}
3330 "#
3331 .to_string(),
3332 ),
3333 (
3334 Url::from_file_path(test::test_file_name("reexport.slint")).unwrap(),
3335 r#"
3336export { Foo } from "source.slint";
3337 "#
3338 .to_string(),
3339 ),
3340 (
3341 Url::from_file_path(test::test_file_name("source.slint")).unwrap(),
3342 r#"
3343export { Foo }
3344
3345global Foo {
3346 in property <bool> test-property: true;
3347}
3348 "#
3349 .to_string(),
3350 ),
3351 ]),
3352 true, // Component `Foo` is replacing a component with the same name
3353 );
3354
3355 let edited_text = rename_tester(&document_cache, &test::main_test_file_name(), "_1");
3356
3357 assert_eq!(edited_text.len(), 3);
3358 for ed in &edited_text {
3359 let ed_path = ed.url.to_file_path().unwrap();
3360 if ed_path == test::main_test_file_name() {
3361 assert!(ed.contents.contains("import { XxxYyyZzz } from \"reexport.slint\";"));
3362 assert!(ed.contents.contains("function baz(bar: int)"));
3363 assert!(ed.contents.contains(
3364 "int) -> bool { return XxxYyyZzz /* <- TEST_ME_1 */.test_property && bar >= 42"
3365 ));
3366 } else if ed_path == test::test_file_name("source.slint") {
3367 assert!(ed.contents.contains("export { XxxYyyZzz }"));
3368 assert!(ed.contents.contains("global XxxYyyZzz {"));
3369 assert!(ed.contents.contains("in property <bool> test-property: true;"));
3370 } else if ed_path == test::test_file_name("reexport.slint") {
3371 assert!(ed.contents.contains("export { XxxYyyZzz } from \"source.slint\""));
3372 } else {
3373 panic!("Unexpected file!");
3374 }
3375 }
3376 }
3377
3378 #[test]
3379 fn test_rename_globals_from_use_with_export_module_renamed() {
3380 let document_cache = test::compile_test_with_sources(
3381 "fluent",
3382 HashMap::from([
3383 (
3384 Url::from_file_path(test::main_test_file_name()).unwrap(),
3385 r#"
3386import { Foobar } from "reexport.slint";
3387
3388export component Bar {
3389 function baz(bar: int) -> bool { return Foobar /* <- TEST_ME_1 */.test_property && bar >= 42; }
3390}
3391 "#
3392 .to_string(),
3393 ),
3394 (
3395 Url::from_file_path(test::test_file_name("reexport.slint")).unwrap(),
3396 r#"
3397export { Foo /* <- TEST_ME_2 */ as Foobar } from "source.slint";
3398 "#
3399 .to_string(),
3400 ),
3401 (
3402 Url::from_file_path(test::test_file_name("source.slint")).unwrap(),
3403 r#"
3404export { Foo }
3405
3406global Foo {
3407 in property <bool> test-property: true;
3408}
3409 "#
3410 .to_string(),
3411 ),
3412 ]),
3413 true, // Component `Foo` is replacing a component with the same name
3414 );
3415
3416 let edited_text = rename_tester(&document_cache, &test::main_test_file_name(), "_1");
3417
3418 assert_eq!(edited_text.len(), 2);
3419 for ed in &edited_text {
3420 let ed_path = ed.url.to_file_path().unwrap();
3421 if ed_path == test::main_test_file_name() {
3422 assert!(ed.contents.contains("import { XxxYyyZzz } from \"reexport.slint\";"));
3423 assert!(ed.contents.contains("function baz(bar: int)"));
3424 assert!(ed.contents.contains(
3425 "int) -> bool { return XxxYyyZzz /* <- TEST_ME_1 */.test_property && bar >= 42"
3426 ));
3427 } else if ed_path == test::test_file_name("reexport.slint") {
3428 assert!(ed.contents.contains(
3429 "export { Foo /* <- TEST_ME_2 */ as XxxYyyZzz } from \"source.slint\""
3430 ));
3431 } else {
3432 panic!("Unexpected file!");
3433 }
3434 }
3435
3436 let edited_text = rename_tester_with_new_name(
3437 &document_cache,
3438 &test::test_file_name("reexport.slint"),
3439 "_2",
3440 "Foobar",
3441 );
3442
3443 assert_eq!(edited_text.len(), 2);
3444 for ed in &edited_text {
3445 let ed_path = ed.url.to_file_path().unwrap();
3446 if ed_path == test::test_file_name("source.slint") {
3447 assert!(ed.contents.contains("export { Foobar }"));
3448 assert!(ed.contents.contains("global Foobar {"));
3449 assert!(ed.contents.contains("in property <bool> test-property: true;"));
3450 } else if ed_path == test::test_file_name("reexport.slint") {
3451 assert!(ed.contents.contains("export { Foobar } from \"source.slint\""));
3452 } else {
3453 panic!("Unexpected file!");
3454 }
3455 }
3456 }
3457
3458 #[test]
3459 fn test_rename_globals_from_use_with_export_module_star() {
3460 let document_cache = test::compile_test_with_sources(
3461 "fluent",
3462 HashMap::from([
3463 (
3464 Url::from_file_path(test::main_test_file_name()).unwrap(),
3465 r#"
3466import { Foo } from "reexport.slint";
3467
3468export component Bar {
3469 function baz(bar: int) -> bool { return Foo /* <- TEST_ME_1 */.test_property && bar >= 42; }
3470}
3471 "#
3472 .to_string(),
3473 ),
3474 (
3475 Url::from_file_path(test::test_file_name("reexport.slint")).unwrap(),
3476 r#"
3477export * from "source.slint";
3478 "#
3479 .to_string(),
3480 ),
3481 (
3482 Url::from_file_path(test::test_file_name("source.slint")).unwrap(),
3483 r#"
3484export { Foo }
3485
3486global Foo {
3487 in property <bool> test-property: true;
3488}
3489 "#
3490 .to_string(),
3491 ),
3492 ]),
3493 true, // Component `Foo` is replacing a component with the same name
3494 );
3495
3496 let edited_text = rename_tester(&document_cache, &test::main_test_file_name(), "_1");
3497
3498 assert_eq!(edited_text.len(), 2);
3499 for ed in &edited_text {
3500 let ed_path = ed.url.to_file_path().unwrap();
3501 if ed_path == test::main_test_file_name() {
3502 assert!(ed.contents.contains("import { XxxYyyZzz } from \"reexport.slint\";"));
3503 assert!(ed.contents.contains("function baz(bar: int)"));
3504 assert!(ed.contents.contains(
3505 "int) -> bool { return XxxYyyZzz /* <- TEST_ME_1 */.test_property && bar >= 42"
3506 ));
3507 } else if ed_path == test::test_file_name("source.slint") {
3508 assert!(ed.contents.contains("export { XxxYyyZzz }"));
3509 assert!(ed.contents.contains("global XxxYyyZzz {"));
3510 assert!(ed.contents.contains("in property <bool> test-property: true;"));
3511 } else {
3512 panic!("Unexpected file!");
3513 }
3514 }
3515 }
3516}
3517