Fork of daniellemaywood.uk/gleam — Wasm codegen work
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(¶ms) {
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, ¶ms, 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(¶ms) {
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, ¶ms, 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(¶ms.text_document.uri) {
277 Some(module) => module,
278 None => return Ok(None),
279 };
280
281 let mut completer = Completer::new(&src, ¶ms, &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(¶ms.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, ¶ms, &mut actions);
443 actions.extend(RemoveUnusedImports::new(module, &lines, ¶ms).code_actions());
444 code_action_fix_names(module, &lines, ¶ms, &this.error, &mut actions);
445 code_action_import_module(module, &lines, ¶ms, &this.error, &mut actions);
446 code_action_generate_type(module, &lines, ¶ms, &this.error, &mut actions);
447 code_action_add_missing_patterns(module, &lines, ¶ms, &this.error, &mut actions);
448 actions
449 .extend(RemoveUnreachableCaseClauses::new(module, &lines, ¶ms).code_actions());
450 actions
451 .extend(RemoveRedundantRecordUpdate::new(module, &lines, ¶ms).code_actions());
452 actions.extend(CollapseNestedCase::new(module, &lines, ¶ms).code_actions());
453 actions.extend(FixBinaryOperation::new(module, &lines, ¶ms).code_actions());
454 actions
455 .extend(FixTruncatedBitArraySegment::new(module, &lines, ¶ms).code_actions());
456 actions.extend(RemovePrivateOpaque::new(module, &lines, ¶ms).code_actions());
457 actions.extend(AddMissingTypeParameter::new(module, &lines, ¶ms).code_actions());
458 code_action_convert_qualified_constructor_to_unqualified(
459 module,
460 &this.compiler,
461 &lines,
462 ¶ms,
463 &mut actions,
464 );
465 code_action_convert_unqualified_constructor_to_qualified(
466 module,
467 &lines,
468 ¶ms,
469 &mut actions,
470 );
471 code_action_inexhaustive_let_to_case(
472 module,
473 &lines,
474 ¶ms,
475 &this.error,
476 &mut actions,
477 );
478 actions.extend(MergeCaseBranches::new(module, &lines, ¶ms).code_actions());
479 actions.extend(LetAssertToCase::new(module, &lines, ¶ms).code_actions());
480 actions
481 .extend(RedundantTupleInCaseSubject::new(module, &lines, ¶ms).code_actions());
482 actions.extend(FillInMissingLabelledArgs::new(module, &lines, ¶ms).code_actions());
483 actions.extend(UseLabelShorthandSyntax::new(module, &lines, ¶ms).code_actions());
484 actions.extend(ConvertFromUse::new(module, &lines, ¶ms).code_actions());
485 actions.extend(RemoveEchos::new(module, &lines, ¶ms).code_actions());
486 actions.extend(ConvertToUse::new(module, &lines, ¶ms).code_actions());
487 actions.extend(ExpandFunctionCapture::new(module, &lines, ¶ms).code_actions());
488 actions.extend(FillUnusedFields::new(module, &lines, ¶ms).code_actions());
489 actions.extend(InterpolateString::new(module, &lines, ¶ms).code_actions());
490 actions.extend(ExtractVariable::new(module, &lines, ¶ms).code_actions());
491 actions.extend(ExtractConstant::new(module, &lines, ¶ms).code_actions());
492 actions.extend(
493 GenerateFunction::new(module, &this.compiler.modules, &lines, ¶ms)
494 .code_actions(),
495 );
496 actions.extend(
497 GenerateVariant::new(module, &this.compiler, &lines, ¶ms).code_actions(),
498 );
499 actions.extend(ConvertToPipe::new(module, &lines, ¶ms).code_actions());
500 actions.extend(ConvertToFunctionCall::new(module, &lines, ¶ms).code_actions());
501 actions.extend(
502 PatternMatchOnValue::new(module, &lines, ¶ms, &this.compiler).code_actions(),
503 );
504 actions.extend(AddOmittedLabels::new(module, &lines, ¶ms).code_actions());
505 actions.extend(InlineVariable::new(module, &lines, ¶ms).code_actions());
506 actions.extend(WrapInBlock::new(module, &lines, ¶ms).code_actions());
507 actions.extend(RemoveBlock::new(module, &lines, ¶ms).code_actions());
508 actions.extend(ExtractFunction::new(module, &lines, ¶ms).code_actions());
509 GenerateDynamicDecoder::new(module, &lines, ¶ms, &mut actions, &this.compiler)
510 .code_actions();
511 actions.extend(WrapInAnonymousFunction::new(module, &lines, ¶ms).code_actions());
512 actions.extend(UnwrapAnonymousFunction::new(module, &lines, ¶ms).code_actions());
513 GenerateJsonEncoder::new(
514 module,
515 &lines,
516 ¶ms,
517 &mut actions,
518 &this.compiler.project_compiler.config,
519 )
520 .code_actions();
521 AddAnnotations::new(module, &lines, ¶ms).code_action(&mut actions);
522 actions
523 .extend(AnnotateTopLevelDefinitions::new(module, &lines, ¶ms).code_actions());
524 actions.extend(ReplaceUnderscoreWithType::new(module, &lines, ¶ms).code_actions());
525 actions.extend(
526 CreateUnknownModule::new(
527 module,
528 &this.compiler,
529 &lines,
530 ¶ms,
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(¶ms.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(¶ms.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(¶ms.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(¶ms.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, ¤t_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 = ¶ms.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 ¶ms,
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 ¶ms,
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 ¶ms,
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, ¶ms, &module_name).into_result()
954 }
955
956 Some(Referenced::TypeVariable { location, name }) => {
957 rename_type_variable(module, &lines, ¶ms, 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 = ¶ms.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 = ¶ms.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(¶ms) {
1152 Some(value) => value,
1153 None => return Ok(None),
1154 };
1155
1156 let Some(module) = this.module_for_uri(¶ms.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(¶ms.text_document_position_params.text_document.uri)
1318 else {
1319 return Ok(None);
1320 };
1321
1322 match this.node_at_position(¶ms.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(¶ms.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 = ¶ms.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 = ¶ms.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}