diff options
author | bors[bot] <26634292+bors[bot]@users.noreply.github.com> | 2020-05-20 12:33:48 +0100 |
---|---|---|
committer | GitHub <[email protected]> | 2020-05-20 12:33:48 +0100 |
commit | 75c7695e86f967ec87f072a3a26fc23239de0a7e (patch) | |
tree | 4bc15c3e4bab37a2eb8b261aaab107ea60e6a1b9 /crates/ra_assists/src/handlers | |
parent | e3373629cd80404388c5da52f991e18f2d715d8d (diff) | |
parent | cec773926f08e2d46b05d923165f8e73c420aa8c (diff) |
Merge #4532
4532: Split change_ and fix_ visibility assists r=matklad a=matklad
bors r+
🤖
Co-authored-by: Aleksey Kladov <[email protected]>
Diffstat (limited to 'crates/ra_assists/src/handlers')
-rw-r--r-- | crates/ra_assists/src/handlers/change_visibility.rs | 507 | ||||
-rw-r--r-- | crates/ra_assists/src/handlers/fix_visibility.rs | 555 |
2 files changed, 556 insertions, 506 deletions
diff --git a/crates/ra_assists/src/handlers/change_visibility.rs b/crates/ra_assists/src/handlers/change_visibility.rs index 71d55e0c3..1d9b8e645 100644 --- a/crates/ra_assists/src/handlers/change_visibility.rs +++ b/crates/ra_assists/src/handlers/change_visibility.rs | |||
@@ -5,14 +5,11 @@ use ra_syntax::{ | |||
5 | ATTR, COMMENT, CONST_DEF, ENUM_DEF, FN_DEF, MODULE, STRUCT_DEF, TRAIT_DEF, VISIBILITY, | 5 | ATTR, COMMENT, CONST_DEF, ENUM_DEF, FN_DEF, MODULE, STRUCT_DEF, TRAIT_DEF, VISIBILITY, |
6 | WHITESPACE, | 6 | WHITESPACE, |
7 | }, | 7 | }, |
8 | SyntaxNode, TextRange, TextSize, T, | 8 | SyntaxNode, TextSize, T, |
9 | }; | 9 | }; |
10 | |||
11 | use hir::{db::HirDatabase, HasSource, HasVisibility, PathResolution}; | ||
12 | use test_utils::mark; | 10 | use test_utils::mark; |
13 | 11 | ||
14 | use crate::{AssistContext, AssistId, Assists}; | 12 | use crate::{AssistContext, AssistId, Assists}; |
15 | use ra_db::FileId; | ||
16 | 13 | ||
17 | // Assist: change_visibility | 14 | // Assist: change_visibility |
18 | // | 15 | // |
@@ -30,8 +27,6 @@ pub(crate) fn change_visibility(acc: &mut Assists, ctx: &AssistContext) -> Optio | |||
30 | return change_vis(acc, vis); | 27 | return change_vis(acc, vis); |
31 | } | 28 | } |
32 | add_vis(acc, ctx) | 29 | add_vis(acc, ctx) |
33 | .or_else(|| add_vis_to_referenced_module_def(acc, ctx)) | ||
34 | .or_else(|| add_vis_to_referenced_record_field(acc, ctx)) | ||
35 | } | 30 | } |
36 | 31 | ||
37 | fn add_vis(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { | 32 | fn add_vis(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { |
@@ -77,143 +72,6 @@ fn add_vis(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { | |||
77 | }) | 72 | }) |
78 | } | 73 | } |
79 | 74 | ||
80 | fn add_vis_to_referenced_module_def(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { | ||
81 | let path: ast::Path = ctx.find_node_at_offset()?; | ||
82 | let path_res = ctx.sema.resolve_path(&path)?; | ||
83 | let def = match path_res { | ||
84 | PathResolution::Def(def) => def, | ||
85 | _ => return None, | ||
86 | }; | ||
87 | |||
88 | let current_module = ctx.sema.scope(&path.syntax()).module()?; | ||
89 | let target_module = def.module(ctx.db)?; | ||
90 | |||
91 | let vis = target_module.visibility_of(ctx.db, &def)?; | ||
92 | if vis.is_visible_from(ctx.db, current_module.into()) { | ||
93 | return None; | ||
94 | }; | ||
95 | |||
96 | let (offset, target, target_file, target_name) = target_data_for_def(ctx.db, def)?; | ||
97 | |||
98 | let missing_visibility = | ||
99 | if current_module.krate() == target_module.krate() { "pub(crate)" } else { "pub" }; | ||
100 | |||
101 | let assist_label = match target_name { | ||
102 | None => format!("Change visibility to {}", missing_visibility), | ||
103 | Some(name) => format!("Change visibility of {} to {}", name, missing_visibility), | ||
104 | }; | ||
105 | |||
106 | acc.add(AssistId("change_visibility"), assist_label, target, |edit| { | ||
107 | edit.set_file(target_file); | ||
108 | edit.insert(offset, format!("{} ", missing_visibility)); | ||
109 | edit.set_cursor(offset); | ||
110 | }) | ||
111 | } | ||
112 | |||
113 | fn add_vis_to_referenced_record_field(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { | ||
114 | let record_field: ast::RecordField = ctx.find_node_at_offset()?; | ||
115 | let (record_field_def, _) = ctx.sema.resolve_record_field(&record_field)?; | ||
116 | |||
117 | let current_module = ctx.sema.scope(record_field.syntax()).module()?; | ||
118 | let visibility = record_field_def.visibility(ctx.db); | ||
119 | if visibility.is_visible_from(ctx.db, current_module.into()) { | ||
120 | return None; | ||
121 | } | ||
122 | |||
123 | let parent = record_field_def.parent_def(ctx.db); | ||
124 | let parent_name = parent.name(ctx.db); | ||
125 | let target_module = parent.module(ctx.db); | ||
126 | |||
127 | let in_file_source = record_field_def.source(ctx.db); | ||
128 | let (offset, target) = match in_file_source.value { | ||
129 | hir::FieldSource::Named(it) => { | ||
130 | let s = it.syntax(); | ||
131 | (vis_offset(s), s.text_range()) | ||
132 | } | ||
133 | hir::FieldSource::Pos(it) => { | ||
134 | let s = it.syntax(); | ||
135 | (vis_offset(s), s.text_range()) | ||
136 | } | ||
137 | }; | ||
138 | |||
139 | let missing_visibility = | ||
140 | if current_module.krate() == target_module.krate() { "pub(crate)" } else { "pub" }; | ||
141 | let target_file = in_file_source.file_id.original_file(ctx.db); | ||
142 | |||
143 | let target_name = record_field_def.name(ctx.db); | ||
144 | let assist_label = | ||
145 | format!("Change visibility of {}.{} to {}", parent_name, target_name, missing_visibility); | ||
146 | |||
147 | acc.add(AssistId("change_visibility"), assist_label, target, |edit| { | ||
148 | edit.set_file(target_file); | ||
149 | edit.insert(offset, format!("{} ", missing_visibility)); | ||
150 | edit.set_cursor(offset) | ||
151 | }) | ||
152 | } | ||
153 | |||
154 | fn target_data_for_def( | ||
155 | db: &dyn HirDatabase, | ||
156 | def: hir::ModuleDef, | ||
157 | ) -> Option<(TextSize, TextRange, FileId, Option<hir::Name>)> { | ||
158 | fn offset_target_and_file_id<S, Ast>( | ||
159 | db: &dyn HirDatabase, | ||
160 | x: S, | ||
161 | ) -> (TextSize, TextRange, FileId) | ||
162 | where | ||
163 | S: HasSource<Ast = Ast>, | ||
164 | Ast: AstNode, | ||
165 | { | ||
166 | let source = x.source(db); | ||
167 | let in_file_syntax = source.syntax(); | ||
168 | let file_id = in_file_syntax.file_id; | ||
169 | let syntax = in_file_syntax.value; | ||
170 | (vis_offset(syntax), syntax.text_range(), file_id.original_file(db.upcast())) | ||
171 | } | ||
172 | |||
173 | let target_name; | ||
174 | let (offset, target, target_file) = match def { | ||
175 | hir::ModuleDef::Function(f) => { | ||
176 | target_name = Some(f.name(db)); | ||
177 | offset_target_and_file_id(db, f) | ||
178 | } | ||
179 | hir::ModuleDef::Adt(adt) => { | ||
180 | target_name = Some(adt.name(db)); | ||
181 | match adt { | ||
182 | hir::Adt::Struct(s) => offset_target_and_file_id(db, s), | ||
183 | hir::Adt::Union(u) => offset_target_and_file_id(db, u), | ||
184 | hir::Adt::Enum(e) => offset_target_and_file_id(db, e), | ||
185 | } | ||
186 | } | ||
187 | hir::ModuleDef::Const(c) => { | ||
188 | target_name = c.name(db); | ||
189 | offset_target_and_file_id(db, c) | ||
190 | } | ||
191 | hir::ModuleDef::Static(s) => { | ||
192 | target_name = s.name(db); | ||
193 | offset_target_and_file_id(db, s) | ||
194 | } | ||
195 | hir::ModuleDef::Trait(t) => { | ||
196 | target_name = Some(t.name(db)); | ||
197 | offset_target_and_file_id(db, t) | ||
198 | } | ||
199 | hir::ModuleDef::TypeAlias(t) => { | ||
200 | target_name = Some(t.name(db)); | ||
201 | offset_target_and_file_id(db, t) | ||
202 | } | ||
203 | hir::ModuleDef::Module(m) => { | ||
204 | target_name = m.name(db); | ||
205 | let in_file_source = m.declaration_source(db)?; | ||
206 | let file_id = in_file_source.file_id.original_file(db.upcast()); | ||
207 | let syntax = in_file_source.value.syntax(); | ||
208 | (vis_offset(syntax), syntax.text_range(), file_id) | ||
209 | } | ||
210 | // Enum variants can't be private, we can't modify builtin types | ||
211 | hir::ModuleDef::EnumVariant(_) | hir::ModuleDef::BuiltinType(_) => return None, | ||
212 | }; | ||
213 | |||
214 | Some((offset, target, target_file, target_name)) | ||
215 | } | ||
216 | |||
217 | fn vis_offset(node: &SyntaxNode) -> TextSize { | 75 | fn vis_offset(node: &SyntaxNode) -> TextSize { |
218 | node.children_with_tokens() | 76 | node.children_with_tokens() |
219 | .skip_while(|it| match it.kind() { | 77 | .skip_while(|it| match it.kind() { |
@@ -334,193 +192,6 @@ mod tests { | |||
334 | } | 192 | } |
335 | 193 | ||
336 | #[test] | 194 | #[test] |
337 | fn change_visibility_of_fn_via_path() { | ||
338 | check_assist( | ||
339 | change_visibility, | ||
340 | r"mod foo { fn foo() {} } | ||
341 | fn main() { foo::foo<|>() } ", | ||
342 | r"mod foo { <|>pub(crate) fn foo() {} } | ||
343 | fn main() { foo::foo() } ", | ||
344 | ); | ||
345 | check_assist_not_applicable( | ||
346 | change_visibility, | ||
347 | r"mod foo { pub fn foo() {} } | ||
348 | fn main() { foo::foo<|>() } ", | ||
349 | ) | ||
350 | } | ||
351 | |||
352 | #[test] | ||
353 | fn change_visibility_of_adt_in_submodule_via_path() { | ||
354 | check_assist( | ||
355 | change_visibility, | ||
356 | r"mod foo { struct Foo; } | ||
357 | fn main() { foo::Foo<|> } ", | ||
358 | r"mod foo { <|>pub(crate) struct Foo; } | ||
359 | fn main() { foo::Foo } ", | ||
360 | ); | ||
361 | check_assist_not_applicable( | ||
362 | change_visibility, | ||
363 | r"mod foo { pub struct Foo; } | ||
364 | fn main() { foo::Foo<|> } ", | ||
365 | ); | ||
366 | check_assist( | ||
367 | change_visibility, | ||
368 | r"mod foo { enum Foo; } | ||
369 | fn main() { foo::Foo<|> } ", | ||
370 | r"mod foo { <|>pub(crate) enum Foo; } | ||
371 | fn main() { foo::Foo } ", | ||
372 | ); | ||
373 | check_assist_not_applicable( | ||
374 | change_visibility, | ||
375 | r"mod foo { pub enum Foo; } | ||
376 | fn main() { foo::Foo<|> } ", | ||
377 | ); | ||
378 | check_assist( | ||
379 | change_visibility, | ||
380 | r"mod foo { union Foo; } | ||
381 | fn main() { foo::Foo<|> } ", | ||
382 | r"mod foo { <|>pub(crate) union Foo; } | ||
383 | fn main() { foo::Foo } ", | ||
384 | ); | ||
385 | check_assist_not_applicable( | ||
386 | change_visibility, | ||
387 | r"mod foo { pub union Foo; } | ||
388 | fn main() { foo::Foo<|> } ", | ||
389 | ); | ||
390 | } | ||
391 | |||
392 | #[test] | ||
393 | fn change_visibility_of_adt_in_other_file_via_path() { | ||
394 | check_assist( | ||
395 | change_visibility, | ||
396 | r" | ||
397 | //- /main.rs | ||
398 | mod foo; | ||
399 | fn main() { foo::Foo<|> } | ||
400 | |||
401 | //- /foo.rs | ||
402 | struct Foo; | ||
403 | ", | ||
404 | r"<|>pub(crate) struct Foo; | ||
405 | |||
406 | ", | ||
407 | ); | ||
408 | } | ||
409 | |||
410 | #[test] | ||
411 | fn change_visibility_of_struct_field_via_path() { | ||
412 | check_assist( | ||
413 | change_visibility, | ||
414 | r"mod foo { pub struct Foo { bar: (), } } | ||
415 | fn main() { foo::Foo { <|>bar: () }; } ", | ||
416 | r"mod foo { pub struct Foo { <|>pub(crate) bar: (), } } | ||
417 | fn main() { foo::Foo { bar: () }; } ", | ||
418 | ); | ||
419 | check_assist( | ||
420 | change_visibility, | ||
421 | r"//- /lib.rs | ||
422 | mod foo; | ||
423 | fn main() { foo::Foo { <|>bar: () }; } | ||
424 | //- /foo.rs | ||
425 | pub struct Foo { bar: () } | ||
426 | ", | ||
427 | r"pub struct Foo { <|>pub(crate) bar: () } | ||
428 | |||
429 | ", | ||
430 | ); | ||
431 | check_assist_not_applicable( | ||
432 | change_visibility, | ||
433 | r"mod foo { pub struct Foo { pub bar: (), } } | ||
434 | fn main() { foo::Foo { <|>bar: () }; } ", | ||
435 | ); | ||
436 | check_assist_not_applicable( | ||
437 | change_visibility, | ||
438 | r"//- /lib.rs | ||
439 | mod foo; | ||
440 | fn main() { foo::Foo { <|>bar: () }; } | ||
441 | //- /foo.rs | ||
442 | pub struct Foo { pub bar: () } | ||
443 | ", | ||
444 | ); | ||
445 | } | ||
446 | |||
447 | #[test] | ||
448 | fn change_visibility_of_enum_variant_field_via_path() { | ||
449 | check_assist( | ||
450 | change_visibility, | ||
451 | r"mod foo { pub enum Foo { Bar { bar: () } } } | ||
452 | fn main() { foo::Foo::Bar { <|>bar: () }; } ", | ||
453 | r"mod foo { pub enum Foo { Bar { <|>pub(crate) bar: () } } } | ||
454 | fn main() { foo::Foo::Bar { bar: () }; } ", | ||
455 | ); | ||
456 | check_assist( | ||
457 | change_visibility, | ||
458 | r"//- /lib.rs | ||
459 | mod foo; | ||
460 | fn main() { foo::Foo::Bar { <|>bar: () }; } | ||
461 | //- /foo.rs | ||
462 | pub enum Foo { Bar { bar: () } } | ||
463 | ", | ||
464 | r"pub enum Foo { Bar { <|>pub(crate) bar: () } } | ||
465 | |||
466 | ", | ||
467 | ); | ||
468 | check_assist_not_applicable( | ||
469 | change_visibility, | ||
470 | r"mod foo { pub struct Foo { pub bar: (), } } | ||
471 | fn main() { foo::Foo { <|>bar: () }; } ", | ||
472 | ); | ||
473 | check_assist_not_applicable( | ||
474 | change_visibility, | ||
475 | r"//- /lib.rs | ||
476 | mod foo; | ||
477 | fn main() { foo::Foo { <|>bar: () }; } | ||
478 | //- /foo.rs | ||
479 | pub struct Foo { pub bar: () } | ||
480 | ", | ||
481 | ); | ||
482 | } | ||
483 | |||
484 | #[test] | ||
485 | #[ignore] | ||
486 | // FIXME reenable this test when `Semantics::resolve_record_field` works with union fields | ||
487 | fn change_visibility_of_union_field_via_path() { | ||
488 | check_assist( | ||
489 | change_visibility, | ||
490 | r"mod foo { pub union Foo { bar: (), } } | ||
491 | fn main() { foo::Foo { <|>bar: () }; } ", | ||
492 | r"mod foo { pub union Foo { <|>pub(crate) bar: (), } } | ||
493 | fn main() { foo::Foo { bar: () }; } ", | ||
494 | ); | ||
495 | check_assist( | ||
496 | change_visibility, | ||
497 | r"//- /lib.rs | ||
498 | mod foo; | ||
499 | fn main() { foo::Foo { <|>bar: () }; } | ||
500 | //- /foo.rs | ||
501 | pub union Foo { bar: () } | ||
502 | ", | ||
503 | r"pub union Foo { <|>pub(crate) bar: () } | ||
504 | |||
505 | ", | ||
506 | ); | ||
507 | check_assist_not_applicable( | ||
508 | change_visibility, | ||
509 | r"mod foo { pub union Foo { pub bar: (), } } | ||
510 | fn main() { foo::Foo { <|>bar: () }; } ", | ||
511 | ); | ||
512 | check_assist_not_applicable( | ||
513 | change_visibility, | ||
514 | r"//- /lib.rs | ||
515 | mod foo; | ||
516 | fn main() { foo::Foo { <|>bar: () }; } | ||
517 | //- /foo.rs | ||
518 | pub union Foo { pub bar: () } | ||
519 | ", | ||
520 | ); | ||
521 | } | ||
522 | |||
523 | #[test] | ||
524 | fn not_applicable_for_enum_variants() { | 195 | fn not_applicable_for_enum_variants() { |
525 | check_assist_not_applicable( | 196 | check_assist_not_applicable( |
526 | change_visibility, | 197 | change_visibility, |
@@ -530,182 +201,6 @@ mod tests { | |||
530 | } | 201 | } |
531 | 202 | ||
532 | #[test] | 203 | #[test] |
533 | fn change_visibility_of_const_via_path() { | ||
534 | check_assist( | ||
535 | change_visibility, | ||
536 | r"mod foo { const FOO: () = (); } | ||
537 | fn main() { foo::FOO<|> } ", | ||
538 | r"mod foo { <|>pub(crate) const FOO: () = (); } | ||
539 | fn main() { foo::FOO } ", | ||
540 | ); | ||
541 | check_assist_not_applicable( | ||
542 | change_visibility, | ||
543 | r"mod foo { pub const FOO: () = (); } | ||
544 | fn main() { foo::FOO<|> } ", | ||
545 | ); | ||
546 | } | ||
547 | |||
548 | #[test] | ||
549 | fn change_visibility_of_static_via_path() { | ||
550 | check_assist( | ||
551 | change_visibility, | ||
552 | r"mod foo { static FOO: () = (); } | ||
553 | fn main() { foo::FOO<|> } ", | ||
554 | r"mod foo { <|>pub(crate) static FOO: () = (); } | ||
555 | fn main() { foo::FOO } ", | ||
556 | ); | ||
557 | check_assist_not_applicable( | ||
558 | change_visibility, | ||
559 | r"mod foo { pub static FOO: () = (); } | ||
560 | fn main() { foo::FOO<|> } ", | ||
561 | ); | ||
562 | } | ||
563 | |||
564 | #[test] | ||
565 | fn change_visibility_of_trait_via_path() { | ||
566 | check_assist( | ||
567 | change_visibility, | ||
568 | r"mod foo { trait Foo { fn foo(&self) {} } } | ||
569 | fn main() { let x: &dyn foo::<|>Foo; } ", | ||
570 | r"mod foo { <|>pub(crate) trait Foo { fn foo(&self) {} } } | ||
571 | fn main() { let x: &dyn foo::Foo; } ", | ||
572 | ); | ||
573 | check_assist_not_applicable( | ||
574 | change_visibility, | ||
575 | r"mod foo { pub trait Foo { fn foo(&self) {} } } | ||
576 | fn main() { let x: &dyn foo::Foo<|>; } ", | ||
577 | ); | ||
578 | } | ||
579 | |||
580 | #[test] | ||
581 | fn change_visibility_of_type_alias_via_path() { | ||
582 | check_assist( | ||
583 | change_visibility, | ||
584 | r"mod foo { type Foo = (); } | ||
585 | fn main() { let x: foo::Foo<|>; } ", | ||
586 | r"mod foo { <|>pub(crate) type Foo = (); } | ||
587 | fn main() { let x: foo::Foo; } ", | ||
588 | ); | ||
589 | check_assist_not_applicable( | ||
590 | change_visibility, | ||
591 | r"mod foo { pub type Foo = (); } | ||
592 | fn main() { let x: foo::Foo<|>; } ", | ||
593 | ); | ||
594 | } | ||
595 | |||
596 | #[test] | ||
597 | fn change_visibility_of_module_via_path() { | ||
598 | check_assist( | ||
599 | change_visibility, | ||
600 | r"mod foo { mod bar { fn bar() {} } } | ||
601 | fn main() { foo::bar<|>::bar(); } ", | ||
602 | r"mod foo { <|>pub(crate) mod bar { fn bar() {} } } | ||
603 | fn main() { foo::bar::bar(); } ", | ||
604 | ); | ||
605 | |||
606 | check_assist( | ||
607 | change_visibility, | ||
608 | r" | ||
609 | //- /main.rs | ||
610 | mod foo; | ||
611 | fn main() { foo::bar<|>::baz(); } | ||
612 | |||
613 | //- /foo.rs | ||
614 | mod bar { | ||
615 | pub fn baz() {} | ||
616 | } | ||
617 | ", | ||
618 | r"<|>pub(crate) mod bar { | ||
619 | pub fn baz() {} | ||
620 | } | ||
621 | |||
622 | ", | ||
623 | ); | ||
624 | |||
625 | check_assist_not_applicable( | ||
626 | change_visibility, | ||
627 | r"mod foo { pub mod bar { pub fn bar() {} } } | ||
628 | fn main() { foo::bar<|>::bar(); } ", | ||
629 | ); | ||
630 | } | ||
631 | |||
632 | #[test] | ||
633 | fn change_visibility_of_inline_module_in_other_file_via_path() { | ||
634 | check_assist( | ||
635 | change_visibility, | ||
636 | r" | ||
637 | //- /main.rs | ||
638 | mod foo; | ||
639 | fn main() { foo::bar<|>::baz(); } | ||
640 | |||
641 | //- /foo.rs | ||
642 | mod bar; | ||
643 | |||
644 | //- /foo/bar.rs | ||
645 | pub fn baz() {} | ||
646 | } | ||
647 | ", | ||
648 | r"<|>pub(crate) mod bar; | ||
649 | ", | ||
650 | ); | ||
651 | } | ||
652 | |||
653 | #[test] | ||
654 | fn change_visibility_of_module_declaration_in_other_file_via_path() { | ||
655 | check_assist( | ||
656 | change_visibility, | ||
657 | r"//- /main.rs | ||
658 | mod foo; | ||
659 | fn main() { foo::bar<|>>::baz(); } | ||
660 | |||
661 | //- /foo.rs | ||
662 | mod bar { | ||
663 | pub fn baz() {} | ||
664 | }", | ||
665 | r"<|>pub(crate) mod bar { | ||
666 | pub fn baz() {} | ||
667 | } | ||
668 | ", | ||
669 | ); | ||
670 | } | ||
671 | |||
672 | #[test] | ||
673 | #[ignore] | ||
674 | // FIXME handle reexports properly | ||
675 | fn change_visibility_of_reexport() { | ||
676 | check_assist( | ||
677 | change_visibility, | ||
678 | r" | ||
679 | mod foo { | ||
680 | use bar::Baz; | ||
681 | mod bar { pub(super) struct Baz; } | ||
682 | } | ||
683 | foo::Baz<|> | ||
684 | ", | ||
685 | r" | ||
686 | mod foo { | ||
687 | <|>pub(crate) use bar::Baz; | ||
688 | mod bar { pub(super) struct Baz; } | ||
689 | } | ||
690 | foo::Baz | ||
691 | ", | ||
692 | ) | ||
693 | } | ||
694 | |||
695 | #[test] | ||
696 | fn adds_pub_when_target_is_in_another_crate() { | ||
697 | check_assist( | ||
698 | change_visibility, | ||
699 | r"//- /main.rs crate:a deps:foo | ||
700 | foo::Bar<|> | ||
701 | //- /lib.rs crate:foo | ||
702 | struct Bar;", | ||
703 | r"<|>pub struct Bar; | ||
704 | ", | ||
705 | ) | ||
706 | } | ||
707 | |||
708 | #[test] | ||
709 | fn change_visibility_target() { | 204 | fn change_visibility_target() { |
710 | check_assist_target(change_visibility, "<|>fn foo() {}", "fn"); | 205 | check_assist_target(change_visibility, "<|>fn foo() {}", "fn"); |
711 | check_assist_target(change_visibility, "pub(crate)<|> fn foo() {}", "pub(crate)"); | 206 | check_assist_target(change_visibility, "pub(crate)<|> fn foo() {}", "pub(crate)"); |
diff --git a/crates/ra_assists/src/handlers/fix_visibility.rs b/crates/ra_assists/src/handlers/fix_visibility.rs new file mode 100644 index 000000000..48ce07ca5 --- /dev/null +++ b/crates/ra_assists/src/handlers/fix_visibility.rs | |||
@@ -0,0 +1,555 @@ | |||
1 | use hir::{db::HirDatabase, HasSource, HasVisibility, PathResolution}; | ||
2 | use ra_db::FileId; | ||
3 | use ra_syntax::{ | ||
4 | ast, AstNode, | ||
5 | SyntaxKind::{ATTR, COMMENT, WHITESPACE}, | ||
6 | SyntaxNode, TextRange, TextSize, | ||
7 | }; | ||
8 | |||
9 | use crate::{AssistContext, AssistId, Assists}; | ||
10 | |||
11 | // FIXME: this really should be a fix for diagnostic, rather than an assist. | ||
12 | |||
13 | // Assist: fix_visibility | ||
14 | // | ||
15 | // Makes inaccessible item public. | ||
16 | // | ||
17 | // ``` | ||
18 | // mod m { | ||
19 | // fn frobnicate() {} | ||
20 | // } | ||
21 | // fn main() { | ||
22 | // m::frobnicate<|>() {} | ||
23 | // } | ||
24 | // ``` | ||
25 | // -> | ||
26 | // ``` | ||
27 | // mod m { | ||
28 | // pub(crate) fn frobnicate() {} | ||
29 | // } | ||
30 | // fn main() { | ||
31 | // m::frobnicate() {} | ||
32 | // } | ||
33 | // ``` | ||
34 | pub(crate) fn fix_visibility(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { | ||
35 | add_vis_to_referenced_module_def(acc, ctx) | ||
36 | .or_else(|| add_vis_to_referenced_record_field(acc, ctx)) | ||
37 | } | ||
38 | |||
39 | fn add_vis_to_referenced_module_def(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { | ||
40 | let path: ast::Path = ctx.find_node_at_offset()?; | ||
41 | let path_res = ctx.sema.resolve_path(&path)?; | ||
42 | let def = match path_res { | ||
43 | PathResolution::Def(def) => def, | ||
44 | _ => return None, | ||
45 | }; | ||
46 | |||
47 | let current_module = ctx.sema.scope(&path.syntax()).module()?; | ||
48 | let target_module = def.module(ctx.db)?; | ||
49 | |||
50 | let vis = target_module.visibility_of(ctx.db, &def)?; | ||
51 | if vis.is_visible_from(ctx.db, current_module.into()) { | ||
52 | return None; | ||
53 | }; | ||
54 | |||
55 | let (offset, target, target_file, target_name) = target_data_for_def(ctx.db, def)?; | ||
56 | |||
57 | let missing_visibility = | ||
58 | if current_module.krate() == target_module.krate() { "pub(crate)" } else { "pub" }; | ||
59 | |||
60 | let assist_label = match target_name { | ||
61 | None => format!("Change visibility to {}", missing_visibility), | ||
62 | Some(name) => format!("Change visibility of {} to {}", name, missing_visibility), | ||
63 | }; | ||
64 | |||
65 | acc.add(AssistId("fix_visibility"), assist_label, target, |edit| { | ||
66 | edit.set_file(target_file); | ||
67 | edit.insert(offset, format!("{} ", missing_visibility)); | ||
68 | edit.set_cursor(offset); | ||
69 | }) | ||
70 | } | ||
71 | |||
72 | fn add_vis_to_referenced_record_field(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { | ||
73 | let record_field: ast::RecordField = ctx.find_node_at_offset()?; | ||
74 | let (record_field_def, _) = ctx.sema.resolve_record_field(&record_field)?; | ||
75 | |||
76 | let current_module = ctx.sema.scope(record_field.syntax()).module()?; | ||
77 | let visibility = record_field_def.visibility(ctx.db); | ||
78 | if visibility.is_visible_from(ctx.db, current_module.into()) { | ||
79 | return None; | ||
80 | } | ||
81 | |||
82 | let parent = record_field_def.parent_def(ctx.db); | ||
83 | let parent_name = parent.name(ctx.db); | ||
84 | let target_module = parent.module(ctx.db); | ||
85 | |||
86 | let in_file_source = record_field_def.source(ctx.db); | ||
87 | let (offset, target) = match in_file_source.value { | ||
88 | hir::FieldSource::Named(it) => { | ||
89 | let s = it.syntax(); | ||
90 | (vis_offset(s), s.text_range()) | ||
91 | } | ||
92 | hir::FieldSource::Pos(it) => { | ||
93 | let s = it.syntax(); | ||
94 | (vis_offset(s), s.text_range()) | ||
95 | } | ||
96 | }; | ||
97 | |||
98 | let missing_visibility = | ||
99 | if current_module.krate() == target_module.krate() { "pub(crate)" } else { "pub" }; | ||
100 | let target_file = in_file_source.file_id.original_file(ctx.db); | ||
101 | |||
102 | let target_name = record_field_def.name(ctx.db); | ||
103 | let assist_label = | ||
104 | format!("Change visibility of {}.{} to {}", parent_name, target_name, missing_visibility); | ||
105 | |||
106 | acc.add(AssistId("fix_visibility"), assist_label, target, |edit| { | ||
107 | edit.set_file(target_file); | ||
108 | edit.insert(offset, format!("{} ", missing_visibility)); | ||
109 | edit.set_cursor(offset) | ||
110 | }) | ||
111 | } | ||
112 | |||
113 | fn target_data_for_def( | ||
114 | db: &dyn HirDatabase, | ||
115 | def: hir::ModuleDef, | ||
116 | ) -> Option<(TextSize, TextRange, FileId, Option<hir::Name>)> { | ||
117 | fn offset_target_and_file_id<S, Ast>( | ||
118 | db: &dyn HirDatabase, | ||
119 | x: S, | ||
120 | ) -> (TextSize, TextRange, FileId) | ||
121 | where | ||
122 | S: HasSource<Ast = Ast>, | ||
123 | Ast: AstNode, | ||
124 | { | ||
125 | let source = x.source(db); | ||
126 | let in_file_syntax = source.syntax(); | ||
127 | let file_id = in_file_syntax.file_id; | ||
128 | let syntax = in_file_syntax.value; | ||
129 | (vis_offset(syntax), syntax.text_range(), file_id.original_file(db.upcast())) | ||
130 | } | ||
131 | |||
132 | let target_name; | ||
133 | let (offset, target, target_file) = match def { | ||
134 | hir::ModuleDef::Function(f) => { | ||
135 | target_name = Some(f.name(db)); | ||
136 | offset_target_and_file_id(db, f) | ||
137 | } | ||
138 | hir::ModuleDef::Adt(adt) => { | ||
139 | target_name = Some(adt.name(db)); | ||
140 | match adt { | ||
141 | hir::Adt::Struct(s) => offset_target_and_file_id(db, s), | ||
142 | hir::Adt::Union(u) => offset_target_and_file_id(db, u), | ||
143 | hir::Adt::Enum(e) => offset_target_and_file_id(db, e), | ||
144 | } | ||
145 | } | ||
146 | hir::ModuleDef::Const(c) => { | ||
147 | target_name = c.name(db); | ||
148 | offset_target_and_file_id(db, c) | ||
149 | } | ||
150 | hir::ModuleDef::Static(s) => { | ||
151 | target_name = s.name(db); | ||
152 | offset_target_and_file_id(db, s) | ||
153 | } | ||
154 | hir::ModuleDef::Trait(t) => { | ||
155 | target_name = Some(t.name(db)); | ||
156 | offset_target_and_file_id(db, t) | ||
157 | } | ||
158 | hir::ModuleDef::TypeAlias(t) => { | ||
159 | target_name = Some(t.name(db)); | ||
160 | offset_target_and_file_id(db, t) | ||
161 | } | ||
162 | hir::ModuleDef::Module(m) => { | ||
163 | target_name = m.name(db); | ||
164 | let in_file_source = m.declaration_source(db)?; | ||
165 | let file_id = in_file_source.file_id.original_file(db.upcast()); | ||
166 | let syntax = in_file_source.value.syntax(); | ||
167 | (vis_offset(syntax), syntax.text_range(), file_id) | ||
168 | } | ||
169 | // Enum variants can't be private, we can't modify builtin types | ||
170 | hir::ModuleDef::EnumVariant(_) | hir::ModuleDef::BuiltinType(_) => return None, | ||
171 | }; | ||
172 | |||
173 | Some((offset, target, target_file, target_name)) | ||
174 | } | ||
175 | |||
176 | fn vis_offset(node: &SyntaxNode) -> TextSize { | ||
177 | node.children_with_tokens() | ||
178 | .skip_while(|it| match it.kind() { | ||
179 | WHITESPACE | COMMENT | ATTR => true, | ||
180 | _ => false, | ||
181 | }) | ||
182 | .next() | ||
183 | .map(|it| it.text_range().start()) | ||
184 | .unwrap_or_else(|| node.text_range().start()) | ||
185 | } | ||
186 | |||
187 | #[cfg(test)] | ||
188 | mod tests { | ||
189 | use crate::tests::{check_assist, check_assist_not_applicable}; | ||
190 | |||
191 | use super::*; | ||
192 | |||
193 | #[test] | ||
194 | fn fix_visibility_of_fn() { | ||
195 | check_assist( | ||
196 | fix_visibility, | ||
197 | r"mod foo { fn foo() {} } | ||
198 | fn main() { foo::foo<|>() } ", | ||
199 | r"mod foo { <|>pub(crate) fn foo() {} } | ||
200 | fn main() { foo::foo() } ", | ||
201 | ); | ||
202 | check_assist_not_applicable( | ||
203 | fix_visibility, | ||
204 | r"mod foo { pub fn foo() {} } | ||
205 | fn main() { foo::foo<|>() } ", | ||
206 | ) | ||
207 | } | ||
208 | |||
209 | #[test] | ||
210 | fn fix_visibility_of_adt_in_submodule() { | ||
211 | check_assist( | ||
212 | fix_visibility, | ||
213 | r"mod foo { struct Foo; } | ||
214 | fn main() { foo::Foo<|> } ", | ||
215 | r"mod foo { <|>pub(crate) struct Foo; } | ||
216 | fn main() { foo::Foo } ", | ||
217 | ); | ||
218 | check_assist_not_applicable( | ||
219 | fix_visibility, | ||
220 | r"mod foo { pub struct Foo; } | ||
221 | fn main() { foo::Foo<|> } ", | ||
222 | ); | ||
223 | check_assist( | ||
224 | fix_visibility, | ||
225 | r"mod foo { enum Foo; } | ||
226 | fn main() { foo::Foo<|> } ", | ||
227 | r"mod foo { <|>pub(crate) enum Foo; } | ||
228 | fn main() { foo::Foo } ", | ||
229 | ); | ||
230 | check_assist_not_applicable( | ||
231 | fix_visibility, | ||
232 | r"mod foo { pub enum Foo; } | ||
233 | fn main() { foo::Foo<|> } ", | ||
234 | ); | ||
235 | check_assist( | ||
236 | fix_visibility, | ||
237 | r"mod foo { union Foo; } | ||
238 | fn main() { foo::Foo<|> } ", | ||
239 | r"mod foo { <|>pub(crate) union Foo; } | ||
240 | fn main() { foo::Foo } ", | ||
241 | ); | ||
242 | check_assist_not_applicable( | ||
243 | fix_visibility, | ||
244 | r"mod foo { pub union Foo; } | ||
245 | fn main() { foo::Foo<|> } ", | ||
246 | ); | ||
247 | } | ||
248 | |||
249 | #[test] | ||
250 | fn fix_visibility_of_adt_in_other_file() { | ||
251 | check_assist( | ||
252 | fix_visibility, | ||
253 | r" | ||
254 | //- /main.rs | ||
255 | mod foo; | ||
256 | fn main() { foo::Foo<|> } | ||
257 | |||
258 | //- /foo.rs | ||
259 | struct Foo; | ||
260 | ", | ||
261 | r"<|>pub(crate) struct Foo; | ||
262 | |||
263 | ", | ||
264 | ); | ||
265 | } | ||
266 | |||
267 | #[test] | ||
268 | fn fix_visibility_of_struct_field() { | ||
269 | check_assist( | ||
270 | fix_visibility, | ||
271 | r"mod foo { pub struct Foo { bar: (), } } | ||
272 | fn main() { foo::Foo { <|>bar: () }; } ", | ||
273 | r"mod foo { pub struct Foo { <|>pub(crate) bar: (), } } | ||
274 | fn main() { foo::Foo { bar: () }; } ", | ||
275 | ); | ||
276 | check_assist( | ||
277 | fix_visibility, | ||
278 | r"//- /lib.rs | ||
279 | mod foo; | ||
280 | fn main() { foo::Foo { <|>bar: () }; } | ||
281 | //- /foo.rs | ||
282 | pub struct Foo { bar: () } | ||
283 | ", | ||
284 | r"pub struct Foo { <|>pub(crate) bar: () } | ||
285 | |||
286 | ", | ||
287 | ); | ||
288 | check_assist_not_applicable( | ||
289 | fix_visibility, | ||
290 | r"mod foo { pub struct Foo { pub bar: (), } } | ||
291 | fn main() { foo::Foo { <|>bar: () }; } ", | ||
292 | ); | ||
293 | check_assist_not_applicable( | ||
294 | fix_visibility, | ||
295 | r"//- /lib.rs | ||
296 | mod foo; | ||
297 | fn main() { foo::Foo { <|>bar: () }; } | ||
298 | //- /foo.rs | ||
299 | pub struct Foo { pub bar: () } | ||
300 | ", | ||
301 | ); | ||
302 | } | ||
303 | |||
304 | #[test] | ||
305 | fn fix_visibility_of_enum_variant_field() { | ||
306 | check_assist( | ||
307 | fix_visibility, | ||
308 | r"mod foo { pub enum Foo { Bar { bar: () } } } | ||
309 | fn main() { foo::Foo::Bar { <|>bar: () }; } ", | ||
310 | r"mod foo { pub enum Foo { Bar { <|>pub(crate) bar: () } } } | ||
311 | fn main() { foo::Foo::Bar { bar: () }; } ", | ||
312 | ); | ||
313 | check_assist( | ||
314 | fix_visibility, | ||
315 | r"//- /lib.rs | ||
316 | mod foo; | ||
317 | fn main() { foo::Foo::Bar { <|>bar: () }; } | ||
318 | //- /foo.rs | ||
319 | pub enum Foo { Bar { bar: () } } | ||
320 | ", | ||
321 | r"pub enum Foo { Bar { <|>pub(crate) bar: () } } | ||
322 | |||
323 | ", | ||
324 | ); | ||
325 | check_assist_not_applicable( | ||
326 | fix_visibility, | ||
327 | r"mod foo { pub struct Foo { pub bar: (), } } | ||
328 | fn main() { foo::Foo { <|>bar: () }; } ", | ||
329 | ); | ||
330 | check_assist_not_applicable( | ||
331 | fix_visibility, | ||
332 | r"//- /lib.rs | ||
333 | mod foo; | ||
334 | fn main() { foo::Foo { <|>bar: () }; } | ||
335 | //- /foo.rs | ||
336 | pub struct Foo { pub bar: () } | ||
337 | ", | ||
338 | ); | ||
339 | } | ||
340 | |||
341 | #[test] | ||
342 | #[ignore] | ||
343 | // FIXME reenable this test when `Semantics::resolve_record_field` works with union fields | ||
344 | fn fix_visibility_of_union_field() { | ||
345 | check_assist( | ||
346 | fix_visibility, | ||
347 | r"mod foo { pub union Foo { bar: (), } } | ||
348 | fn main() { foo::Foo { <|>bar: () }; } ", | ||
349 | r"mod foo { pub union Foo { <|>pub(crate) bar: (), } } | ||
350 | fn main() { foo::Foo { bar: () }; } ", | ||
351 | ); | ||
352 | check_assist( | ||
353 | fix_visibility, | ||
354 | r"//- /lib.rs | ||
355 | mod foo; | ||
356 | fn main() { foo::Foo { <|>bar: () }; } | ||
357 | //- /foo.rs | ||
358 | pub union Foo { bar: () } | ||
359 | ", | ||
360 | r"pub union Foo { <|>pub(crate) bar: () } | ||
361 | |||
362 | ", | ||
363 | ); | ||
364 | check_assist_not_applicable( | ||
365 | fix_visibility, | ||
366 | r"mod foo { pub union Foo { pub bar: (), } } | ||
367 | fn main() { foo::Foo { <|>bar: () }; } ", | ||
368 | ); | ||
369 | check_assist_not_applicable( | ||
370 | fix_visibility, | ||
371 | r"//- /lib.rs | ||
372 | mod foo; | ||
373 | fn main() { foo::Foo { <|>bar: () }; } | ||
374 | //- /foo.rs | ||
375 | pub union Foo { pub bar: () } | ||
376 | ", | ||
377 | ); | ||
378 | } | ||
379 | |||
380 | #[test] | ||
381 | fn fix_visibility_of_const() { | ||
382 | check_assist( | ||
383 | fix_visibility, | ||
384 | r"mod foo { const FOO: () = (); } | ||
385 | fn main() { foo::FOO<|> } ", | ||
386 | r"mod foo { <|>pub(crate) const FOO: () = (); } | ||
387 | fn main() { foo::FOO } ", | ||
388 | ); | ||
389 | check_assist_not_applicable( | ||
390 | fix_visibility, | ||
391 | r"mod foo { pub const FOO: () = (); } | ||
392 | fn main() { foo::FOO<|> } ", | ||
393 | ); | ||
394 | } | ||
395 | |||
396 | #[test] | ||
397 | fn fix_visibility_of_static() { | ||
398 | check_assist( | ||
399 | fix_visibility, | ||
400 | r"mod foo { static FOO: () = (); } | ||
401 | fn main() { foo::FOO<|> } ", | ||
402 | r"mod foo { <|>pub(crate) static FOO: () = (); } | ||
403 | fn main() { foo::FOO } ", | ||
404 | ); | ||
405 | check_assist_not_applicable( | ||
406 | fix_visibility, | ||
407 | r"mod foo { pub static FOO: () = (); } | ||
408 | fn main() { foo::FOO<|> } ", | ||
409 | ); | ||
410 | } | ||
411 | |||
412 | #[test] | ||
413 | fn fix_visibility_of_trait() { | ||
414 | check_assist( | ||
415 | fix_visibility, | ||
416 | r"mod foo { trait Foo { fn foo(&self) {} } } | ||
417 | fn main() { let x: &dyn foo::<|>Foo; } ", | ||
418 | r"mod foo { <|>pub(crate) trait Foo { fn foo(&self) {} } } | ||
419 | fn main() { let x: &dyn foo::Foo; } ", | ||
420 | ); | ||
421 | check_assist_not_applicable( | ||
422 | fix_visibility, | ||
423 | r"mod foo { pub trait Foo { fn foo(&self) {} } } | ||
424 | fn main() { let x: &dyn foo::Foo<|>; } ", | ||
425 | ); | ||
426 | } | ||
427 | |||
428 | #[test] | ||
429 | fn fix_visibility_of_type_alias() { | ||
430 | check_assist( | ||
431 | fix_visibility, | ||
432 | r"mod foo { type Foo = (); } | ||
433 | fn main() { let x: foo::Foo<|>; } ", | ||
434 | r"mod foo { <|>pub(crate) type Foo = (); } | ||
435 | fn main() { let x: foo::Foo; } ", | ||
436 | ); | ||
437 | check_assist_not_applicable( | ||
438 | fix_visibility, | ||
439 | r"mod foo { pub type Foo = (); } | ||
440 | fn main() { let x: foo::Foo<|>; } ", | ||
441 | ); | ||
442 | } | ||
443 | |||
444 | #[test] | ||
445 | fn fix_visibility_of_module() { | ||
446 | check_assist( | ||
447 | fix_visibility, | ||
448 | r"mod foo { mod bar { fn bar() {} } } | ||
449 | fn main() { foo::bar<|>::bar(); } ", | ||
450 | r"mod foo { <|>pub(crate) mod bar { fn bar() {} } } | ||
451 | fn main() { foo::bar::bar(); } ", | ||
452 | ); | ||
453 | |||
454 | check_assist( | ||
455 | fix_visibility, | ||
456 | r" | ||
457 | //- /main.rs | ||
458 | mod foo; | ||
459 | fn main() { foo::bar<|>::baz(); } | ||
460 | |||
461 | //- /foo.rs | ||
462 | mod bar { | ||
463 | pub fn baz() {} | ||
464 | } | ||
465 | ", | ||
466 | r"<|>pub(crate) mod bar { | ||
467 | pub fn baz() {} | ||
468 | } | ||
469 | |||
470 | ", | ||
471 | ); | ||
472 | |||
473 | check_assist_not_applicable( | ||
474 | fix_visibility, | ||
475 | r"mod foo { pub mod bar { pub fn bar() {} } } | ||
476 | fn main() { foo::bar<|>::bar(); } ", | ||
477 | ); | ||
478 | } | ||
479 | |||
480 | #[test] | ||
481 | fn fix_visibility_of_inline_module_in_other_file() { | ||
482 | check_assist( | ||
483 | fix_visibility, | ||
484 | r" | ||
485 | //- /main.rs | ||
486 | mod foo; | ||
487 | fn main() { foo::bar<|>::baz(); } | ||
488 | |||
489 | //- /foo.rs | ||
490 | mod bar; | ||
491 | |||
492 | //- /foo/bar.rs | ||
493 | pub fn baz() {} | ||
494 | } | ||
495 | ", | ||
496 | r"<|>pub(crate) mod bar; | ||
497 | ", | ||
498 | ); | ||
499 | } | ||
500 | |||
501 | #[test] | ||
502 | fn fix_visibility_of_module_declaration_in_other_file() { | ||
503 | check_assist( | ||
504 | fix_visibility, | ||
505 | r"//- /main.rs | ||
506 | mod foo; | ||
507 | fn main() { foo::bar<|>>::baz(); } | ||
508 | |||
509 | //- /foo.rs | ||
510 | mod bar { | ||
511 | pub fn baz() {} | ||
512 | }", | ||
513 | r"<|>pub(crate) mod bar { | ||
514 | pub fn baz() {} | ||
515 | } | ||
516 | ", | ||
517 | ); | ||
518 | } | ||
519 | |||
520 | #[test] | ||
521 | fn adds_pub_when_target_is_in_another_crate() { | ||
522 | check_assist( | ||
523 | fix_visibility, | ||
524 | r"//- /main.rs crate:a deps:foo | ||
525 | foo::Bar<|> | ||
526 | //- /lib.rs crate:foo | ||
527 | struct Bar;", | ||
528 | r"<|>pub struct Bar; | ||
529 | ", | ||
530 | ) | ||
531 | } | ||
532 | |||
533 | #[test] | ||
534 | #[ignore] | ||
535 | // FIXME handle reexports properly | ||
536 | fn fix_visibility_of_reexport() { | ||
537 | check_assist( | ||
538 | fix_visibility, | ||
539 | r" | ||
540 | mod foo { | ||
541 | use bar::Baz; | ||
542 | mod bar { pub(super) struct Baz; } | ||
543 | } | ||
544 | foo::Baz<|> | ||
545 | ", | ||
546 | r" | ||
547 | mod foo { | ||
548 | <|>pub(crate) use bar::Baz; | ||
549 | mod bar { pub(super) struct Baz; } | ||
550 | } | ||
551 | foo::Baz | ||
552 | ", | ||
553 | ) | ||
554 | } | ||
555 | } | ||