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 2197 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::{BeamCompiler, 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_import_module, 61 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 + BeamCompiler 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_add_missing_patterns(module, &lines, &params, &this.error, &mut actions); 447 actions 448 .extend(RemoveUnreachableCaseClauses::new(module, &lines, &params).code_actions()); 449 actions 450 .extend(RemoveRedundantRecordUpdate::new(module, &lines, &params).code_actions()); 451 actions.extend(CollapseNestedCase::new(module, &lines, &params).code_actions()); 452 actions.extend(FixBinaryOperation::new(module, &lines, &params).code_actions()); 453 actions 454 .extend(FixTruncatedBitArraySegment::new(module, &lines, &params).code_actions()); 455 actions.extend(RemovePrivateOpaque::new(module, &lines, &params).code_actions()); 456 actions.extend(AddMissingTypeParameter::new(module, &lines, &params).code_actions()); 457 code_action_convert_qualified_constructor_to_unqualified( 458 module, 459 &this.compiler, 460 &lines, 461 &params, 462 &mut actions, 463 ); 464 code_action_convert_unqualified_constructor_to_qualified( 465 module, 466 &lines, 467 &params, 468 &mut actions, 469 ); 470 code_action_inexhaustive_let_to_case( 471 module, 472 &lines, 473 &params, 474 &this.error, 475 &mut actions, 476 ); 477 actions.extend(MergeCaseBranches::new(module, &lines, &params).code_actions()); 478 actions.extend(LetAssertToCase::new(module, &lines, &params).code_actions()); 479 actions 480 .extend(RedundantTupleInCaseSubject::new(module, &lines, &params).code_actions()); 481 actions.extend(FillInMissingLabelledArgs::new(module, &lines, &params).code_actions()); 482 actions.extend(UseLabelShorthandSyntax::new(module, &lines, &params).code_actions()); 483 actions.extend(ConvertFromUse::new(module, &lines, &params).code_actions()); 484 actions.extend(RemoveEchos::new(module, &lines, &params).code_actions()); 485 actions.extend(ConvertToUse::new(module, &lines, &params).code_actions()); 486 actions.extend(ExpandFunctionCapture::new(module, &lines, &params).code_actions()); 487 actions.extend(FillUnusedFields::new(module, &lines, &params).code_actions()); 488 actions.extend(InterpolateString::new(module, &lines, &params).code_actions()); 489 actions.extend(ExtractVariable::new(module, &lines, &params).code_actions()); 490 actions.extend(ExtractConstant::new(module, &lines, &params).code_actions()); 491 actions.extend( 492 GenerateFunction::new(module, &this.compiler.modules, &lines, &params) 493 .code_actions(), 494 ); 495 actions.extend( 496 GenerateVariant::new(module, &this.compiler, &lines, &params).code_actions(), 497 ); 498 actions.extend(ConvertToPipe::new(module, &lines, &params).code_actions()); 499 actions.extend(ConvertToFunctionCall::new(module, &lines, &params).code_actions()); 500 actions.extend( 501 PatternMatchOnValue::new(module, &lines, &params, &this.compiler).code_actions(), 502 ); 503 actions.extend(AddOmittedLabels::new(module, &lines, &params).code_actions()); 504 actions.extend(InlineVariable::new(module, &lines, &params).code_actions()); 505 actions.extend(WrapInBlock::new(module, &lines, &params).code_actions()); 506 actions.extend(RemoveBlock::new(module, &lines, &params).code_actions()); 507 actions.extend(ExtractFunction::new(module, &lines, &params).code_actions()); 508 GenerateDynamicDecoder::new(module, &lines, &params, &mut actions, &this.compiler) 509 .code_actions(); 510 actions.extend(WrapInAnonymousFunction::new(module, &lines, &params).code_actions()); 511 actions.extend(UnwrapAnonymousFunction::new(module, &lines, &params).code_actions()); 512 GenerateJsonEncoder::new( 513 module, 514 &lines, 515 &params, 516 &mut actions, 517 &this.compiler.project_compiler.config, 518 ) 519 .code_actions(); 520 AddAnnotations::new(module, &lines, &params).code_action(&mut actions); 521 actions 522 .extend(AnnotateTopLevelDefinitions::new(module, &lines, &params).code_actions()); 523 actions.extend(ReplaceUnderscoreWithType::new(module, &lines, &params).code_actions()); 524 actions.extend( 525 CreateUnknownModule::new( 526 module, 527 &this.compiler, 528 &lines, 529 &params, 530 &this.paths, 531 &this.error, 532 ) 533 .code_actions(), 534 ); 535 536 actions.sort_by_key(|one| { 537 let preferred_key = if one.is_preferred == Some(true) { 0 } else { 1 }; 538 let kind_key = match &one.kind { 539 Some(lsp_types::CodeActionKind::QuickFix) => 1, 540 Some(lsp_types::CodeActionKind::Refactor) => 2, 541 Some(lsp_types::CodeActionKind::RefactorExtract) => 2, 542 Some(lsp_types::CodeActionKind::RefactorInline) => 2, 543 Some(lsp_types::CodeActionKind::RefactorMove) => 2, 544 Some(lsp_types::CodeActionKind::RefactorRewrite) => 2, 545 Some(lsp_types::CodeActionKind::Source) => 3, 546 Some(lsp_types::CodeActionKind::SourceOrganizeImports) => 3, 547 Some(lsp_types::CodeActionKind::SourceFixAll) => 3, 548 Some(lsp_types::CodeActionKind::Custom(_)) => 4, 549 Some(lsp_types::CodeActionKind::Notebook) => 5, 550 Some(lsp_types::CodeActionKind::Empty) => 6, 551 None => 7, 552 }; 553 (preferred_key, kind_key) 554 }); 555 556 Ok(if actions.is_empty() { 557 None 558 } else { 559 Some(actions) 560 }) 561 }) 562 } 563 564 pub fn document_symbol( 565 &mut self, 566 params: lsp::DocumentSymbolParams, 567 ) -> Response<Vec<DocumentSymbol>> { 568 self.respond(|this| { 569 let mut symbols = vec![]; 570 let Some(module) = this.module_for_uri(&params.text_document.uri) else { 571 return Ok(symbols); 572 }; 573 let line_numbers = LineNumbers::new(&module.code); 574 575 for function in &module.ast.definitions.functions { 576 // By default, the function's location ends right after the return type. 577 // For the full symbol range, have it end at the end of the body. 578 // Also include the documentation, if available. 579 // 580 // By convention, the symbol span starts from the leading slash in the 581 // documentation comment's marker ('///'), not from its content (of which 582 // we have the position), so we must convert the content start position 583 // to the leading slash's position. 584 let full_function_span = SrcSpan { 585 start: function 586 .documentation 587 .as_ref() 588 .map(|(doc_start, _)| get_doc_marker_position(*doc_start)) 589 .unwrap_or(function.location.start), 590 591 end: function.end_position, 592 }; 593 594 let (name_location, name) = function 595 .name 596 .as_ref() 597 .expect("Function in a definition must be named"); 598 599 // The 'deprecated' field is deprecated, but we have to specify it anyway 600 // to be able to construct the 'DocumentSymbol' type, so 601 // we suppress the warning. We specify 'None' as specifying 'Some' 602 // is what is actually deprecated. 603 #[allow(deprecated)] 604 symbols.push(DocumentSymbol { 605 name: name.to_string(), 606 detail: Some( 607 Printer::new(&module.ast.names) 608 .print_type(&get_function_type(function)) 609 .to_string(), 610 ), 611 kind: SymbolKind::Function, 612 tags: make_deprecated_symbol_tag(&function.deprecation), 613 deprecated: None, 614 range: src_span_to_lsp_range(full_function_span, &line_numbers), 615 selection_range: src_span_to_lsp_range(*name_location, &line_numbers), 616 children: None, 617 }); 618 } 619 620 for alias in &module.ast.definitions.type_aliases { 621 let full_alias_span = match alias.documentation { 622 Some((doc_position, _)) => { 623 SrcSpan::new(get_doc_marker_position(doc_position), alias.location.end) 624 } 625 None => alias.location, 626 }; 627 628 // The 'deprecated' field is deprecated, but we have to specify it anyway 629 // to be able to construct the 'DocumentSymbol' type, so 630 // we suppress the warning. We specify 'None' as specifying 'Some' 631 // is what is actually deprecated. 632 #[allow(deprecated)] 633 symbols.push(DocumentSymbol { 634 name: alias.alias.to_string(), 635 detail: Some( 636 Printer::new(&module.ast.names) 637 // If we print with aliases, we end up printing the alias which the user 638 // is currently hovering, which is not helpful. Instead, we print the 639 // raw type, so the user can see which type the alias represents 640 .print_type_without_aliases(&alias.type_) 641 .to_string(), 642 ), 643 kind: SymbolKind::Class, 644 tags: make_deprecated_symbol_tag(&alias.deprecation), 645 deprecated: None, 646 range: src_span_to_lsp_range(full_alias_span, &line_numbers), 647 selection_range: src_span_to_lsp_range(alias.name_location, &line_numbers), 648 children: None, 649 }); 650 } 651 652 for custom_type in &module.ast.definitions.custom_types { 653 symbols.push(custom_type_symbol(custom_type, &line_numbers, module)); 654 } 655 656 for constant in &module.ast.definitions.constants { 657 // `ModuleConstant.location` ends at the constant's name or type. 658 // For the full symbol span, necessary for `range`, we need to 659 // include the constant value as well. 660 // Also include the documentation at the start, if available. 661 let full_constant_span = SrcSpan { 662 start: constant 663 .documentation 664 .as_ref() 665 .map(|(doc_start, _)| get_doc_marker_position(*doc_start)) 666 .unwrap_or(constant.location.start), 667 668 end: constant.value.location().end, 669 }; 670 671 // The 'deprecated' field is deprecated, but we have to specify it anyway 672 // to be able to construct the 'DocumentSymbol' type, so 673 // we suppress the warning. We specify 'None' as specifying 'Some' 674 // is what is actually deprecated. 675 #[allow(deprecated)] 676 symbols.push(DocumentSymbol { 677 name: constant.name.to_string(), 678 detail: Some( 679 Printer::new(&module.ast.names) 680 .print_type(&constant.type_) 681 .to_string(), 682 ), 683 kind: SymbolKind::Constant, 684 tags: make_deprecated_symbol_tag(&constant.deprecation), 685 deprecated: None, 686 range: src_span_to_lsp_range(full_constant_span, &line_numbers), 687 selection_range: src_span_to_lsp_range(constant.name_location, &line_numbers), 688 children: None, 689 }); 690 } 691 692 Ok(symbols) 693 }) 694 } 695 696 pub fn folding_range( 697 &mut self, 698 params: lsp::FoldingRangeParams, 699 ) -> Response<Vec<FoldingRange>> { 700 self.respond(|this| { 701 let mut ranges: Vec<FoldingRange> = vec![]; 702 let Some(module) = this.module_for_uri(&params.text_document.uri) else { 703 return Ok(vec![]); 704 }; 705 706 let line_numbers = LineNumbers::new(&module.code); 707 708 for import in 709 import_folding_spans(&module.ast.definitions.imports, &module.code, &line_numbers) 710 { 711 let Some(range) = 712 folding_range_for_span(import, &line_numbers, Some(FoldingRangeKind::Imports)) 713 else { 714 continue; 715 }; 716 717 ranges.push(range); 718 } 719 720 for type_ in &module.ast.definitions.custom_types { 721 let span = type_.full_location(); 722 let Some(range) = folding_range_for_span(span, &line_numbers, None) else { 723 continue; 724 }; 725 ranges.push(range); 726 } 727 728 for constant in &module.ast.definitions.constants { 729 let span = SrcSpan::new(constant.location.start, constant.value.location().end); 730 let Some(range) = folding_range_for_span(span, &line_numbers, None) else { 731 continue; 732 }; 733 ranges.push(range); 734 } 735 736 for alias in &module.ast.definitions.type_aliases { 737 let span = alias.location; 738 let Some(range) = folding_range_for_span(span, &line_numbers, None) else { 739 continue; 740 }; 741 ranges.push(range); 742 } 743 744 for function in &module.ast.definitions.functions { 745 let Some(body_start) = function.body_start else { 746 continue; 747 }; 748 749 let span = SrcSpan::new(body_start, function.end_position); 750 let Some(range) = folding_range_for_span(span, &line_numbers, None) else { 751 continue; 752 }; 753 ranges.push(range); 754 } 755 756 ranges.sort_by_key(|range| range.start_line); 757 Ok(ranges) 758 }) 759 } 760 761 /// Check whether a particular module is in the same package as this one 762 fn is_same_package(&self, current_module: &Module, module_name: &str) -> bool { 763 let other_module = self 764 .compiler 765 .project_compiler 766 .get_importable_modules() 767 .get(module_name); 768 match other_module { 769 // We can't rename values from other packages if we are not aliasing an unqualified import. 770 Some(module) => module.package == current_module.ast.type_info.package, 771 None => false, 772 } 773 } 774 775 pub fn prepare_rename( 776 &mut self, 777 params: lsp::PrepareRenameParams, 778 ) -> Response<Option<PrepareRenameResult>> { 779 self.respond(|this| { 780 let (lines, found) = match this.node_at_position(&params.text_document_position_params) 781 { 782 Some(value) => value, 783 None => return Ok(None), 784 }; 785 786 let Some(current_module) = 787 this.module_for_uri(&params.text_document_position_params.text_document.uri) 788 else { 789 return Ok(None); 790 }; 791 792 let success_response = |location| { 793 Some(PrepareRenameResult::Range(src_span_to_lsp_range( 794 location, &lines, 795 ))) 796 }; 797 798 let byte_index = lines.byte_index(params.text_document_position_params.position); 799 800 let referenced = reference_for_ast_node(found, &current_module.name); 801 802 Ok(match referenced { 803 Some(Referenced::LocalVariable { 804 location, origin, .. 805 }) if location.contains(byte_index) => match origin.map(|origin| origin.syntax) { 806 Some(VariableSyntax::Generated) => None, 807 Some( 808 VariableSyntax::Variable(label) | VariableSyntax::LabelShorthand(label), 809 ) => success_response(SrcSpan { 810 start: location.start, 811 end: label 812 .len() 813 .try_into() 814 .map(|len: u32| location.start + len) 815 .unwrap_or(location.end), 816 }), 817 Some(VariableSyntax::AssignmentPattern) | None => success_response(location), 818 }, 819 Some( 820 Referenced::ModuleValue { 821 module, 822 location, 823 target_kind, 824 .. 825 } 826 | Referenced::ModuleType { 827 module, 828 location, 829 target_kind, 830 .. 831 }, 832 ) if location.contains(byte_index) => { 833 // We can't rename types or values from other packages if we are not aliasing an unqualified import. 834 let rename_allowed = match target_kind { 835 RenameTarget::Qualified => this.is_same_package(current_module, &module), 836 RenameTarget::Unqualified | RenameTarget::Definition => true, 837 }; 838 if rename_allowed { 839 success_response(location) 840 } else { 841 None 842 } 843 } 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 )), 855 856 Some(Referenced::TypeVariable { location, name: _ }) => success_response(location), 857 858 _ => None, 859 }) 860 }) 861 } 862 863 pub fn rename( 864 &mut self, 865 params: lsp::RenameParams, 866 ) -> Response<Result<Option<WorkspaceEdit>, ResponseError>> { 867 self.respond(|this| { 868 let position = &params.text_document_position_params; 869 870 let (lines, found) = match this.node_at_position(position) { 871 Some(value) => value, 872 None => return Ok(RenameOutcome::NoRenames.into_result()), 873 }; 874 875 let Some(module) = this.module_for_uri(&position.text_document.uri) else { 876 return Ok(RenameOutcome::NoRenames.into_result()); 877 }; 878 879 let referenced = reference_for_ast_node(found, &module.name); 880 881 Ok(match referenced { 882 Some(Referenced::LocalVariable { 883 origin, 884 definition_location, 885 name, 886 .. 887 }) => { 888 let rename_kind = match origin.map(|origin| origin.syntax) { 889 Some(VariableSyntax::Generated) => { 890 return Ok(RenameOutcome::NoRenames.into_result()); 891 } 892 Some(VariableSyntax::LabelShorthand(_)) => { 893 VariableReferenceKind::LabelShorthand 894 } 895 Some( 896 VariableSyntax::AssignmentPattern | VariableSyntax::Variable { .. }, 897 ) 898 | None => VariableReferenceKind::Variable, 899 }; 900 rename_local_variable( 901 module, 902 &lines, 903 &params, 904 definition_location, 905 name, 906 rename_kind, 907 ) 908 .into_result() 909 } 910 Some(Referenced::ModuleValue { 911 module: module_name, 912 target_kind, 913 name, 914 name_kind, 915 .. 916 }) => rename_module_entity( 917 &params, 918 module, 919 this.compiler.project_compiler.get_importable_modules(), 920 &this.compiler.sources, 921 Renamed { 922 module_name: &module_name, 923 name: &name, 924 name_kind, 925 target_kind, 926 layer: ast::Layer::Value, 927 }, 928 ) 929 .into_result(), 930 931 Some(Referenced::ModuleType { 932 module: module_name, 933 target_kind, 934 name, 935 .. 936 }) => rename_module_entity( 937 &params, 938 module, 939 this.compiler.project_compiler.get_importable_modules(), 940 &this.compiler.sources, 941 Renamed { 942 module_name: &module_name, 943 name: &name, 944 name_kind: Named::Type, 945 target_kind, 946 layer: ast::Layer::Type, 947 }, 948 ) 949 .into_result(), 950 951 Some(Referenced::ModuleName { module_name, .. }) => { 952 rename_module_alias(module, &lines, &params, &module_name).into_result() 953 } 954 955 Some(Referenced::TypeVariable { location, name }) => { 956 rename_type_variable(module, &lines, &params, location, name).into_result() 957 } 958 959 None => RenameOutcome::NoRenames.into_result(), 960 }) 961 }) 962 } 963 964 /// Shared core of find references and document highlight. 965 fn find_references_in_scope( 966 &mut self, 967 position: &lsp_types::TextDocumentPositionParams, 968 search_scope: FindReferencesSearchScope, 969 ) -> Option<Vec<lsp::Location>> { 970 let (lines, found) = self.node_at_position(position)?; 971 972 let uri = position.text_document.uri.clone(); 973 974 let source_module = self.module_for_uri(&uri)?; 975 976 let byte_index = lines.byte_index(position.position); 977 978 let referenced = reference_for_ast_node(found, &source_module.name); 979 980 match referenced { 981 Some(Referenced::LocalVariable { 982 origin, 983 definition_location, 984 location, 985 name, 986 }) if location.contains(byte_index) => match origin.map(|origin| origin.syntax) { 987 Some(VariableSyntax::Generated) => None, 988 Some( 989 VariableSyntax::LabelShorthand(_) 990 | VariableSyntax::AssignmentPattern 991 | VariableSyntax::Variable { .. }, 992 ) 993 | None => { 994 let variable_references = 995 FindVariableReferences::new(definition_location, name) 996 .find_in_module(&source_module.ast); 997 998 let mut reference_locations = Vec::with_capacity(variable_references.len() + 1); 999 reference_locations.push(lsp::Location { 1000 uri: uri.clone(), 1001 range: src_span_to_lsp_range(definition_location, &lines), 1002 }); 1003 1004 for reference in variable_references { 1005 reference_locations.push(lsp::Location { 1006 uri: uri.clone(), 1007 range: src_span_to_lsp_range(reference.location, &lines), 1008 }) 1009 } 1010 1011 Some(reference_locations) 1012 } 1013 }, 1014 Some(Referenced::ModuleValue { 1015 module, 1016 name, 1017 location, 1018 .. 1019 }) if location.contains(byte_index) => match search_scope { 1020 FindReferencesSearchScope::AllModules => Some(find_module_references( 1021 module, 1022 name, 1023 self.compiler.project_compiler.get_importable_modules(), 1024 &self.compiler.sources, 1025 ast::Layer::Value, 1026 )), 1027 FindReferencesSearchScope::CurrentModule => { 1028 let source_information = self.compiler.get_source(&source_module.name)?; 1029 let source_module = self.compiler.get_module_interface(&source_module.name)?; 1030 Some(find_module_references_in_module( 1031 module, 1032 name, 1033 source_module, 1034 source_information, 1035 ast::Layer::Value, 1036 )) 1037 } 1038 }, 1039 Some(Referenced::ModuleType { 1040 module, 1041 name, 1042 location, 1043 .. 1044 }) if location.contains(byte_index) => match search_scope { 1045 FindReferencesSearchScope::AllModules => Some(find_module_references( 1046 module, 1047 name, 1048 self.compiler.project_compiler.get_importable_modules(), 1049 &self.compiler.sources, 1050 ast::Layer::Type, 1051 )), 1052 FindReferencesSearchScope::CurrentModule => { 1053 let source_information = self.compiler.get_source(&source_module.name)?; 1054 let source_module = self.compiler.get_module_interface(&source_module.name)?; 1055 Some(find_module_references_in_module( 1056 module, 1057 name, 1058 source_module, 1059 source_information, 1060 ast::Layer::Type, 1061 )) 1062 } 1063 }, 1064 _ => None, 1065 } 1066 } 1067 1068 pub fn find_references( 1069 &mut self, 1070 params: lsp::ReferenceParams, 1071 ) -> Response<Option<Vec<lsp::Location>>> { 1072 self.respond(|this| { 1073 let position = &params.text_document_position_params; 1074 Ok(this.find_references_in_scope(position, FindReferencesSearchScope::AllModules)) 1075 }) 1076 } 1077 1078 pub fn document_highlight( 1079 &mut self, 1080 params: lsp::DocumentHighlightParams, 1081 ) -> Response<Option<Vec<lsp::DocumentHighlight>>> { 1082 self.respond(|this| { 1083 let position = &params.text_document_position_params; 1084 Ok(this 1085 .find_references_in_scope(position, FindReferencesSearchScope::CurrentModule) 1086 .map(|references| { 1087 references 1088 .into_iter() 1089 .map(|loc| lsp::DocumentHighlight { 1090 range: loc.range, 1091 kind: None, 1092 }) 1093 .collect() 1094 })) 1095 }) 1096 } 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 1128 fn respond<T>(&mut self, handler: impl FnOnce(&mut Self) -> Result<T>) -> Response<T> { 1129 let result = handler(self); 1130 let warnings = self.take_warnings(); 1131 // TODO: test. Ensure hover doesn't report as compiled 1132 let compilation = if self.compiled_since_last_feedback { 1133 let modules = std::mem::take(&mut self.modules_compiled_since_last_feedback); 1134 self.compiled_since_last_feedback = false; 1135 Compilation::Yes(modules) 1136 } else { 1137 Compilation::No 1138 }; 1139 Response { 1140 result, 1141 warnings, 1142 compilation, 1143 } 1144 } 1145 1146 pub fn hover(&mut self, params: lsp::HoverParams) -> Response<Option<Hover>> { 1147 self.respond(|this| { 1148 let params = params.text_document_position_params; 1149 1150 let (lines, found) = match this.node_at_position(&params) { 1151 Some(value) => value, 1152 None => return Ok(None), 1153 }; 1154 1155 let Some(module) = this.module_for_uri(&params.text_document.uri) else { 1156 return Ok(None); 1157 }; 1158 1159 Ok(match found { 1160 Located::Statement(_) => None, // TODO: hover for statement 1161 Located::ModuleFunction(function) => { 1162 Some(hover_for_function_head(function, lines, module)) 1163 } 1164 Located::ModuleConstant(constant) => { 1165 Some(hover_for_module_constant(constant, lines, module)) 1166 } 1167 Located::Constant(constant) => Some(hover_for_constant(constant, lines, module)), 1168 Located::ModuleImport(import) => { 1169 let Some(module) = this.compiler.get_module_interface(&import.module) else { 1170 return Ok(None); 1171 }; 1172 Some(hover_for_module( 1173 module, 1174 import.location, 1175 &lines, 1176 &this.hex_deps, 1177 )) 1178 } 1179 Located::ModuleCustomType(custom_type) => { 1180 Some(hover_for_custom_type(custom_type, lines)) 1181 } 1182 Located::ModuleTypeAlias(_) => None, 1183 Located::VariantConstructorDefinition(constructor) => { 1184 Some(hover_for_constructor(constructor, lines, module)) 1185 } 1186 Located::UnqualifiedImport(UnqualifiedImport { 1187 name, 1188 module: module_name, 1189 is_type, 1190 location, 1191 }) => this 1192 .compiler 1193 .get_module_interface(module_name.as_str()) 1194 .and_then(|module_interface| { 1195 if is_type { 1196 module_interface.types.get(name).map(|constructor| { 1197 hover_for_annotation( 1198 *location, 1199 constructor.type_.as_ref(), 1200 Some(constructor), 1201 lines, 1202 module, 1203 ) 1204 }) 1205 } else { 1206 module_interface.values.get(name).map(|v| { 1207 let m = if this.hex_deps.contains(&module_interface.package) { 1208 Some(module_interface) 1209 } else { 1210 None 1211 }; 1212 hover_for_imported_value(v, location, lines, m, name, module) 1213 }) 1214 } 1215 }), 1216 Located::Pattern(pattern) => Some(hover_for_pattern(pattern, lines, module)), 1217 Located::PatternSpread { 1218 spread_location, 1219 pattern, 1220 } => { 1221 let range = Some(src_span_to_lsp_range(spread_location, &lines)); 1222 1223 let mut printer = Printer::new(&module.ast.names); 1224 1225 let PatternUnusedArguments { 1226 positional, 1227 labelled, 1228 } = pattern.unused_arguments().unwrap_or_default(); 1229 1230 let positional = positional 1231 .iter() 1232 .map(|type_| format!("- `{}`", printer.print_type(type_))) 1233 .join("\n"); 1234 let labelled = labelled 1235 .iter() 1236 .map(|(label, type_)| { 1237 format!("- `{}: {}`", label, printer.print_type(type_)) 1238 }) 1239 .join("\n"); 1240 1241 let content = match (positional.is_empty(), labelled.is_empty()) { 1242 (true, false) => format!("Unused labelled fields:\n{labelled}"), 1243 (false, true) => format!("Unused positional fields:\n{positional}"), 1244 (_, _) => format!( 1245 "Unused positional fields: 1246{positional} 1247 1248Unused labelled fields: 1249{labelled}" 1250 ), 1251 }; 1252 1253 Some(Hover { 1254 contents: Contents::MarkupContent(MarkupContent { 1255 value: content, 1256 kind: lsp_types::MarkupKind::Markdown, 1257 }), 1258 range, 1259 }) 1260 } 1261 Located::StringPrefixPatternVariable { location, .. } => Some( 1262 hover_for_string_prefix_pattern_variable(location, &lines, module), 1263 ), 1264 Located::Expression { 1265 expression, 1266 position, 1267 } => Some(hover_for_expression( 1268 expression, 1269 position, 1270 lines, 1271 module, 1272 &this.hex_deps, 1273 )), 1274 Located::Arg(arg) => Some(hover_for_function_argument(arg, lines, module)), 1275 Located::FunctionBody(_) => None, 1276 Located::Annotation { ast, type_ } => { 1277 let type_constructor = type_constructor_from_modules( 1278 this.compiler.project_compiler.get_importable_modules(), 1279 type_.clone(), 1280 ); 1281 Some(hover_for_annotation( 1282 ast.location(), 1283 &type_, 1284 type_constructor, 1285 lines, 1286 module, 1287 )) 1288 } 1289 Located::Label(location, type_) => { 1290 Some(hover_for_label(location, type_, lines, module)) 1291 } 1292 Located::ModuleName { 1293 location, 1294 module_name, 1295 .. 1296 } => { 1297 let Some(module) = this.compiler.get_module_interface(&module_name) else { 1298 return Ok(None); 1299 }; 1300 Some(hover_for_module(module, location, &lines, &this.hex_deps)) 1301 } 1302 1303 Located::ClauseGuard(guard) => Some(hover_for_clause_guard(guard, lines, module)), 1304 1305 Located::TypeVariable { .. } => None, 1306 }) 1307 }) 1308 } 1309 1310 pub(crate) fn signature_help( 1311 &mut self, 1312 params: lsp_types::SignatureHelpParams, 1313 ) -> Response<Option<SignatureHelp>> { 1314 self.respond(|this| { 1315 let Some(module) = 1316 this.module_for_uri(&params.text_document_position_params.text_document.uri) 1317 else { 1318 return Ok(None); 1319 }; 1320 1321 match this.node_at_position(&params.text_document_position_params) { 1322 Some((_lines, Located::Expression { expression, .. })) => { 1323 Ok(signature_help::for_expression(expression, module)) 1324 } 1325 Some((_lines, _located)) => Ok(None), 1326 None => Ok(None), 1327 } 1328 }) 1329 } 1330 1331 fn module_node_at_position( 1332 &self, 1333 params: &lsp::TextDocumentPositionParams, 1334 module: &'a Module, 1335 ) -> Option<(LineNumbers, Located<'a>)> { 1336 let line_numbers = LineNumbers::new(&module.code); 1337 let byte_index = line_numbers.byte_index(params.position); 1338 let node = module.find_node(byte_index); 1339 let node = node?; 1340 Some((line_numbers, node)) 1341 } 1342 1343 fn node_at_position( 1344 &self, 1345 params: &lsp::TextDocumentPositionParams, 1346 ) -> Option<(LineNumbers, Located<'_>)> { 1347 let module = self.module_for_uri(&params.text_document.uri)?; 1348 self.module_node_at_position(params, module) 1349 } 1350 1351 fn module_name_for_uri(&self, uri: &Url) -> Option<EcoString> { 1352 // The to_file_path method is available on these platforms 1353 #[cfg(any(unix, windows, target_os = "redox", target_os = "wasi"))] 1354 let path = uri.to_file_path().expect("URL file"); 1355 1356 #[cfg(not(any(unix, windows, target_os = "redox", target_os = "wasi")))] 1357 let path: Utf8PathBuf = uri.path().into(); 1358 1359 let components = path 1360 .strip_prefix(self.paths.root()) 1361 .ok()? 1362 .components() 1363 .skip(1) 1364 .map(|c| c.as_os_str().to_string_lossy()); 1365 let name = Itertools::intersperse(components, "/".into()) 1366 .collect::<String>() 1367 .strip_suffix(".gleam")? 1368 .into(); 1369 Some(name) 1370 } 1371 1372 fn module_for_uri(&self, uri: &Url) -> Option<&Module> { 1373 let module_name = self.module_name_for_uri(uri)?; 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") 1381 } 1382} 1383 1384fn import_folding_spans( 1385 imports: &[ast::Import<EcoString>], 1386 code: &str, 1387 line_numbers: &LineNumbers, 1388) -> Vec<SrcSpan> { 1389 let mut spans = vec![]; 1390 let mut imports = imports.iter(); 1391 1392 let Some(first_import) = imports.next() else { 1393 return spans; 1394 }; 1395 1396 let mut previous_line = src_span_to_lsp_range( 1397 SrcSpan::new(first_import.location.start, first_import.location.start), 1398 line_numbers, 1399 ) 1400 .start 1401 .line; 1402 let mut current_start = first_import.location.start; 1403 let mut current_end = first_import.location.end; 1404 let mut current_len = 1; 1405 1406 for import in imports { 1407 let next_line = src_span_to_lsp_range( 1408 SrcSpan::new(import.location.start, import.location.start), 1409 line_numbers, 1410 ) 1411 .start 1412 .line; 1413 let separated_by_blank_line = next_line > previous_line + 1; 1414 let between = &code[current_end as usize..import.location.start as usize]; 1415 let has_non_whitespace_between = between.chars().any(|char| !char.is_whitespace()); 1416 1417 if separated_by_blank_line || has_non_whitespace_between { 1418 if current_len > 1 { 1419 spans.push(SrcSpan::new(current_start, current_end)); 1420 } 1421 current_start = import.location.start; 1422 current_end = import.location.end; 1423 current_len = 1; 1424 } else { 1425 current_end = import.location.end; 1426 current_len += 1; 1427 } 1428 1429 previous_line = next_line; 1430 } 1431 1432 if current_len > 1 { 1433 spans.push(SrcSpan::new(current_start, current_end)); 1434 } 1435 1436 spans 1437} 1438 1439fn folding_range_for_span( 1440 span: SrcSpan, 1441 line_numbers: &LineNumbers, 1442 kind: Option<FoldingRangeKind>, 1443) -> Option<FoldingRange> { 1444 let range = src_span_to_lsp_range(span, line_numbers); 1445 1446 if range.start.line >= range.end.line { 1447 return None; 1448 } 1449 1450 Some(FoldingRange { 1451 start_line: range.start.line, 1452 start_character: None, 1453 end_line: range.end.line, 1454 end_character: None, 1455 kind, 1456 collapsed_text: None, 1457 }) 1458} 1459 1460fn custom_type_symbol( 1461 type_: &CustomType<Arc<Type>>, 1462 line_numbers: &LineNumbers, 1463 module: &Module, 1464) -> DocumentSymbol { 1465 let constructors = type_ 1466 .constructors 1467 .iter() 1468 .map(|constructor| { 1469 let mut arguments = vec![]; 1470 1471 // List named arguments as field symbols. 1472 for argument in &constructor.arguments { 1473 let Some((label_location, label)) = &argument.label else { 1474 continue; 1475 }; 1476 1477 let full_arg_span = match argument.doc { 1478 Some((doc_position, _)) => { 1479 SrcSpan::new(get_doc_marker_position(doc_position), argument.location.end) 1480 } 1481 None => argument.location, 1482 }; 1483 1484 // The 'deprecated' field is deprecated, but we have to specify it anyway 1485 // to be able to construct the 'DocumentSymbol' type, so 1486 // we suppress the warning. We specify 'None' as specifying 'Some' 1487 // is what is actually deprecated. 1488 #[allow(deprecated)] 1489 arguments.push(DocumentSymbol { 1490 name: label.to_string(), 1491 detail: Some( 1492 Printer::new(&module.ast.names) 1493 .print_type(&argument.type_) 1494 .to_string(), 1495 ), 1496 kind: SymbolKind::Field, 1497 tags: None, 1498 deprecated: None, 1499 range: src_span_to_lsp_range(full_arg_span, line_numbers), 1500 selection_range: src_span_to_lsp_range(*label_location, line_numbers), 1501 children: None, 1502 }); 1503 } 1504 1505 // Start from the documentation if available, otherwise from the constructor's name, 1506 // all the way to the end of its arguments. 1507 let full_constructor_span = SrcSpan { 1508 start: constructor 1509 .documentation 1510 .as_ref() 1511 .map(|(doc_start, _)| get_doc_marker_position(*doc_start)) 1512 .unwrap_or(constructor.location.start), 1513 1514 end: constructor.location.end, 1515 }; 1516 1517 // The 'deprecated' field is deprecated, but we have to specify it anyway 1518 // to be able to construct the 'DocumentSymbol' type, so 1519 // we suppress the warning. We specify 'None' as specifying 'Some' 1520 // is what is actually deprecated. 1521 #[allow(deprecated)] 1522 DocumentSymbol { 1523 name: constructor.name.to_string(), 1524 detail: None, 1525 kind: if constructor.arguments.is_empty() { 1526 SymbolKind::EnumMember 1527 } else { 1528 SymbolKind::Constructor 1529 }, 1530 tags: make_deprecated_symbol_tag(&constructor.deprecation), 1531 deprecated: None, 1532 range: src_span_to_lsp_range(full_constructor_span, line_numbers), 1533 selection_range: src_span_to_lsp_range(constructor.name_location, line_numbers), 1534 children: if arguments.is_empty() { 1535 None 1536 } else { 1537 Some(arguments) 1538 }, 1539 } 1540 }) 1541 .collect_vec(); 1542 1543 // The type's location, by default, ranges from "(pub) type" to the end of its name. 1544 // We need it to range to the end of its constructors instead for the full symbol range. 1545 // We also include documentation, if available, by LSP convention. 1546 let full_type_span = SrcSpan { 1547 start: type_ 1548 .documentation 1549 .as_ref() 1550 .map(|(doc_start, _)| get_doc_marker_position(*doc_start)) 1551 .unwrap_or(type_.location.start), 1552 1553 end: type_.end_position, 1554 }; 1555 1556 // The 'deprecated' field is deprecated, but we have to specify it anyway 1557 // to be able to construct the 'DocumentSymbol' type, so 1558 // we suppress the warning. We specify 'None' as specifying 'Some' 1559 // is what is actually deprecated. 1560 #[allow(deprecated)] 1561 DocumentSymbol { 1562 name: type_.name.to_string(), 1563 detail: None, 1564 kind: SymbolKind::Class, 1565 tags: make_deprecated_symbol_tag(&type_.deprecation), 1566 deprecated: None, 1567 range: src_span_to_lsp_range(full_type_span, line_numbers), 1568 selection_range: src_span_to_lsp_range(type_.name_location, line_numbers), 1569 children: if constructors.is_empty() { 1570 None 1571 } else { 1572 Some(constructors) 1573 }, 1574 } 1575} 1576 1577fn hover_for_pattern(pattern: &TypedPattern, line_numbers: LineNumbers, module: &Module) -> Hover { 1578 let documentation = pattern.get_documentation().unwrap_or_default(); 1579 1580 // Show the type of the hovered node to the user 1581 let type_ = Printer::new(&module.ast.names).print_type(pattern.type_().as_ref()); 1582 let contents = format!( 1583 "```gleam 1584{type_} 1585``` 1586{documentation}" 1587 ); 1588 Hover { 1589 contents: Contents::MarkedString(MarkedString::String(contents)), 1590 range: Some(src_span_to_lsp_range(pattern.location(), &line_numbers)), 1591 } 1592} 1593 1594fn get_function_type(fun: &TypedFunction) -> Type { 1595 Type::Fn { 1596 arguments: fun 1597 .arguments 1598 .iter() 1599 .map(|argument| argument.type_.clone()) 1600 .collect(), 1601 return_: fun.return_type.clone(), 1602 } 1603} 1604 1605fn hover_for_function_head( 1606 fun: &TypedFunction, 1607 line_numbers: LineNumbers, 1608 module: &Module, 1609) -> Hover { 1610 let empty_str = EcoString::from(""); 1611 let documentation = fun 1612 .documentation 1613 .as_ref() 1614 .map(|(_, doc)| doc) 1615 .unwrap_or(&empty_str); 1616 let function_type = get_function_type(fun); 1617 let formatted_type = Printer::new(&module.ast.names).print_type(&function_type); 1618 let contents = format!( 1619 "```gleam 1620{formatted_type} 1621``` 1622{documentation}" 1623 ); 1624 Hover { 1625 contents: Contents::MarkedString(MarkedString::String(contents)), 1626 range: Some(src_span_to_lsp_range(fun.location, &line_numbers)), 1627 } 1628} 1629 1630fn hover_for_function_argument( 1631 argument: &TypedArg, 1632 line_numbers: LineNumbers, 1633 module: &Module, 1634) -> Hover { 1635 let type_ = Printer::new(&module.ast.names).print_type(&argument.type_); 1636 let contents = format!("```gleam\n{type_}\n```"); 1637 Hover { 1638 contents: Contents::MarkedString(MarkedString::String(contents)), 1639 range: Some(src_span_to_lsp_range(argument.location, &line_numbers)), 1640 } 1641} 1642 1643fn hover_for_annotation( 1644 location: SrcSpan, 1645 annotation_type: &Type, 1646 type_constructor: Option<&TypeConstructor>, 1647 line_numbers: LineNumbers, 1648 module: &Module, 1649) -> Hover { 1650 let empty_str = EcoString::from(""); 1651 let documentation = type_constructor 1652 .and_then(|constructor| constructor.documentation.as_ref()) 1653 .unwrap_or(&empty_str); 1654 // If a user is hovering an annotation, it's not very useful to show the 1655 // local representation of that type, since that's probably what they see 1656 // in the source code anyway. So here, we print the raw type, 1657 // which is probably more helpful. 1658 let type_ = Printer::new(&module.ast.names).print_type_without_aliases(annotation_type); 1659 let contents = format!( 1660 "```gleam 1661{type_} 1662``` 1663{documentation}" 1664 ); 1665 Hover { 1666 contents: Contents::MarkedString(MarkedString::String(contents)), 1667 range: Some(src_span_to_lsp_range(location, &line_numbers)), 1668 } 1669} 1670 1671fn hover_for_label( 1672 location: SrcSpan, 1673 type_: Arc<Type>, 1674 line_numbers: LineNumbers, 1675 module: &Module, 1676) -> Hover { 1677 let type_ = Printer::new(&module.ast.names).print_type(&type_); 1678 let contents = format!("```gleam\n{type_}\n```"); 1679 Hover { 1680 contents: Contents::MarkedString(MarkedString::String(contents)), 1681 range: Some(src_span_to_lsp_range(location, &line_numbers)), 1682 } 1683} 1684 1685fn hover_for_module_constant( 1686 constant: &ModuleConstant<Arc<Type>>, 1687 line_numbers: LineNumbers, 1688 module: &Module, 1689) -> Hover { 1690 let empty_str = EcoString::from(""); 1691 let type_ = Printer::new(&module.ast.names).print_type(&constant.type_); 1692 let documentation = constant 1693 .documentation 1694 .as_ref() 1695 .map(|(_, doc)| doc) 1696 .unwrap_or(&empty_str); 1697 let contents = format!("```gleam\n{type_}\n```\n{documentation}"); 1698 Hover { 1699 contents: Contents::MarkedString(MarkedString::String(contents)), 1700 range: Some(src_span_to_lsp_range(constant.location, &line_numbers)), 1701 } 1702} 1703 1704fn hover_for_constant( 1705 constant: &TypedConstant, 1706 line_numbers: LineNumbers, 1707 module: &Module, 1708) -> Hover { 1709 let type_ = Printer::new(&module.ast.names).print_type(&constant.type_()); 1710 let contents = format!("```gleam\n{type_}\n```"); 1711 Hover { 1712 contents: Contents::MarkedString(MarkedString::String(contents)), 1713 range: Some(src_span_to_lsp_range(constant.location(), &line_numbers)), 1714 } 1715} 1716 1717fn hover_for_clause_guard( 1718 guard: &TypedClauseGuard, 1719 line_numbers: LineNumbers, 1720 module: &Module, 1721) -> Hover { 1722 let type_ = Printer::new(&module.ast.names).print_type(&guard.type_()); 1723 let contents = format!("```gleam\n{type_}\n```"); 1724 Hover { 1725 contents: Contents::MarkedString(MarkedString::String(contents)), 1726 range: Some(src_span_to_lsp_range(guard.location(), &line_numbers)), 1727 } 1728} 1729 1730fn hover_for_string_prefix_pattern_variable( 1731 location: SrcSpan, 1732 lines: &LineNumbers, 1733 module: &Module, 1734) -> Hover { 1735 let type_ = Printer::new(&module.ast.names).print_type(&type_::string()); 1736 let contents = format!("```gleam\n{type_}\n```"); 1737 Hover { 1738 contents: Contents::MarkedString(MarkedString::String(contents)), 1739 range: Some(src_span_to_lsp_range(location, lines)), 1740 } 1741} 1742 1743fn hover_for_expression<'a>( 1744 expression: &'a TypedExpr, 1745 position: ExpressionPosition<'a>, 1746 line_numbers: LineNumbers, 1747 module: &Module, 1748 hex_deps: &HashSet<EcoString>, 1749) -> Hover { 1750 let documentation = expression.get_documentation().unwrap_or_default(); 1751 1752 let link_section = get_expr_qualified_name(expression) 1753 .and_then(|(module_name, name)| { 1754 get_hexdocs_link_section(module_name, name, &module.ast, hex_deps) 1755 }) 1756 .unwrap_or("".to_string()); 1757 1758 // Show the type of the hovered node to the user 1759 let mut printer = Printer::new(&module.ast.names); 1760 let type_ = printer.print_type(expression.type_().as_ref()); 1761 1762 // If the expression is a record update and there's record fields that are 1763 // begin implicitly updated, then we want to list them in the hover. 1764 let unchanged_record_update_fields = match position { 1765 ExpressionPosition::Expression | ExpressionPosition::ArgumentOrLabel { .. } => "", 1766 1767 ExpressionPosition::UpdatedRecord { 1768 unchanged_record_fields, 1769 } if !unchanged_record_fields.is_empty() => &format!( 1770 "Unchanged record fields:\n{}", 1771 unchanged_record_fields 1772 .iter() 1773 .map(|(label, type_)| format!("- `{}: {}`", label, printer.print_type(type_))) 1774 .join("\n") 1775 ), 1776 ExpressionPosition::UpdatedRecord { .. } => "", 1777 }; 1778 1779 let description = [documentation, unchanged_record_update_fields, &link_section] 1780 .iter() 1781 .filter(|string| !string.is_empty()) 1782 .join("\n\n"); 1783 1784 let contents = format!( 1785 "```gleam 1786{type_} 1787``` 1788{description}" 1789 ); 1790 1791 Hover { 1792 contents: Contents::MarkedString(MarkedString::String(contents)), 1793 range: Some(src_span_to_lsp_range(expression.location(), &line_numbers)), 1794 } 1795} 1796 1797fn hover_for_imported_value( 1798 value: &ValueConstructor, 1799 location: &SrcSpan, 1800 line_numbers: LineNumbers, 1801 hex_module_imported_from: Option<&ModuleInterface>, 1802 name: &EcoString, 1803 module: &Module, 1804) -> Hover { 1805 let documentation = value.get_documentation().unwrap_or_default(); 1806 1807 let link_section = hex_module_imported_from.map_or("".to_string(), |m| { 1808 format_hexdocs_link_section(m.package.as_str(), m.name.as_str(), Some(name)) 1809 }); 1810 1811 // Show the type of the hovered node to the user 1812 let type_ = Printer::new(&module.ast.names).print_type(value.type_.as_ref()); 1813 let contents = format!( 1814 "```gleam 1815{type_} 1816``` 1817{documentation}{link_section}" 1818 ); 1819 Hover { 1820 contents: Contents::MarkedString(MarkedString::String(contents)), 1821 range: Some(src_span_to_lsp_range(*location, &line_numbers)), 1822 } 1823} 1824 1825fn hover_for_module( 1826 module: &ModuleInterface, 1827 location: SrcSpan, 1828 line_numbers: &LineNumbers, 1829 hex_deps: &HashSet<EcoString>, 1830) -> Hover { 1831 let documentation = module.documentation.join("\n"); 1832 let name = &module.name; 1833 1834 let link_section = if hex_deps.contains(&module.package) { 1835 format_hexdocs_link_section(&module.package, name, None) 1836 } else { 1837 String::new() 1838 }; 1839 1840 let contents = format!( 1841 "```gleam 1842{name} 1843``` 1844{documentation} 1845{link_section}", 1846 ); 1847 Hover { 1848 contents: Contents::MarkedString(MarkedString::String(contents)), 1849 range: Some(src_span_to_lsp_range(location, line_numbers)), 1850 } 1851} 1852 1853fn hover_for_custom_type(type_: &CustomType<Arc<Type>>, line_numbers: LineNumbers) -> Hover { 1854 let name = &type_.name; 1855 let documentation = type_ 1856 .documentation 1857 .as_ref() 1858 .map(|(_, documentation)| documentation.clone()) 1859 .unwrap_or_default(); 1860 1861 let contents = format!("```gleam\n{name}\n```\n{documentation}"); 1862 Hover { 1863 contents: Contents::MarkedString(MarkedString::String(contents)), 1864 range: Some(src_span_to_lsp_range(type_.full_location(), &line_numbers)), 1865 } 1866} 1867 1868fn hover_for_constructor( 1869 constructor: &TypedRecordConstructor, 1870 line_numbers: LineNumbers, 1871 module: &Module, 1872) -> Hover { 1873 let mut printer = Printer::new(&module.ast.names); 1874 1875 let arguments = constructor 1876 .arguments 1877 .iter() 1878 .map(|argument| match &argument.label { 1879 Some((_, label)) => eco_format!("{label}: {}", printer.print_type(&argument.type_)), 1880 None => printer.print_type(&argument.type_), 1881 }) 1882 .join(", "); 1883 1884 let documentation = constructor 1885 .documentation 1886 .as_ref() 1887 .map(|(_, documentation)| documentation.clone()) 1888 .unwrap_or_default(); 1889 1890 let constructor_doc = if arguments.is_empty() { 1891 constructor.name.clone() 1892 } else { 1893 eco_format!("{}({arguments})", constructor.name) 1894 }; 1895 1896 let contents = format!("```gleam\n{constructor_doc}\n```\n{documentation}"); 1897 Hover { 1898 contents: Contents::MarkedString(MarkedString::String(contents)), 1899 range: Some(src_span_to_lsp_range(constructor.location, &line_numbers)), 1900 } 1901} 1902 1903/// Returns true if any part of either range overlaps with the other. 1904pub fn overlaps(a: Range, b: Range) -> bool { 1905 position_within(a.start, b) 1906 || position_within(a.end, b) 1907 || position_within(b.start, a) 1908 || position_within(b.end, a) 1909} 1910 1911/// Returns true if the first range is within the second range. 1912/// The ranges might touch on their extremes and still be considered one within 1913/// the other! 1914/// 1915/// `within(a, b)` is true in all of these cases: 1916/// 1917/// - ```txt 1918/// |------| b 1919/// |---| a 1920/// ``` 1921/// - ```txt 1922/// |------| b 1923/// |---| a 1924/// ``` 1925/// - ```txt 1926/// |------| b 1927/// |---| a 1928/// ``` 1929/// - ```txt 1930/// |------| b 1931/// |------| a 1932/// ``` 1933/// 1934pub fn within(a: Range, b: Range) -> bool { 1935 position_within(a.start, b) && position_within(a.end, b) 1936} 1937 1938/// Returns true if the first range is completely within the second range. 1939/// The range cannot have any extreme in common to be considered completely 1940/// within another. 1941/// 1942/// `completely_within(a, b)` is true this case: 1943/// 1944/// - ```txt 1945/// |------| b 1946/// |---| a 1947/// ``` 1948/// 1949/// And `completely_within(a, b)` is false in all these cases: 1950/// 1951/// - ```txt 1952/// |------| b 1953/// |---| a 1954/// ``` 1955/// - ```txt 1956/// |------| b 1957/// |---| a 1958/// ``` 1959/// - ```txt 1960/// |------| b 1961/// |------| a 1962/// ``` 1963/// 1964pub fn completely_within(a: Range, b: Range) -> bool { 1965 a.start > b.start && a.start < b.end && a.end > b.start && a.end < b.end 1966} 1967 1968// Returns true if a position is within a range. 1969pub fn position_within(position: Position, range: Range) -> bool { 1970 position >= range.start && position <= range.end 1971} 1972 1973/// Builds the code action to assign an unused value to `_`. 1974/// 1975fn code_action_unused_values( 1976 module: &Module, 1977 line_numbers: &LineNumbers, 1978 params: &lsp::CodeActionParams, 1979 actions: &mut Vec<CodeAction>, 1980) { 1981 let uri = &params.text_document.uri; 1982 let mut unused_values: Vec<&SrcSpan> = module 1983 .ast 1984 .type_info 1985 .warnings 1986 .iter() 1987 .filter_map(|warning| { 1988 if let type_::Warning::ImplicitlyDiscardedResult { location } = warning { 1989 Some(location) 1990 } else { 1991 None 1992 } 1993 }) 1994 .collect(); 1995 1996 if unused_values.is_empty() { 1997 return; 1998 } 1999 2000 // Sort spans by start position, with longer spans coming first 2001 unused_values.sort_by_key(|span| (span.start, -(span.len() as i64))); 2002 2003 let mut processed_lsp_range = Vec::new(); 2004 2005 for unused in unused_values { 2006 let SrcSpan { start, end } = *unused; 2007 let hover_range = src_span_to_lsp_range(SrcSpan::new(start, end), line_numbers); 2008 2009 // Check if this span is contained within any previously processed span 2010 if processed_lsp_range 2011 .iter() 2012 .any(|&prev_lsp_range| within(hover_range, prev_lsp_range)) 2013 { 2014 continue; 2015 } 2016 2017 // Check if the cursor is within this span 2018 if !within(params.range, hover_range) { 2019 continue; 2020 } 2021 2022 let edit = TextEdit { 2023 range: src_span_to_lsp_range(SrcSpan::new(start, start), line_numbers), 2024 new_text: "let _ = ".into(), 2025 }; 2026 2027 CodeActionBuilder::new("Assign unused Result value to `_`") 2028 .kind(lsp_types::CodeActionKind::QuickFix) 2029 .changes(uri.clone(), vec![edit]) 2030 .preferred(true) 2031 .push_to(actions); 2032 2033 processed_lsp_range.push(hover_range); 2034 } 2035} 2036 2037struct NameCorrection { 2038 pub location: SrcSpan, 2039 pub correction: EcoString, 2040} 2041 2042fn code_action_fix_names( 2043 module: &Module, 2044 line_numbers: &LineNumbers, 2045 params: &lsp::CodeActionParams, 2046 error: &Option<Error>, 2047 actions: &mut Vec<CodeAction>, 2048) { 2049 let uri = &params.text_document.uri; 2050 let Some(errors) = type_errors_for_module(error, module) else { 2051 return; 2052 }; 2053 let name_corrections = errors 2054 .iter() 2055 .filter_map(|error| { 2056 if let type_::Error::BadName { 2057 location, 2058 name, 2059 kind, 2060 } = error 2061 { 2062 Some(NameCorrection { 2063 correction: correct_name_case(name, *kind), 2064 location: *location, 2065 }) 2066 } else { 2067 None 2068 } 2069 }) 2070 .collect_vec(); 2071 2072 if name_corrections.is_empty() { 2073 return; 2074 } 2075 2076 for name_correction in name_corrections { 2077 let NameCorrection { 2078 location, 2079 correction, 2080 } = name_correction; 2081 2082 let range = src_span_to_lsp_range(location, line_numbers); 2083 // Check if the user's cursor is on the invalid name 2084 if overlaps(params.range, range) { 2085 let edit = TextEdit { 2086 range, 2087 new_text: correction.to_string(), 2088 }; 2089 2090 CodeActionBuilder::new(&format!("Rename to {correction}")) 2091 .kind(lsp_types::CodeActionKind::QuickFix) 2092 .changes(uri.clone(), vec![edit]) 2093 .preferred(true) 2094 .push_to(actions); 2095 } 2096 } 2097} 2098 2099fn get_expr_qualified_name(expression: &TypedExpr) -> Option<(&EcoString, &EcoString)> { 2100 match expression { 2101 TypedExpr::Var { 2102 name, constructor, .. 2103 } if constructor.publicity.is_importable() => match &constructor.variant { 2104 ValueConstructorVariant::ModuleFn { 2105 module: module_name, 2106 .. 2107 } => Some((module_name, name)), 2108 2109 ValueConstructorVariant::ModuleConstant { 2110 module: module_name, 2111 .. 2112 } => Some((module_name, name)), 2113 2114 ValueConstructorVariant::LocalVariable { .. } 2115 | ValueConstructorVariant::Record { .. } => None, 2116 }, 2117 2118 TypedExpr::ModuleSelect { 2119 label, module_name, .. 2120 } => Some((module_name, label)), 2121 2122 TypedExpr::Int { .. } 2123 | TypedExpr::Float { .. } 2124 | TypedExpr::String { .. } 2125 | TypedExpr::Block { .. } 2126 | TypedExpr::Pipeline { .. } 2127 | TypedExpr::Var { .. } 2128 | TypedExpr::Fn { .. } 2129 | TypedExpr::List { .. } 2130 | TypedExpr::Call { .. } 2131 | TypedExpr::BinOp { .. } 2132 | TypedExpr::Case { .. } 2133 | TypedExpr::RecordAccess { .. } 2134 | TypedExpr::PositionalAccess { .. } 2135 | TypedExpr::Tuple { .. } 2136 | TypedExpr::TupleIndex { .. } 2137 | TypedExpr::Todo { .. } 2138 | TypedExpr::Panic { .. } 2139 | TypedExpr::Echo { .. } 2140 | TypedExpr::BitArray { .. } 2141 | TypedExpr::RecordUpdate { .. } 2142 | TypedExpr::NegateBool { .. } 2143 | TypedExpr::NegateInt { .. } 2144 | TypedExpr::Invalid { .. } => None, 2145 } 2146} 2147 2148fn format_hexdocs_link_section( 2149 package_name: &str, 2150 module_name: &str, 2151 name: Option<&str>, 2152) -> String { 2153 let package_name = package_name.replace('_', "-"); 2154 let link = match name { 2155 Some(name) => format!("https://{package_name}.hexdocs.pm/{module_name}.html#{name}"), 2156 None => format!("https://{package_name}.hexdocs.pm/{module_name}.html"), 2157 }; 2158 format!("\nView on [HexDocs]({link})") 2159} 2160 2161fn get_hexdocs_link_section( 2162 module_name: &str, 2163 name: &str, 2164 ast: &TypedModule, 2165 hex_deps: &HashSet<EcoString>, 2166) -> Option<String> { 2167 let package_name = ast.definitions.imports.iter().find_map(|import| { 2168 if import.module == module_name && hex_deps.contains(&import.package) { 2169 Some(&import.package) 2170 } else { 2171 None 2172 } 2173 })?; 2174 2175 Some(format_hexdocs_link_section( 2176 package_name, 2177 module_name, 2178 Some(name), 2179 )) 2180} 2181 2182/// Converts the source start position of a documentation comment's contents into 2183/// the position of the leading slash in its marker ('///'). 2184fn get_doc_marker_position(content_pos: u32) -> u32 { 2185 content_pos.saturating_sub(3) 2186} 2187 2188fn make_deprecated_symbol_tag(deprecation: &Deprecation) -> Option<Vec<SymbolTag>> { 2189 deprecation 2190 .is_deprecated() 2191 .then(|| vec![SymbolTag::Deprecated]) 2192} 2193 2194enum FindReferencesSearchScope { 2195 AllModules, 2196 CurrentModule, 2197}