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

Configure Feed

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

gleam / language-server / src / engine.rs
82 kB 2198 lines
1// SPDX-License-Identifier: Apache-2.0 2// SPDX-FileCopyrightText: 2023 The Gleam contributors 3 4use camino::Utf8PathBuf; 5use ecow::{EcoString, eco_format}; 6use gleam_core::{ 7 Error, Result, Warning, 8 analyse::name::correct_name_case, 9 ast::{ 10 self, Constant, CustomType, DefinitionLocation, ModuleConstant, PatternUnusedArguments, 11 SrcSpan, TypedArg, TypedClauseGuard, TypedConstant, TypedExpr, TypedFunction, TypedModule, 12 TypedPattern, TypedRecordConstructor, 13 }, 14 build::{ 15 ExpressionPosition, Located, Module, UnqualifiedImport, type_constructor_from_modules, 16 }, 17 config::PackageConfig, 18 io::{BeamCompilerIO, CommandExecutor, FileSystemReader, FileSystemWriter}, 19 line_numbers::LineNumbers, 20 paths::ProjectPaths, 21 type_::{ 22 self, Deprecation, ModuleInterface, Type, TypeConstructor, ValueConstructor, 23 ValueConstructorVariant, 24 error::{Named, VariableSyntax}, 25 printer::Printer, 26 }, 27}; 28use itertools::Itertools; 29use lsp::CodeAction; 30use lsp_server::ResponseError; 31use lsp_types::{ 32 self as lsp, Contents, DocumentSymbol, FoldingRange, FoldingRangeKind, Hover, MarkedString, 33 MarkupContent, Position, PrepareRenameResult, Range, SignatureHelp, SymbolKind, SymbolTag, 34 TextEdit, Uri as Url, WorkspaceEdit, 35}; 36use std::{ 37 collections::{HashMap, HashSet}, 38 sync::Arc, 39}; 40 41use crate::{ 42 code_action::{RemoveRedundantRecordUpdate, ReplaceUnderscoreWithType, type_errors_for_module}, 43 reference::find_module_references_in_module, 44 rename::{rename_module_alias, rename_module_occurrences, rename_type_variable}, 45}; 46 47use super::{ 48 DownloadDependencies, MakeLocker, 49 code_action::{ 50 AddAnnotations, AddMissingTypeParameter, AddOmittedLabels, AnnotateTopLevelDefinitions, 51 CodeActionBuilder, CollapseNestedCase, ConvertFromUse, ConvertToFunctionCall, 52 ConvertToPipe, ConvertToUse, CreateUnknownModule, ExpandFunctionCapture, ExtractConstant, 53 ExtractFunction, ExtractVariable, FillInMissingLabelledArgs, FillUnusedFields, 54 FixBinaryOperation, FixTruncatedBitArraySegment, GenerateDynamicDecoder, GenerateFunction, 55 GenerateJsonEncoder, GenerateVariant, InlineVariable, InterpolateString, LetAssertToCase, 56 MergeCaseBranches, PatternMatchOnValue, RedundantTupleInCaseSubject, RemoveBlock, 57 RemoveEchos, RemovePrivateOpaque, RemoveUnreachableCaseClauses, RemoveUnusedImports, 58 UnwrapAnonymousFunction, UseLabelShorthandSyntax, WrapInAnonymousFunction, WrapInBlock, 59 code_action_add_missing_patterns, code_action_convert_qualified_constructor_to_unqualified, 60 code_action_convert_unqualified_constructor_to_qualified, code_action_generate_type, 61 code_action_import_module, code_action_inexhaustive_let_to_case, 62 }, 63 compiler::LspProjectCompiler, 64 completer::Completer, 65 files::FileSystemProxy, 66 progress::ProgressReporter, 67 reference::{ 68 FindVariableReferences, Referenced, VariableReferenceKind, find_module_references, 69 reference_for_ast_node, 70 }, 71 rename::{RenameOutcome, RenameTarget, Renamed, rename_local_variable, rename_module_entity}, 72 signature_help, src_span_to_lsp_range, 73}; 74 75#[derive(Debug, PartialEq, Eq)] 76pub struct Response<T> { 77 pub result: Result<T, Error>, 78 pub warnings: Vec<Warning>, 79 pub compilation: Compilation, 80} 81 82#[derive(Debug, PartialEq, Eq)] 83pub enum Compilation { 84 /// Compilation was attempted and succeeded for these modules. 85 Yes(Vec<Utf8PathBuf>), 86 /// Compilation was not attempted for this operation. 87 No, 88} 89 90#[derive(Debug)] 91pub struct LanguageServerEngine<IO, Reporter> { 92 pub(crate) paths: ProjectPaths, 93 94 /// A compiler for the project that supports repeat compilation of the root 95 /// package. 96 /// In the event the project config changes this will need to be 97 /// discarded and reloaded to handle any changes to dependencies. 98 pub(crate) compiler: LspProjectCompiler<FileSystemProxy<IO>>, 99 100 modules_compiled_since_last_feedback: Vec<Utf8PathBuf>, 101 compiled_since_last_feedback: bool, 102 error: Option<Error>, 103 104 // Used to publish progress notifications to the client without waiting for 105 // the usual request-response loop. 106 progress_reporter: Reporter, 107 108 /// Used to know if to show the "View on HexDocs" link 109 /// when hovering on an imported value 110 hex_deps: HashSet<EcoString>, 111} 112 113impl<'a, IO, Reporter> LanguageServerEngine<IO, Reporter> 114where 115 // IO to be supplied from outside of gleam-core 116 IO: FileSystemReader 117 + FileSystemWriter 118 + BeamCompilerIO 119 + CommandExecutor 120 + DownloadDependencies 121 + MakeLocker 122 + Clone, 123 // IO to be supplied from inside of gleam-core 124 Reporter: ProgressReporter + Clone + 'a, 125{ 126 pub fn new( 127 config: PackageConfig, 128 progress_reporter: Reporter, 129 io: FileSystemProxy<IO>, 130 paths: ProjectPaths, 131 ) -> Result<Self> { 132 let locker = io.inner().make_locker(&paths, config.target)?; 133 134 // Download dependencies to ensure they are up-to-date for this new 135 // configuration and new instance of the compiler 136 progress_reporter.dependency_downloading_started(); 137 let manifest = io.inner().download_dependencies(&paths); 138 progress_reporter.dependency_downloading_finished(); 139 140 // NOTE: This must come after the progress reporter has finished! 141 let manifest = manifest?; 142 143 let compiler: LspProjectCompiler<FileSystemProxy<IO>> = 144 LspProjectCompiler::new(manifest, config, paths.clone(), io.clone(), locker)?; 145 146 let hex_deps = compiler 147 .project_compiler 148 .packages 149 .iter() 150 .flat_map(|(k, v)| match &v.source { 151 gleam_core::manifest::ManifestPackageSource::Hex { .. } => { 152 Some(EcoString::from(k.as_str())) 153 } 154 155 gleam_core::manifest::ManifestPackageSource::Git { .. } 156 | gleam_core::manifest::ManifestPackageSource::Local { .. } => None, 157 }) 158 .collect(); 159 160 Ok(Self { 161 modules_compiled_since_last_feedback: vec![], 162 compiled_since_last_feedback: false, 163 progress_reporter, 164 compiler, 165 paths, 166 error: None, 167 hex_deps, 168 }) 169 } 170 171 pub fn compile_please(&mut self) -> Response<()> { 172 self.respond(Self::compile) 173 } 174 175 /// Compile the project if we are in one. Otherwise do nothing. 176 fn compile(&mut self) -> Result<(), Error> { 177 self.compiled_since_last_feedback = true; 178 179 self.progress_reporter.compilation_started(); 180 let outcome = self.compiler.compile(); 181 self.progress_reporter.compilation_finished(); 182 183 let result = outcome 184 // Register which modules have changed 185 .map(|modules| self.modules_compiled_since_last_feedback.extend(modules)) 186 // Return the error, if present 187 .into_result(); 188 189 self.error = match &result { 190 Ok(_) => None, 191 Err(error) => Some(error.clone()), 192 }; 193 194 result 195 } 196 197 fn take_warnings(&mut self) -> Vec<Warning> { 198 self.compiler.take_warnings() 199 } 200 201 pub fn goto_definition( 202 &mut self, 203 params: lsp::DefinitionParams, 204 ) -> Response<Option<lsp::Location>> { 205 self.respond(|this| { 206 let params = params.text_document_position_params; 207 let (line_numbers, node) = match this.node_at_position(&params) { 208 Some(location) => location, 209 None => return Ok(None), 210 }; 211 212 let Some(location) = 213 node.definition_location(this.compiler.project_compiler.get_importable_modules()) 214 else { 215 return Ok(None); 216 }; 217 218 Ok(this.definition_location_to_lsp_location(&line_numbers, &params, location)) 219 }) 220 } 221 222 pub(crate) fn goto_type_definition( 223 &mut self, 224 params: lsp_types::TypeDefinitionParams, 225 ) -> Response<Vec<lsp::Location>> { 226 self.respond(|this| { 227 let params = params.text_document_position_params; 228 let (line_numbers, node) = match this.node_at_position(&params) { 229 Some(location) => location, 230 None => return Ok(vec![]), 231 }; 232 233 let Some(locations) = node 234 .type_definition_locations(this.compiler.project_compiler.get_importable_modules()) 235 else { 236 return Ok(vec![]); 237 }; 238 239 let locations = locations 240 .into_iter() 241 .filter_map(|location| { 242 this.definition_location_to_lsp_location(&line_numbers, &params, location) 243 }) 244 .collect_vec(); 245 246 Ok(locations) 247 }) 248 } 249 250 fn definition_location_to_lsp_location( 251 &self, 252 line_numbers: &LineNumbers, 253 params: &lsp_types::TextDocumentPositionParams, 254 location: DefinitionLocation, 255 ) -> Option<lsp::Location> { 256 let (uri, line_numbers) = match location.module { 257 None => (params.text_document.uri.clone(), line_numbers), 258 Some(name) => { 259 let module = self.compiler.get_source(&name)?; 260 let url = Url::parse(&format!("file:///{}", &module.path)) 261 .expect("goto definition URL parse"); 262 (url, &module.line_numbers) 263 } 264 }; 265 let range = src_span_to_lsp_range(location.span, line_numbers); 266 267 Some(lsp::Location { uri, range }) 268 } 269 270 pub fn completion( 271 &mut self, 272 params: lsp::TextDocumentPositionParams, 273 src: EcoString, 274 ) -> Response<Option<Vec<lsp::CompletionItem>>> { 275 self.respond(|this| { 276 let module = match this.module_for_uri(&params.text_document.uri) { 277 Some(module) => module, 278 None => return Ok(None), 279 }; 280 281 let mut completer = Completer::new(&src, &params, &this.compiler, module); 282 let byte_index = completer.module_line_numbers.byte_index(params.position); 283 284 // If in comment context, do not provide completions 285 if module.extra.is_within_comment(byte_index) { 286 return Ok(None); 287 } 288 289 // Check current file contents if the user is writing an import 290 // and handle separately from the rest of the completion flow 291 // Check if an import is being written 292 if let Some(value) = completer.import_completions() { 293 return value; 294 } 295 296 let Some(found) = module.find_node(byte_index) else { 297 return Ok(None); 298 }; 299 300 let completions = match found { 301 Located::PatternSpread { .. } => None, 302 Located::Pattern(_pattern) => None, 303 Located::StringPrefixPatternVariable { .. } => None, 304 305 // Do not show completions when typing inside a string. 306 Located::Expression { 307 expression: TypedExpr::String { .. }, 308 .. 309 } 310 | Located::Constant(Constant::String { .. }) => None, 311 312 Located::Expression { 313 expression: 314 TypedExpr::Call { fun, arguments, .. } 315 | TypedExpr::RecordUpdate { 316 constructor: fun, 317 arguments, 318 .. 319 }, 320 .. 321 } => { 322 let mut completions = vec![]; 323 completions.append(&mut completer.completion_values()); 324 completions.append(&mut completer.completion_labels(fun, arguments)); 325 Some(completions) 326 } 327 328 Located::Expression { 329 expression: TypedExpr::RecordAccess { record, type_, .. }, 330 .. 331 } => { 332 completer.expected_type = Some(type_.clone()); 333 let mut completions = vec![]; 334 completions.append(&mut completer.completion_values()); 335 completions.append(&mut completer.completion_field_accessors(record.type_())); 336 Some(completions) 337 } 338 339 Located::Expression { 340 position: 341 ExpressionPosition::ArgumentOrLabel { 342 called_function, 343 function_arguments, 344 }, 345 .. 346 } => { 347 let mut completions = vec![]; 348 completions.append(&mut completer.completion_values()); 349 completions.append( 350 &mut completer.completion_labels(called_function, function_arguments), 351 ); 352 Some(completions) 353 } 354 355 // If we're typing inside an expression body (and not being any 356 // more specific than this, meaning we're not editing some 357 // specific value inside it) we don't want to set the expected 358 // type to the type of the entire anonymous function, otherwise 359 // the language server would start recommending the wrong 360 // values: 361 // 362 // ``` 363 // fn(x: Int) -> String { 364 // | // <- Typing here 365 // // We don't want the language server to suggest values 366 // // of type `fn(Int) -> String`! 367 // todo 368 // } 369 // ``` 370 // 371 Located::Expression { 372 position: ExpressionPosition::Expression, 373 expression: TypedExpr::Fn { .. }, 374 } => Some(completer.completion_values()), 375 376 Located::Expression { expression, .. } => { 377 completer.expected_type = Some(expression.type_()); 378 Some(completer.completion_values()) 379 } 380 381 Located::ModuleFunction(_) => Some(completer.completion_types()), 382 383 Located::Statement(_) => Some(completer.completion_values()), 384 385 Located::FunctionBody(_) => Some(completer.completion_values()), 386 387 Located::ModuleTypeAlias(_) 388 | Located::ModuleCustomType(_) 389 | Located::VariantConstructorDefinition(_) => Some(completer.completion_types()), 390 391 // If the import completions returned no results and we are in an import then 392 // we should try to provide completions for unqualified values 393 Located::ModuleImport(import) => this 394 .compiler 395 .get_module_interface(import.module.as_str()) 396 .map(|importing_module| { 397 completer.unqualified_completions_from_module(importing_module, true) 398 }), 399 400 Located::ModuleConstant(_) | Located::Constant(_) => { 401 Some(completer.completion_values()) 402 } 403 404 Located::UnqualifiedImport(_) => None, 405 406 Located::Arg(_) => None, 407 408 Located::Annotation { .. } => Some(completer.completion_types()), 409 410 Located::Label(_, _) => None, 411 412 Located::ModuleName { 413 layer: ast::Layer::Type, 414 .. 415 } => Some(completer.completion_types()), 416 Located::ModuleName { 417 layer: ast::Layer::Value, 418 .. 419 } => Some(completer.completion_values()), 420 421 Located::ClauseGuard(_) => Some(completer.completion_values()), 422 423 Located::TypeVariable { .. } => None, 424 }; 425 426 Ok(completions) 427 }) 428 } 429 430 pub fn code_actions( 431 &mut self, 432 params: lsp::CodeActionParams, 433 ) -> Response<Option<Vec<CodeAction>>> { 434 self.respond(|this| { 435 let mut actions = vec![]; 436 let Some(module) = this.module_for_uri(&params.text_document.uri) else { 437 return Ok(None); 438 }; 439 440 let lines = LineNumbers::new(&module.code); 441 442 code_action_unused_values(module, &lines, &params, &mut actions); 443 actions.extend(RemoveUnusedImports::new(module, &lines, &params).code_actions()); 444 code_action_fix_names(module, &lines, &params, &this.error, &mut actions); 445 code_action_import_module(module, &lines, &params, &this.error, &mut actions); 446 code_action_generate_type(module, &lines, &params, &this.error, &mut actions); 447 code_action_add_missing_patterns(module, &lines, &params, &this.error, &mut actions); 448 actions 449 .extend(RemoveUnreachableCaseClauses::new(module, &lines, &params).code_actions()); 450 actions 451 .extend(RemoveRedundantRecordUpdate::new(module, &lines, &params).code_actions()); 452 actions.extend(CollapseNestedCase::new(module, &lines, &params).code_actions()); 453 actions.extend(FixBinaryOperation::new(module, &lines, &params).code_actions()); 454 actions 455 .extend(FixTruncatedBitArraySegment::new(module, &lines, &params).code_actions()); 456 actions.extend(RemovePrivateOpaque::new(module, &lines, &params).code_actions()); 457 actions.extend(AddMissingTypeParameter::new(module, &lines, &params).code_actions()); 458 code_action_convert_qualified_constructor_to_unqualified( 459 module, 460 &this.compiler, 461 &lines, 462 &params, 463 &mut actions, 464 ); 465 code_action_convert_unqualified_constructor_to_qualified( 466 module, 467 &lines, 468 &params, 469 &mut actions, 470 ); 471 code_action_inexhaustive_let_to_case( 472 module, 473 &lines, 474 &params, 475 &this.error, 476 &mut actions, 477 ); 478 actions.extend(MergeCaseBranches::new(module, &lines, &params).code_actions()); 479 actions.extend(LetAssertToCase::new(module, &lines, &params).code_actions()); 480 actions 481 .extend(RedundantTupleInCaseSubject::new(module, &lines, &params).code_actions()); 482 actions.extend(FillInMissingLabelledArgs::new(module, &lines, &params).code_actions()); 483 actions.extend(UseLabelShorthandSyntax::new(module, &lines, &params).code_actions()); 484 actions.extend(ConvertFromUse::new(module, &lines, &params).code_actions()); 485 actions.extend(RemoveEchos::new(module, &lines, &params).code_actions()); 486 actions.extend(ConvertToUse::new(module, &lines, &params).code_actions()); 487 actions.extend(ExpandFunctionCapture::new(module, &lines, &params).code_actions()); 488 actions.extend(FillUnusedFields::new(module, &lines, &params).code_actions()); 489 actions.extend(InterpolateString::new(module, &lines, &params).code_actions()); 490 actions.extend(ExtractVariable::new(module, &lines, &params).code_actions()); 491 actions.extend(ExtractConstant::new(module, &lines, &params).code_actions()); 492 actions.extend( 493 GenerateFunction::new(module, &this.compiler.modules, &lines, &params) 494 .code_actions(), 495 ); 496 actions.extend( 497 GenerateVariant::new(module, &this.compiler, &lines, &params).code_actions(), 498 ); 499 actions.extend(ConvertToPipe::new(module, &lines, &params).code_actions()); 500 actions.extend(ConvertToFunctionCall::new(module, &lines, &params).code_actions()); 501 actions.extend( 502 PatternMatchOnValue::new(module, &lines, &params, &this.compiler).code_actions(), 503 ); 504 actions.extend(AddOmittedLabels::new(module, &lines, &params).code_actions()); 505 actions.extend(InlineVariable::new(module, &lines, &params).code_actions()); 506 actions.extend(WrapInBlock::new(module, &lines, &params).code_actions()); 507 actions.extend(RemoveBlock::new(module, &lines, &params).code_actions()); 508 actions.extend(ExtractFunction::new(module, &lines, &params).code_actions()); 509 GenerateDynamicDecoder::new(module, &lines, &params, &mut actions, &this.compiler) 510 .code_actions(); 511 actions.extend(WrapInAnonymousFunction::new(module, &lines, &params).code_actions()); 512 actions.extend(UnwrapAnonymousFunction::new(module, &lines, &params).code_actions()); 513 GenerateJsonEncoder::new( 514 module, 515 &lines, 516 &params, 517 &mut actions, 518 &this.compiler.project_compiler.config, 519 ) 520 .code_actions(); 521 AddAnnotations::new(module, &lines, &params).code_action(&mut actions); 522 actions 523 .extend(AnnotateTopLevelDefinitions::new(module, &lines, &params).code_actions()); 524 actions.extend(ReplaceUnderscoreWithType::new(module, &lines, &params).code_actions()); 525 actions.extend( 526 CreateUnknownModule::new( 527 module, 528 &this.compiler, 529 &lines, 530 &params, 531 &this.paths, 532 &this.error, 533 ) 534 .code_actions(), 535 ); 536 537 actions.sort_by_key(|one| { 538 let preferred_key = if one.is_preferred == Some(true) { 0 } else { 1 }; 539 let kind_key = match &one.kind { 540 Some(lsp_types::CodeActionKind::QuickFix) => 1, 541 Some(lsp_types::CodeActionKind::Refactor) => 2, 542 Some(lsp_types::CodeActionKind::RefactorExtract) => 2, 543 Some(lsp_types::CodeActionKind::RefactorInline) => 2, 544 Some(lsp_types::CodeActionKind::RefactorMove) => 2, 545 Some(lsp_types::CodeActionKind::RefactorRewrite) => 2, 546 Some(lsp_types::CodeActionKind::Source) => 3, 547 Some(lsp_types::CodeActionKind::SourceOrganizeImports) => 3, 548 Some(lsp_types::CodeActionKind::SourceFixAll) => 3, 549 Some(lsp_types::CodeActionKind::Custom(_)) => 4, 550 Some(lsp_types::CodeActionKind::Notebook) => 5, 551 Some(lsp_types::CodeActionKind::Empty) => 6, 552 None => 7, 553 }; 554 (preferred_key, kind_key) 555 }); 556 557 Ok(if actions.is_empty() { 558 None 559 } else { 560 Some(actions) 561 }) 562 }) 563 } 564 565 pub fn document_symbol( 566 &mut self, 567 params: lsp::DocumentSymbolParams, 568 ) -> Response<Vec<DocumentSymbol>> { 569 self.respond(|this| { 570 let mut symbols = vec![]; 571 let Some(module) = this.module_for_uri(&params.text_document.uri) else { 572 return Ok(symbols); 573 }; 574 let line_numbers = LineNumbers::new(&module.code); 575 576 for function in &module.ast.definitions.functions { 577 // By default, the function's location ends right after the return type. 578 // For the full symbol range, have it end at the end of the body. 579 // Also include the documentation, if available. 580 // 581 // By convention, the symbol span starts from the leading slash in the 582 // documentation comment's marker ('///'), not from its content (of which 583 // we have the position), so we must convert the content start position 584 // to the leading slash's position. 585 let full_function_span = SrcSpan { 586 start: function 587 .documentation 588 .as_ref() 589 .map(|(doc_start, _)| get_doc_marker_position(*doc_start)) 590 .unwrap_or(function.location.start), 591 592 end: function.end_position, 593 }; 594 595 let (name_location, name) = function 596 .name 597 .as_ref() 598 .expect("Function in a definition must be named"); 599 600 // The 'deprecated' field is deprecated, but we have to specify it anyway 601 // to be able to construct the 'DocumentSymbol' type, so 602 // we suppress the warning. We specify 'None' as specifying 'Some' 603 // is what is actually deprecated. 604 #[allow(deprecated)] 605 symbols.push(DocumentSymbol { 606 name: name.to_string(), 607 detail: Some( 608 Printer::new(&module.ast.names) 609 .print_type(&get_function_type(function)) 610 .to_string(), 611 ), 612 kind: SymbolKind::Function, 613 tags: make_deprecated_symbol_tag(&function.deprecation), 614 deprecated: None, 615 range: src_span_to_lsp_range(full_function_span, &line_numbers), 616 selection_range: src_span_to_lsp_range(*name_location, &line_numbers), 617 children: None, 618 }); 619 } 620 621 for alias in &module.ast.definitions.type_aliases { 622 let full_alias_span = match alias.documentation { 623 Some((doc_position, _)) => { 624 SrcSpan::new(get_doc_marker_position(doc_position), alias.location.end) 625 } 626 None => alias.location, 627 }; 628 629 // The 'deprecated' field is deprecated, but we have to specify it anyway 630 // to be able to construct the 'DocumentSymbol' type, so 631 // we suppress the warning. We specify 'None' as specifying 'Some' 632 // is what is actually deprecated. 633 #[allow(deprecated)] 634 symbols.push(DocumentSymbol { 635 name: alias.alias.to_string(), 636 detail: Some( 637 Printer::new(&module.ast.names) 638 // If we print with aliases, we end up printing the alias which the user 639 // is currently hovering, which is not helpful. Instead, we print the 640 // raw type, so the user can see which type the alias represents 641 .print_type_without_aliases(&alias.type_) 642 .to_string(), 643 ), 644 kind: SymbolKind::Class, 645 tags: make_deprecated_symbol_tag(&alias.deprecation), 646 deprecated: None, 647 range: src_span_to_lsp_range(full_alias_span, &line_numbers), 648 selection_range: src_span_to_lsp_range(alias.name_location, &line_numbers), 649 children: None, 650 }); 651 } 652 653 for custom_type in &module.ast.definitions.custom_types { 654 symbols.push(custom_type_symbol(custom_type, &line_numbers, module)); 655 } 656 657 for constant in &module.ast.definitions.constants { 658 // `ModuleConstant.location` ends at the constant's name or type. 659 // For the full symbol span, necessary for `range`, we need to 660 // include the constant value as well. 661 // Also include the documentation at the start, if available. 662 let full_constant_span = SrcSpan { 663 start: constant 664 .documentation 665 .as_ref() 666 .map(|(doc_start, _)| get_doc_marker_position(*doc_start)) 667 .unwrap_or(constant.location.start), 668 669 end: constant.value.location().end, 670 }; 671 672 // The 'deprecated' field is deprecated, but we have to specify it anyway 673 // to be able to construct the 'DocumentSymbol' type, so 674 // we suppress the warning. We specify 'None' as specifying 'Some' 675 // is what is actually deprecated. 676 #[allow(deprecated)] 677 symbols.push(DocumentSymbol { 678 name: constant.name.to_string(), 679 detail: Some( 680 Printer::new(&module.ast.names) 681 .print_type(&constant.type_) 682 .to_string(), 683 ), 684 kind: SymbolKind::Constant, 685 tags: make_deprecated_symbol_tag(&constant.deprecation), 686 deprecated: None, 687 range: src_span_to_lsp_range(full_constant_span, &line_numbers), 688 selection_range: src_span_to_lsp_range(constant.name_location, &line_numbers), 689 children: None, 690 }); 691 } 692 693 Ok(symbols) 694 }) 695 } 696 697 pub fn folding_range( 698 &mut self, 699 params: lsp::FoldingRangeParams, 700 ) -> Response<Vec<FoldingRange>> { 701 self.respond(|this| { 702 let mut ranges: Vec<FoldingRange> = vec![]; 703 let Some(module) = this.module_for_uri(&params.text_document.uri) else { 704 return Ok(vec![]); 705 }; 706 707 let line_numbers = LineNumbers::new(&module.code); 708 709 for import in 710 import_folding_spans(&module.ast.definitions.imports, &module.code, &line_numbers) 711 { 712 let Some(range) = 713 folding_range_for_span(import, &line_numbers, Some(FoldingRangeKind::Imports)) 714 else { 715 continue; 716 }; 717 718 ranges.push(range); 719 } 720 721 for type_ in &module.ast.definitions.custom_types { 722 let span = type_.full_location(); 723 let Some(range) = folding_range_for_span(span, &line_numbers, None) else { 724 continue; 725 }; 726 ranges.push(range); 727 } 728 729 for constant in &module.ast.definitions.constants { 730 let span = SrcSpan::new(constant.location.start, constant.value.location().end); 731 let Some(range) = folding_range_for_span(span, &line_numbers, None) else { 732 continue; 733 }; 734 ranges.push(range); 735 } 736 737 for alias in &module.ast.definitions.type_aliases { 738 let span = alias.location; 739 let Some(range) = folding_range_for_span(span, &line_numbers, None) else { 740 continue; 741 }; 742 ranges.push(range); 743 } 744 745 for function in &module.ast.definitions.functions { 746 let Some(body_start) = function.body_start else { 747 continue; 748 }; 749 750 let span = SrcSpan::new(body_start, function.end_position); 751 let Some(range) = folding_range_for_span(span, &line_numbers, None) else { 752 continue; 753 }; 754 ranges.push(range); 755 } 756 757 ranges.sort_by_key(|range| range.start_line); 758 Ok(ranges) 759 }) 760 } 761 762 /// Check whether a particular module is in the same package as this one 763 fn is_same_package(&self, current_module: &Module, module_name: &str) -> bool { 764 let other_module = self 765 .compiler 766 .project_compiler 767 .get_importable_modules() 768 .get(module_name); 769 match other_module { 770 // We can't rename values from other packages if we are not aliasing an unqualified import. 771 Some(module) => module.package == current_module.ast.type_info.package, 772 None => false, 773 } 774 } 775 776 pub fn prepare_rename( 777 &mut self, 778 params: lsp::PrepareRenameParams, 779 ) -> Response<Option<PrepareRenameResult>> { 780 self.respond(|this| { 781 let (lines, found) = match this.node_at_position(&params.text_document_position_params) 782 { 783 Some(value) => value, 784 None => return Ok(None), 785 }; 786 787 let Some(current_module) = 788 this.module_for_uri(&params.text_document_position_params.text_document.uri) 789 else { 790 return Ok(None); 791 }; 792 793 let success_response = |location| { 794 Some(PrepareRenameResult::Range(src_span_to_lsp_range( 795 location, &lines, 796 ))) 797 }; 798 799 let byte_index = lines.byte_index(params.text_document_position_params.position); 800 801 let referenced = reference_for_ast_node(found, &current_module.name); 802 803 Ok(match referenced { 804 Some(Referenced::LocalVariable { 805 location, origin, .. 806 }) if location.contains(byte_index) => match origin.map(|origin| origin.syntax) { 807 Some(VariableSyntax::Generated) => None, 808 Some( 809 VariableSyntax::Variable(label) | VariableSyntax::LabelShorthand(label), 810 ) => success_response(SrcSpan { 811 start: location.start, 812 end: label 813 .len() 814 .try_into() 815 .map(|len: u32| location.start + len) 816 .unwrap_or(location.end), 817 }), 818 Some(VariableSyntax::AssignmentPattern) | None => success_response(location), 819 }, 820 Some( 821 Referenced::ModuleValue { 822 module, 823 location, 824 target_kind, 825 .. 826 } 827 | Referenced::ModuleType { 828 module, 829 location, 830 target_kind, 831 .. 832 }, 833 ) if location.contains(byte_index) => { 834 // We can't rename types or values from other packages if we are not aliasing an unqualified import. 835 let rename_allowed = match target_kind { 836 RenameTarget::Qualified => this.is_same_package(current_module, &module), 837 RenameTarget::Unqualified | RenameTarget::Definition => true, 838 }; 839 if rename_allowed { 840 success_response(location) 841 } else { 842 None 843 } 844 } 845 Some(Referenced::ModuleName { 846 location, 847 module_alias, 848 .. 849 }) => success_response(SrcSpan::new( 850 // Since the location contains the full module name (e.g. `wibble/wobble/woo`), 851 // we just want to include the last segment so we get a rename of the string 852 // `woo`, as that's the being referenced in module access expressions. 853 location.end - module_alias.len() as u32, 854 location.end, 855 )), 856 857 Some(Referenced::TypeVariable { location, name: _ }) => success_response(location), 858 859 _ => None, 860 }) 861 }) 862 } 863 864 pub fn rename( 865 &mut self, 866 params: lsp::RenameParams, 867 ) -> Response<Result<Option<WorkspaceEdit>, ResponseError>> { 868 self.respond(|this| { 869 let position = &params.text_document_position_params; 870 871 let (lines, found) = match this.node_at_position(position) { 872 Some(value) => value, 873 None => return Ok(RenameOutcome::NoRenames.into_result()), 874 }; 875 876 let Some(module) = this.module_for_uri(&position.text_document.uri) else { 877 return Ok(RenameOutcome::NoRenames.into_result()); 878 }; 879 880 let referenced = reference_for_ast_node(found, &module.name); 881 882 Ok(match referenced { 883 Some(Referenced::LocalVariable { 884 origin, 885 definition_location, 886 name, 887 .. 888 }) => { 889 let rename_kind = match origin.map(|origin| origin.syntax) { 890 Some(VariableSyntax::Generated) => { 891 return Ok(RenameOutcome::NoRenames.into_result()); 892 } 893 Some(VariableSyntax::LabelShorthand(_)) => { 894 VariableReferenceKind::LabelShorthand 895 } 896 Some( 897 VariableSyntax::AssignmentPattern | VariableSyntax::Variable { .. }, 898 ) 899 | None => VariableReferenceKind::Variable, 900 }; 901 rename_local_variable( 902 module, 903 &lines, 904 &params, 905 definition_location, 906 name, 907 rename_kind, 908 ) 909 .into_result() 910 } 911 Some(Referenced::ModuleValue { 912 module: module_name, 913 target_kind, 914 name, 915 name_kind, 916 .. 917 }) => rename_module_entity( 918 &params, 919 module, 920 this.compiler.project_compiler.get_importable_modules(), 921 &this.compiler.sources, 922 Renamed { 923 module_name: &module_name, 924 name: &name, 925 name_kind, 926 target_kind, 927 layer: ast::Layer::Value, 928 }, 929 ) 930 .into_result(), 931 932 Some(Referenced::ModuleType { 933 module: module_name, 934 target_kind, 935 name, 936 .. 937 }) => rename_module_entity( 938 &params, 939 module, 940 this.compiler.project_compiler.get_importable_modules(), 941 &this.compiler.sources, 942 Renamed { 943 module_name: &module_name, 944 name: &name, 945 name_kind: Named::Type, 946 target_kind, 947 layer: ast::Layer::Type, 948 }, 949 ) 950 .into_result(), 951 952 Some(Referenced::ModuleName { module_name, .. }) => { 953 rename_module_alias(module, &lines, &params, &module_name).into_result() 954 } 955 956 Some(Referenced::TypeVariable { location, name }) => { 957 rename_type_variable(module, &lines, &params, location, name).into_result() 958 } 959 960 None => RenameOutcome::NoRenames.into_result(), 961 }) 962 }) 963 } 964 965 /// Shared core of find references and document highlight. 966 fn find_references_in_scope( 967 &mut self, 968 position: &lsp_types::TextDocumentPositionParams, 969 search_scope: FindReferencesSearchScope, 970 ) -> Option<Vec<lsp::Location>> { 971 let (lines, found) = self.node_at_position(position)?; 972 973 let uri = position.text_document.uri.clone(); 974 975 let source_module = self.module_for_uri(&uri)?; 976 977 let byte_index = lines.byte_index(position.position); 978 979 let referenced = reference_for_ast_node(found, &source_module.name); 980 981 match referenced { 982 Some(Referenced::LocalVariable { 983 origin, 984 definition_location, 985 location, 986 name, 987 }) if location.contains(byte_index) => match origin.map(|origin| origin.syntax) { 988 Some(VariableSyntax::Generated) => None, 989 Some( 990 VariableSyntax::LabelShorthand(_) 991 | VariableSyntax::AssignmentPattern 992 | VariableSyntax::Variable { .. }, 993 ) 994 | None => { 995 let variable_references = 996 FindVariableReferences::new(definition_location, name) 997 .find_in_module(&source_module.ast); 998 999 let mut reference_locations = Vec::with_capacity(variable_references.len() + 1); 1000 reference_locations.push(lsp::Location { 1001 uri: uri.clone(), 1002 range: src_span_to_lsp_range(definition_location, &lines), 1003 }); 1004 1005 for reference in variable_references { 1006 reference_locations.push(lsp::Location { 1007 uri: uri.clone(), 1008 range: src_span_to_lsp_range(reference.location, &lines), 1009 }) 1010 } 1011 1012 Some(reference_locations) 1013 } 1014 }, 1015 Some(Referenced::ModuleValue { 1016 module, 1017 name, 1018 location, 1019 .. 1020 }) if location.contains(byte_index) => match search_scope { 1021 FindReferencesSearchScope::AllModules => Some(find_module_references( 1022 module, 1023 name, 1024 self.compiler.project_compiler.get_importable_modules(), 1025 &self.compiler.sources, 1026 ast::Layer::Value, 1027 )), 1028 FindReferencesSearchScope::CurrentModule => { 1029 let source_information = self.compiler.get_source(&source_module.name)?; 1030 let source_module = self.compiler.get_module_interface(&source_module.name)?; 1031 Some(find_module_references_in_module( 1032 module, 1033 name, 1034 source_module, 1035 source_information, 1036 ast::Layer::Value, 1037 )) 1038 } 1039 }, 1040 Some(Referenced::ModuleType { 1041 module, 1042 name, 1043 location, 1044 .. 1045 }) if location.contains(byte_index) => match search_scope { 1046 FindReferencesSearchScope::AllModules => Some(find_module_references( 1047 module, 1048 name, 1049 self.compiler.project_compiler.get_importable_modules(), 1050 &self.compiler.sources, 1051 ast::Layer::Type, 1052 )), 1053 FindReferencesSearchScope::CurrentModule => { 1054 let source_information = self.compiler.get_source(&source_module.name)?; 1055 let source_module = self.compiler.get_module_interface(&source_module.name)?; 1056 Some(find_module_references_in_module( 1057 module, 1058 name, 1059 source_module, 1060 source_information, 1061 ast::Layer::Type, 1062 )) 1063 } 1064 }, 1065 _ => None, 1066 } 1067 } 1068 1069 pub fn find_references( 1070 &mut self, 1071 params: lsp::ReferenceParams, 1072 ) -> Response<Option<Vec<lsp::Location>>> { 1073 self.respond(|this| { 1074 let position = &params.text_document_position_params; 1075 Ok(this.find_references_in_scope(position, FindReferencesSearchScope::AllModules)) 1076 }) 1077 } 1078 1079 pub fn document_highlight( 1080 &mut self, 1081 params: lsp::DocumentHighlightParams, 1082 ) -> Response<Option<Vec<lsp::DocumentHighlight>>> { 1083 self.respond(|this| { 1084 let position = &params.text_document_position_params; 1085 Ok(this 1086 .find_references_in_scope(position, FindReferencesSearchScope::CurrentModule) 1087 .map(|references| { 1088 references 1089 .into_iter() 1090 .map(|loc| lsp::DocumentHighlight { 1091 range: loc.range, 1092 kind: None, 1093 }) 1094 .collect() 1095 })) 1096 }) 1097 } 1098 1099 /// Triggers after the renaming of one or more `.gleam` files, updating any 1100 /// imports to those modules. 1101 pub fn rename_files(&mut self, renames: Vec<(Url, Url)>) -> Response<Option<WorkspaceEdit>> { 1102 self.respond(|this| { 1103 let mut changes = HashMap::new(); 1104 1105 for (old_uri, new_uri) in renames { 1106 let Some(old_name) = this.module_name_for_uri(&old_uri) else { 1107 continue; 1108 }; 1109 let Some(new_name) = this.module_name_for_uri(&new_uri) else { 1110 continue; 1111 }; 1112 rename_module_occurrences( 1113 old_name, 1114 new_name, 1115 this.compiler.project_compiler.get_importable_modules(), 1116 &this.compiler.sources, 1117 &mut changes, 1118 ); 1119 } 1120 1121 Ok(Some(WorkspaceEdit { 1122 changes: Some(changes), 1123 document_changes: None, 1124 change_annotations: None, 1125 })) 1126 }) 1127 } 1128 1129 fn respond<T>(&mut self, handler: impl FnOnce(&mut Self) -> Result<T>) -> Response<T> { 1130 let result = handler(self); 1131 let warnings = self.take_warnings(); 1132 // TODO: test. Ensure hover doesn't report as compiled 1133 let compilation = if self.compiled_since_last_feedback { 1134 let modules = std::mem::take(&mut self.modules_compiled_since_last_feedback); 1135 self.compiled_since_last_feedback = false; 1136 Compilation::Yes(modules) 1137 } else { 1138 Compilation::No 1139 }; 1140 Response { 1141 result, 1142 warnings, 1143 compilation, 1144 } 1145 } 1146 1147 pub fn hover(&mut self, params: lsp::HoverParams) -> Response<Option<Hover>> { 1148 self.respond(|this| { 1149 let params = params.text_document_position_params; 1150 1151 let (lines, found) = match this.node_at_position(&params) { 1152 Some(value) => value, 1153 None => return Ok(None), 1154 }; 1155 1156 let Some(module) = this.module_for_uri(&params.text_document.uri) else { 1157 return Ok(None); 1158 }; 1159 1160 Ok(match found { 1161 Located::Statement(_) => None, // TODO: hover for statement 1162 Located::ModuleFunction(function) => { 1163 Some(hover_for_function_head(function, lines, module)) 1164 } 1165 Located::ModuleConstant(constant) => { 1166 Some(hover_for_module_constant(constant, lines, module)) 1167 } 1168 Located::Constant(constant) => Some(hover_for_constant(constant, lines, module)), 1169 Located::ModuleImport(import) => { 1170 let Some(module) = this.compiler.get_module_interface(&import.module) else { 1171 return Ok(None); 1172 }; 1173 Some(hover_for_module( 1174 module, 1175 import.location, 1176 &lines, 1177 &this.hex_deps, 1178 )) 1179 } 1180 Located::ModuleCustomType(custom_type) => { 1181 Some(hover_for_custom_type(custom_type, lines)) 1182 } 1183 Located::ModuleTypeAlias(_) => None, 1184 Located::VariantConstructorDefinition(constructor) => { 1185 Some(hover_for_constructor(constructor, lines, module)) 1186 } 1187 Located::UnqualifiedImport(UnqualifiedImport { 1188 name, 1189 module: module_name, 1190 is_type, 1191 location, 1192 }) => this 1193 .compiler 1194 .get_module_interface(module_name.as_str()) 1195 .and_then(|module_interface| { 1196 if is_type { 1197 module_interface.types.get(name).map(|constructor| { 1198 hover_for_annotation( 1199 *location, 1200 constructor.type_.as_ref(), 1201 Some(constructor), 1202 lines, 1203 module, 1204 ) 1205 }) 1206 } else { 1207 module_interface.values.get(name).map(|v| { 1208 let m = if this.hex_deps.contains(&module_interface.package) { 1209 Some(module_interface) 1210 } else { 1211 None 1212 }; 1213 hover_for_imported_value(v, location, lines, m, name, module) 1214 }) 1215 } 1216 }), 1217 Located::Pattern(pattern) => Some(hover_for_pattern(pattern, lines, module)), 1218 Located::PatternSpread { 1219 spread_location, 1220 pattern, 1221 } => { 1222 let range = Some(src_span_to_lsp_range(spread_location, &lines)); 1223 1224 let mut printer = Printer::new(&module.ast.names); 1225 1226 let PatternUnusedArguments { 1227 positional, 1228 labelled, 1229 } = pattern.unused_arguments().unwrap_or_default(); 1230 1231 let positional = positional 1232 .iter() 1233 .map(|type_| format!("- `{}`", printer.print_type(type_))) 1234 .join("\n"); 1235 let labelled = labelled 1236 .iter() 1237 .map(|(label, type_)| { 1238 format!("- `{}: {}`", label, printer.print_type(type_)) 1239 }) 1240 .join("\n"); 1241 1242 let content = match (positional.is_empty(), labelled.is_empty()) { 1243 (true, false) => format!("Unused labelled fields:\n{labelled}"), 1244 (false, true) => format!("Unused positional fields:\n{positional}"), 1245 (_, _) => format!( 1246 "Unused positional fields: 1247{positional} 1248 1249Unused labelled fields: 1250{labelled}" 1251 ), 1252 }; 1253 1254 Some(Hover { 1255 contents: Contents::MarkupContent(MarkupContent { 1256 value: content, 1257 kind: lsp_types::MarkupKind::Markdown, 1258 }), 1259 range, 1260 }) 1261 } 1262 Located::StringPrefixPatternVariable { location, .. } => Some( 1263 hover_for_string_prefix_pattern_variable(location, &lines, module), 1264 ), 1265 Located::Expression { 1266 expression, 1267 position, 1268 } => Some(hover_for_expression( 1269 expression, 1270 position, 1271 lines, 1272 module, 1273 &this.hex_deps, 1274 )), 1275 Located::Arg(arg) => Some(hover_for_function_argument(arg, lines, module)), 1276 Located::FunctionBody(_) => None, 1277 Located::Annotation { ast, type_ } => { 1278 let type_constructor = type_constructor_from_modules( 1279 this.compiler.project_compiler.get_importable_modules(), 1280 type_.clone(), 1281 ); 1282 Some(hover_for_annotation( 1283 ast.location(), 1284 &type_, 1285 type_constructor, 1286 lines, 1287 module, 1288 )) 1289 } 1290 Located::Label(location, type_) => { 1291 Some(hover_for_label(location, type_, lines, module)) 1292 } 1293 Located::ModuleName { 1294 location, 1295 module_name, 1296 .. 1297 } => { 1298 let Some(module) = this.compiler.get_module_interface(&module_name) else { 1299 return Ok(None); 1300 }; 1301 Some(hover_for_module(module, location, &lines, &this.hex_deps)) 1302 } 1303 1304 Located::ClauseGuard(guard) => Some(hover_for_clause_guard(guard, lines, module)), 1305 1306 Located::TypeVariable { .. } => None, 1307 }) 1308 }) 1309 } 1310 1311 pub(crate) fn signature_help( 1312 &mut self, 1313 params: lsp_types::SignatureHelpParams, 1314 ) -> Response<Option<SignatureHelp>> { 1315 self.respond(|this| { 1316 let Some(module) = 1317 this.module_for_uri(&params.text_document_position_params.text_document.uri) 1318 else { 1319 return Ok(None); 1320 }; 1321 1322 match this.node_at_position(&params.text_document_position_params) { 1323 Some((_lines, Located::Expression { expression, .. })) => { 1324 Ok(signature_help::for_expression(expression, module)) 1325 } 1326 Some((_lines, _located)) => Ok(None), 1327 None => Ok(None), 1328 } 1329 }) 1330 } 1331 1332 fn module_node_at_position( 1333 &self, 1334 params: &lsp::TextDocumentPositionParams, 1335 module: &'a Module, 1336 ) -> Option<(LineNumbers, Located<'a>)> { 1337 let line_numbers = LineNumbers::new(&module.code); 1338 let byte_index = line_numbers.byte_index(params.position); 1339 let node = module.find_node(byte_index); 1340 let node = node?; 1341 Some((line_numbers, node)) 1342 } 1343 1344 fn node_at_position( 1345 &self, 1346 params: &lsp::TextDocumentPositionParams, 1347 ) -> Option<(LineNumbers, Located<'_>)> { 1348 let module = self.module_for_uri(&params.text_document.uri)?; 1349 self.module_node_at_position(params, module) 1350 } 1351 1352 fn module_name_for_uri(&self, uri: &Url) -> Option<EcoString> { 1353 // The to_file_path method is available on these platforms 1354 #[cfg(any(unix, windows, target_os = "redox", target_os = "wasi"))] 1355 let path = uri.to_file_path().expect("URL file"); 1356 1357 #[cfg(not(any(unix, windows, target_os = "redox", target_os = "wasi")))] 1358 let path: Utf8PathBuf = uri.path().into(); 1359 1360 let components = path 1361 .strip_prefix(self.paths.root()) 1362 .ok()? 1363 .components() 1364 .skip(1) 1365 .map(|c| c.as_os_str().to_string_lossy()); 1366 let name = Itertools::intersperse(components, "/".into()) 1367 .collect::<String>() 1368 .strip_suffix(".gleam")? 1369 .into(); 1370 Some(name) 1371 } 1372 1373 fn module_for_uri(&self, uri: &Url) -> Option<&Module> { 1374 let module_name = self.module_name_for_uri(uri)?; 1375 self.compiler.modules.get(&module_name) 1376 } 1377 1378 #[cfg(test)] 1379 pub fn path_for_module_name(&self, module_name: &str) -> Utf8PathBuf { 1380 let src_directory = self.paths.src_directory(); 1381 src_directory.join(module_name).with_extension("gleam") 1382 } 1383} 1384 1385fn import_folding_spans( 1386 imports: &[ast::Import<EcoString>], 1387 code: &str, 1388 line_numbers: &LineNumbers, 1389) -> Vec<SrcSpan> { 1390 let mut spans = vec![]; 1391 let mut imports = imports.iter(); 1392 1393 let Some(first_import) = imports.next() else { 1394 return spans; 1395 }; 1396 1397 let mut previous_line = src_span_to_lsp_range( 1398 SrcSpan::new(first_import.location.start, first_import.location.start), 1399 line_numbers, 1400 ) 1401 .start 1402 .line; 1403 let mut current_start = first_import.location.start; 1404 let mut current_end = first_import.location.end; 1405 let mut current_len = 1; 1406 1407 for import in imports { 1408 let next_line = src_span_to_lsp_range( 1409 SrcSpan::new(import.location.start, import.location.start), 1410 line_numbers, 1411 ) 1412 .start 1413 .line; 1414 let separated_by_blank_line = next_line > previous_line + 1; 1415 let between = &code[current_end as usize..import.location.start as usize]; 1416 let has_non_whitespace_between = between.chars().any(|char| !char.is_whitespace()); 1417 1418 if separated_by_blank_line || has_non_whitespace_between { 1419 if current_len > 1 { 1420 spans.push(SrcSpan::new(current_start, current_end)); 1421 } 1422 current_start = import.location.start; 1423 current_end = import.location.end; 1424 current_len = 1; 1425 } else { 1426 current_end = import.location.end; 1427 current_len += 1; 1428 } 1429 1430 previous_line = next_line; 1431 } 1432 1433 if current_len > 1 { 1434 spans.push(SrcSpan::new(current_start, current_end)); 1435 } 1436 1437 spans 1438} 1439 1440fn folding_range_for_span( 1441 span: SrcSpan, 1442 line_numbers: &LineNumbers, 1443 kind: Option<FoldingRangeKind>, 1444) -> Option<FoldingRange> { 1445 let range = src_span_to_lsp_range(span, line_numbers); 1446 1447 if range.start.line >= range.end.line { 1448 return None; 1449 } 1450 1451 Some(FoldingRange { 1452 start_line: range.start.line, 1453 start_character: None, 1454 end_line: range.end.line, 1455 end_character: None, 1456 kind, 1457 collapsed_text: None, 1458 }) 1459} 1460 1461fn custom_type_symbol( 1462 type_: &CustomType<Arc<Type>>, 1463 line_numbers: &LineNumbers, 1464 module: &Module, 1465) -> DocumentSymbol { 1466 let constructors = type_ 1467 .constructors 1468 .iter() 1469 .map(|constructor| { 1470 let mut arguments = vec![]; 1471 1472 // List named arguments as field symbols. 1473 for argument in &constructor.arguments { 1474 let Some((label_location, label)) = &argument.label else { 1475 continue; 1476 }; 1477 1478 let full_arg_span = match argument.doc { 1479 Some((doc_position, _)) => { 1480 SrcSpan::new(get_doc_marker_position(doc_position), argument.location.end) 1481 } 1482 None => argument.location, 1483 }; 1484 1485 // The 'deprecated' field is deprecated, but we have to specify it anyway 1486 // to be able to construct the 'DocumentSymbol' type, so 1487 // we suppress the warning. We specify 'None' as specifying 'Some' 1488 // is what is actually deprecated. 1489 #[allow(deprecated)] 1490 arguments.push(DocumentSymbol { 1491 name: label.to_string(), 1492 detail: Some( 1493 Printer::new(&module.ast.names) 1494 .print_type(&argument.type_) 1495 .to_string(), 1496 ), 1497 kind: SymbolKind::Field, 1498 tags: None, 1499 deprecated: None, 1500 range: src_span_to_lsp_range(full_arg_span, line_numbers), 1501 selection_range: src_span_to_lsp_range(*label_location, line_numbers), 1502 children: None, 1503 }); 1504 } 1505 1506 // Start from the documentation if available, otherwise from the constructor's name, 1507 // all the way to the end of its arguments. 1508 let full_constructor_span = SrcSpan { 1509 start: constructor 1510 .documentation 1511 .as_ref() 1512 .map(|(doc_start, _)| get_doc_marker_position(*doc_start)) 1513 .unwrap_or(constructor.location.start), 1514 1515 end: constructor.location.end, 1516 }; 1517 1518 // The 'deprecated' field is deprecated, but we have to specify it anyway 1519 // to be able to construct the 'DocumentSymbol' type, so 1520 // we suppress the warning. We specify 'None' as specifying 'Some' 1521 // is what is actually deprecated. 1522 #[allow(deprecated)] 1523 DocumentSymbol { 1524 name: constructor.name.to_string(), 1525 detail: None, 1526 kind: if constructor.arguments.is_empty() { 1527 SymbolKind::EnumMember 1528 } else { 1529 SymbolKind::Constructor 1530 }, 1531 tags: make_deprecated_symbol_tag(&constructor.deprecation), 1532 deprecated: None, 1533 range: src_span_to_lsp_range(full_constructor_span, line_numbers), 1534 selection_range: src_span_to_lsp_range(constructor.name_location, line_numbers), 1535 children: if arguments.is_empty() { 1536 None 1537 } else { 1538 Some(arguments) 1539 }, 1540 } 1541 }) 1542 .collect_vec(); 1543 1544 // The type's location, by default, ranges from "(pub) type" to the end of its name. 1545 // We need it to range to the end of its constructors instead for the full symbol range. 1546 // We also include documentation, if available, by LSP convention. 1547 let full_type_span = SrcSpan { 1548 start: type_ 1549 .documentation 1550 .as_ref() 1551 .map(|(doc_start, _)| get_doc_marker_position(*doc_start)) 1552 .unwrap_or(type_.location.start), 1553 1554 end: type_.end_position, 1555 }; 1556 1557 // The 'deprecated' field is deprecated, but we have to specify it anyway 1558 // to be able to construct the 'DocumentSymbol' type, so 1559 // we suppress the warning. We specify 'None' as specifying 'Some' 1560 // is what is actually deprecated. 1561 #[allow(deprecated)] 1562 DocumentSymbol { 1563 name: type_.name.to_string(), 1564 detail: None, 1565 kind: SymbolKind::Class, 1566 tags: make_deprecated_symbol_tag(&type_.deprecation), 1567 deprecated: None, 1568 range: src_span_to_lsp_range(full_type_span, line_numbers), 1569 selection_range: src_span_to_lsp_range(type_.name_location, line_numbers), 1570 children: if constructors.is_empty() { 1571 None 1572 } else { 1573 Some(constructors) 1574 }, 1575 } 1576} 1577 1578fn hover_for_pattern(pattern: &TypedPattern, line_numbers: LineNumbers, module: &Module) -> Hover { 1579 let documentation = pattern.get_documentation().unwrap_or_default(); 1580 1581 // Show the type of the hovered node to the user 1582 let type_ = Printer::new(&module.ast.names).print_type(pattern.type_().as_ref()); 1583 let contents = format!( 1584 "```gleam 1585{type_} 1586``` 1587{documentation}" 1588 ); 1589 Hover { 1590 contents: Contents::MarkedString(MarkedString::String(contents)), 1591 range: Some(src_span_to_lsp_range(pattern.location(), &line_numbers)), 1592 } 1593} 1594 1595fn get_function_type(fun: &TypedFunction) -> Type { 1596 Type::Fn { 1597 arguments: fun 1598 .arguments 1599 .iter() 1600 .map(|argument| argument.type_.clone()) 1601 .collect(), 1602 return_: fun.return_type.clone(), 1603 } 1604} 1605 1606fn hover_for_function_head( 1607 fun: &TypedFunction, 1608 line_numbers: LineNumbers, 1609 module: &Module, 1610) -> Hover { 1611 let empty_str = EcoString::from(""); 1612 let documentation = fun 1613 .documentation 1614 .as_ref() 1615 .map(|(_, doc)| doc) 1616 .unwrap_or(&empty_str); 1617 let function_type = get_function_type(fun); 1618 let formatted_type = Printer::new(&module.ast.names).print_type(&function_type); 1619 let contents = format!( 1620 "```gleam 1621{formatted_type} 1622``` 1623{documentation}" 1624 ); 1625 Hover { 1626 contents: Contents::MarkedString(MarkedString::String(contents)), 1627 range: Some(src_span_to_lsp_range(fun.location, &line_numbers)), 1628 } 1629} 1630 1631fn hover_for_function_argument( 1632 argument: &TypedArg, 1633 line_numbers: LineNumbers, 1634 module: &Module, 1635) -> Hover { 1636 let type_ = Printer::new(&module.ast.names).print_type(&argument.type_); 1637 let contents = format!("```gleam\n{type_}\n```"); 1638 Hover { 1639 contents: Contents::MarkedString(MarkedString::String(contents)), 1640 range: Some(src_span_to_lsp_range(argument.location, &line_numbers)), 1641 } 1642} 1643 1644fn hover_for_annotation( 1645 location: SrcSpan, 1646 annotation_type: &Type, 1647 type_constructor: Option<&TypeConstructor>, 1648 line_numbers: LineNumbers, 1649 module: &Module, 1650) -> Hover { 1651 let empty_str = EcoString::from(""); 1652 let documentation = type_constructor 1653 .and_then(|constructor| constructor.documentation.as_ref()) 1654 .unwrap_or(&empty_str); 1655 // If a user is hovering an annotation, it's not very useful to show the 1656 // local representation of that type, since that's probably what they see 1657 // in the source code anyway. So here, we print the raw type, 1658 // which is probably more helpful. 1659 let type_ = Printer::new(&module.ast.names).print_type_without_aliases(annotation_type); 1660 let contents = format!( 1661 "```gleam 1662{type_} 1663``` 1664{documentation}" 1665 ); 1666 Hover { 1667 contents: Contents::MarkedString(MarkedString::String(contents)), 1668 range: Some(src_span_to_lsp_range(location, &line_numbers)), 1669 } 1670} 1671 1672fn hover_for_label( 1673 location: SrcSpan, 1674 type_: Arc<Type>, 1675 line_numbers: LineNumbers, 1676 module: &Module, 1677) -> Hover { 1678 let type_ = Printer::new(&module.ast.names).print_type(&type_); 1679 let contents = format!("```gleam\n{type_}\n```"); 1680 Hover { 1681 contents: Contents::MarkedString(MarkedString::String(contents)), 1682 range: Some(src_span_to_lsp_range(location, &line_numbers)), 1683 } 1684} 1685 1686fn hover_for_module_constant( 1687 constant: &ModuleConstant<Arc<Type>>, 1688 line_numbers: LineNumbers, 1689 module: &Module, 1690) -> Hover { 1691 let empty_str = EcoString::from(""); 1692 let type_ = Printer::new(&module.ast.names).print_type(&constant.type_); 1693 let documentation = constant 1694 .documentation 1695 .as_ref() 1696 .map(|(_, doc)| doc) 1697 .unwrap_or(&empty_str); 1698 let contents = format!("```gleam\n{type_}\n```\n{documentation}"); 1699 Hover { 1700 contents: Contents::MarkedString(MarkedString::String(contents)), 1701 range: Some(src_span_to_lsp_range(constant.location, &line_numbers)), 1702 } 1703} 1704 1705fn hover_for_constant( 1706 constant: &TypedConstant, 1707 line_numbers: LineNumbers, 1708 module: &Module, 1709) -> Hover { 1710 let type_ = Printer::new(&module.ast.names).print_type(&constant.type_()); 1711 let contents = format!("```gleam\n{type_}\n```"); 1712 Hover { 1713 contents: Contents::MarkedString(MarkedString::String(contents)), 1714 range: Some(src_span_to_lsp_range(constant.location(), &line_numbers)), 1715 } 1716} 1717 1718fn hover_for_clause_guard( 1719 guard: &TypedClauseGuard, 1720 line_numbers: LineNumbers, 1721 module: &Module, 1722) -> Hover { 1723 let type_ = Printer::new(&module.ast.names).print_type(&guard.type_()); 1724 let contents = format!("```gleam\n{type_}\n```"); 1725 Hover { 1726 contents: Contents::MarkedString(MarkedString::String(contents)), 1727 range: Some(src_span_to_lsp_range(guard.location(), &line_numbers)), 1728 } 1729} 1730 1731fn hover_for_string_prefix_pattern_variable( 1732 location: SrcSpan, 1733 lines: &LineNumbers, 1734 module: &Module, 1735) -> Hover { 1736 let type_ = Printer::new(&module.ast.names).print_type(&type_::string()); 1737 let contents = format!("```gleam\n{type_}\n```"); 1738 Hover { 1739 contents: Contents::MarkedString(MarkedString::String(contents)), 1740 range: Some(src_span_to_lsp_range(location, lines)), 1741 } 1742} 1743 1744fn hover_for_expression<'a>( 1745 expression: &'a TypedExpr, 1746 position: ExpressionPosition<'a>, 1747 line_numbers: LineNumbers, 1748 module: &Module, 1749 hex_deps: &HashSet<EcoString>, 1750) -> Hover { 1751 let documentation = expression.get_documentation().unwrap_or_default(); 1752 1753 let link_section = get_expr_qualified_name(expression) 1754 .and_then(|(module_name, name)| { 1755 get_hexdocs_link_section(module_name, name, &module.ast, hex_deps) 1756 }) 1757 .unwrap_or("".to_string()); 1758 1759 // Show the type of the hovered node to the user 1760 let mut printer = Printer::new(&module.ast.names); 1761 let type_ = printer.print_type(expression.type_().as_ref()); 1762 1763 // If the expression is a record update and there's record fields that are 1764 // begin implicitly updated, then we want to list them in the hover. 1765 let unchanged_record_update_fields = match position { 1766 ExpressionPosition::Expression | ExpressionPosition::ArgumentOrLabel { .. } => "", 1767 1768 ExpressionPosition::UpdatedRecord { 1769 unchanged_record_fields, 1770 } if !unchanged_record_fields.is_empty() => &format!( 1771 "Unchanged record fields:\n{}", 1772 unchanged_record_fields 1773 .iter() 1774 .map(|(label, type_)| format!("- `{}: {}`", label, printer.print_type(type_))) 1775 .join("\n") 1776 ), 1777 ExpressionPosition::UpdatedRecord { .. } => "", 1778 }; 1779 1780 let description = [documentation, unchanged_record_update_fields, &link_section] 1781 .iter() 1782 .filter(|string| !string.is_empty()) 1783 .join("\n\n"); 1784 1785 let contents = format!( 1786 "```gleam 1787{type_} 1788``` 1789{description}" 1790 ); 1791 1792 Hover { 1793 contents: Contents::MarkedString(MarkedString::String(contents)), 1794 range: Some(src_span_to_lsp_range(expression.location(), &line_numbers)), 1795 } 1796} 1797 1798fn hover_for_imported_value( 1799 value: &ValueConstructor, 1800 location: &SrcSpan, 1801 line_numbers: LineNumbers, 1802 hex_module_imported_from: Option<&ModuleInterface>, 1803 name: &EcoString, 1804 module: &Module, 1805) -> Hover { 1806 let documentation = value.get_documentation().unwrap_or_default(); 1807 1808 let link_section = hex_module_imported_from.map_or("".to_string(), |m| { 1809 format_hexdocs_link_section(m.package.as_str(), m.name.as_str(), Some(name)) 1810 }); 1811 1812 // Show the type of the hovered node to the user 1813 let type_ = Printer::new(&module.ast.names).print_type(value.type_.as_ref()); 1814 let contents = format!( 1815 "```gleam 1816{type_} 1817``` 1818{documentation}{link_section}" 1819 ); 1820 Hover { 1821 contents: Contents::MarkedString(MarkedString::String(contents)), 1822 range: Some(src_span_to_lsp_range(*location, &line_numbers)), 1823 } 1824} 1825 1826fn hover_for_module( 1827 module: &ModuleInterface, 1828 location: SrcSpan, 1829 line_numbers: &LineNumbers, 1830 hex_deps: &HashSet<EcoString>, 1831) -> Hover { 1832 let documentation = module.documentation.join("\n"); 1833 let name = &module.name; 1834 1835 let link_section = if hex_deps.contains(&module.package) { 1836 format_hexdocs_link_section(&module.package, name, None) 1837 } else { 1838 String::new() 1839 }; 1840 1841 let contents = format!( 1842 "```gleam 1843{name} 1844``` 1845{documentation} 1846{link_section}", 1847 ); 1848 Hover { 1849 contents: Contents::MarkedString(MarkedString::String(contents)), 1850 range: Some(src_span_to_lsp_range(location, line_numbers)), 1851 } 1852} 1853 1854fn hover_for_custom_type(type_: &CustomType<Arc<Type>>, line_numbers: LineNumbers) -> Hover { 1855 let name = &type_.name; 1856 let documentation = type_ 1857 .documentation 1858 .as_ref() 1859 .map(|(_, documentation)| documentation.clone()) 1860 .unwrap_or_default(); 1861 1862 let contents = format!("```gleam\n{name}\n```\n{documentation}"); 1863 Hover { 1864 contents: Contents::MarkedString(MarkedString::String(contents)), 1865 range: Some(src_span_to_lsp_range(type_.full_location(), &line_numbers)), 1866 } 1867} 1868 1869fn hover_for_constructor( 1870 constructor: &TypedRecordConstructor, 1871 line_numbers: LineNumbers, 1872 module: &Module, 1873) -> Hover { 1874 let mut printer = Printer::new(&module.ast.names); 1875 1876 let arguments = constructor 1877 .arguments 1878 .iter() 1879 .map(|argument| match &argument.label { 1880 Some((_, label)) => eco_format!("{label}: {}", printer.print_type(&argument.type_)), 1881 None => printer.print_type(&argument.type_), 1882 }) 1883 .join(", "); 1884 1885 let documentation = constructor 1886 .documentation 1887 .as_ref() 1888 .map(|(_, documentation)| documentation.clone()) 1889 .unwrap_or_default(); 1890 1891 let constructor_doc = if arguments.is_empty() { 1892 constructor.name.clone() 1893 } else { 1894 eco_format!("{}({arguments})", constructor.name) 1895 }; 1896 1897 let contents = format!("```gleam\n{constructor_doc}\n```\n{documentation}"); 1898 Hover { 1899 contents: Contents::MarkedString(MarkedString::String(contents)), 1900 range: Some(src_span_to_lsp_range(constructor.location, &line_numbers)), 1901 } 1902} 1903 1904/// Returns true if any part of either range overlaps with the other. 1905pub fn overlaps(a: Range, b: Range) -> bool { 1906 position_within(a.start, b) 1907 || position_within(a.end, b) 1908 || position_within(b.start, a) 1909 || position_within(b.end, a) 1910} 1911 1912/// Returns true if the first range is within the second range. 1913/// The ranges might touch on their extremes and still be considered one within 1914/// the other! 1915/// 1916/// `within(a, b)` is true in all of these cases: 1917/// 1918/// - ```txt 1919/// |------| b 1920/// |---| a 1921/// ``` 1922/// - ```txt 1923/// |------| b 1924/// |---| a 1925/// ``` 1926/// - ```txt 1927/// |------| b 1928/// |---| a 1929/// ``` 1930/// - ```txt 1931/// |------| b 1932/// |------| a 1933/// ``` 1934/// 1935pub fn within(a: Range, b: Range) -> bool { 1936 position_within(a.start, b) && position_within(a.end, b) 1937} 1938 1939/// Returns true if the first range is completely within the second range. 1940/// The range cannot have any extreme in common to be considered completely 1941/// within another. 1942/// 1943/// `completely_within(a, b)` is true this case: 1944/// 1945/// - ```txt 1946/// |------| b 1947/// |---| a 1948/// ``` 1949/// 1950/// And `completely_within(a, b)` is false in all these cases: 1951/// 1952/// - ```txt 1953/// |------| b 1954/// |---| a 1955/// ``` 1956/// - ```txt 1957/// |------| b 1958/// |---| a 1959/// ``` 1960/// - ```txt 1961/// |------| b 1962/// |------| a 1963/// ``` 1964/// 1965pub fn completely_within(a: Range, b: Range) -> bool { 1966 a.start > b.start && a.start < b.end && a.end > b.start && a.end < b.end 1967} 1968 1969// Returns true if a position is within a range. 1970pub fn position_within(position: Position, range: Range) -> bool { 1971 position >= range.start && position <= range.end 1972} 1973 1974/// Builds the code action to assign an unused value to `_`. 1975/// 1976fn code_action_unused_values( 1977 module: &Module, 1978 line_numbers: &LineNumbers, 1979 params: &lsp::CodeActionParams, 1980 actions: &mut Vec<CodeAction>, 1981) { 1982 let uri = &params.text_document.uri; 1983 let mut unused_values: Vec<&SrcSpan> = module 1984 .ast 1985 .type_info 1986 .warnings 1987 .iter() 1988 .filter_map(|warning| { 1989 if let type_::Warning::ImplicitlyDiscardedResult { location } = warning { 1990 Some(location) 1991 } else { 1992 None 1993 } 1994 }) 1995 .collect(); 1996 1997 if unused_values.is_empty() { 1998 return; 1999 } 2000 2001 // Sort spans by start position, with longer spans coming first 2002 unused_values.sort_by_key(|span| (span.start, -(span.len() as i64))); 2003 2004 let mut processed_lsp_range = Vec::new(); 2005 2006 for unused in unused_values { 2007 let SrcSpan { start, end } = *unused; 2008 let hover_range = src_span_to_lsp_range(SrcSpan::new(start, end), line_numbers); 2009 2010 // Check if this span is contained within any previously processed span 2011 if processed_lsp_range 2012 .iter() 2013 .any(|&prev_lsp_range| within(hover_range, prev_lsp_range)) 2014 { 2015 continue; 2016 } 2017 2018 // Check if the cursor is within this span 2019 if !within(params.range, hover_range) { 2020 continue; 2021 } 2022 2023 let edit = TextEdit { 2024 range: src_span_to_lsp_range(SrcSpan::new(start, start), line_numbers), 2025 new_text: "let _ = ".into(), 2026 }; 2027 2028 CodeActionBuilder::new("Assign unused Result value to `_`") 2029 .kind(lsp_types::CodeActionKind::QuickFix) 2030 .changes(uri.clone(), vec![edit]) 2031 .preferred(true) 2032 .push_to(actions); 2033 2034 processed_lsp_range.push(hover_range); 2035 } 2036} 2037 2038struct NameCorrection { 2039 pub location: SrcSpan, 2040 pub correction: EcoString, 2041} 2042 2043fn code_action_fix_names( 2044 module: &Module, 2045 line_numbers: &LineNumbers, 2046 params: &lsp::CodeActionParams, 2047 error: &Option<Error>, 2048 actions: &mut Vec<CodeAction>, 2049) { 2050 let uri = &params.text_document.uri; 2051 let Some(errors) = type_errors_for_module(error, module) else { 2052 return; 2053 }; 2054 let name_corrections = errors 2055 .iter() 2056 .filter_map(|error| { 2057 if let type_::Error::BadName { 2058 location, 2059 name, 2060 kind, 2061 } = error 2062 { 2063 Some(NameCorrection { 2064 correction: correct_name_case(name, *kind), 2065 location: *location, 2066 }) 2067 } else { 2068 None 2069 } 2070 }) 2071 .collect_vec(); 2072 2073 if name_corrections.is_empty() { 2074 return; 2075 } 2076 2077 for name_correction in name_corrections { 2078 let NameCorrection { 2079 location, 2080 correction, 2081 } = name_correction; 2082 2083 let range = src_span_to_lsp_range(location, line_numbers); 2084 // Check if the user's cursor is on the invalid name 2085 if overlaps(params.range, range) { 2086 let edit = TextEdit { 2087 range, 2088 new_text: correction.to_string(), 2089 }; 2090 2091 CodeActionBuilder::new(&format!("Rename to {correction}")) 2092 .kind(lsp_types::CodeActionKind::QuickFix) 2093 .changes(uri.clone(), vec![edit]) 2094 .preferred(true) 2095 .push_to(actions); 2096 } 2097 } 2098} 2099 2100fn get_expr_qualified_name(expression: &TypedExpr) -> Option<(&EcoString, &EcoString)> { 2101 match expression { 2102 TypedExpr::Var { 2103 name, constructor, .. 2104 } if constructor.publicity.is_importable() => match &constructor.variant { 2105 ValueConstructorVariant::ModuleFn { 2106 module: module_name, 2107 .. 2108 } => Some((module_name, name)), 2109 2110 ValueConstructorVariant::ModuleConstant { 2111 module: module_name, 2112 .. 2113 } => Some((module_name, name)), 2114 2115 ValueConstructorVariant::LocalVariable { .. } 2116 | ValueConstructorVariant::Record { .. } => None, 2117 }, 2118 2119 TypedExpr::ModuleSelect { 2120 label, module_name, .. 2121 } => Some((module_name, label)), 2122 2123 TypedExpr::Int { .. } 2124 | TypedExpr::Float { .. } 2125 | TypedExpr::String { .. } 2126 | TypedExpr::Block { .. } 2127 | TypedExpr::Pipeline { .. } 2128 | TypedExpr::Var { .. } 2129 | TypedExpr::Fn { .. } 2130 | TypedExpr::List { .. } 2131 | TypedExpr::Call { .. } 2132 | TypedExpr::BinOp { .. } 2133 | TypedExpr::Case { .. } 2134 | TypedExpr::RecordAccess { .. } 2135 | TypedExpr::PositionalAccess { .. } 2136 | TypedExpr::Tuple { .. } 2137 | TypedExpr::TupleIndex { .. } 2138 | TypedExpr::Todo { .. } 2139 | TypedExpr::Panic { .. } 2140 | TypedExpr::Echo { .. } 2141 | TypedExpr::BitArray { .. } 2142 | TypedExpr::RecordUpdate { .. } 2143 | TypedExpr::NegateBool { .. } 2144 | TypedExpr::NegateInt { .. } 2145 | TypedExpr::Invalid { .. } => None, 2146 } 2147} 2148 2149fn format_hexdocs_link_section( 2150 package_name: &str, 2151 module_name: &str, 2152 name: Option<&str>, 2153) -> String { 2154 let package_name = package_name.replace('_', "-"); 2155 let link = match name { 2156 Some(name) => format!("https://{package_name}.hexdocs.pm/{module_name}.html#{name}"), 2157 None => format!("https://{package_name}.hexdocs.pm/{module_name}.html"), 2158 }; 2159 format!("\nView on [HexDocs]({link})") 2160} 2161 2162fn get_hexdocs_link_section( 2163 module_name: &str, 2164 name: &str, 2165 ast: &TypedModule, 2166 hex_deps: &HashSet<EcoString>, 2167) -> Option<String> { 2168 let package_name = ast.definitions.imports.iter().find_map(|import| { 2169 if import.module == module_name && hex_deps.contains(&import.package) { 2170 Some(&import.package) 2171 } else { 2172 None 2173 } 2174 })?; 2175 2176 Some(format_hexdocs_link_section( 2177 package_name, 2178 module_name, 2179 Some(name), 2180 )) 2181} 2182 2183/// Converts the source start position of a documentation comment's contents into 2184/// the position of the leading slash in its marker ('///'). 2185fn get_doc_marker_position(content_pos: u32) -> u32 { 2186 content_pos.saturating_sub(3) 2187} 2188 2189fn make_deprecated_symbol_tag(deprecation: &Deprecation) -> Option<Vec<SymbolTag>> { 2190 deprecation 2191 .is_deprecated() 2192 .then(|| vec![SymbolTag::Deprecated]) 2193} 2194 2195enum FindReferencesSearchScope { 2196 AllModules, 2197 CurrentModule, 2198}