Fork of daniellemaywood.uk/gleam — Wasm codegen work
2

Configure Feed

Select the types of activity you want to include in your feed.

Implement renaming modules

author
Gears
committer
Louis Pilfold
date (Jun 12, 2026, 7:13 PM +0100) commit 47be6013 parent 6803de78 change-id mlmvwsrr
+1081 -364
+23
CHANGELOG.md
··· 88 88 89 89 ([Surya Rose](https://github.com/GearsDatapacks)) 90 90 91 + - The language server now automatically updates imports when a Gleam module is 92 + renamed. For example: 93 + 94 + ```gleam 95 + import db_users 96 + 97 + pub fn main() -> db_users.User { 98 + db_users.new("username") 99 + } 100 + ``` 101 + 102 + Renaming `db_users.gleam` to `database/user.gleam` would produce: 103 + 104 + ```gleam 105 + import database/user 106 + 107 + pub fn main() -> user.User { 108 + user.new("username") 109 + } 110 + ``` 111 + 112 + ([Surya Rose](https://github.com/GearsDatapacks)) 113 + 91 114 ### Formatter 92 115 93 116 ### Bug fixes
+1
compiler-core/src/analyse.rs
··· 409 409 .collect(), 410 410 value_references: env.references.value_references, 411 411 type_references: env.references.type_references, 412 + module_references: env.references.module_references, 412 413 }, 413 414 inline_functions: self.inline_functions, 414 415 },
+3
compiler-core/src/analyse/imports.rs
··· 298 298 import.module.clone(), 299 299 alias_location, 300 300 import.location, 301 + import.module_location, 301 302 ); 302 303 } else { 303 304 self.environment.references.register_module( 304 305 used_name.clone(), 305 306 import.module.clone(), 306 307 import.location, 308 + import.module_location, 309 + None, 307 310 ); 308 311 } 309 312
+34 -5
compiler-core/src/metadata/tests.rs
··· 15 15 build::Origin, 16 16 line_numbers::LineNumbers, 17 17 parse::LiteralFloatValue, 18 - reference::{Reference, ReferenceKind}, 18 + reference::{ModuleNameReference, Reference, ReferenceKind}, 19 19 type_::{ 20 20 self, Deprecation, ModuleInterface, Opaque, References, Type, TypeAliasConstructor, 21 21 TypeConstructor, TypeValueConstructor, TypeValueConstructorField, TypeVariantConstructors, ··· 1985 1985 vec![ 1986 1986 Reference { 1987 1987 location: SrcSpan::new(26, 35), 1988 - kind: ReferenceKind::Qualified, 1988 + kind: ReferenceKind::Qualified { 1989 + module_alias: "some_other_module".into(), 1990 + module_location: SrcSpan::new(26, 29), 1991 + }, 1989 1992 }, 1990 1993 Reference { 1991 1994 location: SrcSpan::new(152, 204), 1992 - kind: ReferenceKind::Qualified, 1995 + kind: ReferenceKind::Qualified { 1996 + module_alias: "some_other_module".into(), 1997 + module_location: SrcSpan::new(26, 29), 1998 + }, 1993 1999 }, 1994 2000 Reference { 1995 2001 location: SrcSpan::new(0, 8), 1996 - kind: ReferenceKind::Qualified, 2002 + kind: ReferenceKind::Qualified { 2003 + module_alias: "some_alias".into(), 2004 + module_location: SrcSpan::new(26, 29), 2005 + }, 1997 2006 }, 1998 2007 ], 1999 2008 ), ··· 2004 2013 vec![ 2005 2014 Reference { 2006 2015 location: SrcSpan::new(26, 35), 2007 - kind: ReferenceKind::Qualified, 2016 + kind: ReferenceKind::Qualified { 2017 + module_alias: "some_alias".into(), 2018 + module_location: SrcSpan::new(26, 29), 2019 + }, 2008 2020 }, 2009 2021 Reference { 2010 2022 location: SrcSpan::new(152, 204), ··· 2014 2026 location: SrcSpan::new(0, 8), 2015 2027 kind: ReferenceKind::Definition, 2016 2028 }, 2029 + ], 2030 + )] 2031 + .into(), 2032 + module_references: [( 2033 + "some_module".into(), 2034 + vec![ 2035 + ModuleNameReference::Import { 2036 + module_location: SrcSpan::new(5, 20), 2037 + import_end: 31, 2038 + }, 2039 + ModuleNameReference::AliasedImport { 2040 + module_location: SrcSpan::new(5, 20), 2041 + alias_location: SrcSpan::new(26, 32), 2042 + alias: "some_alias".into(), 2043 + }, 2044 + ModuleNameReference::ModuleSelect(SrcSpan::new(92, 100)), 2045 + ModuleNameReference::AliasedModuleSelect(SrcSpan::new(152, 160)), 2017 2046 ], 2018 2047 )] 2019 2048 .into(),
+1 -3
compiler-core/src/parse.rs
··· 3006 3006 let mut start = 0; 3007 3007 let mut end; 3008 3008 let mut module = EcoString::new(); 3009 - let mut last_segment_start; 3010 3009 let mut last_segment_end; 3011 3010 3012 3011 // Gather module names ··· 3019 3018 } 3020 3019 module.push_str(&name); 3021 3020 end = e; 3022 - last_segment_start = s; 3023 3021 last_segment_end = e; 3024 3022 3025 3023 // Useful error for : import a/.{b} ··· 3100 3098 end, 3101 3099 }, 3102 3100 module_location: SrcSpan { 3103 - start: last_segment_start, 3101 + start, 3104 3102 end: last_segment_end, 3105 3103 }, 3106 3104 unqualified_values,
+225 -11
compiler-core/src/reference.rs
··· 11 11 stable_graph::{NodeIndex, StableGraph}, 12 12 }; 13 13 14 - #[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)] 14 + /// Describes one of a number of situations where references can be generated. 15 + /// See each variant for an explanation. 16 + #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] 15 17 pub enum ReferenceKind { 16 - Qualified, 18 + /// A type or value which is referenced using the qualified syntax, along with 19 + /// some information about the qualifier which is used for tracking module name 20 + /// references. For example: 21 + /// ```gleam 22 + /// import gleam/option.{Some} 23 + /// 24 + /// pub fn main() -> option.Option(Int) { 25 + /// // ^^^^^^ `module_location` covers this 26 + /// Some(1) 27 + /// } 28 + /// ``` 29 + /// 30 + /// Here, `module_alias` is `option`, as that's what's being used as a 31 + /// qualifier. 32 + /// 33 + /// 34 + /// ```gleam 35 + /// import gleam/int as integer 36 + /// 37 + /// pub fn main() { 38 + /// integer.add(1, 2) 39 + /// //^^^^^^^ `module_location` covers this 40 + /// } 41 + /// ``` 42 + /// 43 + /// In this case, `module_alias` is `integer`, due to the aliased import. 44 + /// 45 + Qualified { 46 + module_alias: EcoString, 47 + module_location: SrcSpan, 48 + }, 49 + /// A type or value is being referenced using unqualified syntax. This may 50 + /// be due to being imported unqualified, or because it's from the same 51 + /// module. For example: 52 + /// 53 + /// ```gleam 54 + /// import gleam/option.{None} 55 + /// 56 + /// pub fn main() { 57 + /// none() 58 + /// //^^^^ Unqualified 59 + /// } 60 + /// 61 + /// fn none() { 62 + /// None 63 + /// //^^^^ Unqualified 64 + /// } 65 + /// ``` 66 + /// 17 67 Unqualified, 68 + /// A value or type is being referenced inside an unqualified import. For 69 + /// example: 70 + /// ```gleam 71 + /// import gleam/option.{None} 72 + /// // ^^^^ Import 73 + /// import gleam/dynamic/decode.{type Dynamic} 74 + /// // ^^^^^^^ Import 75 + /// ``` 18 76 Import, 77 + /// The original definition location of a type or value. This also counts as 78 + /// a reference for renaming and "find references" purposes. For example: 79 + /// 80 + /// ```gleam 81 + /// pub type Wibble { 82 + /// // ^^^^^^ Definition 83 + /// Wibble(Int) 84 + /// //^^^^^^ Definition 85 + /// } 86 + /// 87 + /// pub fn extract(w: Wibble) { 88 + /// // ^^^^^^^ Definition 89 + /// let Wibble(x) = w 90 + /// x 91 + /// } 92 + /// ``` 19 93 Definition, 94 + /// A value or type is being referenced using unqualified syntax, with a 95 + /// name other than its original definition. This can be due to importing it 96 + /// using an alias, or due to referencing it through a type alias. For example: 97 + /// 98 + /// ```gleam 99 + /// import gleam/option.{None as Nothing, type Option as Maybe} 100 + /// 101 + /// pub fn nothing() -> Maybe(_) { 102 + /// // ^^^^^ Alias 103 + /// Nothing 104 + /// //^^^^^^^ Alias 105 + /// } 106 + /// ``` 107 + /// 20 108 Alias, 21 109 } 22 110 23 - #[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)] 111 + #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] 24 112 pub struct Reference { 25 113 pub location: SrcSpan, 26 114 pub kind: ReferenceKind, 27 115 } 28 116 117 + /// A reference to a module name. This is similar to a `Reference`, which covers 118 + /// types and values, but it is separate because we care about slightly different 119 + /// pieces of information when, for example, renaming modules vs. renaming types 120 + /// or values. 121 + /// 122 + #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] 123 + pub enum ModuleNameReference { 124 + /// The location of a module name in a `ModuleSelect`, when the module name 125 + /// is not aliased. For example: 126 + /// ```gleam 127 + /// import gleam/option 128 + /// 129 + /// pub fn main() -> option.Option(_) { 130 + /// // ^^^^^^ ModuleSelect 131 + /// option.None 132 + /// //^^^^^^ ModuleSelect 133 + /// } 134 + /// ``` 135 + /// 136 + ModuleSelect(SrcSpan), 137 + /// The location of a module name in a `ModuleSelect`, when the module name 138 + /// *is* aliased. For example: 139 + /// ```gleam 140 + /// import gleam/option as maybe 141 + /// 142 + /// pub fn main() -> maybe.Option(_) { 143 + /// // ^^^^^ AliasedModuleSelect 144 + /// maybe.None 145 + /// //^^^^^ AliasedModuleSelect 146 + /// } 147 + /// ``` 148 + /// 149 + AliasedModuleSelect(SrcSpan), 150 + /// The location of a module name in an `import` statement, when the module 151 + /// name is not aliased. For example: 152 + /// ```gleam 153 + /// import gleam/option.{None, Some} 154 + /// // ^^^^^^^^^^^^ Import ^ `import_end` 155 + /// ``` 156 + /// 157 + Import { 158 + module_location: SrcSpan, 159 + import_end: u32, 160 + }, 161 + /// The location of a module name in an `import` statement, when the module 162 + /// name *is* aliased. Also stores the location of the alias (including the 163 + /// `as` keyword), and what the alias is. For example: 164 + /// ```gleam 165 + /// import gleam/option.{None, Some} as maybe 166 + /// // ^^^^^^^^^^^^ `module_location` 167 + /// // ^^^^^^^^ `alias_location` 168 + /// ``` 169 + /// In this example, `alias` would be `maybe`. 170 + /// 171 + AliasedImport { 172 + module_location: SrcSpan, 173 + alias_location: SrcSpan, 174 + alias: EcoString, 175 + }, 176 + } 177 + 29 178 pub type ReferenceMap = HashMap<(EcoString, EcoString), Vec<Reference>>; 30 179 31 180 #[derive(Debug, Clone)] ··· 96 245 /// The locations of the references to each type in this module, used for 97 246 /// renaming and go-to reference. 98 247 pub type_references: ReferenceMap, 248 + /// The locations of the references to each imported module, used for 249 + /// renaming and go-to reference. 250 + pub module_references: HashMap<EcoString, Vec<ModuleNameReference>>, 99 251 100 252 /// This map is used to access the nodes of modules that were not 101 253 /// aliased, given their name. ··· 280 432 module_name: EcoString, 281 433 alias_location: SrcSpan, 282 434 import_location: SrcSpan, 435 + module_location: SrcSpan, 283 436 ) { 284 - // We first record a node for the module being aliased. We use its entire 285 - // name to identify it in this case and keep track of the node it's 286 - // associated with. 287 - self.register_module(module_name.clone(), module_name.clone(), import_location); 437 + // We first record a node for the module being aliased. 438 + self.register_module( 439 + used_name.clone(), 440 + module_name.clone(), 441 + import_location, 442 + module_location, 443 + Some(alias_location), 444 + ); 288 445 289 446 // Then we create a node for the alias, as the alias itself might be 290 447 // unused! ··· 315 472 used_name: EcoString, 316 473 module_name: EcoString, 317 474 location: SrcSpan, 475 + module_location: SrcSpan, 476 + alias_location: Option<SrcSpan>, 318 477 ) { 319 478 self.current_node = self.create_node(used_name.clone(), EntityLayer::Module); 320 479 let _ = self 321 480 .module_name_to_node 322 481 .insert(module_name.clone(), self.current_node); 323 482 483 + let reference = if let Some(alias_location) = alias_location { 484 + ModuleNameReference::AliasedImport { 485 + module_location, 486 + alias_location, 487 + alias: used_name.clone(), 488 + } 489 + } else { 490 + ModuleNameReference::Import { 491 + module_location, 492 + import_end: location.end, 493 + } 494 + }; 495 + 496 + self.register_module_name_reference(module_name.clone(), reference); 497 + 324 498 let entity = Entity { 325 499 name: used_name, 326 500 layer: EntityLayer::Module, ··· 360 534 location: SrcSpan, 361 535 kind: ReferenceKind, 362 536 ) { 363 - match kind { 364 - ReferenceKind::Qualified | ReferenceKind::Import | ReferenceKind::Definition => {} 537 + match &kind { 538 + ReferenceKind::Qualified { 539 + module_alias, 540 + module_location, 541 + } => { 542 + let last_module_segment = module.split('/').next_back().unwrap_or(&module); 543 + let reference = if last_module_segment == module_alias { 544 + ModuleNameReference::ModuleSelect(*module_location) 545 + } else { 546 + ModuleNameReference::AliasedModuleSelect(*module_location) 547 + }; 548 + self.register_module_name_reference(module.clone(), reference); 549 + } 550 + ReferenceKind::Import | ReferenceKind::Definition => {} 365 551 ReferenceKind::Alias | ReferenceKind::Unqualified => { 366 552 let target = self.get_or_create_node(referenced_name.clone(), EntityLayer::Value); 367 553 _ = self.graph.add_edge(self.current_node, target, ()); ··· 382 568 location: SrcSpan, 383 569 kind: ReferenceKind, 384 570 ) { 385 - match kind { 386 - ReferenceKind::Qualified | ReferenceKind::Import | ReferenceKind::Definition => {} 571 + match &kind { 572 + ReferenceKind::Qualified { 573 + module_alias, 574 + module_location, 575 + } => { 576 + let last_module_segment = module.split('/').next_back().unwrap_or(&module); 577 + let reference = if last_module_segment == module_alias { 578 + ModuleNameReference::ModuleSelect(*module_location) 579 + } else { 580 + ModuleNameReference::AliasedModuleSelect(*module_location) 581 + }; 582 + self.register_module_name_reference(module.clone(), reference); 583 + } 584 + ReferenceKind::Import | ReferenceKind::Definition => {} 387 585 ReferenceKind::Alias | ReferenceKind::Unqualified => { 388 586 self.register_type_reference_in_call_graph(referenced_name.clone()) 389 587 } ··· 393 591 .entry((module, name)) 394 592 .or_default() 395 593 .push(Reference { location, kind }); 594 + } 595 + 596 + /// Register a reference to a module in the code. This is separate to 597 + /// `register_module_reference`, as references to modules can be created 598 + /// implicitly, for example when using unqualified imports. This only register 599 + /// explicit references in the source code, when the module name or local 600 + /// alias is written. 601 + pub fn register_module_name_reference( 602 + &mut self, 603 + module: EcoString, 604 + reference: ModuleNameReference, 605 + ) { 606 + self.module_references 607 + .entry(module) 608 + .or_default() 609 + .push(reference); 396 610 } 397 611 398 612 /// Like `register_type_reference`, but doesn't modify `self.type_references`.
+2 -1
compiler-core/src/type_.rs
··· 35 35 build::{Origin, Target}, 36 36 inline::InlinableFunction, 37 37 line_numbers::LineNumbers, 38 - reference::ReferenceMap, 38 + reference::{ModuleNameReference, ReferenceMap}, 39 39 type_::expression::Implementations, 40 40 }; 41 41 use error::*; ··· 1056 1056 pub imported_modules: HashSet<EcoString>, 1057 1057 pub value_references: ReferenceMap, 1058 1058 pub type_references: ReferenceMap, 1059 + pub module_references: HashMap<EcoString, Vec<ModuleNameReference>>, 1059 1060 } 1060 1061 1061 1062 #[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
+21 -14
compiler-core/src/type_/expression.rs
··· 1455 1455 // We only register the reference here, if we know that this is a module access. 1456 1456 // Otherwise we would register module access even if we are actually accessing 1457 1457 // the field on a record 1458 + let module_location = 1459 + SrcSpan::new(location.start, location.start + module_alias.len() as u32); 1458 1460 self.environment.references.register_value_reference( 1459 1461 module_name.clone(), 1460 1462 label.clone(), 1461 1463 &label, 1462 1464 SrcSpan::new(field_start, location.end), 1463 - ReferenceKind::Qualified, 1465 + ReferenceKind::Qualified { 1466 + module_alias: module_alias.clone(), 1467 + module_location, 1468 + }, 1464 1469 ); 1465 1470 TypedExpr::ModuleSelect { 1466 1471 location, ··· 2577 2582 match self.infer_clause_guard_variable(name.clone(), location) { 2578 2583 // If the variable itself cannot be inferred as one, then 2579 2584 // it could really be a module select. We try that one 2580 - // as an elternative. 2585 + // as an alternative. 2581 2586 Err(error) => self.infer_guard_module_access( 2582 2587 name, 2583 2588 label, ··· 2594 2599 } 2595 2600 } else { 2596 2601 // If it doesn't this has to be a regular record access and 2597 - // we try and inferr it as such. 2602 + // we try and infer it as such. 2598 2603 let inferred_container = self.infer_clause_guard(*container.clone()); 2599 2604 self.infer_guard_record_access( 2600 2605 inferred_container, ··· 2854 2859 label.clone(), 2855 2860 &label, 2856 2861 label_location, 2857 - ReferenceKind::Qualified, 2862 + ReferenceKind::Qualified { 2863 + module_alias: module_alias.clone(), 2864 + module_location, 2865 + }, 2858 2866 ); 2859 2867 2860 2868 Ok(ClauseGuard::ModuleSelect { ··· 3717 3725 ReferenceRegistration::DoNotRegister => (), 3718 3726 3719 3727 ReferenceRegistration::Register | ReferenceRegistration::VariableArgument { .. } => { 3720 - self.register_value_constructor_reference( 3721 - name, 3722 - &variant, 3723 - *location, 3724 - if module.is_some() { 3725 - ReferenceKind::Qualified 3726 - } else { 3727 - ReferenceKind::Unqualified 3728 - }, 3729 - ); 3728 + let kind = if let Some((module_alias, module_location)) = module { 3729 + ReferenceKind::Qualified { 3730 + module_alias: module_alias.clone(), 3731 + module_location: *module_location, 3732 + } 3733 + } else { 3734 + ReferenceKind::Unqualified 3735 + }; 3736 + self.register_value_constructor_reference(name, &variant, *location, kind); 3730 3737 } 3731 3738 } 3732 3739
+5 -2
compiler-core/src/type_/hydrator.rs
··· 179 179 .clone(); 180 180 181 181 if let Some((type_module, type_name)) = return_type.named_type_name() { 182 - let reference_kind = if module.is_some() { 183 - ReferenceKind::Qualified 182 + let reference_kind = if let Some((module_alias, module_location)) = &module { 183 + ReferenceKind::Qualified { 184 + module_alias: module_alias.clone(), 185 + module_location: *module_location, 186 + } 184 187 } else if name != &type_name { 185 188 ReferenceKind::Alias 186 189 } else {
+9 -5
compiler-core/src/type_/pattern.rs
··· 1188 1188 } 1189 1189 } 1190 1190 1191 + let kind = if let Some((module_alias, module_location)) = &module { 1192 + ReferenceKind::Qualified { 1193 + module_alias: module_alias.clone(), 1194 + module_location: *module_location, 1195 + } 1196 + } else { 1197 + ReferenceKind::Unqualified 1198 + }; 1191 1199 self.environment.references.register_value_reference( 1192 1200 pattern_constructor.module.clone(), 1193 1201 pattern_constructor.name.clone(), 1194 1202 &name, 1195 1203 name_location, 1196 - if module.is_some() { 1197 - ReferenceKind::Qualified 1198 - } else { 1199 - ReferenceKind::Unqualified 1200 - }, 1204 + kind, 1201 1205 ); 1202 1206 1203 1207 let instantiated_constructor_type =
+61 -11
language-server/src/engine.rs
··· 33 33 MarkupContent, Position, PrepareRenameResult, Range, SignatureHelp, SymbolKind, SymbolTag, 34 34 TextEdit, Uri as Url, WorkspaceEdit, 35 35 }; 36 - use std::{collections::HashSet, sync::Arc}; 36 + use std::{ 37 + collections::{HashMap, HashSet}, 38 + sync::Arc, 39 + }; 37 40 38 41 use crate::{ 39 42 code_action::{RemoveRedundantRecordUpdate, ReplaceUnderscoreWithType, type_errors_for_module}, 40 43 reference::find_module_references_in_module, 41 - rename::{rename_module_alias, rename_type_variable}, 44 + rename::{rename_module_alias, rename_module_occurrences, rename_type_variable}, 42 45 }; 43 46 44 47 use super::{ ··· 838 841 None 839 842 } 840 843 } 841 - Some(Referenced::ModuleName { location, .. }) => success_response(location), 844 + Some(Referenced::ModuleName { 845 + location, 846 + module_alias, 847 + .. 848 + }) => success_response(SrcSpan::new( 849 + // Since the location contains the full module name (e.g. `wibble/wobble/woo`), 850 + // we just want to include the last segment so we get a rename of the string 851 + // `woo`, as that's the being referenced in module access expressions. 852 + location.end - module_alias.len() as u32, 853 + location.end, 854 + )), 842 855 843 856 Some(Referenced::TypeVariable { location, name: _ }) => success_response(location), 844 857 ··· 935 948 ) 936 949 .into_result(), 937 950 938 - Some(Referenced::ModuleName { 939 - module_name, 940 - module_alias, 941 - .. 942 - }) => rename_module_alias(module, &lines, &params, &module_name, &module_alias) 943 - .into_result(), 951 + Some(Referenced::ModuleName { module_name, .. }) => { 952 + rename_module_alias(module, &lines, &params, &module_name).into_result() 953 + } 944 954 945 955 Some(Referenced::TypeVariable { location, name }) => { 946 956 rename_type_variable(module, &lines, &params, location, name).into_result() ··· 1085 1095 }) 1086 1096 } 1087 1097 1098 + /// Triggers after the renaming of one or more `.gleam` files, updating any 1099 + /// imports to those modules. 1100 + pub fn rename_files(&mut self, renames: Vec<(Url, Url)>) -> Response<Option<WorkspaceEdit>> { 1101 + self.respond(|this| { 1102 + let mut changes = HashMap::new(); 1103 + 1104 + for (old_uri, new_uri) in renames { 1105 + let Some(old_name) = this.module_name_for_uri(&old_uri) else { 1106 + continue; 1107 + }; 1108 + let Some(new_name) = this.module_name_for_uri(&new_uri) else { 1109 + continue; 1110 + }; 1111 + rename_module_occurrences( 1112 + old_name, 1113 + new_name, 1114 + this.compiler.project_compiler.get_importable_modules(), 1115 + &this.compiler.sources, 1116 + &mut changes, 1117 + ); 1118 + } 1119 + 1120 + Ok(Some(WorkspaceEdit { 1121 + changes: Some(changes), 1122 + document_changes: None, 1123 + change_annotations: None, 1124 + })) 1125 + }) 1126 + } 1127 + 1088 1128 fn respond<T>(&mut self, handler: impl FnOnce(&mut Self) -> Result<T>) -> Response<T> { 1089 1129 let result = handler(self); 1090 1130 let warnings = self.take_warnings(); ··· 1308 1348 self.module_node_at_position(params, module) 1309 1349 } 1310 1350 1311 - fn module_for_uri(&self, uri: &Url) -> Option<&Module> { 1351 + fn module_name_for_uri(&self, uri: &Url) -> Option<EcoString> { 1312 1352 // The to_file_path method is available on these platforms 1313 1353 #[cfg(any(unix, windows, target_os = "redox", target_os = "wasi"))] 1314 1354 let path = uri.to_file_path().expect("URL file"); ··· 1322 1362 .components() 1323 1363 .skip(1) 1324 1364 .map(|c| c.as_os_str().to_string_lossy()); 1325 - let module_name: EcoString = Itertools::intersperse(components, "/".into()) 1365 + let name = Itertools::intersperse(components, "/".into()) 1326 1366 .collect::<String>() 1327 1367 .strip_suffix(".gleam")? 1328 1368 .into(); 1369 + Some(name) 1370 + } 1329 1371 1372 + fn module_for_uri(&self, uri: &Url) -> Option<&Module> { 1373 + let module_name = self.module_name_for_uri(uri)?; 1330 1374 self.compiler.modules.get(&module_name) 1375 + } 1376 + 1377 + #[cfg(test)] 1378 + pub fn path_for_module_name(&self, module_name: &str) -> Utf8PathBuf { 1379 + let src_directory = self.paths.src_directory(); 1380 + src_directory.join(module_name).with_extension("gleam") 1331 1381 } 1332 1382 } 1333 1383
+6 -1
language-server/src/messages.rs
··· 8 8 DidCloseTextDocumentNotification, DidSaveTextDocumentNotification, DocumentFormattingRequest, 9 9 DocumentHighlightRequest, DocumentSymbolRequest, FoldingRangeRequest, HoverRequest, 10 10 PrepareRenameRequest, ReferencesRequest, RenameRequest, SignatureHelpRequest, 11 - TextDocumentContentChangeEvent, TypeDefinitionRequest, 11 + TextDocumentContentChangeEvent, TypeDefinitionRequest, WillRenameFilesRequest, 12 12 }; 13 13 use std::time::Duration; 14 14 ··· 33 33 Rename(lsp::RenameParams), 34 34 FindReferences(lsp::ReferenceParams), 35 35 DocumentHighlight(lsp::DocumentHighlightParams), 36 + RenameFiles(lsp::RenameFilesParams), 36 37 } 37 38 38 39 impl Request { ··· 90 91 "textDocument/documentHighlight" => { 91 92 let params = cast_request::<DocumentHighlightRequest>(request); 92 93 Some(Message::Request(id, Request::DocumentHighlight(params))) 94 + } 95 + "workspace/willRenameFiles" => { 96 + let params = cast_request::<WillRenameFilesRequest>(request); 97 + Some(Message::Request(id, Request::RenameFiles(params))) 93 98 } 94 99 _ => None, 95 100 }
+1 -256
language-server/src/reference.rs
··· 10 10 analyse, 11 11 ast::{ 12 12 self, ArgNames, AssignName, BitArraySize, ClauseGuard, CustomType, Function, 13 - ModuleConstant, Pattern, RecordConstructor, SrcSpan, TypeAstConstructorName, TypedExpr, 14 - TypedModule, visit::Visit, 13 + ModuleConstant, Pattern, RecordConstructor, SrcSpan, TypedExpr, TypedModule, visit::Visit, 15 14 }, 16 15 build::Located, 17 16 type_::{ ··· 830 829 right_location, 831 830 left_side_string, 832 831 right_side_assignment, 833 - ); 834 - } 835 - } 836 - 837 - pub struct ModuleNameReference { 838 - pub location: SrcSpan, 839 - pub kind: ModuleNameReferenceKind, 840 - } 841 - 842 - pub enum ModuleNameReferenceKind { 843 - Import, 844 - AliasedImport, 845 - ModuleSelect, 846 - } 847 - 848 - pub struct FindModuleNameReferences<'a> { 849 - pub references: Vec<ModuleNameReference>, 850 - pub module_name: &'a EcoString, 851 - pub module_alias: &'a EcoString, 852 - } 853 - 854 - impl<'ast> Visit<'ast> for FindModuleNameReferences<'_> { 855 - fn visit_typed_module(&mut self, module: &'ast TypedModule) { 856 - ast::visit::visit_typed_module(self, module); 857 - } 858 - 859 - fn visit_typed_clause_guard(&mut self, guard: &'ast ast::TypedClauseGuard) { 860 - ast::visit::visit_typed_clause_guard(self, guard); 861 - } 862 - 863 - fn visit_typed_import(&mut self, import: &'ast ast::TypedImport) { 864 - match import.as_name.as_ref() { 865 - None => { 866 - if import.module == *self.module_name { 867 - self.references.push(ModuleNameReference { 868 - location: import.location, 869 - kind: ModuleNameReferenceKind::Import, 870 - }) 871 - } 872 - } 873 - Some((AssignName::Variable(alias) | AssignName::Discard(alias), alias_location)) => { 874 - if alias == self.module_alias { 875 - self.references.push(ModuleNameReference { 876 - location: *alias_location, 877 - kind: ModuleNameReferenceKind::AliasedImport, 878 - }) 879 - } 880 - } 881 - } 882 - 883 - ast::visit::visit_typed_import(self, import); 884 - } 885 - 886 - fn visit_typed_clause_guard_module_select( 887 - &mut self, 888 - location: &'ast SrcSpan, 889 - field_start: &'ast u32, 890 - definition_location: &'ast SrcSpan, 891 - type_: &'ast std::sync::Arc<Type>, 892 - label: &'ast EcoString, 893 - module_name: &'ast EcoString, 894 - module_alias: &'ast EcoString, 895 - literal: &'ast ast::TypedConstant, 896 - ) { 897 - if module_alias == self.module_alias { 898 - self.references.push(ModuleNameReference { 899 - location: SrcSpan::new( 900 - location.start, 901 - location.start + (module_alias.len() as u32), 902 - ), 903 - kind: ModuleNameReferenceKind::ModuleSelect, 904 - }); 905 - } 906 - 907 - ast::visit::visit_typed_clause_guard_module_select( 908 - self, 909 - location, 910 - field_start, 911 - definition_location, 912 - type_, 913 - label, 914 - module_name, 915 - module_alias, 916 - literal, 917 - ); 918 - } 919 - 920 - fn visit_typed_expr_module_select( 921 - &mut self, 922 - location: &'ast SrcSpan, 923 - field_start: &'ast u32, 924 - type_: &'ast std::sync::Arc<Type>, 925 - label: &'ast EcoString, 926 - module_name: &'ast EcoString, 927 - module_alias: &'ast EcoString, 928 - constructor: &'ast ModuleValueConstructor, 929 - ) { 930 - if module_alias == self.module_alias { 931 - self.references.push(ModuleNameReference { 932 - location: SrcSpan::new( 933 - location.start, 934 - location.start + (module_alias.len() as u32), 935 - ), 936 - kind: ModuleNameReferenceKind::ModuleSelect, 937 - }); 938 - } 939 - 940 - ast::visit::visit_typed_expr_module_select( 941 - self, 942 - location, 943 - field_start, 944 - type_, 945 - label, 946 - module_name, 947 - module_alias, 948 - constructor, 949 - ); 950 - } 951 - 952 - fn visit_type_ast_constructor( 953 - &mut self, 954 - location: &'ast SrcSpan, 955 - name: &'ast TypeAstConstructorName, 956 - arguments: &'ast [ast::TypeAst], 957 - arguments_types: Option<Vec<std::sync::Arc<Type>>>, 958 - ) { 959 - if let TypeAstConstructorName::Qualified { 960 - module: module_alias, 961 - module_location, 962 - .. 963 - } = name 964 - && module_alias == self.module_alias 965 - { 966 - self.references.push(ModuleNameReference { 967 - location: *module_location, 968 - kind: ModuleNameReferenceKind::ModuleSelect, 969 - }) 970 - } 971 - 972 - ast::visit::visit_type_ast_constructor(self, location, name, arguments, arguments_types); 973 - } 974 - 975 - fn visit_typed_constant_record( 976 - &mut self, 977 - location: &'ast SrcSpan, 978 - module: &'ast Option<(EcoString, SrcSpan)>, 979 - name: &'ast EcoString, 980 - arguments: &'ast Option<Vec<ast::CallArg<ast::TypedConstant>>>, 981 - type_: &'ast std::sync::Arc<Type>, 982 - field_map: &'ast analyse::Inferred<gleam_core::type_::FieldMap>, 983 - record_constructor: &'ast Option<Box<ValueConstructor>>, 984 - ) { 985 - if let Some((module_alias, module_location)) = module 986 - && module_alias == self.module_alias 987 - { 988 - self.references.push(ModuleNameReference { 989 - location: *module_location, 990 - kind: ModuleNameReferenceKind::ModuleSelect, 991 - }) 992 - } 993 - 994 - ast::visit::visit_typed_constant_record( 995 - self, 996 - location, 997 - module, 998 - name, 999 - arguments, 1000 - type_, 1001 - field_map, 1002 - record_constructor, 1003 - ); 1004 - } 1005 - 1006 - fn visit_typed_constant_record_update( 1007 - &mut self, 1008 - location: &'ast SrcSpan, 1009 - constructor_location: &'ast SrcSpan, 1010 - module: &'ast Option<(EcoString, SrcSpan)>, 1011 - name: &'ast EcoString, 1012 - record: &'ast ast::RecordBeingUpdated<ast::TypedConstant>, 1013 - arguments: &'ast [ast::RecordUpdateArg<ast::TypedConstant>], 1014 - type_: &'ast std::sync::Arc<Type>, 1015 - field_map: &'ast analyse::Inferred<gleam_core::type_::FieldMap>, 1016 - ) { 1017 - if let Some((module_alias, module_location)) = module 1018 - && module_alias == self.module_alias 1019 - { 1020 - self.references.push(ModuleNameReference { 1021 - location: *module_location, 1022 - kind: ModuleNameReferenceKind::ModuleSelect, 1023 - }) 1024 - } 1025 - 1026 - ast::visit::visit_typed_constant_record_update( 1027 - self, 1028 - location, 1029 - constructor_location, 1030 - module, 1031 - name, 1032 - record, 1033 - arguments, 1034 - type_, 1035 - field_map, 1036 - ) 1037 - } 1038 - 1039 - fn visit_typed_constant_var( 1040 - &mut self, 1041 - _location: &'ast SrcSpan, 1042 - module: &'ast Option<(EcoString, SrcSpan)>, 1043 - _name: &'ast EcoString, 1044 - _constructor: &'ast Option<Box<ValueConstructor>>, 1045 - _type_: &'ast std::sync::Arc<Type>, 1046 - ) { 1047 - if let Some((module_alias, module_location)) = module 1048 - && module_alias == self.module_alias 1049 - { 1050 - self.references.push(ModuleNameReference { 1051 - location: *module_location, 1052 - kind: ModuleNameReferenceKind::ModuleSelect, 1053 - }) 1054 - } 1055 - } 1056 - 1057 - fn visit_typed_pattern_constructor( 1058 - &mut self, 1059 - location: &'ast SrcSpan, 1060 - name_location: &'ast SrcSpan, 1061 - name: &'ast EcoString, 1062 - arguments: &'ast Vec<ast::CallArg<ast::TypedPattern>>, 1063 - module: &'ast Option<(EcoString, SrcSpan)>, 1064 - constructor: &'ast analyse::Inferred<gleam_core::type_::PatternConstructor>, 1065 - spread: &'ast Option<SrcSpan>, 1066 - type_: &'ast std::sync::Arc<Type>, 1067 - ) { 1068 - if let Some((module_alias, module_location)) = module 1069 - && module_alias == self.module_alias 1070 - { 1071 - self.references.push(ModuleNameReference { 1072 - location: *module_location, 1073 - kind: ModuleNameReferenceKind::ModuleSelect, 1074 - }); 1075 - } 1076 - 1077 - ast::visit::visit_typed_pattern_constructor( 1078 - self, 1079 - location, 1080 - name_location, 1081 - name, 1082 - arguments, 1083 - module, 1084 - constructor, 1085 - spread, 1086 - type_, 1087 832 ); 1088 833 } 1089 834 }
+106 -26
language-server/src/rename.rs
··· 9 9 10 10 use gleam_core::{ 11 11 analyse::name, 12 - ast::{self, SrcSpan, visit::Visit}, 12 + ast::{self, SrcSpan}, 13 13 build::Module, 14 14 line_numbers::LineNumbers, 15 - reference::ReferenceKind, 15 + reference::{ModuleNameReference, ReferenceKind}, 16 16 type_::{ModuleInterface, error::Named}, 17 17 }; 18 18 19 - use crate::reference::{self, FindTypeVariableReferences, ModuleNameReferenceKind}; 19 + use crate::reference::FindTypeVariableReferences; 20 20 21 21 use super::{ 22 22 TextEdits, ··· 224 224 match reference.kind { 225 225 // If the reference is an alias, the alias name will remain unchanged. 226 226 ReferenceKind::Alias => {} 227 - ReferenceKind::Qualified 227 + ReferenceKind::Qualified { .. } 228 228 | ReferenceKind::Unqualified 229 229 | ReferenceKind::Import 230 230 | ReferenceKind::Definition => edits.replace(reference.location, new_name.clone()), ··· 261 261 262 262 for reference in references { 263 263 match reference.kind { 264 - ReferenceKind::Qualified => {} 264 + ReferenceKind::Qualified { .. } => {} 265 265 ReferenceKind::Unqualified | ReferenceKind::Alias => { 266 266 edits.replace(reference.location, params.new_name.clone()) 267 267 } ··· 346 346 line_numbers: &LineNumbers, 347 347 params: &RenameParams, 348 348 module_name: &EcoString, 349 - module_alias: &EcoString, 350 349 ) -> RenameOutcome { 351 350 let new_name = EcoString::from(&params.new_name); 352 351 if name::check_name_case(SrcSpan::default(), &new_name, Named::Variable).is_err() { ··· 360 359 .clone(); 361 360 let mut edits = TextEdits::new(line_numbers); 362 361 363 - let mut finder = reference::FindModuleNameReferences { 364 - references: Vec::new(), 365 - module_name, 366 - module_alias, 367 - }; 368 - finder.visit_typed_module(&module.ast); 369 - 370 362 let original_module_name = module_name.split('/').next_back().unwrap_or(""); 371 363 372 - for reference in finder.references { 373 - match reference.kind { 374 - ModuleNameReferenceKind::Import => { 375 - edits.insert(reference.location.end, format!(" as {}", &params.new_name)) 376 - } 377 - ModuleNameReferenceKind::AliasedImport => { 364 + let Some(references) = module 365 + .ast 366 + .type_info 367 + .references 368 + .module_references 369 + .get(module_name) 370 + else { 371 + return RenameOutcome::Renamed { 372 + edit: workspace_edit(uri, edits.edits), 373 + }; 374 + }; 375 + 376 + for reference in references { 377 + match reference { 378 + ModuleNameReference::Import { 379 + module_location: _, 380 + import_end, 381 + } => edits.insert(*import_end, format!(" as {}", &params.new_name)), 382 + ModuleNameReference::AliasedImport { 383 + alias_location, 384 + module_location: _, 385 + alias: _, 386 + } => { 378 387 if params.new_name == original_module_name { 379 - edits.delete(SrcSpan::new( 380 - reference.location.start - 1, 381 - reference.location.end, 382 - )); 388 + edits.delete(SrcSpan::new(alias_location.start - 1, alias_location.end)); 383 389 } else { 384 - edits.replace(reference.location, format!("as {}", &params.new_name)) 390 + edits.replace(*alias_location, format!("as {}", &params.new_name)) 385 391 } 386 392 } 387 - ModuleNameReferenceKind::ModuleSelect => { 388 - edits.replace(reference.location, params.new_name.to_string()) 393 + ModuleNameReference::ModuleSelect(location) 394 + | ModuleNameReference::AliasedModuleSelect(location) => { 395 + edits.replace(*location, params.new_name.to_string()); 389 396 } 390 397 } 391 398 } ··· 424 431 edit: workspace_edit(uri, edits.edits), 425 432 } 426 433 } 434 + 435 + pub fn rename_module_occurrences( 436 + old_name: EcoString, 437 + new_name: EcoString, 438 + modules: &im::HashMap<EcoString, ModuleInterface>, 439 + sources: &HashMap<EcoString, ModuleSourceInformation>, 440 + changes: &mut HashMap<Url, Vec<TextEdit>>, 441 + ) { 442 + let name_parts = new_name.split('/'); 443 + for part in name_parts { 444 + if name::check_name_case(SrcSpan::default(), &part.into(), Named::Variable).is_err() { 445 + return; 446 + } 447 + } 448 + 449 + let last_component_of_new_name = new_name 450 + .split('/') 451 + .next_back() 452 + .unwrap_or(&new_name) 453 + .to_string(); 454 + 455 + for module in modules.values() { 456 + if !module.references.imported_modules.contains(&old_name) { 457 + continue; 458 + } 459 + 460 + let Some(source_information) = sources.get(&module.name) else { 461 + continue; 462 + }; 463 + 464 + let Some(references) = module.references.module_references.get(&old_name) else { 465 + continue; 466 + }; 467 + 468 + let Some(uri) = url_from_path(source_information.path.as_str()) else { 469 + continue; 470 + }; 471 + 472 + let mut edits = TextEdits::new(&source_information.line_numbers); 473 + 474 + for reference in references { 475 + match reference { 476 + ModuleNameReference::Import { 477 + module_location: location, 478 + import_end: _, 479 + } => edits.replace(*location, new_name.to_string()), 480 + ModuleNameReference::AliasedImport { 481 + module_location: location, 482 + alias_location, 483 + alias, 484 + } => { 485 + edits.replace(*location, new_name.to_string()); 486 + // If we've imported a module using an alias, for example 487 + // `import wibble as wobble`, and we then rename the file 488 + // to `wobble.gleam`, the alias is no longer needed as the 489 + // name is already `wobble`. 490 + if *alias == last_component_of_new_name { 491 + edits.delete(SrcSpan::new(alias_location.start - 1, alias_location.end)); 492 + } 493 + } 494 + // If we've imported a module using an alias, we don't touch the 495 + // alias, so any expressions referencing the alias name don't need 496 + // to change. 497 + ModuleNameReference::AliasedModuleSelect(_) => {} 498 + ModuleNameReference::ModuleSelect(location) => { 499 + edits.replace(*location, last_component_of_new_name.clone()) 500 + } 501 + } 502 + } 503 + 504 + changes.entry(uri).or_default().extend(edits.edits); 505 + } 506 + }
+51 -3
language-server/src/server.rs
··· 19 19 io::{BeamCompiler, CommandExecutor, FileSystemReader, FileSystemWriter}, 20 20 line_numbers::LineNumbers, 21 21 }; 22 + use itertools::Itertools; 22 23 use lsp_server::ResponseError; 23 24 use lsp_types::{ 24 - self as lsp, InitializeParams, Position, PublishDiagnosticsParams, Range, RenameOptions, 25 - TextEdit, Uri as Url, 25 + self as lsp, FileOperationFilter, FileOperationOptions, FileOperationPattern, 26 + FileOperationPatternKind, FileOperationRegistrationOptions, InitializeParams, Position, 27 + PublishDiagnosticsParams, Range, RenameFilesParams, RenameOptions, TextEdit, Uri as Url, 28 + WorkspaceOptions, 26 29 }; 27 30 use serde_json::Value as Json; 28 31 use std::collections::{HashMap, HashSet}; ··· 113 116 Request::GoToTypeDefinition(param) => self.goto_type_definition(param), 114 117 Request::FindReferences(param) => self.find_references(param), 115 118 Request::DocumentHighlight(param) => self.document_highlight(param), 119 + Request::RenameFiles(param) => self.rename_files(param), 116 120 }; 117 121 118 122 self.publish_feedback(feedback); ··· 435 439 ) 436 440 } 437 441 442 + fn rename_files( 443 + &mut self, 444 + params: RenameFilesParams, 445 + ) -> (Result<Json, ResponseError>, Feedback) { 446 + let renames = params 447 + .files 448 + .into_iter() 449 + .map(|file| { 450 + ( 451 + Url::parse(&file.old_uri).expect("Uri should be valid"), 452 + Url::parse(&file.new_uri).expect("Uri should be valid"), 453 + ) 454 + }) 455 + .collect_vec(); 456 + 457 + let Some((_, first_renamed_file)) = renames.first() else { 458 + return (Ok(serde_json::json!(null)), Feedback::none()); 459 + }; 460 + 461 + self.respond_with_engine(super::path(first_renamed_file), |engine| { 462 + engine.rename_files(renames) 463 + }) 464 + } 465 + 438 466 fn find_references( 439 467 &mut self, 440 468 params: lsp_types::ReferenceParams, ··· 567 595 folding_range_provider: Some(true.into()), 568 596 declaration_provider: None, 569 597 execute_command_provider: None, 570 - workspace: None, 598 + workspace: Some(WorkspaceOptions { 599 + workspace_folders: None, 600 + file_operations: Some(FileOperationOptions { 601 + did_create: None, 602 + will_create: None, 603 + will_rename: Some(FileOperationRegistrationOptions { 604 + filters: vec![FileOperationFilter { 605 + scheme: Some("file".into()), 606 + pattern: FileOperationPattern { 607 + glob: "**/*.gleam".into(), 608 + matches: Some(FileOperationPatternKind::File), 609 + options: None, 610 + }, 611 + }], 612 + }), 613 + did_rename: None, 614 + did_delete: None, 615 + will_delete: None, 616 + }), 617 + text_document_content: None, 618 + }), 571 619 call_hierarchy_provider: None, 572 620 semantic_tokens_provider: None, 573 621 moniker_provider: None,
+14
language-server/src/tests.rs
··· 809 809 810 810 executor(&mut engine, params, code.into()) 811 811 } 812 + 813 + /// Run a test in a project without a specific position (for workspace-wide 814 + /// actions). 815 + pub fn run<T>( 816 + &self, 817 + executor: impl FnOnce( 818 + &mut LanguageServerEngine<LanguageServerTestIO, LanguageServerTestIO>, 819 + ) -> T, 820 + ) -> T { 821 + // Use a throwaway position and ignore it 822 + let (mut engine, _) = self.positioned_with_io(Position::default()); 823 + 824 + executor(&mut engine) 825 + } 812 826 } 813 827 814 828 #[derive(Clone)]
+246 -26
language-server/src/tests/rename.rs
··· 5 5 6 6 use lsp_types::{ 7 7 Position, PrepareRenameParams, PrepareRenamePlaceholder, Range, RenameParams, 8 - TextDocumentPositionParams, Uri as Url, WorkDoneProgressParams, 8 + TextDocumentPositionParams, Uri as Url, WorkDoneProgressParams, WorkspaceEdit, 9 9 }; 10 + 11 + use crate::url_from_path; 10 12 11 13 use super::{TestProject, find_position_of, hover}; 12 14 ··· 18 20 tester: &TestProject<'_>, 19 21 new_name: &str, 20 22 position: Position, 21 - ) -> Result<Option<(Range, lsp_types::WorkspaceEdit)>, String> { 23 + ) -> Result<Option<(Range, WorkspaceEdit)>, String> { 22 24 let prepare_rename_response = tester.at(position, |engine, params, _| { 23 25 let params = PrepareRenameParams { 24 26 text_document_position_params: TextDocumentPositionParams { ··· 59 61 } 60 62 } 61 63 64 + fn rename_files(tester: &TestProject<'_>, renames: &[(&str, &str)]) -> HashMap<String, String> { 65 + let edit = tester.run(|engine| { 66 + let params = renames 67 + .iter() 68 + .map(|(old_name, new_name)| { 69 + let old_url = 70 + url_from_path(engine.path_for_module_name(old_name).as_str()).unwrap(); 71 + let new_url = 72 + url_from_path(engine.path_for_module_name(new_name).as_str()).unwrap(); 73 + (old_url, new_url) 74 + }) 75 + .collect(); 76 + engine.rename_files(params).result.unwrap() 77 + }); 78 + 79 + let changes = edit 80 + .expect("No text edit found") 81 + .changes 82 + .expect("No text edit found"); 83 + 84 + apply_code_edit(tester, changes) 85 + } 86 + 62 87 fn apply_rename( 63 88 tester: &TestProject<'_>, 64 89 new_name: &str, ··· 86 111 modules 87 112 } 88 113 114 + fn display_result( 115 + project: &TestProject<'_>, 116 + modules: HashMap<String, String>, 117 + renamed_modules: HashMap<&str, &str>, 118 + range: Option<Range>, 119 + ) -> String { 120 + let mut output = String::from("----- BEFORE RENAME\n"); 121 + for (name, src) in project.root_package_modules.iter() { 122 + output.push_str(&format!("-- {name}.gleam\n{src}\n\n")); 123 + } 124 + 125 + let src = project.src; 126 + let app_src_before = if let Some(range) = range { 127 + hover::show_hover(src, range, range.start) 128 + } else { 129 + src.to_string() 130 + }; 131 + output.push_str(&format!( 132 + "-- app.gleam\n{app_src_before}\n\n----- AFTER RENAME\n", 133 + )); 134 + 135 + for &(name, src) in project.root_package_modules.iter() { 136 + let used_name = if let Some(new_name) = renamed_modules.get(name) { 137 + new_name 138 + } else { 139 + name 140 + }; 141 + output.push_str(&format!( 142 + "-- {used_name}.gleam\n{}\n\n", 143 + modules 144 + .get(name) 145 + .map(|string| string.as_str()) 146 + .unwrap_or(src) 147 + )); 148 + } 149 + output.push_str(&format!( 150 + "-- app.gleam\n{}", 151 + modules 152 + .get("app") 153 + .map(|string| string.as_str()) 154 + .unwrap_or(src) 155 + )); 156 + output 157 + } 158 + 89 159 macro_rules! assert_rename { 90 160 ($code:literal, $new_name:literal, $position:expr $(,)?) => { 91 161 assert_rename!(TestProject::for_source($code), $new_name, $position); ··· 105 175 let position = $position.find_position(src); 106 176 let (range, result) = apply_rename(&project, $new_name, position); 107 177 108 - let mut output = String::from("----- BEFORE RENAME\n"); 109 - for (name, src) in project.root_package_modules.iter() { 110 - output.push_str(&format!("-- {name}.gleam\n{src}\n\n")); 111 - } 112 - output.push_str(&format!( 113 - "-- app.gleam\n{}\n\n----- AFTER RENAME\n", 114 - hover::show_hover(src, range, range.start) 115 - )); 116 - for (name, src) in project.root_package_modules.iter() { 117 - output.push_str(&format!( 118 - "-- {name}.gleam\n{}\n\n", 119 - result 120 - .get(*name) 121 - .map(|string| string.as_str()) 122 - .unwrap_or(*src) 123 - )); 124 - } 125 - output.push_str(&format!( 126 - "-- app.gleam\n{}", 127 - result 128 - .get("app") 129 - .map(|string| string.as_str()) 130 - .unwrap_or(src) 131 - )); 178 + let output = display_result(&project, result, HashMap::new(), Some(range)); 132 179 133 180 insta::assert_snapshot!(insta::internals::AutoName, output, src); 134 181 }; ··· 160 207 let error = rename($project, $new_name, position).unwrap_err(); 161 208 let snapshot = format!("Error response message:\n\n{error}"); 162 209 insta::assert_snapshot!(insta::internals::AutoName, snapshot, src); 210 + }; 211 + } 212 + 213 + macro_rules! assert_rename_files { 214 + ($(($old_name:literal, $new_name:literal, $module_src:literal)),+, $code:literal, $(,)?) => { 215 + let project = TestProject::for_source($code)$(.add_module($old_name, $module_src))*; 216 + 217 + let renames = [$(($old_name, $new_name),)*]; 218 + let result = rename_files(&project, &renames); 219 + let output = display_result(&project, result, renames.into(), None); 220 + 221 + insta::assert_snapshot!(insta::internals::AutoName, output, project.src); 163 222 }; 164 223 } 165 224 ··· 2604 2663 find_position_of("anything") 2605 2664 ); 2606 2665 } 2666 + #[test] 2667 + fn renaming_file_modifies_imports_and_references() { 2668 + assert_rename_files!( 2669 + ( 2670 + "wibble/wobble", 2671 + "wibble/wubble", 2672 + " 2673 + pub type Wibble { 2674 + Wibble 2675 + Wobble 2676 + } 2677 + 2678 + pub const wibble = Wibble 2679 + " 2680 + ), 2681 + " 2682 + import wibble/wobble.{Wibble} 2683 + 2684 + pub fn main() -> wobble.Wibble { 2685 + assert wobble.wibble == Wibble 2686 + wobble.Wobble 2687 + } 2688 + ", 2689 + ); 2690 + } 2691 + 2692 + #[test] 2693 + fn change_directory_of_file() { 2694 + assert_rename_files!( 2695 + ( 2696 + "wobble", 2697 + "wibble/wobble", 2698 + " 2699 + pub type Wibble { 2700 + Wibble 2701 + Wobble 2702 + } 2703 + 2704 + pub const wibble = Wibble 2705 + " 2706 + ), 2707 + " 2708 + import wobble.{Wibble} 2709 + 2710 + pub fn main() -> wobble.Wibble { 2711 + assert wobble.wibble == Wibble 2712 + wobble.Wobble 2713 + } 2714 + ", 2715 + ); 2716 + } 2717 + 2718 + #[test] 2719 + fn rename_file_does_not_modify_aliased_imports() { 2720 + assert_rename_files!( 2721 + ( 2722 + "wibble/wobble", 2723 + "wibble/wubble", 2724 + " 2725 + pub type Wibble { 2726 + Wibble 2727 + Wobble 2728 + } 2729 + 2730 + pub const wibble = Wibble 2731 + " 2732 + ), 2733 + " 2734 + import wibble/wobble.{Wibble} as wibble 2735 + 2736 + pub fn main() -> wibble.Wibble { 2737 + assert wibble.wibble == Wibble 2738 + wibble.Wobble 2739 + } 2740 + ", 2741 + ); 2742 + } 2743 + 2744 + #[test] 2745 + fn rename_file_removes_unnecessary_alias() { 2746 + assert_rename_files!( 2747 + ( 2748 + "wibble/wobble", 2749 + "wibble/wibble", 2750 + " 2751 + pub type Wibble { 2752 + Wibble 2753 + Wobble 2754 + } 2755 + 2756 + pub const wibble = Wibble 2757 + " 2758 + ), 2759 + " 2760 + import wibble/wobble.{Wibble} as wibble 2761 + 2762 + pub fn main() -> wibble.Wibble { 2763 + assert wibble.wibble == Wibble 2764 + wibble.Wobble 2765 + } 2766 + ", 2767 + ); 2768 + } 2769 + 2770 + #[test] 2771 + fn rename_file_changes_all_correct_ast_nodes() { 2772 + assert_rename_files!( 2773 + ( 2774 + "wibble", 2775 + "wobble", 2776 + " 2777 + pub type Wibble { 2778 + Wibble 2779 + Wobble 2780 + } 2781 + 2782 + pub const wibble = Wibble 2783 + " 2784 + ), 2785 + " 2786 + import wibble 2787 + 2788 + pub const one = wibble.Wibble 2789 + 2790 + pub const two = wibble.wibble 2791 + 2792 + pub fn main() -> wibble.Wibble { 2793 + case wibble.Wobble { 2794 + x if x == wibble.Wibble -> x 2795 + x if x == wibble.wibble -> x 2796 + wibble.Wobble -> wibble.wibble 2797 + } 2798 + } 2799 + ", 2800 + ); 2801 + } 2802 + 2803 + #[test] 2804 + fn rename_multiple_files() { 2805 + assert_rename_files!( 2806 + ( 2807 + "wibble", 2808 + "wibble/wibble", 2809 + "pub type Wibble { Wibble Wobble }" 2810 + ), 2811 + ( 2812 + "wobble", 2813 + "wibble/wobble", 2814 + "import wibble 2815 + pub const wibble = wibble.Wobble" 2816 + ), 2817 + " 2818 + import wibble 2819 + import wobble 2820 + 2821 + pub fn main() -> wibble.Wibble { 2822 + wobble.wibble 2823 + } 2824 + ", 2825 + ); 2826 + }
+44
language-server/src/tests/snapshots/gleam_language_server__tests__rename__change_directory_of_file.snap
··· 1 + --- 2 + source: language-server/src/tests/rename.rs 3 + expression: "\nimport wobble.{Wibble}\n\npub fn main() -> wobble.Wibble {\n assert wobble.wibble == Wibble\n wobble.Wobble\n}\n" 4 + --- 5 + ----- BEFORE RENAME 6 + -- wobble.gleam 7 + 8 + pub type Wibble { 9 + Wibble 10 + Wobble 11 + } 12 + 13 + pub const wibble = Wibble 14 + 15 + 16 + -- app.gleam 17 + 18 + import wobble.{Wibble} 19 + 20 + pub fn main() -> wobble.Wibble { 21 + assert wobble.wibble == Wibble 22 + wobble.Wobble 23 + } 24 + 25 + 26 + ----- AFTER RENAME 27 + -- wibble/wobble.gleam 28 + 29 + pub type Wibble { 30 + Wibble 31 + Wobble 32 + } 33 + 34 + pub const wibble = Wibble 35 + 36 + 37 + -- app.gleam 38 + 39 + import wibble/wobble.{Wibble} 40 + 41 + pub fn main() -> wobble.Wibble { 42 + assert wobble.wibble == Wibble 43 + wobble.Wobble 44 + }
+58
language-server/src/tests/snapshots/gleam_language_server__tests__rename__rename_file_changes_all_correct_ast_nodes.snap
··· 1 + --- 2 + source: language-server/src/tests/rename.rs 3 + expression: "\nimport wibble\n\npub const one = wibble.Wibble\n\npub const two = wibble.wibble\n\npub fn main() -> wibble.Wibble {\n case wibble.Wobble {\n x if x == wibble.Wibble -> x\n x if x == wibble.wibble -> x\n wibble.Wobble -> wibble.wibble\n }\n}\n" 4 + --- 5 + ----- BEFORE RENAME 6 + -- wibble.gleam 7 + 8 + pub type Wibble { 9 + Wibble 10 + Wobble 11 + } 12 + 13 + pub const wibble = Wibble 14 + 15 + 16 + -- app.gleam 17 + 18 + import wibble 19 + 20 + pub const one = wibble.Wibble 21 + 22 + pub const two = wibble.wibble 23 + 24 + pub fn main() -> wibble.Wibble { 25 + case wibble.Wobble { 26 + x if x == wibble.Wibble -> x 27 + x if x == wibble.wibble -> x 28 + wibble.Wobble -> wibble.wibble 29 + } 30 + } 31 + 32 + 33 + ----- AFTER RENAME 34 + -- wobble.gleam 35 + 36 + pub type Wibble { 37 + Wibble 38 + Wobble 39 + } 40 + 41 + pub const wibble = Wibble 42 + 43 + 44 + -- app.gleam 45 + 46 + import wobble 47 + 48 + pub const one = wobble.Wibble 49 + 50 + pub const two = wobble.wibble 51 + 52 + pub fn main() -> wobble.Wibble { 53 + case wobble.Wobble { 54 + x if x == wobble.Wibble -> x 55 + x if x == wobble.wibble -> x 56 + wobble.Wobble -> wobble.wibble 57 + } 58 + }
+44
language-server/src/tests/snapshots/gleam_language_server__tests__rename__rename_file_does_not_modify_aliased_imports.snap
··· 1 + --- 2 + source: language-server/src/tests/rename.rs 3 + expression: "\nimport wibble/wobble.{Wibble} as wibble\n\npub fn main() -> wibble.Wibble {\n assert wibble.wibble == Wibble\n wibble.Wobble\n}\n" 4 + --- 5 + ----- BEFORE RENAME 6 + -- wibble/wobble.gleam 7 + 8 + pub type Wibble { 9 + Wibble 10 + Wobble 11 + } 12 + 13 + pub const wibble = Wibble 14 + 15 + 16 + -- app.gleam 17 + 18 + import wibble/wobble.{Wibble} as wibble 19 + 20 + pub fn main() -> wibble.Wibble { 21 + assert wibble.wibble == Wibble 22 + wibble.Wobble 23 + } 24 + 25 + 26 + ----- AFTER RENAME 27 + -- wibble/wubble.gleam 28 + 29 + pub type Wibble { 30 + Wibble 31 + Wobble 32 + } 33 + 34 + pub const wibble = Wibble 35 + 36 + 37 + -- app.gleam 38 + 39 + import wibble/wubble.{Wibble} as wibble 40 + 41 + pub fn main() -> wibble.Wibble { 42 + assert wibble.wibble == Wibble 43 + wibble.Wobble 44 + }
+44
language-server/src/tests/snapshots/gleam_language_server__tests__rename__rename_file_removes_unnecessary_alias.snap
··· 1 + --- 2 + source: language-server/src/tests/rename.rs 3 + expression: "\nimport wibble/wobble.{Wibble} as wibble\n\npub fn main() -> wibble.Wibble {\n assert wibble.wibble == Wibble\n wibble.Wobble\n}\n" 4 + --- 5 + ----- BEFORE RENAME 6 + -- wibble/wobble.gleam 7 + 8 + pub type Wibble { 9 + Wibble 10 + Wobble 11 + } 12 + 13 + pub const wibble = Wibble 14 + 15 + 16 + -- app.gleam 17 + 18 + import wibble/wobble.{Wibble} as wibble 19 + 20 + pub fn main() -> wibble.Wibble { 21 + assert wibble.wibble == Wibble 22 + wibble.Wobble 23 + } 24 + 25 + 26 + ----- AFTER RENAME 27 + -- wibble/wibble.gleam 28 + 29 + pub type Wibble { 30 + Wibble 31 + Wobble 32 + } 33 + 34 + pub const wibble = Wibble 35 + 36 + 37 + -- app.gleam 38 + 39 + import wibble/wibble.{Wibble} 40 + 41 + pub fn main() -> wibble.Wibble { 42 + assert wibble.wibble == Wibble 43 + wibble.Wobble 44 + }
+38
language-server/src/tests/snapshots/gleam_language_server__tests__rename__rename_multiple_files.snap
··· 1 + --- 2 + source: language-server/src/tests/rename.rs 3 + expression: "\nimport wibble\nimport wobble\n\npub fn main() -> wibble.Wibble {\n wobble.wibble\n}\n" 4 + --- 5 + ----- BEFORE RENAME 6 + -- wibble.gleam 7 + pub type Wibble { Wibble Wobble } 8 + 9 + -- wobble.gleam 10 + import wibble 11 + pub const wibble = wibble.Wobble 12 + 13 + -- app.gleam 14 + 15 + import wibble 16 + import wobble 17 + 18 + pub fn main() -> wibble.Wibble { 19 + wobble.wibble 20 + } 21 + 22 + 23 + ----- AFTER RENAME 24 + -- wibble/wibble.gleam 25 + pub type Wibble { Wibble Wobble } 26 + 27 + -- wibble/wobble.gleam 28 + import wibble/wibble 29 + pub const wibble = wibble.Wobble 30 + 31 + -- app.gleam 32 + 33 + import wibble/wibble 34 + import wibble/wobble 35 + 36 + pub fn main() -> wibble.Wibble { 37 + wobble.wibble 38 + }
+44
language-server/src/tests/snapshots/gleam_language_server__tests__rename__renaming_file_modifies_imports_and_references.snap
··· 1 + --- 2 + source: language-server/src/tests/rename.rs 3 + expression: "\nimport wibble/wobble.{Wibble}\n\npub fn main() -> wobble.Wibble {\n assert wobble.wibble == Wibble\n wobble.Wobble\n}\n" 4 + --- 5 + ----- BEFORE RENAME 6 + -- wibble/wobble.gleam 7 + 8 + pub type Wibble { 9 + Wibble 10 + Wobble 11 + } 12 + 13 + pub const wibble = Wibble 14 + 15 + 16 + -- app.gleam 17 + 18 + import wibble/wobble.{Wibble} 19 + 20 + pub fn main() -> wobble.Wibble { 21 + assert wobble.wibble == Wibble 22 + wobble.Wobble 23 + } 24 + 25 + 26 + ----- AFTER RENAME 27 + -- wibble/wubble.gleam 28 + 29 + pub type Wibble { 30 + Wibble 31 + Wobble 32 + } 33 + 34 + pub const wibble = Wibble 35 + 36 + 37 + -- app.gleam 38 + 39 + import wibble/wubble.{Wibble} 40 + 41 + pub fn main() -> wubble.Wibble { 42 + assert wubble.wibble == Wibble 43 + wubble.Wobble 44 + }